diff --git a/ql-web/trunk/css/default.css b/ql-web/trunk/css/default.css new file mode 100644 index 0000000..ca149dd --- /dev/null +++ b/ql-web/trunk/css/default.css @@ -0,0 +1,325 @@ +body { + margin: 0; + padding: 0; + font: normal 100% sans-serif; + color: #606760; + } + +font.ez { + font-style: italic; + color: #505050; + } + +font.hint { + font-size: 85%; + } + +font.feature { + font-weight: bold; + } + +#nav_bar { + float: left; + width: 20%; + margin-left: 1%; + margin-right: 1%; + margin-bottom: 1%; + margin-top: 5px; + padding-left: 0.5%; + padding-right: 0.5%; + padding-top: 1.5%; + padding-bottom: 1.5%; + background-color: #c0c0b0; + } + +.nav_group { + margin-top: 1%; + padding-bottom: 1%; + } + +#nav_bar ul { + color: #af8060; + margin: 0; + padding-left: 15px; + list-style: none; + } + +#nav_bar ul li { + margin-top: 0; + /* small space between highest level entries */ + margin-bottom: 5px; + } + +#nav_bar ul li ul li { + /* no space between lower level entries */ + margin-bottom: 0; + } + +#nav_bar li a.nav_active { + color: #fff; + } + +#nav_bar font.no_link { + color: #2f4860; + font-style: italic; + } + +#nav_bar a { + color: #2f4860; + text-decoration: none; + } + +#nav_bar a:hover { + background-color: #d8d8d8; + color: #2f4860; + } + +#main_content { + margin-left: 22%; + padding-left: 3%; + padding-right: 1%; + min-height: 440px; + } + +#main_content li { + list-style: none; + } + +#main_content button { + margin-top: 12px; + } + +#main_content ul { + line-height: 1.8em; + list-style: none; + } + +#main_content ul li ul { + font-size: 85%; + } + +#main_content fieldset { + margin-top: 0.5%; + margin-bottom: 1%; + padding-top: 1%; + padding-bottom: 1.5%; + } + +#main_content fieldset.form form ul { + padding-left: 1%; + } + +#main_content fieldset.form form ul li ul { + padding-left: 3%; + } + +#main_content table.list_select { + table-layout: fixed; + width: 100%; + text-align: left; + line-height: 1.2em; + border-width: 0px; + padding-left: 2%; + padding-right: 0px; + } + +div.formfield { + font-style: normal; + } + +#news { + background: #e8947c; + color: #000000; + margin: 1%; + padding: 5px; + width: 30%; + float: right; + font-size: 80%; + } + +#news font.title { + font-style: italic; + font-size: 110%; + } + +#news ul.changes { + font-size: 90%; + margin-top: 0px; + } + +a:visited { + color: #005040; + } + +#oben { + background-color: #2f4860; + } + +h1.oben { + text-align: left; + border-bottom: solid 2px #ffffff; + padding: 5px; + font-weight: bold; + letter-spacing: -1px; + color: #ffffff; + margin: 0; + } + +#perm_nav { + float: right; + padding-right: 5px; + font-size: 100%; + color: #ffd7f0; + background-color: inherit; + } + +#perm_nav a { + font-weight: bold; + color: #ffffff; + padding-left: 2px; + padding-right: 2px; + text-decoration: none; + } + +#perm_nav a:hover { + color: #ffcc00; + } + +table.subscribers { + border-width: 0; + margin: 0; + padding: 1%; + width: 100%; + } + +tr, td { + margin: 0; + padding: 0; + } + +button { + color: #5e5e5e; + background-color: #d8d8d8; + border: 1px dotted #5e5e5e; + font-size: 90%; + cursor: pointer; + } + +button:hover { + color: #505050; + background-color: #d0d0d0; + border: 1px dotted #ACE149; + font-size: 90%; + cursor: pointer; + } + +.lid { + margin: 0; + padding: 3px; + border-bottom: solid 1px #606070; + background-color: #c0c0b0; + font: bold 100% sans-serif; + letter-spacing: -1px; + color: #ffffff; + } + +#content { + position: absolute; + right: 0px; + width: 85%; + font: normal 82% sans-serif; + background-color: #ffffff; + padding: 2px; + } + +#content h2 { + margin-left: 5px; + margin-right: 5px; + margin-top: 10px; + font-weight: normal; + letter-spacing: -1px; + color: #2f4860; + } + +.push { + margin: 5px; + padding: 0; + } + +.push p { + text-align: justify; + } + +#info_title { + text-align: center; + background-color: #c0c0b0; + margin-top: 5px; + margin-left: 0%; + margin-right: 1%; + margin-bottom: 0.2%; + color: #ffffff; + font-size: 125%; + font-style: italic; + } + +#header { + text-align: right; + background-color: #2f4860; + font-size: 90%; + color: #ffffff; + margin: 0; + top: 0; + width: 100%; + } + +#footer { + text-align: center; + background-color: #2f4860; + font-size: 90%; + color: #ffffff; + margin: 0px; + padding: 3px; + } + +#footer a { + color: #a0d0b0; + } + +#footer a:visited { + color: #a0d0b0; + } + +#main_content div.introduction { + font-size: 80%; + } + +#main_content div.warning, div.error, div.success { + margin-left: 10%; + margin-right: 10%; + margin-top: 2%; + margin-bottom: 3%; + padding: 2%; + text-align: center; + font-style: italic; + font-size: 90%; + } + +#main_content div.success { + background-color: #40d070; + color: #202020; + } + +#main_content div.warning { + background-color: #e0a0a0; + color: #202020; + } + +#main_content div.error { + background-color: #d05050; + color: #202020; + } + +.mail { + font-style: italic; + color: #2f4860; + } diff --git a/ql-web/trunk/lang/de.hdf b/ql-web/trunk/lang/de.hdf new file mode 100644 index 0000000..28d2719 --- /dev/null +++ b/ql-web/trunk/lang/de.hdf @@ -0,0 +1,85 @@ +LanguageID = de + +LanguageName = Deutsch + +Lang { + + Menu { + Overview = Ueberblick + Password = Passwort + Forward = Weiterleitung + Filter = Spam-Filter + Vacation = Abwesenheit + } + + + Title { + Overview = Ueberblick + Password = Passwortaenderung + Forward = Weiterleitungen konfigurieren + Filter = Spam-Filter konfigurieren + Vacation = Abwesenheitsbenachrichtigung + } + + + Buttons { + Password = Passwort aendern + AddForward = Weiterleitung hinzufuegen + DelForward = Weiterleitung entfernen + Filter = Einstellung speichern + Vacation = Einstellung speichern + } + + + Options { + filter_on = Aktiviere den Spam-Filter + spam_move = Verschiebe Spam in ein eigenes Verzeichnis + vacation_on = Aktivieren Abwesenheits-Benachrichtigung + } + + + Misc { + NewForwardAddress = Neue Weiterleitungsadresse + VacationText = Benachrichtigungstext + Password = Mailaccount-Passwort + OldPassword = Altes Passwort + NewPassword = Neues Passwort + NewPasswordAgain = Neues Passwort wiederholen + FooterText = eine Web-Oberflaeche fuer + Filter_None = keine Spam-Kontrolle + Filter_Mark = markiere Spam-Nachrichten + Filter_Move = Verschiebe Spam-Nachrichten + } + + ErrorMessage { + UnknownAction = Diese Aktion ist undefiniert! + ParameterMissing = Diese Aktion benoetigt weitere Parameter! + } + + + WarningMessage { + FilterConnfig = Die Filterungseinstellungen konnten nicht gespeichert werden! + } + + + SuccessMessage { + UpdatePassword = Das Passwort wurde erfolgreich geaendert. + AddForward = Die Weiterleitung wurde hinzugefuegt. + DelForward = Die Weiterleitung wurde entfernt. + UpdateFilter = Die Spam-Filter-Einstellungen wurden gespeichert. + UpdateVacation = Die Abwesenheits-Einstellungen wurden gespeichert. + } + + + Introduction { + } + + + Legend { + Password = Passwort aendern + Forward = Weiterleitungen verwalten + Filter = Spam-Filterung einrichten + Vacation = Abwesenheitsbenachrichtigung einrichten + } +} + diff --git a/ql-web/trunk/ql-web.conf b/ql-web/trunk/ql-web.conf new file mode 100644 index 0000000..c6a8e9f --- /dev/null +++ b/ql-web/trunk/ql-web.conf @@ -0,0 +1,18 @@ +$CSS_URL = '/ql-web/css/default.css'; + +$HTML_TITLE = 'QL-Web - Entwicklung'; + +$QL_WEB_DIR = '/home/lars/subversion/admin-tools/ql-web/trunk'; +$TEMPLATE_DIR = "$QL_WEB_DIR/template"; +$LANGUAGE_DIR = "$QL_WEB_DIR/lang"; + +$HTML_LANGUAGE = 'de'; + +$LDAP_HOST = 'ldap.sao'; + +# the string '_USERNAME_' will be replaced by the real username +$LDAP_USER_DN = "cn=_USERNAME_,ou=People,o=neofaxe,dc=systemausfall,dc=org"; + +$LDAP_SPAM_MOVE = "| ifspam spam-_USERNAME_ || true"; +$LDAP_SPAM_MARK = "| ifspam spam-_USERNAME_"; + diff --git a/ql-web/trunk/ql-web.pl b/ql-web/trunk/ql-web.pl new file mode 100755 index 0000000..d8cdb4c --- /dev/null +++ b/ql-web/trunk/ql-web.pl @@ -0,0 +1,353 @@ +#!/usr/bin/perl +#=========================================================================== +# ql-web v0.1 +# ========================================================================== + +package ql_web; + +# Modules to include +use strict; +use ClearSilver; +use Mail::Address; +use CGI; +use IO::File; +use Net::LDAP; + +my $q = new CGI; +$q->import_names('Q'); + +# Suid stuff requires a secure path. +$ENV{'PATH'} = '/bin'; + +# use strict is a good thing++ + +# We run suid so we can't use $ENV{'HOME'} and $ENV{'USER'} to determine the user +my @tmp = getpwuid($>); + +use vars qw[$HOME_DIR]; $HOME_DIR=$tmp[7]; +use vars qw[$HTML_TITLE]; +use vars qw[$CSS_URL $TEMPLATE_DIR $LANGUAGE_DIR $HTML_LANGUAGE]; +use vars qw[$LDAP_HOST $LDAP_USER_DN $LDAP_SPAM_MOVE $LDAP_SPAM_MARK]; + +# set default TEXT_ENCODE +use vars qw[$TEXT_ENCODE]; $TEXT_ENCODE='us-ascii'; + +# pagedata contains the hdf tree for clearsilver +# pagename refers to the template file that should be used +use vars qw[$pagedata $pagename $error $customError $warning $customWarning $success]; +use vars qw[$mail_user]; + +# Get user configuration stuff +if(-e "$HOME_DIR/.ql-web.conf") { + require "$HOME_DIR/.ql-web.conf"; # User +} elsif(-e "./ql-web.conf") { + require "./ql-web.conf"; # Install +} elsif(-e "/etc/ql-web/ql-web.conf") { + require "/etc/ql-web/ql-web.conf"; # System (new style) +} else { + &fatal_error("Unable to read config file"); +} + +# check optional stylesheet +$CSS_URL = '' unless defined($CSS_URL); + +# check template directory +$TEMPLATE_DIR = 'template' unless defined($TEMPLATE_DIR); + +# Untaint form input ... +&untaint; + +$mail_user = $ENV{'REMOTE_USER'}; + +my $pagedata = load_hdf(); +my $action = $q->param('action'); + +# This is where we decide what to do, depending on the form state and the +# users chosen course of action ... +# TODO: unify all these "is list param set?" checks ... +if ($action eq '' || $action eq 'overview') { + # Default action - display the current mail account configuration + $pagename = 'overview'; +} elsif ($action eq 'password_form') { + # display password change dialog + $pagename = 'password_form'; +} elsif ($action eq 'password_update') { + $success = 'UpdatePassword' if (&update_password()); +} elsif ($action eq 'forward_form') { + $pagename = 'forward_form'; +} elsif ($action eq 'forward_add') { + # add a forwarding address + if (defined($q->param('options_forward_add_address'))) { + $success = 'AddForward' if (&add_forward()); + $pagename = 'forward_form'; + } else { + $error = 'ParameterMissing'; + $pagename = 'forward_form'; + } +} elsif ($action eq 'forward_del') { + # remove a forwarding address + # no selected address -> no error + if (defined($q->param('options_forward_del_address'))) { + $success = 'DelForward' if (&del_forward()); + $pagename = 'forward_form'; + } else { + $error = 'ParameterMissing'; + $pagename = 'forward_form'; + } +} elsif ($action eq 'filter_form') { + $pagename = 'filter_form'; +} elsif ($action eq 'filter_update') { + # update filtering setting + $success = 'UpdateFilter' if (&update_filter()); + $pagename = 'filter_form'; +} elsif ($action eq 'vacation_form') { + $pagename = 'vacation_form'; +} elsif ($action eq 'vacation_update') { + # update vacation reply setting + $success = 'UpdateVacation' if (&update_vacation()); + $pagename = 'vacation_form'; +} else { + $pagename = 'overview'; + $error = 'UnknownAction'; +} + +# read the current state (after the changes are done) +&set_pagedata(); + +# Print page and exit :) ... +&output_page; +exit; + + +# ========================================================================= + +sub set_pagedata { + $pagedata->setValue('Data.isSpamMove', &is_spam_move()? 1 : 0); + $pagedata->setValue('Data.isSpamMark', &is_spam_mark()? 1 : 0); +} + +# --------------------------------------------------------------------------- + +sub update_filter { + + my $ldif; + my $password = $q->param('pw'); + my $result; + my $ldap; + my $user_dn = $LDAP_USER_DN; + $user_dn =~ s/_USERNAME_/$mail_user/g; + + $ldap = Net::LDAP->new($LDAP_HOST); + $result = $ldap->bind($mail_user, password => $password); + if ($result->is_error) { + $warning = 'WrongPassword'; + return (0==1); + } + + if ($q->param('filter_type') eq 'none') { + if (&is_spam_mark() || &is_spam_move()) { + $result = $ldap->modify($user_dn, delete => ['deliveryProgramPath']); + } + } elsif ($q->param('filter_type') eq 'move') { + if (!&is_spam_move()) { + $ldif = $LDAP_SPAM_MOVE; + $ldif =~ s/_USERNAME_/$mail_user/g; + $ldap->modify($user_dn, delete => [ 'deliveryProgramPath' ]) + if (&is_spam_mark()); + $result = $ldap->modify($user_dn, add => { deliveryProgramPath => $ldif }); + } + } elsif ($q->param('filter_type') eq 'mark') { + if (!&is_spam_mark()) { + $ldif = $LDAP_SPAM_MARK; + $ldif =~ s/_USERNAME_/$mail_user/g; + $ldap->modify($user_dn, delete => [ 'deliveryProgramPath' ]) + if (&is_spam_move()); + $result = $ldap->modify($user_dn, add => { deliveryProgramPath => $ldif }); + } + } else { + $error = 'ParameterMissing'; + warn "unknown filter_type: " . $q->param('filter_type'); + } + $ldap->unbind; + + if ($result->is_error) { + $warning = 'FilterConfig'; + return (0==1); + } else { + return (0==0); + } +} + +# --------------------------------------------------------------------------- + +sub is_spam_move { + +} + +# --------------------------------------------------------------------------- +sub is_spam_mark { + +} + +# --------------------------------------------------------------------------- + +sub load_hdf { + # initialize the data for clearsilver + my $hdf = ClearSilver::HDF->new(); + + $hdf->readFile($LANGUAGE_DIR . '/' . $HTML_LANGUAGE . '.hdf'); + + &fatal_error("Template dir ($TEMPLATE_DIR) not found!") unless (-e $TEMPLATE_DIR); + $hdf->setValue("Config.TemplateDir", "$TEMPLATE_DIR/"); + &fatal_error("Language data dir ($LANGUAGE_DIR) not found!") unless (-e $LANGUAGE_DIR); + $hdf->setValue("Config.LanguageDir", "$LANGUAGE_DIR/"); + $hdf->setValue("Config.ScriptName", $ENV{'SCRIPT_NAME'}); + $hdf->setValue("Config.Stylesheet", "$CSS_URL"); + $hdf->setValue("Config.PageTitle", "$HTML_TITLE"); + + return $hdf; +} + +# --------------------------------------------------------------------------- + +sub output_page { + # Print the page + + $pagedata->setValue('Data.Success', "$success") if (defined($success)); + $pagedata->setValue('Data.Error', "$error") if (defined($error)); + $pagedata->setValue('Data.Warning', "$warning") if (defined($warning)); + $pagedata->setValue('Data.CustomError', "$customError") if (defined($customError)); + $pagedata->setValue('Data.CustomWarning', "$customWarning") if (defined($customWarning)); + + $pagedata->setValue('Data.Action', "$pagename"); + + my $pagefile = $TEMPLATE_DIR . "/main.cs"; + &fatal_error("main template ($pagefile) not found!") unless (-e "$pagefile"); + &fatal_error("sub template ($TEMPLATE_DIR/$pagename.cs) not found!") unless (-e "$TEMPLATE_DIR/$pagename.cs"); + + # print http header + print "Content-Type: text/html; charset=utf-8\n\n"; + + my $cs = ClearSilver::CS->new($pagedata); + + $cs->parseFile($pagefile); + + print $cs->render(); +} + +# --------------------------------------------------------------------------- + +sub untaint { + + # Go through all the CGI input and make sure it is not tainted. Log any + # tainted data that we come accross ... See the perlsec(1) man page ... + + my (@params, $i, $param); + @params = $q->param; + + foreach $i (0 .. $#params) { + my(@values); + foreach $param ($q->param($params[$i])) { + next if $param eq ''; + if ($param =~ /^([#-\@\w\.\/\[\]\:\n\r\>\< _"']+)$/) { + push @values, $1; + } else { + warn "Tainted input in '$params[$i]': " . $q->param($params[$i]); + } + $q->param(-name=>$params[$i], -values=>\@values); + } + } +} + +# ------------------------------------------------------------------------ + +sub check_language { + my ($list, $lang) = @_; + my $found = 0; + my $item; + foreach $item ($list->get_available_languages()) { + $found++ if ($item eq $q->param('list_language')); + } + return ($found > 0); +} + +# --------------------------------------------------------------------------- + +sub fatal_error() { + my $text = shift; + + print "Content-Type: text/html; charset=utf-8\n\n"; + print "\n"; + print "ezmlm-web\n"; + print "

a fatal error occoured!

\n"; + print "

$text

\n"; + print "

check the error log of your web server for details

\n"; + print "\n"; + die "$text"; +} + +# ------------------------------------------------------------------------ +# End of ql-web.pl +# ------------------------------------------------------------------------ +__END__ + +=head1 NAME + +ezmlm-web - A web configuration interface to ezmlm mailing lists + +=head1 SYNOPSIS + +ezmlm-web [B<-c>] [B<-C> EFE] [B<-d> EFE] + +=head1 DESCRIPTION + +=over 4 + +=item B<-C> Specify an alternate configuration file given as F +If not specified, ezmlm-web checks first in the users home directory, then in +F and then the current directory + +=item B<-d> Specify an alternate directory where lists live. This is now +depreciated in favour of using a custom ezmlmwebrc, but is left for backward +compatibility. + +=back + +=head1 SUID WRAPPER + +C<#include stdio.h> + +C + C + C +C<}> + + +=head1 DOCUMENTATION/CONFIGURATION + + Please refer to the example ezmlmwebrc which is well commented, and + to the README file in this distribution. + +=head1 FILES + +F<~/.ezmlmwebrc> +F +F<./ezmlmwebrc> + +=head1 AUTHOR + + Guy Antony Halse + Lars Kruse + +=head1 BUGS + + None known yet. Please report bugs to the author. + +=head1 S + + ezmlm(5), ezmlm-cgi(1), Mail::Ezmlm(3) + + https://systemausfall.org/toolforge/ezmlm-web + http://rucus.ru.ac.za/~guy/ezmlm/ + http://www.ezmlm.org/ + http://www.qmail.org/ diff --git a/ql-web/trunk/template/filter_form.cs b/ql-web/trunk/template/filter_form.cs new file mode 100644 index 0000000..e052d43 --- /dev/null +++ b/ql-web/trunk/template/filter_form.cs @@ -0,0 +1,23 @@ +
+

+
+ +
+ + +
+
    +
  • +
  • +
  • +
+ + + +
+ +
diff --git a/ql-web/trunk/template/footer.cs b/ql-web/trunk/template/footer.cs new file mode 100644 index 0000000..b36f27d --- /dev/null +++ b/ql-web/trunk/template/footer.cs @@ -0,0 +1,8 @@ + + + + + diff --git a/ql-web/trunk/template/forward_form.cs b/ql-web/trunk/template/forward_form.cs new file mode 100644 index 0000000..899495e --- /dev/null +++ b/ql-web/trunk/template/forward_form.cs @@ -0,0 +1,22 @@ +
+

+
+ +
+ + +
+
    +
  • not yet implemented
  • + + +
+ + + +
+ +
diff --git a/ql-web/trunk/template/header.cs b/ql-web/trunk/template/header.cs new file mode 100644 index 0000000..1adf663 --- /dev/null +++ b/ql-web/trunk/template/header.cs @@ -0,0 +1,25 @@ + + + + + + <?cs var:Config.PageTitle ?> + + + + + + + + + + +
+
+ +
+

ql-web

+
+ diff --git a/ql-web/trunk/template/macros.cs b/ql-web/trunk/template/macros.cs new file mode 100644 index 0000000..975166d --- /dev/null +++ b/ql-web/trunk/template/macros.cs @@ -0,0 +1,52 @@ +checked="checked" /> + unknown option () + +checked="checked" /> + +
unknown setting () + +
+ unknown warning message () +
+ +
+ unknown error message () +
+ +
+ unknown success message () +
+ + limit ?>... + diff --git a/ql-web/trunk/template/main.cs b/ql-web/trunk/template/main.cs new file mode 100644 index 0000000..d0ba89a --- /dev/null +++ b/ql-web/trunk/template/main.cs @@ -0,0 +1,17 @@ + + + + + + +
+ + + + + + +
+ + + diff --git a/ql-web/trunk/template/nav.cs b/ql-web/trunk/template/nav.cs new file mode 100644 index 0000000..dd85652 --- /dev/null +++ b/ql-web/trunk/template/nav.cs @@ -0,0 +1,28 @@ + + diff --git a/ql-web/trunk/template/overview.cs b/ql-web/trunk/template/overview.cs new file mode 100644 index 0000000..b5aba65 --- /dev/null +++ b/ql-web/trunk/template/overview.cs @@ -0,0 +1 @@ +mainly empty diff --git a/ql-web/trunk/template/password_form.cs b/ql-web/trunk/template/password_form.cs new file mode 100644 index 0000000..972c189 --- /dev/null +++ b/ql-web/trunk/template/password_form.cs @@ -0,0 +1,24 @@ +
+

+
+ +
+ + +
+
    +
  • +
  • +
  • +
  • +
  • +
  • + + +
+ + + +
+ +