diff --git a/README b/README index a76ede8..131966a 100644 --- a/README +++ b/README @@ -173,8 +173,8 @@ them in any future releases of ezmlm-web. VI. Encrypted mailing lists =========================== If you want to manage encrypted mailing lists (see -http://www.synacklabs.net/projects/crypt-ml/) with ezmlm-web, then you should -read README.gnupg and follow the instructions. +https://systemausfall.org/toolforge/gpgpy-ezmlm) with ezmlm-web, then you +should read README.gnupg and follow the instructions. VII. Bugs && Bug Reports diff --git a/README.gnupg b/README.gnupg index 3809934..dc16e74 100644 --- a/README.gnupg +++ b/README.gnupg @@ -4,7 +4,7 @@ $Id$ Content: 1) Requirements -2) Installation of gpg-ezmlm +2) Installation of gpgpy-ezmlm 3) Setup of ezmlm-web 4) Notes diff --git a/TODO b/TODO index 26440db..95d68b3 100644 --- a/TODO +++ b/TODO @@ -1,27 +1,11 @@ -urgent: - - add basic/normal/expert to web interface - - export subscribers - - fix display of language name - -clearsilver-Infos updaten - -die nicht-Abonnenten-Listen (allow/deny/mod) sagen trotzdem: "x AbonnentInnen" +add basic/normal/expert to web interface multi-domain-Support? charset support for idx <= 5.0 -suppress success message, if no new subscriber addresses were selected for adding - -Maximale Anzahl von Mailinglisten definieren? - - wird wohl von qmailadmin in der .qmailadmin-limits-Datei festgelegt - Infos fuer Massen-Hosting: http://www.fbis.ch/index-de.php?page=14&frameset=4 -restore user input after failed list_create (especially options) - -support for: - * show subscription log - * 'mailinglist' (maybe) +support for 'mailinglist' (maybe) diff --git a/changelog b/changelog index bcf1528..0c4225b 100644 --- a/changelog +++ b/changelog @@ -7,8 +7,8 @@ Version 3.2 - 04/14/02006 * script for creating binary suid wrappers added * bug in MySQL support fixed * treatment of the special character "dot" in listname and list address fixed - * use gettext for translations (Locale::gettext is now required) * the formerly required module "Encode" is now optional + * support for listing of subscription log Version 3.1.4 - 03/26/02006 * new setting: DOTQMAIL_DIR (useful for multi domain vpopmail setups) diff --git a/examples/ezmlmwebrc.dist b/examples/ezmlmwebrc.dist index 7be6896..71aa4c3 100644 --- a/examples/ezmlmwebrc.dist +++ b/examples/ezmlmwebrc.dist @@ -29,7 +29,7 @@ $WEBUSERS_FILE = "$LIST_DIR/webusers"; $TEMPLATE_DIR = "/usr/local/share/ezmlm-web/template"; # Safe list deletion? -# 0 = move list to $LIST_DIR/_deleted_lists -> recoverable :) +# 0 = move List to $LIST_DIR/_deleted_lists -> recoverable :) # 1 = allow user to delete list completely. No backup, therefore no recovery. $UNSAFE_RM = 0; @@ -62,13 +62,10 @@ $HTML_TITLE = "ezmlm-web - a mailinglists' administration interface"; # this is a URL - you have to copy the css file to the right location before $HTML_CSS_FILE = "/ezmlm-web.css"; -# choose a language (en|de) +# the default interface language +# can only be changed via the web interface if gettext support is available $HTML_LANGUAGE = "en"; -# list of available languages - this should include the default language above -# example: LANGUAGE_LIST = ( "en", "de", "si", "jp" ); -@LANGUAGE_LIST = ( "en", "de" ); - # turn support for encrypted mailing lists on or off - defaults to 0 (off) # see https://systemausfall.org/toolforge/gpgpy-ezmlm for details $GPG_SUPPORT = 0; diff --git a/ezmlm-web.cgi b/ezmlm-web.cgi index a931631..6571783 100755 --- a/ezmlm-web.cgi +++ b/ezmlm-web.cgi @@ -22,6 +22,14 @@ use CGI; use IO::File; use POSIX; use English; +use Time::localtime; + +# gettext support is optional +my $GETTEXT_SUPPORT = 1; +unless (&safely_import_module("Locale::gettext")) { + $GETTEXT_SUPPORT = 0; + warn "Gettext support is not available - the multilingual web interface is not available!"; +} # the Encode module is optional - we do not break if it is absent my $ENCODE_SUPPORT = 1; @@ -125,10 +133,11 @@ do $config_file; # do we support encrypted mailing lists? # see https://systemausfall.org/toolforge/crypto-ezmlm $GPG_SUPPORT = 0 unless defined($GPG_SUPPORT); -if (defined($GPG_SUPPORT) && ($GPG_SUPPORT)) { +if ($GPG_SUPPORT) { if (&safely_import_module("Mail::Ezmlm::Gpg")) { $GPG_SUPPORT = 1; } else { + $GPG_SUPPORT = 0; warn "WARNING: Support for encrypted mailinglists is disabled, as the module Mail::Ezmlm::Gpg failed to load!"; } } @@ -173,15 +182,14 @@ if (defined($MAIL_DOMAIN) && ($MAIL_DOMAIN ne '')) { my $pagedata = &init_hdf(); my $action = $q->param('action'); -# check permissions -unless (&check_permission_for_action) { - $pagename = 'list_select'; - $error = 'Forbidden'; -} # 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 ... -elsif ($action eq '' || $action eq 'list_select') { +# check permissions +unless (&check_permission_for_action()) { + $pagename = 'list_select'; + $error = 'Forbidden'; +} elsif ($action eq '' || $action eq 'list_select') { # Default action. Present a list of available lists to the user ... $pagename = 'list_select'; } elsif ($action eq 'show_page') { @@ -217,6 +225,24 @@ elsif ($action eq '' || $action eq 'list_select') { $error = 'ParameterMissing'; $pagename = 'list_select'; } +} elsif ($action eq 'download_subscribers') { + # requesting a text file of all subscribers + if (defined($q->param('list'))) { + &download_subscribers(); + # just in case we return (something bad happened) + $pagename = 'subscribers'; + } else { + $pagename = 'list_select'; + $error = 'ParameterMissing'; + } +} elsif ($action eq 'subscribe_log') { + if (defined($q->param('list'))) { + &set_pagedata_subscription_log($q->param('list')); + $pagename = 'show_subscription_log'; + } else { + $pagename = 'list_select'; + $error = 'ParameterMissing'; + } } elsif ($action eq 'list_delete_ask') { # Confirm list removal if (defined($q->param('list'))) { @@ -332,7 +358,7 @@ elsif ($action eq '' || $action eq 'list_select') { exit 0; } else { $warning = 'GnupgExportKey'; - # TODO: pagename is quite random here ... + # pagename is quite random here ... $pagename = 'gnupg_public'; } } else { @@ -419,7 +445,9 @@ elsif ($action eq '' || $action eq 'list_select') { # set default action, if there is no list available and the user is # allowed to create a new one -if (($action eq '') && (&webauth_create_allowed()) && ($pagedata->getValue('Data.Lists.0','') eq '')) { +if (((!defined($action)) || ($action eq '')) + && (&webauth_create_allowed()) + && ($pagedata->getValue('Data.Lists.0','') eq '')) { $pagename = 'list_create'; } @@ -432,6 +460,7 @@ exit; sub init_hdf { # initialize the data for clearsilver + my $hdf = ClearSilver::HDF->new(); &fatal_error("Template dir ($TEMPLATE_DIR) not found!") unless (-e $TEMPLATE_DIR); @@ -451,7 +480,7 @@ sub init_hdf { $hdf = &load_interface_language($hdf); - $hdf->setValue("ScriptName", $ENV{'SCRIPT_NAME'}); + $hdf->setValue("ScriptName", $ENV{SCRIPT_NAME}) if (defined($ENV{SCRIPT_NAME})); $hdf->setValue("Stylesheet", "$HTML_CSS_FILE"); $hdf->setValue("Config.PageTitle", "$HTML_TITLE"); @@ -543,29 +572,35 @@ sub translate_language_data { my ($hdf, $language) = @_; my $langdata; - my %translation; + my %language_strings; my $key; - # create gettext object - # TODO: getttext support seems to be broken??? - # TODO: provide an alternative, if no gettext is available - #&setlocale(POSIX::LC_MESSAGES, $language); - &textdomain("ezmlm-web"); - warn "failed to set locale: $@" unless (&setlocale(LC_ALL, '')); - # "setlocale" seems to need "de_DE" instead of just "de" - so we will - # use the environment setting instead - # see http://lists.debian.org/debian-perl/2000/01/msg00016.html - # beware that no other programs are called afterwards: their output may suffer :) - $ENV{LC_ALL} = "$language"; - # read language template $langdata = ClearSilver::HDF->new(); $langdata->readFile("$TEMPLATE_DIR/language.hdf"); - # translate all strings + # parse all strings my $subtree = $langdata->getObj("Lang"); - %translation = &recurse_hdf($subtree, "Lang"); - foreach $key (keys %translation) { - $hdf->setValue($key, gettext($translation{$key})) + %language_strings = &recurse_hdf($subtree, "Lang"); + + if ($GETTEXT_SUPPORT) { + # create gettext object + &textdomain("ezmlm-web"); + warn "failed to set locale: $@" unless (&setlocale(LC_MESSAGES, '')); + # "setlocale" seems to need "de_DE" instead of just "de" - so we will + # use the environment setting instead + # see http://lists.debian.org/debian-perl/2000/01/msg00016.html + # avoid calling other programs later: their output may suffer :) + $ENV{LC_ALL} = "$language"; + + # translate every string + foreach $key (keys %language_strings) { + $hdf->setValue($key, &gettext($language_strings{$key})) + } + } else { + # just copy all strings + foreach $key (keys %language_strings) { + $hdf->setValue($key, $language_strings{$key}) + } } } @@ -578,7 +613,6 @@ sub recurse_hdf { $value = $node->objValue(); if ($value) { #print "Prefix: " . $prefix . " / " . $value . "\n"; - #TODO: check if this works on the same single object - no tests up to now $result{$prefix} = $value; } $next = $node->objChild(); @@ -594,20 +628,19 @@ sub recurse_hdf { # --------------------------------------------------------------------------- -# look for preferred browser language setting -# this code was adapted from Per Cederberg -# http://www.percederberg.net/home/perl/select.perl -# it returns an empty string, if no supported language was found sub get_browser_language { + # look for preferred browser language setting + # this code was adapted from Per Cederberg + # http://www.percederberg.net/home/perl/select.perl + # it returns an empty string, if no supported language was found my ($str, @langs, @res); # Use language preference settings - if ($ENV{'HTTP_ACCEPT_LANGUAGE'} ne '') - { - @langs = split(/,/, $ENV{'HTTP_ACCEPT_LANGUAGE'}); - foreach (@langs) - { + if (defined($ENV{HTTP_ACCEPT_LANGUAGE}) + && ($ENV{HTTP_ACCEPT_LANGUAGE} ne '')) { + @langs = split(/,/, $ENV{HTTP_ACCEPT_LANGUAGE}); + foreach (@langs) { # get the first part of the language setting ($str) = ($_ =~ m/([a-z]+)/); # check, if it is available @@ -698,7 +731,7 @@ sub set_pagedata { $pagedata->setValue("Data.WebUser.UserName", $ENV{'REMOTE_USER'}||'ALL'); # list specific configuration - use defaults if no list is selected - if ($q->param('list') ne '' ) { + if (defined($q->param('list')) && ($q->param('list') ne '')) { &set_pagedata4list(&get_list_part()); } else { &set_pagedata4options($DEFAULT_OPTIONS); @@ -744,8 +777,9 @@ sub set_pagedata4list { # --------------------------------------------------------------------------- -# extract hdf-data for encrypted lists sub set_pagedata_crypto { + # extract hdf-data for encrypted lists + my ($listname) = @_; my ($gpg_list, %config, $item, @gpg_keys, $gpg_key); @@ -829,8 +863,8 @@ sub set_pagedata_subscribers { # --------------------------------------------------------------------------- -# set the names of the textfiles of this list sub set_pagedata_textfiles { + # set the names of the textfiles of this list my $list = shift; my ($i, @files, $item); @@ -925,9 +959,6 @@ sub set_pagedata4options { my($options) = shift; my($i, $list, $key, $state, $value, $dir_of_list); - $dir_of_list = $LIST_DIR . '/' . $q->param('list'); - $list = new Mail::Ezmlm("$LIST_DIR/" . $q->param('list')); - $i = 0; $key = lc(substr($options,$i,1)); # parse the first part of the options string @@ -939,6 +970,43 @@ sub set_pagedata4options { $key = lc(substr($options,$i,1)); } + # scan the config for settings + for ($i=0; $i<=9; $i++) { + unless (($i eq 1) || ($i eq 2)) { + $state = ($options =~ /\s-$i (?:'(.+?)')/); + unless ($state) { + # set default values + if ($i eq 0) { + $value = 'mainlist@' . $DEFAULT_HOST; + } elsif ($i eq 3) { + $value = 'from_address@domain.org'; + } elsif ($i eq 4) { + $value = '-t24 -m30 -k64'; + } elsif ($i eq 5) { + $value = 'owner_address@domain.org'; + } elsif ($i eq 6) { + $value = 'host:port:user:password:database:table'; + } elsif (($i >= 7) && ($i <= 9)) { + if (defined($q->param('list'))) { + $value = $LIST_DIR . '/' . $q->param('list') . "/mod"; + } else { + $value = "mod"; + } + } + } else { + # use the configured value (extracted by the pattern matching for 'state') + $value = $1; + } + $pagedata->setValue("Data.List.Settings." . $i . ".value", $value); + $pagedata->setValue("Data.List.Settings." . $i . ".state", $state ? 1 : 0); + } + } + + # the list dependent stuff follows - we can stop if no list is selected + return unless (defined($q->param('list'))); + $dir_of_list = $LIST_DIR . '/' . $q->param('list'); + $list = new Mail::Ezmlm($dir_of_list); + # the options "tpxmsr" are used to create a default value # if they are unset, the next ezmlm-make will remove the appropriate files # but: these files are used, if they exist - regardless of the flag @@ -959,48 +1027,72 @@ sub set_pagedata4options { if (-e "$dir_of_list/modsub"); $pagedata->setValue("Data.List.Options.r" , 1) if (-e "$dir_of_list/remote"); - - for ($i=0; $i<=9; $i++) { - unless (($i eq 1) || ($i eq 2)) { - $state = ($options =~ /\s-$i (?:'(.+?)')/); - unless ($state) { - # set default values - if ($i eq 0) { - $value = 'mainlist@' . $DEFAULT_HOST; - } elsif ($i eq 3) { - $value = 'from_address@domain.org'; - } elsif ($i eq 4) { - $value = '-t24 -m30 -k64'; - } elsif ($i eq 5) { - $value = 'owner_address@domain.org'; - } elsif ($i eq 6) { - $value = 'host:port:user:password:database:table'; - } elsif (($i >= 7) && ($i <= 9)) { - $value = "$dir_of_list/mod"; - } - } else { - # use the configured value (extracted by the pattern matching for 'state') - $value = $1; - } - $pagedata->setValue("Data.List.Settings." . $i . ".value", $value); - $pagedata->setValue("Data.List.Settings." . $i . ".state", $state ? 1 : 0); - } - } } # --------------------------------------------------------------------------- -sub check_filename() -{ +sub download_subscribers { + # return a list of subscribers of a list for download + + my ($list, $listname, $filename, $part_type); + my (%pretty, $address, $address_name, @subscribers); + + $listname = $q->param('list'); + $list = new Mail::Ezmlm("$LIST_DIR/$listname"); + + if (defined($q->param('part'))) { + $part_type = $q->param('part'); + $filename = "mailinglist-$listname-$part_type.txt"; + } else { + $filename = "mailinglist-$listname-subscribers.txt"; + } + + tie %pretty, "DB_File", "$LIST_DIR/$listname/webnames" if ($PRETTY_NAMES); + foreach $address (sort $list->subscribers($part_type)) { + if ($address ne '') { + if ($PRETTY_NAMES) { + $address_name = $pretty{$address}; + if ($address_name eq '') { + push @subscribers, $address; + } else { + push @subscribers, "$address_name <$address>"; + } + } else { + push @subscribers, $address; + } + } + } + untie %pretty if ($PRETTY_NAMES); + + if ($#subscribers lt 0) { + $warning = 'EmptyList'; + return (1==0); + } + + print "Content-Type: text/plain\n"; + # suggest a download filename + # (taken from http://www.bewley.net/perl/download.pl) + print "Content-Disposition: attachment; filename=$filename\n"; + print "Content-Description: exported subscribers list of $listname\n\n"; + foreach $address (@subscribers) { + print "$address\r\n"; + } + exit; +} + +# --------------------------------------------------------------------------- + +sub check_filename { + my $filename = shift; return ($filename =~ m/[^\w-]/) ? (1==0) : (0==0); } # --------------------------------------------------------------------------- -sub get_list_part -# return the name of the part list (deny, allow, mod, digest or '') -{ +sub get_list_part { + # return the name of the part list (deny, allow, mod, digest or '') + $q->param('part') =~ m/^(allow|deny|digest|mod)$/; return $1; } @@ -1008,6 +1100,7 @@ sub get_list_part # --------------------------------------------------------------------------- sub is_list_encrypted { + my ($listname) = @_; return (1==0) unless ($GPG_SUPPORT); @@ -1018,6 +1111,7 @@ sub is_list_encrypted { # --------------------------------------------------------------------------- sub get_dotqmail_files { + my ($list, @files, $qmail_prefix); $list = new Mail::Ezmlm("$LIST_DIR/" . $q->param('list')); @@ -1062,6 +1156,44 @@ sub get_dotqmail_files { # --------------------------------------------------------------------------- +sub set_pagedata_subscription_log { + + my ($listname) = @_; + + my ($log_file, @event, $i, $epoch_seconds, $note, $address); + $log_file = "$LIST_DIR/" . $q->param('list') . "/Log"; + + # break if there is no log_file + return unless (-e "$log_file"); + + unless (open LOG_FILE, "<$log_file") { + warn "Failed to open log file: $log_file"; + $warning = 'LogFile'; + return (1==0); + } + + $i = 0; + + while () { + chomp; + split; + @event = @_; + if ($#event eq 2) { + $epoch_seconds = $event[0]; + my $datetext = ctime($epoch_seconds); + $note = $event[1]; + $address = $event[2]; + $pagedata->setValue("Data.List.SubscribeLog.$i.date", $datetext); + $pagedata->setValue("Data.List.SubscribeLog.$i.text", $note); + $pagedata->setValue("Data.List.SubscribeLog.$i.address", $address); + $i++; + } + } + close LOG_FILE; +} + +# --------------------------------------------------------------------------- + sub delete_list { # Delete a list ... @@ -1133,13 +1265,13 @@ sub delete_list { } # ------------------------------------------------------------------------ -sub untaint { - $DEFAULT_HOST = $1 if $DEFAULT_HOST =~ /^([\w\d\.-]+)$/; - +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 ... + $DEFAULT_HOST = $1 if $DEFAULT_HOST =~ /^([\w\d\.-]+)$/; + my (@params, $i, $param); @params = $q->param; @@ -1162,7 +1294,9 @@ sub untaint { # special stuff # check the list name - if (($q->param('list') =~ /[^\w\.-]/) && ($q->param('action') !~ /^list_create_(do|ask)$/)) { + if (defined($q->param('list')) && + ($q->param('list') =~ /[^\w\.-]/) && + ($q->param('action') !~ /^list_create_(do|ask)$/)) { $warning = 'InvalidListName' if ($warning eq ''); $q->param(-name=>'list', -values=>''); } @@ -1172,19 +1306,20 @@ sub untaint { # ------------------------------------------------------------------------ sub check_permission_for_action { - # test if the user is allowed to modify the choosen list or to create an new one - # the user would still be allowed to fill out the create-form (however he got there), - # but the final creation is omitted + # test if the user is allowed to modify the choosen list or to create a + # new one the user would still be allowed to fill out the create-form + # (however he got there), but the final creation is omitted - my $ret; - if ($action eq 'list_create_ask' || $action eq 'list_create_do') { - $ret = &webauth_create_allowed(); - } elsif (defined($q->param('list'))) { - $ret = &webauth($q->param('list')); - } else { - $ret = (0==0); - } - return $ret; + my $ret; + if (defined($action) && + (($action eq 'list_create_ask' || $action eq 'list_create_do'))) { + $ret = &webauth_create_allowed(); + } elsif (defined($q->param('list'))) { + $ret = &webauth($q->param('list')); + } else { + $ret = (0==0); + } + return $ret; } # ------------------------------------------------------------------------ @@ -1192,11 +1327,12 @@ sub check_permission_for_action { sub add_address { # Add an address to a list .. - my ($address, $list, $part, @addresses, $fail_count); + my ($address, $list, $part, @addresses, $fail_count, $success_count); $list = new Mail::Ezmlm("$LIST_DIR/" . $q->param('list')); $part = &get_list_part(); $fail_count = 0; + $success_count = 0; if (($q->param('mailaddressfile')) && ($FILE_UPLOAD)) { # Sanity check @@ -1252,6 +1388,7 @@ sub add_address { if (defined($add->name()) && $PRETTY_NAMES) { $pretty{$add->address()} = $add->name(); } + $success_count++; } else { $fail_count++; } @@ -1260,7 +1397,11 @@ sub add_address { if ($fail_count gt 0) { $warning = 'AddAddress'; return (1==0); + } elsif ($success_count eq 0) { + # no subscribers - we report an error without issuing a warning + return (1==0); } else { + # no failures and at least one subscriber -> success return (0==0); } } @@ -1296,9 +1437,9 @@ sub delete_address { # ------------------------------------------------------------------------ sub set_pagedata4part_list { - my($part) = @_; # Deal with list parts .... + my($part) = @_; my ($i, $list, $listaddress,); # Work out the address of this list ... @@ -1405,9 +1546,9 @@ sub create_list { # ------------------------------------------------------------------------ -sub extract_options_from_params() -{ +sub extract_options_from_params { # Work out the command line options ... + my ($options, $settings, $i); my ($listname, $old_options, $state, $old_key); @@ -1473,9 +1614,9 @@ sub extract_options_from_params() # ------------------------------------------------------------------------ -sub manage_gnupg_keys() -# manage gnupg keys -{ +sub manage_gnupg_keys { + # manage gnupg keys + my ($list, $listname, $upload_file); $listname = $q->param('list'); @@ -1506,8 +1647,8 @@ sub manage_gnupg_keys() # ------------------------------------------------------------------------ -sub gnupg_export_key() -{ +sub gnupg_export_key { + my ($listname, $keyid) = @_; my $list = new Mail::Ezmlm::Gpg("$LIST_DIR/$listname"); @@ -1530,7 +1671,7 @@ sub gnupg_export_key() # suggest a download filename # (taken from http://www.bewley.net/perl/download.pl) print "Content-Disposition: attachment; filename=$name\n"; - print "Content-Description: exported key"; + print "Content-Description: exported key\n\n"; print $key_armor; return (0==0); } else { @@ -1540,8 +1681,8 @@ sub gnupg_export_key() # ------------------------------------------------------------------------ -sub gnupg_import_key() -{ +sub gnupg_import_key { + my ($list, $upload_file) = @_; if ($upload_file) { @@ -1556,7 +1697,6 @@ sub gnupg_import_key() # Handle key upload my @ascii_key = <$upload_file>; - # TODO: filter content? if ($list->import_key(join ('',@ascii_key))) { $success = 'GnupgKeyImport'; return (0==0); @@ -1572,7 +1712,7 @@ sub gnupg_import_key() # ------------------------------------------------------------------------ -sub gnupg_generate_key() { +sub gnupg_generate_key { my ($list, $listname) = @_; my ($key_name, $key_comment, $key_size, $key_expires); @@ -1628,7 +1768,8 @@ sub gnupg_generate_key() { # ------------------------------------------------------------------------ -sub gnupg_remove_key() { +sub gnupg_remove_key { + my ($list) = @_; my $removed = 0; @@ -1902,7 +2043,7 @@ sub webauth { return (0==0) if (! -e "$WEBUSERS_FILE"); # if there was no user authentication, then everything is allowed - return (0==0) if ($ENV{'REMOTE_USER'} eq ''); + return (0==0) if (!defined($ENV{REMOTE_USER}) or ($ENV{REMOTE_USER} eq '')); # Read authentication level from webusers file. Format of this file is # somewhat similar to the unix groups file @@ -1919,7 +2060,7 @@ sub webauth { while() { if (/^($listname|ALL):/im) { # the following line should be synchronized with the webauth_create_allowed sub - if (/^[^:]*:(|.*[\s,])($ENV{'REMOTE_USER'}|ALL)(,|\s|$)/m) { + if (/^[^:]*:(|.*[\s,])($ENV{REMOTE_USER}|ALL)(,|\s|$)/m) { close USERS; return (0==0); } @@ -1937,7 +2078,7 @@ sub webauth_create_allowed { return (0==0) if (defined($opt_c)); # if there was no user authentication, then everything is allowed - return (0==0) if ($ENV{'REMOTE_USER'} eq ''); + return (0==0) if (!defined($ENV{REMOTE_USER}) || ($ENV{REMOTE_USER} eq '')); # Check if webusers file exists - if not, then access is granted return (0==0) if (! -e "$WEBUSERS_FILE"); @@ -1980,6 +2121,7 @@ sub get_available_interface_languages { # --------------------------------------------------------------------------- sub check_interface_language { + my ($language) = @_; my %languages = &get_available_interface_languages(); return defined($languages{$language}); @@ -1988,6 +2130,7 @@ sub check_interface_language { # --------------------------------------------------------------------------- sub check_list_language { + my ($list, $lang) = @_; my $found = 0; my $item; @@ -2016,6 +2159,7 @@ sub safely_import_module { # --------------------------------------------------------------------------- sub fatal_error() { + my $text = shift; print "Content-Type: text/html; charset=utf-8\n\n"; diff --git a/scripts/update_po_files.py b/scripts/update_po_files.py index 8a65375..58c854c 100755 --- a/scripts/update_po_files.py +++ b/scripts/update_po_files.py @@ -55,18 +55,21 @@ except ImportError, errMsg: sys.exit(1) -LANGUAGE_FILE = 'language.hdf' +HDF_DIR = 'lang' ## name of the main domain and prefix for all plugin domains GETTEXT_DOMAIN = 'ezmlm-web' ## set the msgstrs for this language to the value of the respective msgids DEFAULT_LANG = 'en' -LANG_DIR = 'intl' +PO_DIR = 'intl' ## mail adress for translation bugs MAIL_ADDRESS = 'devel@sumpfralle.de' ## the complete list of languages wastes a lot of space - for now we use only a few #ALL_LANGUAGES = "af aka am ar bn ca cs da de el en es et eu fa fi fr fur gl he hi hr hu hy is it ja ka kg ko ku lt lv mr ms mt nb ne nl nn ns pa pl pt ru sl sr st sv tr uk ve vi xh".split(" ") ALL_LANGUAGES = "cs da de en es fi fr hu it ja nl pl pt ru sl sv".split(" ") +## use subversion for reverting? +USE_SVN = False + # --------------=-=-=- functions -=-=-=-------------------- def revert_if_unchanged(po_file): @@ -99,9 +102,9 @@ def revert_if_unchanged(po_file): proc.wait() -def process_language_file(hdf_file, po_dir, textDomain): +def generate_po_files(hdf_file, po_dir, textDomain): ## prepare hdf - if not os.path.isfile(hdf_file) or not os.access(hdf_file, os.R_OK): + if ((not os.path.isfile(hdf_file)) or (not os.access(hdf_file, os.R_OK))): sys.stderr.write("Unable to read the hdf file: %s\n" % hdf_file) return if not os.path.isdir(po_dir): @@ -157,7 +160,9 @@ def process_language_file(hdf_file, po_dir, textDomain): for ld in ALL_LANGUAGES: if not os.path.isdir(os.path.join(po_dir,ld)): os.mkdir(os.path.join(po_dir, ld)) - po_file = os.path.join(po_dir, ld, "%s.po" % textDomain) + if not os.path.isdir(os.path.join(po_dir,ld, 'LC_MESSAGES')): + os.mkdir(os.path.join(po_dir, ld, 'LC_MESSAGES')) + po_file = os.path.join(po_dir, ld, 'LC_MESSAGES', "%s.po" % textDomain) if not os.path.isfile(po_file): translate.convert.pot2po.convertpot(file(pot_file), file(po_file,'w'), None) else: @@ -177,7 +182,8 @@ def process_language_file(hdf_file, po_dir, textDomain): po_content.removeduplicates() po_content.removeblanks() po_content.savefile(po_file) - revert_if_unchanged(po_file) + if USE_SVN: + revert_if_unchanged(po_file) ## make it writeable for pootle os.chmod(po_file, 0666) ## compile po file @@ -185,6 +191,67 @@ def process_language_file(hdf_file, po_dir, textDomain): translate.tools.pocompile.convertmo(file(po_file), file(mo_file,'w'), file(pot_file)) +def generate_translated_hdf_files(orig_hdf_file, po_dir, hdf_dir, textdomain): + for lang in ALL_LANGUAGES: + if lang != DEFAULT_LANG: + generate_translated_hdf_file(orig_hdf_file, po_dir, hdf_dir, textdomain, lang) + +def generate_translated_hdf_file(orig_hdf_file, po_dir, hdf_dir, textdomain, language): + import gettext + ## prepare original hdf + if ((not os.path.isfile(orig_hdf_file)) or (not os.access(orig_hdf_file, os.R_OK))): + sys.stderr.write("Unable to read the hdf file: %s\n" % orig_hdf_file) + return + hdf = neo_util.HDF() + hdf.readFile(orig_hdf_file) + ## name of new hdf file + new_hdf_file = os.path.join(hdf_dir, language + '.hdf') + ## create translation object + translator = gettext.translation( + textdomain, + localedir=po_dir, + languages=[language]) + ## translate entries + ## count the number of translated items - so we can decide later, if we + ## want to create the language file + translate_count = 0 + def walk_hdf(prefix, node): + def addHdfItem(hdf_node): + ## ignore hdf values with a "LINK" attribute + for (key,value) in hdf_node.attrs(): + if key == "LINK": + return + if not hdf_node.value(): + return + translated = translator.gettext(hdf_node.value()) + if translated: + translate_count += 1 + hdf.setValue("%s%s" % (prefix, hdf_node.name()), translated) + else: + hdf.setValue("%s%s" % (prefix, hdf_node.name()), hdf_node.value()) + while node: + if node.name(): + new_prefix = prefix + node.name() + '.' + else: + new_prefix = prefix + ## as the attribute feature of clearsilver does not work yet, we + ## have to rely on magic names to prevent the translation of links + if not (new_prefix.endswith(".Link.Rel.") \ + or new_prefix.endswith(".Link.Prot.") \ + or new_prefix.endswith(".Link.Abs.") \ + or new_prefix.endswith(".Link.Attr1.name.") \ + or new_prefix.endswith(".Link.Attr1.value.") \ + or new_prefix.endswith(".Link.Attr2.name.") \ + or new_prefix.endswith(".Link.Attr2.value.")): + addHdfItem(node) + walk_hdf(new_prefix, node.child()) + node = node.next() + walk_hdf("", hdf) + ## if there was at least one valid translation, then we should write + ## the language file + if translate_count > 0: + hdf.writeFile(new_hdf_file) + # ----------------=-=-=- main -=-=-=----------------------- @@ -195,8 +262,14 @@ if __name__ == "__main__": ## the project directory is the parent of the directory of this script PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),os.path.pardir)) - process_language_file( - os.path.join(PROJECT_DIR, 'templates', LANGUAGE_FILE), - os.path.join(PROJECT_DIR, LANG_DIR), + generate_po_files( + os.path.join(PROJECT_DIR, HDF_DIR, DEFAULT_LANG + '.hdf'), + os.path.join(PROJECT_DIR, PO_DIR), + GETTEXT_DOMAIN) + + generate_translated_hdf_files( + os.path.join(PROJECT_DIR, HDF_DIR, DEFAULT_LANG + '.hdf'), + os.path.join(PROJECT_DIR, PO_DIR), + os.path.join(PROJECT_DIR, HDF_DIR), GETTEXT_DOMAIN) diff --git a/template/language.hdf b/template/language.hdf index 1fab457..49e6d37 100644 --- a/template/language.hdf +++ b/template/language.hdf @@ -26,6 +26,7 @@ Lang { Properties = Properties of Language = Language Help = Help (external) + SubscribeLog = Subscriber's log } @@ -47,6 +48,7 @@ Lang { ListDelete = Delete list FileSelect = Choose a file for editing FileEdit = Editing file + SubscribeLog = Subscription events GnupgConvert = Encryption GnupgPublic = Public keys GnupgSecret = Secret keys @@ -59,6 +61,7 @@ Lang { Create = Create list ConfirmDeletion = Delete the list DeleteAddress = Delete address(es) + DownloadSubscribersList = Download subscribers AddAddress = Add address(es) UpdateConfiguration = Update configuration UpdateGnupg = Update keyring @@ -112,10 +115,12 @@ Lang { InvalidListName = The name of the list contains invalid characters ReservedListName = This listname may not be used as it is reserved for internal purposes EmptyListName = The name of the list may not be empty + EmptyList = This list has no subscribers. InvalidLocalPart = The local part of the list address is not valid RequiresIDX5 = This action requires ezmlm-idx v5.0 or higher. ResetFileIsDefault = There is no customized text file, that can be removed. ResetFile = Removal of custimized text file failed. + LogFile = Reading of log file failed. GnupgNoKeyFile = There was no key file selected for upload! GnupgDelKey = Removal of (at least) one key failed! GnupgNoKeySelected = There was no key selected to be removed! @@ -281,6 +286,7 @@ Lang { TextFileReset = Discard customized text TextFileInfo = Useful placeholders AvailableLists = Available lists + SubscribeLog = Events GnupgConvert = Encryption support GnupgPublicKeys = Public keys of this list GnupgSecretKeys = Secret keys of this list diff --git a/template/nav.cs b/template/nav.cs index a33a606..278eab7 100644 --- a/template/nav.cs +++ b/template/nav.cs @@ -17,7 +17,7 @@ 0) && (UI.Navigation.ListSelect == 1)) || (Data.Permissions.Create && (UI.Navigation.ListCreate == 1)) ?> -
+

  • @@ -164,6 +164,11 @@ title=""> +
  • class="nav_active" + href="" + title="">
  • class="nav_active" href="" @@ -172,13 +177,13 @@
  • -
    +

  • -
    +

  • diff --git a/template/show_subscription_log.cs b/template/show_subscription_log.cs new file mode 100644 index 0000000..4e9b14c --- /dev/null +++ b/template/show_subscription_log.cs @@ -0,0 +1,18 @@ +
    +

    +
    + +
    + + + + + + + + + + +
    +
    + diff --git a/template/subscribers.cs b/template/subscribers.cs index 2911e44..393cb37 100644 --- a/template/subscribers.cs +++ b/template/subscribers.cs @@ -88,21 +88,32 @@
      - 15 ?> - - - - -
    • -
    • -
    • -
    • -
    + 15 ?> + + + + +
  • +
  • +
  • +
  • +
  • + + + + +
  • +
    " enctype="multipart/form-data"> diff --git a/template/ui/normal.hdf b/template/ui/normal.hdf index adb99c5..f23b478 100644 --- a/template/ui/normal.hdf +++ b/template/ui/normal.hdf @@ -30,6 +30,7 @@ UI { TextEdit = 1 ListDelete = 1 + SubscribeLog = 1 GnupgConvert = 1 Language = 1 Interface = 1