/usr/share/usermin/filter
NameSizeModeActions
images/-0755rm
lang/-0755rm
aliases-lib.pl81220755editdlrm
autoreply-file-lib.pl23390755editdlrm
CHANGELOG30330644editdlrm
config2180644editdlrm
config.info4890644editdlrm
config.info.ca5750644editdlrm
config.info.cs3700644editdlrm
config.info.de5940644editdlrm
config.info.el4040644editdlrm
config.info.hu00644editdlrm
config.info.nl5840644editdlrm
config.info.no5190644editdlrm
config.info.pl6040644editdlrm
delete.cgi4310755editdlrm
down.cgi2610755editdlrm
edit.cgi92630755editdlrm
edit_auto.cgi31870755editdlrm
edit_forward.cgi12100755editdlrm
filter-lib.pl181360755editdlrm
index.cgi49090755editdlrm
module.info2600644editdlrm
module.info.ar00644editdlrm
module.info.ar.auto1750644editdlrm
module.info.bg00644editdlrm
module.info.bg.auto1950644editdlrm
module.info.ca1090644editdlrm
module.info.cs460644editdlrm
module.info.cs.auto880644editdlrm
module.info.da00644editdlrm
module.info.da.auto1100644editdlrm
module.info.de1140644editdlrm
module.info.el00644editdlrm
module.info.el.auto2890644editdlrm
module.info.es00644editdlrm
module.info.es.auto1140644editdlrm
module.info.eu00644editdlrm
module.info.eu.auto1200644editdlrm
module.info.fa00644editdlrm
module.info.fa.auto1440644editdlrm
module.info.fi00644editdlrm
module.info.fi.auto1320644editdlrm
module.info.fr00644editdlrm
module.info.fr.auto1240644editdlrm
module.info.hr00644editdlrm
module.info.hr.auto1240644editdlrm
module.info.hu920644editdlrm
module.info.it00644editdlrm
module.info.it.auto1000644editdlrm
module.info.ja00644editdlrm
module.info.ja.auto1510644editdlrm
module.info.ko00644editdlrm
module.info.ko.auto1210644editdlrm
module.info.ms1030644editdlrm
module.info.nl340644editdlrm
module.info.nl.auto750644editdlrm
module.info.no370644editdlrm
module.info.no.auto740644editdlrm
module.info.pl1340644editdlrm
module.info.pt00644editdlrm
module.info.pt.auto1040644editdlrm
module.info.pt_BR00644editdlrm
module.info.pt_BR.auto1100644editdlrm
module.info.ru00644editdlrm
module.info.ru.auto2000644editdlrm
module.info.sk00644editdlrm
module.info.sk.auto1250644editdlrm
module.info.sv00644editdlrm
module.info.sv.auto1240644editdlrm
module.info.tr00644editdlrm
module.info.tr.auto1270644editdlrm
module.info.uk00644editdlrm
module.info.uk.auto1940644editdlrm
module.info.zh00644editdlrm
module.info.zh.auto970644editdlrm
module.info.zh_TW00644editdlrm
module.info.zh_TW.auto1030644editdlrm
move.cgi16500755editdlrm
save.cgi78150755editdlrm
save_auto.cgi27980755editdlrm
save_forward.cgi9590755editdlrm
up.cgi2590755editdlrm
Edit: /usr/share/usermin/filter/filter-lib.pl (18136B)
# Functions for creating simple mail filtering rules # XXX use same virtualmin spam detection trick for spam module BEGIN { push(@INC, ".."); }; use WebminCore; &init_config(); do 'aliases-lib.pl'; do 'autoreply-file-lib.pl'; if (&get_product_name() eq 'usermin') { # If configured, check if this user has virtualmin spam filtering # enabled before switching away from root $autoreply_cmd = "$config_directory/forward/autoreply.pl"; if ($< == 0) { if ($config{'virtualmin_spam'} && -x $config{'virtualmin_spam'}) { local $out = &backquote_command( "$config{'virtualmin_spam'} $remote_user ". "/dev/null"); $out =~ s/\r|\n//g; if ($out =~ /\d/) { # Yes - we can show the user this $global_spamassassin = 2; $virtualmin_domain_id = $out; } } # Copy autoreply.pl to /etc/usermin/forward, while we # are still root local $autoreply_src = "$root_directory/forward/autoreply.pl"; local @rst = stat($autoreply_src); local @cst = stat($autoreply_cmd); if (!@cst || $cst[7] != $rst[7]) { ©_source_dest($autoreply_src, $autoreply_cmd); &set_ownership_permissions( undef, undef, 0755, $autoreply_cmd); } &switch_to_remote_user(); } &create_user_config_dirs(); &foreign_require("mailbox"); } else { # Running under Webmin, so different modules are used &foreign_require("mailboxes"); &foreign_require("usermin"); $mail_system_module = $mailboxes::config{'mail_system'} == 1 ? "postfix" : $mailboxes::config{'mail_system'} == 2 ? "qmailadmin" : "sendmail"; $autoreply_cmd = "$config_directory/$mail_system_module/autoreply.pl"; $user_autoreply_cmd = "$usermin::config{'usermin_dir'}/forward/autoreply.pl"; } &foreign_require("spam"); &foreign_require("procmail"); # list_filters([file]) # Returns a list of filter objects, which have a 1-to-1 correlation with # procmail recipes. Any recipes too complex for parsing are not included. sub list_filters { local ($file) = @_; local @rv; local @pmrc = &procmail::parse_procmail_file($file || $procmail::procmailrc); foreach my $r (@pmrc) { # Check for un-supported recipes local @conds = @{$r->{'conds'}}; if ($r->{'block'} || $r->{'name'}) { next; } # Check for flags local %flags = map { $_, 1 } @{$r->{'flags'}}; # Check for bounce condition local $nobounce; if (@conds && $conds[0]->[0] eq '!' && $conds[0]->[1] =~ /FROM_MAILER/) { $nobounce = 1; shift(@conds); } next if (@conds > 1); # Multiple conditions are not supported # Work out condition type local ($condtype, $cond); if (@conds) { ($condtype, $cond) = @{$conds[0]}; if ($condtype && $condtype ne "<" && $condtype ne ">") { # Unsupported conditon type next; } } # Work out action type local ($actionspam, $actionreply); if ($r->{'type'} eq '|' && $r->{'action'} =~ /spamassassin|spamc/) { $actionspam = 1; } elsif ($r->{'type'} eq '|' && ($r->{'action'} =~ /^\Q$autoreply_cmd\E\s+(\S+)/ || $user_autoreply_cmd && $user_autoreply_cmd && $r->{'action'} =~ /^\Q$user_autoreply_cmd\E\s+(\S+)/)) { $actionreply = $1; } elsif ($r->{'type'} && $r->{'type'} ne '!') { # Unsupported action type next; } # Finally create the simple object local $simple = { 'condtype' => $condtype, 'cond' => $cond, 'nocond' => !scalar(@conds), 'body' => $flags{'B'}, 'continue' => $flags{'c'}, 'actiontype' => $r->{'type'}, 'action' => $r->{'action'}, 'nobounce' => $nobounce, 'index' => scalar(@rv), 'recipe' => $r }; # Set spam flag if ($actionspam) { $simple->{'actionspam'} = 1; delete($simple->{'actiontype'}); delete($simple->{'action'}); } # Check for throw away if ($simple->{'actiontype'} eq '' && $simple->{'action'} eq '/dev/null') { $simple->{'actionthrow'} = 1; delete($simple->{'actiontype'}); delete($simple->{'action'}); } # Check for default delivery if ($simple->{'actiontype'} eq '' && $simple->{'action'} eq '$DEFAULT') { $simple->{'actiondefault'} = 1; delete($simple->{'actiontype'}); delete($simple->{'action'}); } # Read autoreply file if ($actionreply) { $simple->{'actionreply'} = $actionreply; $simple->{'reply'} = { }; &read_autoreply($actionreply, $simple->{'reply'}); delete($simple->{'actiontype'}); delete($simple->{'action'}); } # Split condition regexp into header and value, if possible if ($simple->{'condtype'} ne '<' && $simple->{'condtype'} ne '>' && !$simple->{'body'} && $simple->{'cond'} =~ /^\^?([a-zA-Z0-9\-]+):\s*(.*)/) { local ($h, $v) = ($1, $2); if ($h eq "X-Spam-Status" && $v eq "Yes") { # Special case for spam detection $simple->{'condspam'} = 1; } elsif ($h eq "X-Spam-Level" && $v =~ /^(\\\*)+$/) { # Spam above some level $simple->{'condlevel'} = length($v)/2; } else { # Match on some header $simple->{'condheader'} = $h; $simple->{'condvalue'} = $v; } delete($simple->{'cond'}); } push(@rv, $simple); } return @rv; } # create_filter(&filter) # Create a new filter by adding a procmail recipe sub create_filter { local ($filter) = @_; local $recipe = { }; &update_filter_recipe($filter, $recipe); &procmail::create_recipe($recipe); &setup_forward_procmail(); } # modify_filter(&filter) # Change a filter by modifying the underlying procmail recipe sub modify_filter { local ($filter) = @_; &update_filter_recipe($filter, $filter->{'recipe'}); &procmail::modify_recipe($filter->{'recipe'}); &setup_forward_procmail(); } # insert_filter(&filter) # Like create_filter, but adds to the top of the .procmailrc sub insert_filter { local ($filter) = @_; local $recipe = { }; &update_filter_recipe($filter, $recipe); local @pmrc = &procmail::parse_procmail_file( $filter->{'file'} || $procmail::procmailrc); if (@pmrc) { &procmail::create_recipe_before($recipe, $pmrc[0]); } else { &procmail::create_recipe($recipe); } &setup_forward_procmail(); } # update_filter_recipe(&filter, &recipe) # Update a procmail recipe based on some filter sub update_filter_recipe { local ($filter, $recipe) = @_; # Set condition section local @conds; local @flags; if ($filter->{'condspam'}) { @conds = ( [ "", "X-Spam-Status: Yes" ] ); } elsif ($filter->{'condlevel'}) { local $stars = join("", map { "\\*" } (1..$filter->{'condlevel'})); @conds = ( [ "", "^"."X-Spam-Level: $stars" ] ); } elsif ($filter->{'condheader'}) { @conds = ( [ "", "^".$filter->{'condheader'}.": ". $filter->{'condvalue'} ] ); } elsif ($filter->{'condtype'} eq '<' || $filter->{'condtype'} eq '>') { @conds = ( [ $filter->{'condtype'}, $filter->{'cond'} ] ); } elsif ($filter->{'cond'}) { @conds = ( [ "", $filter->{'cond'} ] ); } # Set action section if ($filter->{'actionspam'}) { $recipe->{'type'} = '|'; $recipe->{'action'} = &spam::get_procmail_command(); push(@flags, "f", "w"); } elsif ($filter->{'actionthrow'}) { $recipe->{'type'} = ''; $recipe->{'action'} = '/dev/null'; } elsif ($filter->{'actiondefault'}) { $recipe->{'type'} = ''; $recipe->{'action'} = '$DEFAULT'; } elsif ($filter->{'actionreply'}) { $recipe->{'type'} = '|'; $recipe->{'action'} = "$autoreply_cmd $filter->{'reply'}->{'autoreply'} $remote_user"; &write_autoreply($filter->{'reply'}->{'autoreply'}, $filter->{'reply'}); } else { $recipe->{'type'} = $filter->{'actiontype'}; $recipe->{'action'} = $filter->{'action'}; local $folder = &file_to_folder($filter->{'action'}, [ ], undef, 1); if ($recipe->{'type'} eq '' && $folder->{'type'} == 1) { # Enable locking for file delivery $recipe->{'lockfile'} ||= ""; } if ($filter->{'actiontype'} eq '!' && $filter->{'nobounce'}) { # Add condition to suppress forwarding of bounces unshift(@conds, [ '!', '^FROM_MAILER' ]); } } $recipe->{'conds'} = \@conds; # Set flags push(@flags, "B") if ($filter->{'body'}); push(@flags, "c") if ($filter->{'continue'}); $recipe->{'flags'} = [ &unique(@flags) ]; } # delete_filter(&filter) # Delete a filter by removing the underlying procmail rule sub delete_filter { local ($filter) = @_; &procmail::delete_recipe($filter->{'recipe'}); &setup_forward_procmail(); if ($filter->{'actionreply'} && !-d $filter->{'actionreply'}) { &unlink_file($filter->{'actionreply'}); } } # swap_filters(&filter1, &filter2) # Swap two filters in the config file sub swap_filters { local ($filter1, $filter2) = @_; &procmail::swap_recipes($filter1->{'recipe'}, $filter2->{'recipe'}); &setup_forward_procmail(); } # file_to_folder(file, &folders, [homedir], [fake-if-missing]) # Given a path like mail/foo or ~/mail/foo or $HOME/mail/foo or # /home/bob/mail/foo, returns the folder object for it. sub file_to_folder { local ($file, $folders, $home, $fake) = @_; $home ||= $remote_user_info[7]; $file =~ s/^\~/$home/; $file =~ s/^\$HOME/$home/; if ($file !~ /^\//) { $file = "$home/$file"; } local ($folder) = grep { $_->{'file'} eq $file || $_->{'file'}.'/' eq $file } @$folders; if (!$folder && $fake) { # Create a fake folder object to match $folder = { 'file' => $file, 'type' => 1, 'fake' => 1 }; if ($folder->{'file'} =~ s/\/$//) { $folder->{'type'} = 2; } $folder->{'file'} =~ /\/\.?([^\/]+)$/; $folder->{'name'} = $1; if (lc($folder->{'name'}) eq 'spam') { $folder->{'spam'} = 1; $folder->{'name'} = "Spam"; } } return $folder; } # get_global_spamassassin() # Returns true if spamasassin is run globally sub get_global_spamassassin { return $global_spamassassin if ($global_spamassassin); local @recipes = &procmail::parse_procmail_file( $spam::config{'global_procmailrc'}); return &spam::find_spam_recipe(\@recipes) ? 1 : 0; } # get_global_spam_path() # Returns the global path to which spam is delivered, typically by a # Virtualmin per-domain procmail file sub get_global_spam_path { if ($virtualmin_domain_id) { # Read the Virtualmin procmailrc for the domain local $vmpmrc = "$config{'virtualmin_config'}/procmail/". $virtualmin_domain_id; local @vmrecipes = &procmail::parse_procmail_file($vmpmrc); local $spamrec = &spam::find_file_recipe(\@vmrecipes); if ($spamrec) { return $spamrec->{'action'}; } } # Also check the global /etc/procmailrc local @recipes = &procmail::parse_procmail_file( $spam::config{'global_procmailrc'}); local $spamrec = &spam::find_file_recipe(\@recipes); if ($spamrec) { return $spamrec->{'action'}; } else { return undef; } } # get_global_spam_delete() # Returns the global score above which spam is deleted, typically by a # Virtualmin per-domain procmail file sub get_global_spam_delete { if ($virtualmin_domain_id) { # Read the Virtualmin procmailrc for the domain local $vmpmrc = "$config{'virtualmin_config'}/procmail/". $virtualmin_domain_id; local @vmrecipes = &procmail::parse_procmail_file($vmpmrc); local ($spamrec, $level) = &spam::find_delete_recipe(\@vmrecipes); if ($spamrec) { return $level; } } # Also check the global /etc/procmailrc local @recipes = &procmail::parse_procmail_file( $spam::config{'global_procmailrc'}); local ($spamrec, $level) = &spam::find_delete_recipe(\@recipes); if ($spamrec) { return $level; } else { return undef; } } sub has_spamassassin { return &foreign_installed("spam"); } # get_override_alias() # Check for any mail alias matching this user, which is defined in /etc/aliases # as an entry matching his username. sub get_override_alias { local @afiles = split(/\t+/, $config{'alias_files'}); foreach my $alias (&list_aliases(\@afiles)) { if ($alias->{'name'} eq $remote_user && $alias->{'enabled'}) { return $alias; } } return undef; } # describe_alias_dest(&values) # Returns a text description of some alias destination sub describe_alias_dest { local ($values) = @_; local @rv; foreach my $v (@$values) { local ($atype, $adesc) = &alias_type($v); if ($atype == 1 && $adesc eq "\\$remote_user") { push(@rv, $text{'aliases_your'}); } elsif ($atype == 1 && $adesc =~ /^\\(\S+)$/) { push(@rv, &text('aliases_other', "$1")); } elsif ($atype == 3 && $adesc eq "/dev/null") { push(@rv, $text{'aliases_delete'}); } elsif ($atype == 4 && $adesc =~ /^(.*)\/autoreply.pl\s+(\S+)/) { # Autoreply from file .. check contents local $auto = &read_file_contents("$2"); if ($auto) { local @lines = grep { !/^(\S+):/} split(/\r?\n/, $auto); local $msg = join(" ", @lines); $msg = substr($msg, 0, 100)." ..." if (length($msg) > 100); push(@rv, &text('aliases_auto', "$msg")); } else { push(@rv, &text('aliases_type5', "$2")); } } elsif ($atype == 4 && $adesc =~ /^(.*)\/filter.pl\s+(\S+)/) { # Apply filter file push(@rv, &text('aliases_type6', "$2")); } else { push(@rv, &text('aliases_type'.$atype, $adesc)); } } return @rv; } # is_table_comment(line, [force-prefix]) # Returns the comment text if a line contains a comment, like # foo. This is # defined only because functions in aliases-lib.pl call it. sub is_table_comment { local ($line, $force) = @_; if ($force) { return $line =~ /^\s*#+\s*Webmin:\s*(.*)/ ? $1 : undef; } else { return $line =~ /^\s*#+\s*(.*)/ ? $1 : undef; } } # describe_condition(&filter) # Returns a human-readable description of the filter condition, and a flag # indicating if this is an 'always' condition. sub describe_condition { local ($f) = @_; local $cond; local $lastalways = 0; if ($f->{'condspam'}) { $cond = $text{'index_cspam'}; } elsif ($f->{'condlevel'}) { $cond = &text('index_clevel', $f->{'condlevel'}); } elsif ($f->{'condheader'}) { if ($f->{'condvalue'} =~ /^\.\*(.*)\$$/) { $cond = &text('index_cheader2', "".&html_escape($f->{'condheader'})."", "".&html_escape(&prettify_regexp("$1")).""); } elsif ($f->{'condvalue'} =~ /^\.\*(.*)\.\*$/ || $f->{'condvalue'} =~ /^\.\*(.*)$/) { $cond = &text('index_cheader1', "".&html_escape($f->{'condheader'})."", "".&html_escape(&prettify_regexp("$1")).""); } elsif ($f->{'condvalue'} =~ /^(.*)\.\*$/ || $f->{'condvalue'} =~ /^(.*)$/) { $cond = &text('index_cheader0', "".&html_escape($f->{'condheader'})."", "".&html_escape(&prettify_regexp("$1")).""); } } elsif ($f->{'condtype'} eq '<' || $f->{'condtype'} eq '>') { $cond = &text('index_csize'.$f->{'condtype'}, &nice_size($f->{'cond'})); } elsif ($f->{'cond'}) { $cond = &text($f->{'body'} ? 'index_cre2' : 'index_cre', "".&html_escape($f->{'cond'}).""); } else { $cond = $text{'index_calways'}; if (!$f->{'continue'} && !$f->{'actionspam'}) { $lastalways = 1; } } return wantarray ? ( $cond, $lastalways ) : $cond; } # prettify_regexp(string) # If a string contains only \ quoted special characters, remove the \s # Also, undo any mimewords encoding. sub prettify_regexp { my ($str) = @_; my $re = $str; $re =~ s/\\./x/g; if ($re =~ /^[a-zA-Z0-9_ ]*$/) { $str =~ s/\\(.)/$1/g; } if (&get_product_name() eq "webmin") { &foreign_require("mailboxes"); return &mailboxes::decode_mimewords($str); } elsif (&get_product_name() eq "usermin") { &foreign_require("mailbox"); return &mailbox::decode_mimewords($str); } else { return $str; } } # describe_action(&filter, &folder, [homedir]) # Returns a human-readable description for the delivery action for some folder sub describe_action { local ($f, $folders, $home) = @_; local $action; if ($f->{'actionspam'}) { $action = $text{'index_aspam'}; } elsif ($f->{'actionthrow'}) { $action = $text{'index_athrow'}; } elsif ($f->{'actiondefault'}) { $action = $text{'index_adefault'}; } elsif ($f->{'actiontype'} eq '!') { $action = &text('index_aforward', "".&html_escape($f->{'action'}).""); } elsif ($f->{'actionreply'}) { $action = &text('index_areply', "".&html_escape(substr( $f->{'reply'}->{'autotext'}, 0, 50)).""); } else { # Work out what folder local $folder = &file_to_folder($f->{'action'}, $folders, $home); if ($folder) { if (&get_product_name() eq 'usermin') { local $id = &mailbox::folder_name($folder); $action = &text('index_afolder', "". "$folder->{'name'}"); } else { local $id = &mailboxes::folder_name($folder); if (&foreign_available("mailboxes")) { $action = &text('index_afolder', "$folder->{'name'}"); } else { $action = &text('index_afolder', $folder->{'name'}); } } } else { $action = &text('index_afile', "$f->{'action'}"); } } if ($f->{'continue'}) { $action = &text('index_acontinue', $action); } return $action; } # can_simple_autoreply() # Returns 1 if the current filter rules are simple enough to allow an autoreply # to be added or removed. sub can_simple_autoreply { return 1; # Always true for now } # can_simple_forward() # Returns 1 if the current filter rules are simple enough to allow a mail # forwarder to be added or removed sub can_simple_forward { return 1; # Always can for now } # no_user_procmailrc() # Returns 1 if /etc/procmailrc has a recipe to always deliver to the user's # mailbox, which prevents this module from configuring anything useful sub no_user_procmailrc { local %sconfig = &foreign_config("spam"); local @recipes = &procmail::parse_procmail_file( $sconfig{'global_procmailrc'}); local ($force) = grep { $_->{'action'} eq '$DEFAULT' && !@{$_->{'conds'}} } @recipes; return $force; } # setup_forward_procmail() # If configured, create a .forward file that runs procmail (if not setup yet) sub setup_forward_procmail { return 0 if (!$config{'forward_procmail'}); return 0 if (!$module_info{'usermin'}); local $fwdfile = "$remote_user_info[7]/.forward"; local $procmail = &has_command("procmail"); return 0 if (!$procmail); local $lref = &read_file_lines($fwdfile); local $found; foreach my $l (@$lref) { if ($l =~ /\Q$procmail\E/) { $found++; } } if ($found) { &unflush_file_lines($fwdfile); } else { # Add procmail call push(@$lref, "|$procmail"); &flush_file_lines($fwdfile); } } 1;