--- amavisd~	Sat Mar 22 01:35:48 2003
+++ amavisd	Sat Mar 22 01:26:33 2003
@@ -230,7 +230,7 @@
 
 use vars @EXPORT;
 
-$myversion = 'amavisd-new-20030314';
+$myversion = 'amavisd-new-20030314-p1';
 
 $eol = $/;  # native record separator in files: \n or \r\n
 $unicode_aware = $]>=5.008 && length("\x{263a}")==1 && eval { require Encode };
@@ -936,7 +936,7 @@
     my($filename,$encoding) = @_;
     local(*INP); my($str) = ''; # must not be undef - work around a UTF8 bug
     open(INP, $filename) or die "Can't open file $filename for reading: $!";
-    if ($unicode_aware && $encoding ne '') { 
+    if ($unicode_aware && $encoding ne '') {
       binmode(INP,":encoding($encoding)")
 	or die "Can't set :encoding($encoding) on file $filename: $!";
     }
@@ -955,7 +955,7 @@
 # whitespace and comment) are ignored. Addresses are converted from quoted
 # form into internal (raw) form  and inserted as keys into a given hash,
 # with a value of 1.
-# 
+#
 sub read_hash($$) {
     my($hashref,$filename,$keep_case) = @_;
     local(*INP);
@@ -1267,22 +1267,21 @@
         # (and there is at least one Reject)
 	my($notall,$done_level); my($bounce_cnt) = 0;
 	for my $r (@$per_recip_data) {
-	    if ($r->recip_destiny == D_DISCARD) {
+	    my($dest,$resp) = ($r->recip_destiny, $r->recip_smtp_response);
+	    if ($dest == D_DISCARD) {
 		# ok, this one is discard, let's see the rest
-	    } elsif ($r->recip_smtp_response =~ /^5/ &&
-		     $r->recip_destiny == D_REJECT) {
+	    } elsif ($resp =~ /^5/ && $dest != D_BOUNCE) {
 		# prefer to report SMTP response code of genuine rejects
 		# from MTA, over internal rejects by content filters
 		if (!defined $smtp_resp || $r->recip_done > $done_level) {
-		    $smtp_resp = $r->recip_smtp_response;
-		    $done_level = $r->recip_done;
+		    $smtp_resp = $resp; $done_level = $r->recip_done;
 		}
 	    } else { $notall++; last }     # one must be Pass or Bounce, nogood
 	}
 	if ($notall) { $smtp_resp = undef }
 	if (defined $smtp_resp) {
 	    $exit_code = EX_UNAVAILABLE;
-	    do_log(5, "one_response_for_all <$sender>: some REJECTs, '$smtp_resp'");
+	    do_log(5, "one_response_for_all <$sender>: REJECTs, '$smtp_resp'");
 	}
     }
     if (!defined $smtp_resp) {
@@ -1290,19 +1289,18 @@
 	my($rej_cnt) = 0; my($bounce_cnt) = 0; my($drop_cnt) = 0;
 	for my $r (@$per_recip_data) {
 	    my($dest,$resp) = ($r->recip_destiny, $r->recip_smtp_response);
-	    if ($dest==D_PASS && $resp=~/^2/) {   # genuine successful delivery
+	    if ($resp=~/^2/ && $dest==D_PASS) {   # genuine successful delivery
 		$smtp_resp = $resp  if !defined $smtp_resp;
 	    }
-	    $rej_cnt++    if $dest == D_REJECT;
-	    $bounce_cnt++ if $dest == D_BOUNCE;
 	    $drop_cnt++   if $dest == D_DISCARD;
+	    if ($resp =~ /^5/)
+		{ if ($dest==D_BOUNCE) { $bounce_cnt++ } else { $rej_cnt++ } }
 	}
 	$exit_code = EX_OK;
 	if (!defined $smtp_resp) {  # no genuine Pass/2xx
 	    $smtp_resp = "250 2.5.0 Ok"; # declare success, we'll handle bounce
 	    if ($any_not_done) { $smtp_resp .= ", continue delivery" }
 	    elsif ($forward_method eq '') { $exit_code = 99 }  # milter DISCARD
-	    else { do_log(0, "one_response_for_all: SHOULDN'T HAPPEN") }
 	}
 	$smtp_resp .= ", but "  if $rej_cnt+$bounce_cnt+$drop_cnt > 0;
 	$smtp_resp .= join(", and ",
@@ -1989,7 +1987,7 @@
   { my($self)=shift; !@_ ? $$self[5] : ($$self[5]=shift) }
 sub recip_remote_mta # remote MTA that issued the smtp response
   { my($self)=shift; !@_ ? $$self[6] : ($$self[6]=shift) }
-sub recip_mbxname    # mailbox file name when delivered to 'local:' 
+sub recip_mbxname    # mailbox file name when delivered to 'local:'
   { my($self)=shift; !@_ ? $$self[7] : ($$self[7]=shift) }
 sub recip_whitelisted_sender  # recip considers this sender whitelisted
   { my($self)=shift; !@_ ? $$self[8] : ($$self[8]=shift) }
@@ -2371,7 +2369,7 @@
 			die "File $mbxname already exists, refuse to overwrite";
 		    }
 		    if (defined($gzip) && $mbxname =~ /\.gz$/) {
-			open(MP,"|$gzip -c -a >$mbxname")         # uses shell!
+			open(MP,"|$gzip -c >$mbxname")         # uses shell!
 			    or die "gzip failed: $!";
 			$pipe = 1;
 		    } else {
@@ -2599,7 +2597,7 @@
     my($msg) = $msginfo->mail_text; # a scalar reference, or a file handle
     my($smtp_handle,$smtp_response);
     my($smtp_code, $smtp_status, $smtp_msg, $received_cnt);
-    my($any_valid_recips) = 0; my($any_tempfail_recips) = 0; 
+    my($any_valid_recips) = 0; my($any_tempfail_recips) = 0;
     my($any_valid_recips_and_data_sent) = 0;
     if (defined($msg) && !$msg->isa('MIME::Entity')) {
 	# at this point, we have no idea what the user gave us...
@@ -2697,10 +2695,17 @@
 		# Using fixed-size reads instead of line-by-line approach
 		# makes feeding mail back to MTA (e.g. Postfix) more than
 		# twice as fast for larger mail.
+
+# to reduce the likelyhood of a qmail bare-LF bug (bare LF reported
+# when CR and LF are separated by a TCP packet boundary) one may use
+# this 'while' line, instead of the normal one:
+###		while (defined($_=$msg->getline)) {
+
 		while ( $msg->read($_,16384) > 0 ) {
 		    $smtp_handle->datasend($_)
 			or die "sending mail body from file\n";
 		}
+
 	    }
 	    section_time('fwd-data');
 	    # don't check status of dataend here, it may not yet be available
@@ -4181,10 +4186,6 @@
 	do_log(4, "Not sending DSN to empty return path");
     } elsif ($msginfo->sender_contact eq '') {
 	do_log(4, "Not sending DSN to believed-to-be-faked return path");
-    } elsif ($msginfo->mime_entity->head->get("precedence")
-	     =~ /bulk|list|junk/i ) {
-	# don't send notifications in response to mail from mailing lists
-	do_log(4, "Not sending DSN in response to bulk mail");
     } else {
 	my($from_mta,$client_ip) = ($conn->smtp_helo, $conn->client_ip);
 	my($msg)='';  # constructed dsn text according to rfc1894
@@ -4501,17 +4502,17 @@
 		# fieldtype: B=boolean, N=numeric, S=string,
 		#            B0: boolean, nonexistent field treated as false,
 		#            B1: boolean, nonexistent field treated as true
-		sub nf {Amavis::Lookup::SQLfield->new($sql_policy,@_)}; #shorthand
-		$user_id_sql		= nf('id',                 'N');
-		$virus_lovers_sql       = nf('virus_lover',        'B0');
-		$banned_files_lovers_sql= nf('banned_files_lover', 'B0');
-		$bypass_virus_checks_sql= nf('bypass_virus_checks','B0');
-		$bypass_spam_checks_sql = nf('bypass_spam_checks', 'B0');
-		$spam_tag_level_sql     = nf('spam_tag_level',     'N' );
-	        $spam_tag2_level_sql    = nf('spam_tag2_level',    'N' );
-		$spam_kill_level_sql    = nf('spam_kill_level',    'N' );
-		$spam_modifies_subj_sql = nf('spam_modifies_subj', 'B1');
-		$local_domains_sql      = nf('local',              'B1');
+		my $nf = sub {Amavis::Lookup::SQLfield->new($sql_policy,@_)}; #shorthand
+		$user_id_sql		= $nf->('id',                 'N');
+		$virus_lovers_sql       = $nf->('virus_lover',        'B0');
+		$banned_files_lovers_sql= $nf->('banned_files_lover', 'B0');
+		$bypass_virus_checks_sql= $nf->('bypass_virus_checks','B0');
+		$bypass_spam_checks_sql = $nf->('bypass_spam_checks', 'B0');
+		$spam_tag_level_sql     = $nf->('spam_tag_level',     'N' );
+	        $spam_tag2_level_sql    = $nf->('spam_tag2_level',    'N' );
+		$spam_kill_level_sql    = $nf->('spam_kill_level',    'N' );
+		$spam_modifies_subj_sql = $nf->('spam_modifies_subj', 'B1');
+		$local_domains_sql      = $nf->('local',              'B1');
 		section_time('sql-prepare');
 	    }
 	    undef @lookup_sql_dsn;   # destroy sensitive information
@@ -4857,16 +4858,15 @@
 	    for my $r (@{$msginfo->per_recip_data}) {
 		if ($r->recip_done) {    # already dealt with
 		} elsif (@virusname &&
-			 lookup($r->recip_addr,
+			 !lookup($r->recip_addr,
 				$virus_lovers_sql, $virus_lovers_ldap,
 				\%virus_lovers, \@virus_lovers_acl,
 				$virus_lovers_re)) {
-		    # not considered unwanted by this recipient
-		} elsif (@banned_filename && lookup($r->recip_addr,
+		    push(@offended_recips, $r);  # considered unwanted
+		} elsif (@banned_filename &&
+			 !lookup($r->recip_addr,
 			   $banned_files_lovers_sql, $banned_files_lovers_ldap,
 			   \%banned_files_lovers, \@banned_files_lovers_acl)) {
-		    # not considered unwanted by this recipient
-		} else {
 		    push(@offended_recips, $r);  # considered unwanted
 		}
 	    }
@@ -5001,13 +5001,22 @@
 	do_log(5, "warnsender_with_pass=$warnsender_with_pass, dsn_needed=$dsn_needed, exit=$exit_code, $smtp_resp");
 	if ($dsn_needed || $warnsender_with_pass) {
 	    ensure_mime_entity($msginfo,$fh,$tempdir,\@virusname);# need header
-	    # generate delivery status notification according to rfc1892
-	    # and rfc1894, but only if necessary
-	    my($notification) = delivery_status_notification(
-		$conn, $msginfo, $warnsender_with_pass, \%builtins,
-		@virusname+@banned_filename ? \$notify_virus_sender_templ
-		 : $considered_spam_by_some_recips ? \$notify_spam_sender_templ
-		 : \$notify_sender_templ);
+	    my($notification);
+	    if ((@virusname || @banned_filename ||
+		 $considered_spam_by_some_recips) &&
+		$msginfo->mime_entity->head->get("precedence")
+		   =~ /bulk|list|junk/i ) {
+		# don't send notifications in response to mailing lists mail
+		do_log(4, "Not sending DSN in response to bulk mail");
+	    } else {
+		# generate delivery status notification according to rfc1892
+		# and rfc1894, but only if necessary
+		$notification = delivery_status_notification(
+		    $conn, $msginfo, $warnsender_with_pass, \%builtins,
+		    @virusname+@banned_filename ? \$notify_virus_sender_templ
+			: $considered_spam_by_some_recips ? \$notify_spam_sender_templ
+			: \$notify_sender_templ);
+	    }
 	    if (defined $notification) {  # dsn needed
 		mail_dispatch($notify_method,$notification,1); # send delivery notification
 		my($n_smtp_resp, $n_exit_code, $n_dsn_needed) =
@@ -6627,7 +6636,7 @@
 		}
 		# begin SMTP transaction
 		if (!$seq) {# the first connect
-		    section_time('SMTP pre-MAIL');  
+		    section_time('SMTP pre-MAIL');
 		} else {    # establish new time reference for each transaction
 		    Amavis::Timing::init();
 		}
@@ -6939,7 +6948,7 @@
 use vars qw(%st_socket_created %st_sock); # keep persistent state (per-socket)
 use vars qw($savi);
 
-sub sophos_savi_init { 
+sub sophos_savi_init {
     my($av_name, $command) = @_;
     my(@savi_bool_options) = qw(
 	FullSweep DynamicDecompression FullMacroSweep OLE2Handling
@@ -7190,14 +7199,38 @@
 	do_log(2,"Using $av_name: (built-in interface)");
 	($scan_status,$output,$virusnames) = &$command(@_);
     } else {
-	$args =~ s[{}][$tempdir/parts]g;  # replace {} with directory name
+	my(@args) = split(' ',$args);
+	if (grep { m[^({}/)?\*$] } @args) {  # must list each file individually
+	    local(*DIR); my($f); my(@bare_fnames);
+	    opendir(DIR, "$tempdir/parts")
+		or die "Can't open directory $tempdir/parts: $!";
+	    while (defined($f = readdir(DIR))) {
+		my($fname) = "$tempdir/parts/$f";
+		my($errn) = stat($fname) ? 0 : 0+$!;
+		next  if $errn == ENOENT;
+		if ($errn) { die "run_av: $fname inaccessible: $!" }
+		if (!-r _) { die "run_av: $fname not readable" }
+		next  if -d _ && $f =~ /^\.\.?$/;   # this or parent directory
+		next  if -z _;   # empty file
+		if ($f =~ /^([A-Za-z0-9_.-]+)$/s) { push(@bare_fnames, $1) }
+		else { do_log(0, "run_av: WARN: refused to untaint: $f") }
+	    }
+	    closedir(DIR) or die "$av_name: Can't close directory: $!";
+	    # replace * with bare file name
+	    for my $a (@args)
+		{ $a =~ s[^({}/)?\*$][ join(' ', map {$1.$_} @bare_fnames) ]e }
+	}
+	for (@args) { s[{}][$tempdir/parts]g } # replace {} with directory name
 	# NOTE: RAV does not like '</dev/null' in its command!
-	do_log(2,"Using $av_name: $command $args");
-	my($proc_fh) = run_command(undef, "&1", $command, split(' ',$args));
+	do_log(2, "Using $av_name: " . join(' ',$command,@args));
+	my($proc_fh) = run_command(undef, "&1", $command, @args);
 	while( defined($_ = $proc_fh->getline) ) { $output .= $_ }
 	$! = undef; $proc_fh->close; my($err) = $!; my($retval) = retcode($?);
-	chomp($output);
-	do_log(2, "run_av: $command status=$retval ($? $err), $output");
+	chomp($output); my($output_trimmed) = $output;
+	$output_trimmed =~ s/([ \t\n\r])[ \t\n\r]{4,}/$1.../gs;
+	$output_trimmed = "..." . substr($output_trimmed,-900)
+			  if length($output_trimmed) > 900;
+	do_log(2, "run_av: $command status=$retval ($? $err),$output_trimmed");
 	# test for infected first, in case both expressions match
 	if (ref($sts_infected) eq 'ARRAY' ? (grep {$_==$retval} @$sts_infected)
 			: $output =~ /$sts_infected/m) {  # is infected
@@ -7213,8 +7246,9 @@
 	    $scan_status = 0; # 'false' (but defined) indicates no viruses
 	    do_log(5,"run_av: clean");
 	} else {
-	    do_log(0,"Virus scanner failure: $command (error code: $retval)");
+	    do_log(0,"Virus scanner failure: $command (exit status: $retval)");
 	}
+	$output = $output_trimmed  if length($output) > 900;
     }
     &$post_code(@_)  if defined $post_code;
     $virusnames = []        if !defined $virusnames;
@@ -7231,7 +7265,7 @@
     	    last  if $anyone_done;
 	    do_log(0,"WARN: all $tier virus scanners failed, trying backups");
 	    $tier = 'secondary';  next;
-	}   
+	}
 	next  if !defined $av || !ref $av || !defined $av->[1];
 	my($this_status,$this_output,$this_vn);
 	eval { ($this_status,$this_output,$this_vn) = run_av($tempdir,@$av) };
@@ -7424,7 +7458,7 @@
 
 #sub nomailaudit_to_mime_entity($) {
 #   my($mail_obj) = @_;  # expect a Mail::SpamAssassin::NoMailAudit object
-#   my(@m_hdr) = $mail_obj->header;  # in array context returns array of lines 
+#   my(@m_hdr) = $mail_obj->header;  # in array context returns array of lines
 #   my($m_body) = $mail_obj->body;   # returns array ref
 #   my($entity);
 #   # make sure _our_ source line number is reported in case of failure
@@ -7474,7 +7508,7 @@
 # This is a template for VIRUS/BANNED-FILE SENDER NOTIFICATIONS.
 # For syntax and customization instructions see README.customize
 #
-Subject: [? %#V |[? %#F ||BANNED FILENAME (%F)]|VIRUS (%V)] IN YOUR MAIL 
+Subject: [? %#V |[? %#F ||BANNED FILENAME (%F)]|VIRUS (%V)] IN YOUR MAIL
 [? %m  |#|In-Reply-To: %m]
 Message-ID: <VS%n@%h>
 
@@ -7533,15 +7567,15 @@
 [? %#S |Notification to sender will not be mailed.
 
 ]#
-[? %#D |#|The message WAS delivered to:[
+[? %#D |#|The message WILL BE delivered to:[
 %D]
 ]
 [? %#N |#|The message WAS NOT delivered to:[
 %N]
 ]
-[? %#v |#|Virus scanner output:[
+[? %#V |#|[? %#v |#|Virus scanner output:[
    %v]
-]
+]]
 [? %q  |Not quarantined.|The message has been quarantined as:
    %q
 ]
@@ -7620,7 +7654,7 @@
 [? %t |#|According to the 'Received:' trace, the message originated at:
    %t
 ]
-[? %#D ||The message WAS delivered to:[
+[? %#D ||The message WILL BE delivered to:[
 %D]
 
 ]#
