/
usr
/
share
/
usermin
/
filter
/
/usr/share/usermin/filter
mkdir
upload
Name
Size
Mode
Actions
images/
-
0755
rm
lang/
-
0755
rm
aliases-lib.pl
8122
0755
edit
dl
rm
autoreply-file-lib.pl
2339
0755
edit
dl
rm
CHANGELOG
3033
0644
edit
dl
rm
config
218
0644
edit
dl
rm
config.info
489
0644
edit
dl
rm
config.info.ca
575
0644
edit
dl
rm
config.info.cs
370
0644
edit
dl
rm
config.info.de
594
0644
edit
dl
rm
config.info.el
404
0644
edit
dl
rm
config.info.hu
0
0644
edit
dl
rm
config.info.nl
584
0644
edit
dl
rm
config.info.no
519
0644
edit
dl
rm
config.info.pl
604
0644
edit
dl
rm
delete.cgi
431
0755
edit
dl
rm
down.cgi
261
0755
edit
dl
rm
edit.cgi
9263
0755
edit
dl
rm
edit_auto.cgi
3187
0755
edit
dl
rm
edit_forward.cgi
1210
0755
edit
dl
rm
filter-lib.pl
18136
0755
edit
dl
rm
index.cgi
4909
0755
edit
dl
rm
module.info
260
0644
edit
dl
rm
module.info.ar
0
0644
edit
dl
rm
module.info.ar.auto
175
0644
edit
dl
rm
module.info.bg
0
0644
edit
dl
rm
module.info.bg.auto
195
0644
edit
dl
rm
module.info.ca
109
0644
edit
dl
rm
module.info.cs
46
0644
edit
dl
rm
module.info.cs.auto
88
0644
edit
dl
rm
module.info.da
0
0644
edit
dl
rm
module.info.da.auto
110
0644
edit
dl
rm
module.info.de
114
0644
edit
dl
rm
module.info.el
0
0644
edit
dl
rm
module.info.el.auto
289
0644
edit
dl
rm
module.info.es
0
0644
edit
dl
rm
module.info.es.auto
114
0644
edit
dl
rm
module.info.eu
0
0644
edit
dl
rm
module.info.eu.auto
120
0644
edit
dl
rm
module.info.fa
0
0644
edit
dl
rm
module.info.fa.auto
144
0644
edit
dl
rm
module.info.fi
0
0644
edit
dl
rm
module.info.fi.auto
132
0644
edit
dl
rm
module.info.fr
0
0644
edit
dl
rm
module.info.fr.auto
124
0644
edit
dl
rm
module.info.hr
0
0644
edit
dl
rm
module.info.hr.auto
124
0644
edit
dl
rm
module.info.hu
92
0644
edit
dl
rm
module.info.it
0
0644
edit
dl
rm
module.info.it.auto
100
0644
edit
dl
rm
module.info.ja
0
0644
edit
dl
rm
module.info.ja.auto
151
0644
edit
dl
rm
module.info.ko
0
0644
edit
dl
rm
module.info.ko.auto
121
0644
edit
dl
rm
module.info.ms
103
0644
edit
dl
rm
module.info.nl
34
0644
edit
dl
rm
module.info.nl.auto
75
0644
edit
dl
rm
module.info.no
37
0644
edit
dl
rm
module.info.no.auto
74
0644
edit
dl
rm
module.info.pl
134
0644
edit
dl
rm
module.info.pt
0
0644
edit
dl
rm
module.info.pt.auto
104
0644
edit
dl
rm
module.info.pt_BR
0
0644
edit
dl
rm
module.info.pt_BR.auto
110
0644
edit
dl
rm
module.info.ru
0
0644
edit
dl
rm
module.info.ru.auto
200
0644
edit
dl
rm
module.info.sk
0
0644
edit
dl
rm
module.info.sk.auto
125
0644
edit
dl
rm
module.info.sv
0
0644
edit
dl
rm
module.info.sv.auto
124
0644
edit
dl
rm
module.info.tr
0
0644
edit
dl
rm
module.info.tr.auto
127
0644
edit
dl
rm
module.info.uk
0
0644
edit
dl
rm
module.info.uk.auto
194
0644
edit
dl
rm
module.info.zh
0
0644
edit
dl
rm
module.info.zh.auto
97
0644
edit
dl
rm
module.info.zh_TW
0
0644
edit
dl
rm
module.info.zh_TW.auto
103
0644
edit
dl
rm
move.cgi
1650
0755
edit
dl
rm
save.cgi
7815
0755
edit
dl
rm
save_auto.cgi
2798
0755
edit
dl
rm
save_forward.cgi
959
0755
edit
dl
rm
up.cgi
259
0755
edit
dl
rm
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 2>/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', "<tt>$1</tt>")); } 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', "<i>$msg</i>")); } else { push(@rv, &text('aliases_type5', "<tt>$2</tt>")); } } elsif ($atype == 4 && $adesc =~ /^(.*)\/filter.pl\s+(\S+)/) { # Apply filter file push(@rv, &text('aliases_type6', "<tt>$2</tt>")); } 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', "<tt>".&html_escape($f->{'condheader'})."</tt>", "<tt>".&html_escape(&prettify_regexp("$1"))."</tt>"); } elsif ($f->{'condvalue'} =~ /^\.\*(.*)\.\*$/ || $f->{'condvalue'} =~ /^\.\*(.*)$/) { $cond = &text('index_cheader1', "<tt>".&html_escape($f->{'condheader'})."</tt>", "<tt>".&html_escape(&prettify_regexp("$1"))."</tt>"); } elsif ($f->{'condvalue'} =~ /^(.*)\.\*$/ || $f->{'condvalue'} =~ /^(.*)$/) { $cond = &text('index_cheader0', "<tt>".&html_escape($f->{'condheader'})."</tt>", "<tt>".&html_escape(&prettify_regexp("$1"))."</tt>"); } } 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', "<tt>".&html_escape($f->{'cond'})."</tt>"); } 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', "<tt>".&html_escape($f->{'action'})."</tt>"); } elsif ($f->{'actionreply'}) { $action = &text('index_areply', "<i>".&html_escape(substr( $f->{'reply'}->{'autotext'}, 0, 50))."</i>"); } 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', "<a href='../mailbox/index.cgi?id=$id'>". "$folder->{'name'}</a>"); } else { local $id = &mailboxes::folder_name($folder); if (&foreign_available("mailboxes")) { $action = &text('index_afolder', "<a href='../mailboxes/list_mail.cgi?user=". &urlize($folder->{'user'})."&folder=". $folder->{'index'}."'>$folder->{'name'}</a>"); } else { $action = &text('index_afolder', $folder->{'name'}); } } } else { $action = &text('index_afile', "<tt>$f->{'action'}</tt>"); } } 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;
Save
cmd:
run