Server IP : 195.201.23.43 / Your IP : 3.145.28.3 Web Server : Apache System : Linux webserver2.vercom.be 5.4.0-192-generic #212-Ubuntu SMP Fri Jul 5 09:47:39 UTC 2024 x86_64 User : kdecoratie ( 1041) PHP Version : 7.1.33-63+ubuntu20.04.1+deb.sury.org+1 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals, MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /usr/share/webmin/filter/ |
Upload File : |
# 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;Private