--- amavisd.conf~ Thu Nov 21 22:35:33 2002 +++ amavisd.conf Thu Nov 21 23:08:20 2002 @@ -1,4 +1,4 @@ -#use strict; +use strict; # Configuration file for amavisd-new # @@ -37,7 +37,7 @@ # Set the user and group to which the daemon will change when started as root: $daemon_user = 'vscan'; # (no default (undef)) -$daemon_group = 'sweep'; # (no default (undef)) +$daemon_group = 'amavis'; # (no default (undef)) # Runtime directory (no trailing slash, defaults to '/var/amavis') $TEMPBASE = '/var/amavis'; @@ -86,7 +86,7 @@ # and see below what these two lookup list really mean. # # @bypass_virus_checks_acl = qw( . ); # uncomment to DISABLE ANTI-VIRUS code -# @bypass_spam_checks_acl = qw( . ); # uncommend to DISABLE ANTI-SPAM code +# @bypass_spam_checks_acl = qw( . ); # uncomment to DISABLE ANTI-SPAM code # # Any setting can be changed with a new assignment, so make sure # you do not unintentionally override these settings further down! @@ -135,7 +135,7 @@ # (default is qw( 127.0.0.1 ) ) # when MTA (one or more) is on a different host, use the following -# @inet_acl = qw(127/8 10.1.0.1 10.1.0.2); +# @inet_acl = qw(127/8 10.1.0.1 10.1.0.2); # adjust the list as appropriate # $inet_socket_bind = undef; # bind to all IP interfaces # # Example1: @@ -226,8 +226,8 @@ # Safe to leave empty, but set it up correctly is you need features that # rely on this setting. # -#@local_domains = qw(); # default is empty, no domain is treated as local -@local_domains = qw( .example.com ); +# @local_domains = qw(); # default is empty, no domain is treated as local +# @local_domains = qw( .example.com ); # # $local_domains_re = Amavis::Lookup::RE->new( qr'[@.]example\.com$'i ); @@ -238,7 +238,7 @@ # which in turn defaults to undef (empty) ) $mailfrom_notify_admin = 'virusalert@example.com'; $mailfrom_notify_recip = 'virusalert@example.com'; -$mailfrom_notify_sender = ''; # null reserse path, like in MTA notifications +$mailfrom_notify_sender = ''; # null reverse path, like in MTA notifications $mailfrom_notify_spamadmin = 'spam.police@example.com'; # whom quarantined messages appear to be sent from (envelope sender) @@ -379,7 +379,7 @@ # Checking for banned MIME types and names. If any mail part matches, # the whole mail is rejected, much like the way viruses are handled. -# A list @banned_filename_patterns can be defined to provide a list +# A list @banned_filename_patterns_re can be defined to provide a list # of Perl regular expressions to be matched against each part's: # # * Content-Type value (both declared and effective mime-type), @@ -554,13 +554,9 @@ # does no harm, provided the $recipients_delimiter matches the setting # on the final MTA's LDA. -# $addr_extension_virus = undef; # (default is undef, same as empty) -# $addr_extension_spam = undef; # (default is undef, same as empty) -# $addr_extension_banned = undef; # (default is undef, same as empty) -# -$addr_extension_virus = 'virus'; -$addr_extension_spam = 'spam'; -$addr_extension_banned= 'banned'; +# $addr_extension_virus = 'virus'; # (default is undef, same as empty) +# $addr_extension_spam = 'spam'; # (default is undef, same as empty) +# $addr_extension_banned = 'banned'; # (default is undef, same as empty) # Delimiter between local part of the recipient address and address extension @@ -815,8 +811,9 @@ sub {chdir($TEMPBASE) or die "Can't chdir back to $TEMPBASE $!"}, ], - ['KasperskyLab AVPDaemonClient', 'avpdc', - [], [0], [3,4,5,6], qr/(?m)infected: (.+)/ ], + ['KasperskyLab AVPDaemonClient', + ['/opt/AVP/AvpDaemonClient','AvpDaemonClient','avpdc'], + '{}', [0], [3,4,5,6], qr/(?m)infected: (.+)/ ], ['H+B EDV AntiVir', 'antivir', '-allfiles -noboot -s -z {}', [0], [1], @@ -907,7 +904,9 @@ '-vexit {}', [0], [23], qr/(?m)##==>>>> VIRUS ID: CVDL (.+)/ ], - ['Trend Micro FileScanner', 'vscan', + ['Trend Micro FileScanner', ['/etc/iscan/vscan','vscan'], '-a {}/*', [0], qr/(?m)Found virus/, qr/(?m)Found virus (.+) in/ ], ); + +1; # insure a defined return --- amavisd~ Tue Nov 19 20:54:04 2002 +++ amavisd Thu Nov 21 22:56:25 2002 @@ -1,4 +1,4 @@ -#!/usr/local/bin/perl -T +#!/usr/bin/perl -T #------------------------------------------------------------------------------ # This is amavisd-new. @@ -405,7 +405,8 @@ my($config_file) = @_; -e($config_file) or die "Cannot find config file $config_file"; -r(_) or die "Cannot read config file $config_file"; - do $config_file or die "Error in config file $config_file: $@"; + do $config_file; + if ($@ ne '') { die "Error in config file $config_file: $@" } # compatibility with $mailfrom: if (!$mailfrom_notify_admin && !$mailfrom_notify_sender && !$mailfrom_notify_recip && !$mailfrom_notify_spamadmin) { @@ -1169,7 +1170,7 @@ $sth->execute(@keys); # do the query my($a_ref,$found,$match); $match = {}; while ( defined($a_ref=$sth->fetch) ) { # fetch query results - my(@names) = @{$sth->{NAME_lc}}; + my(@names) = @{$sth->{NAME_lc}}; $found = 1; $match = {}; @$match{@names} = @$a_ref; my($keyname) = @names[0]; my($keyvalue) = $a_ref->[0]; do_log(5, "lookup_sql: key($keyname)=\"$keyvalue\" matches, result=(". @@ -1177,7 +1178,10 @@ last if $found; # first match wins, the loop is for possible future use } $sth->finish(); - do_log(5, "lookup_sql, no match") if !$found; + if (!$found) { + $match = undef; + do_log(5, "lookup_sql, no match"); + } # save for future use, but only within processing of this message $self->{cache}->{$addr} = $match; section_time('lookup_sql'); @@ -2824,11 +2828,11 @@ my($entity) = shift; my($first_received); if (defined($entity)) { - my($received) = $entity->head->get('received',-1); # last Received: header + my($received) = $entity->head->get('received',-1); # last Received: $received =~ s/\n([ \t])/$1/g; # unfold $received =~ s/[\r\n]/ /g; # turn remaining CR or NL into spaces $first_received = $received; - if ($received =~ # not an exact science this parsing + if ($received =~ # not an exact science this parsing /^ (?: \( [^)]* \) | < [^>]* > | \[ [^]]* \] | [^(<\[] )*? \b from \s+ ( (?: \( [^)]* \) | < [^>]* > | \[ [^]]* \] | [^(<\[] )*? ) @@ -3108,8 +3112,9 @@ do_log(3, "Checking for banned (contents-based) file types, " . scalar(@$parts) . " parts"); for my $part (@$parts) { - my($ft) = $file_generator_object->file_type($part); - if ($ft ne '') { # file type as determined by 'file' util + for my $ft ($file_generator_object->file_type($part), + $file_generator_object->file_type_long($part) ) { + next if $ft eq ''; do_log(5, "check_for_banned ($part) - file type: $ft"); my($result,$patt) = $acl_re->lookup_re($ft); if ($result) { @@ -3204,6 +3209,7 @@ /^TIFF image data/i and $ty = '.tif'; /^MP3\b/i and $ty = '.mp3'; /^MPEG\b.*\bstream data/i and $ty = '.mpeg'; + /^RIFF.*\bAVI/ and $ty = '.avi'; /^PostScript document text/i and $ty = '.ps'; /^PDF document/i and $ty = '.pdf'; @@ -4058,7 +4064,7 @@ use vars qw($spam_level $spam_status $spam_report); -use vars qw($virus_lovers_sql $banned_files_lovers_sql $spam_lovers_sql +use vars qw($virus_lovers_sql $banned_files_lovers_sql $bypass_virus_checks_sql $bypass_spam_checks_sql $spam_tag_level_sql $spam_kill_level_sql); @@ -4185,8 +4191,6 @@ Amavis::Lookup::SQLfield->new($sql, 'virus_lover', 'B'); $banned_files_lovers_sql = Amavis::Lookup::SQLfield->new($sql, 'banned_file_lover', 'B'); - $spam_lovers_sql = - Amavis::Lookup::SQLfield->new($sql, 'spam_lover', 'B'); $bypass_virus_checks_sql = Amavis::Lookup::SQLfield->new($sql, 'bypass_virus_checks','B'); $bypass_spam_checks_sql = @@ -4598,8 +4602,7 @@ my(@offended_recips); # recipients that consider this mail spam for my $r (@{$msginfo->per_recip_data}) { if ($r->recip_done) { # already dealt with - } elsif (lookup($r->recip_addr, $spam_lovers_sql) || - $spam_level < lookup($r->recip_addr, + } elsif ($spam_level < lookup($r->recip_addr, $spam_kill_level_sql, $sa_kill_level_deflt) || lookup($r->recip_addr, \%spam_lovers, \@spam_lovers_acl, $spam_lovers_re) ) { @@ -4642,21 +4645,25 @@ prolong_timer($which_section); - if ($forward_method ne '') { - # message must be delivered explicitly + if ($forward_method ne '') { # message must be delivered explicitly $which_section = "forwarding"; - - # UNFINISHED: if spam levels are different for multiple recipients, - # they should get individually delivered mail. For now the - # first recipient dictates the spam thresholds for all !!! - - my($hdr_edits) = Amavis::Out::EditHeader->new; - $hdr_edits = add_forwarding_header_edits( - $conn,$msginfo,$hdr_edits,1,$hold); - $msginfo->header_edits($hdr_edits); # will forward only to those recipients not yet marked # as 'done' by the above content filtering sections - mail_dispatch($forward_method,$msginfo,0); + for (;;) { + my($hdr_edits) = Amavis::Out::EditHeader->new; + $hdr_edits = add_forwarding_header_edits_common( + $conn,$msginfo,$hdr_edits,$hold); + my($done_all); + my($recip_cl); # ref to a list of similar recip objects + ($hdr_edits,$recip_cl,$done_all) = + add_forwarding_header_edits_per_recip( + $conn,$msginfo,$hdr_edits,$hold); + last if !@$recip_cl; + $msginfo->header_edits($hdr_edits); + mail_dispatch($forward_method,$msginfo,0, + sub {my($r)=@_; grep {$_ eq $r} @$recip_cl} ); + last if $done_all; + } } prolong_timer($which_section); @@ -4717,12 +4724,12 @@ ($smtp_resp,$exit_code,$preserve_evidence); } -sub add_forwarding_header_edits($$$$$) { - my($conn, $msginfo, $hdr_edits, $allow_edits, $hold) = @_; +sub add_forwarding_header_edits_common($$$$) { + my($conn, $msginfo, $hdr_edits, $hold) = @_; + $hdr_edits->prepend_header('Received', received_line($conn,$msginfo,am_id(),1), - 1) if $insert_received_line && $mta_in_type ne 'milter'; - + 1) if $insert_received_line && $forward_method ne ''; # discard existing X-AMaViS-HOLD header field, only allow our own $hdr_edits->delete_header('X-Amavis-Hold'); if ($hold ne '') { @@ -4731,8 +4738,8 @@ } if ($extra_code_antivirus) { if ($X_HEADER_LINE && $X_HEADER_TAG =~ /^[!-9;-\176]+$/) { - $hdr_edits->delete_header($X_HEADER_TAG) if $allow_edits && - $remove_existing_x_scanned_headers; + if ($remove_existing_x_scanned_headers) + { $hdr_edits->delete_header($X_HEADER_TAG) } $hdr_edits->append_header($X_HEADER_TAG, $X_HEADER_LINE); } $hdr_edits->delete_header('X-Amavis-Alert'); @@ -4748,34 +4755,84 @@ $hdr_edits->append_header('X-Amavis-Alert', $msg, 1); } } - if ($extra_code_antispam) { -# NOT FINISHED: the first recipient gives the levels to all recipients !!! - my($tag_level) = lookup($msginfo->recips->[0], - $spam_tag_level_sql, $sa_tag_level_deflt); - my($kill_level) = lookup($msginfo->recips->[0], - $spam_kill_level_sql, $sa_kill_level_deflt); - $hdr_edits->edit_header('Subject', sub {$sa_spam_subject_tag . $_[1]}) - if $allow_edits && $sa_spam_subject_tag ne '' && $spam_level>=$kill_level; - $hdr_edits->delete_header('X-Spam-Status'); - $hdr_edits->delete_header('X-Spam-Flag'); - $hdr_edits->delete_header('X-Spam-Level'); - $hdr_edits->delete_header('X-Spam-Report'); - if ($spam_level >= $tag_level) { - my($full_spam_status) = + $hdr_edits; +} + +# Prepare header edits for the first not-yet-done recipient. +# Inspect remaining recipients, returning the list of recipient objects +# that are receiving the same set of header edits (so the message may be +# delivered to them in one transaction). +# +sub add_forwarding_header_edits_per_recip($$$$$) { + my($conn, $msginfo, $hdr_edits, $hold, $filter) = @_; + my(@recip_cluster); + my(@per_recip_data) = grep {!$_->recip_done && (!$filter || &$filter($_))} + @{$msginfo->per_recip_data}; + my($per_recip_data_len) = scalar(@per_recip_data); + if (!$extra_code_antispam) + { @recip_cluster = @per_recip_data; @per_recip_data = () } + my($first) = 1; my($cluster_key); + for my $r (@per_recip_data) { + my($recip) = $r->recip_addr; + my($is_local) = + lookup($recip, \@local_domains, $local_domains_re); + my($tag_level) = + lookup($recip, $spam_tag_level_sql, $sa_tag_level_deflt); + my($kill_level) = + lookup($recip, $spam_kill_level_sql, $sa_kill_level_deflt); + my($do_tag) = $is_local && $spam_level >= min($tag_level,$kill_level); + my($do_kill) = $is_local && $spam_level >= $kill_level; + $do_tag = $do_tag ? 1 : 0; $do_kill = $do_kill ? 1 : 0; # normalize + my($spam_level_bar, $full_spam_status); + if ($do_tag) { + $spam_level_bar = '*' x (min( max(int($spam_level+0.5),0), 60)); + $full_spam_status = sprintf("%s,\n hits=%3.1f\n tagged_above=%3.1f\n required=%3.1f\n %s", ($spam_level >= $kill_level ? 'Yes' : 'No'), $spam_level, $tag_level, $kill_level, $spam_status); - $hdr_edits->append_header('X-Spam-Status', $full_spam_status, 1); - if ($spam_level >= $kill_level) { + } + my($key) = join("\000", $do_tag, $do_kill, + $spam_level_bar, $full_spam_status); + if ($first) { + do_log(5, "headers CLUSTERING: NEW CLUSTER <$recip>: $do_tag, $do_kill"); + $cluster_key = $key; + } elsif ($key eq $cluster_key) { + do_log(5, "headers CLUSTERING: <$recip> joining cluster"); + } else { + do_log(5, "headers CLUSTERING: skipping <$recip> ($do_tag, $do_kill)" ); + next; + } + if ($do_tag || $do_kill) { + $hdr_edits->delete_header('X-Spam-Status'); + $hdr_edits->delete_header('X-Spam-Level'); + $hdr_edits->delete_header('X-Spam-Flag'); + $hdr_edits->delete_header('X-Spam-Report'); + if ($do_tag) { + $hdr_edits->append_header('X-Spam-Status',$full_spam_status,1); + $hdr_edits->append_header('X-Spam-Level',$spam_level_bar); + } + if ($do_kill) { + $hdr_edits->edit_header('Subject', + sub { $sa_spam_subject_tag . $_[1] } + ) if $sa_spam_subject_tag ne ''; $hdr_edits->append_header('X-Spam-Flag', 'YES'); - $hdr_edits->append_header('X-Spam-Level', - '*' x (min( max(int($spam_level+0.5),0), 40) )); # $hdr_edits->append_header('X-Spam-Report', # $spam_report, 1) if $spam_report ne ''; } } + push(@recip_cluster, $r); $first = 0; } - $hdr_edits; + my($done_all); + if (@recip_cluster == $per_recip_data_len) { + do_log(3, "headers CLUSTERING: ". + "done all $per_recip_data_len recips in one go"); + $done_all = 1; + } else { + do_log(3, sprintf("headers CLUSTERING: got %d recips out of %d: %s", + scalar(@recip_cluster), $per_recip_data_len, + join(", ", map {"<".$_->recip_addr.">"} @recip_cluster) )); + } + ($hdr_edits, \@recip_cluster, $done_all); } sub do_quarantine($$$$) { @@ -4900,7 +4957,7 @@ # suggest a name to be used as 'X-Quarantine-id:' or file name $VIRUSFILE = sprintf("spam-%s-%s-%s", $body_digest, strftime("%Y%m%d-%H%M%S",localtime), am_id()); - # NOT FINISHED: the first recipient gives the levels to all recipients !!! + # !!! the first recipient gives the levels to quarantined headers !!! my($tag_level) = lookup($msginfo->recips->[0], $spam_tag_level_sql, $sa_tag_level_deflt); my($kill_level) = lookup($msginfo->recips->[0], @@ -5023,12 +5080,12 @@ my(@fv_cmd) = split(' ',$fv); if (!@fv_cmd) { # empty, not available } elsif ($fv_cmd[0] =~ /^\//) { # absolute path - if (-f $fv_cmd[0]) { $found = join(' ',@fv_cmd) } + if (-x $fv_cmd[0] && !-d _) { $found = join(' ',@fv_cmd) } } elsif ($fv_cmd[0] =~ /\//) { # relative path die "find_program_path: relative paths not implemented: @fv_cmd\n"; } else { # walk through the specified PATH for my $p (@$path_list_ref) { - if (-f "$p/$fv_cmd[0]") { + if (-x "$p/$fv_cmd[0]" && !-d _) { $found = $p . '/' . join(' ',@fv_cmd); last; }