--- amavisd.conf.ori	Tue Jun 29 18:14:44 2004
+++ amavisd.conf	Tue Jun 29 18:15:07 2004
@@ -70,4 +70,5 @@
 # Set the user and group to which the daemon will change if started as root
 # (otherwise just keeps the UID unchanged, and these settings have no effect):
+# For courier, the group must be the same as the group which courier runs as
 $daemon_user  = 'amavis';	# (no default (undef))
 $daemon_group = 'amavis';	# (no default (undef))
@@ -128,4 +129,8 @@
 #$notify_method = $forward_method;
 
+# COURIER (using courierfilter)
+#$forward_method = undef;  # no explicit forwarding, courier does it itself
+#$notify_method = 'pipe:flags=q argv=/usr/sbin/sendmail -f ${sender} -- ${recipient}';
+
 # prefer to collect mail for forwarding as BSMTP files?
 #$forward_method = "bsmtp:$MYHOME/out-%i-%n.bsmtp";
@@ -217,11 +222,14 @@
 			          # (default is true (1) )
 
-# AMAVIS-CLIENT PROTOCOL INPUT SETTINGS (e.g. with sendmail milter)
+# AMAVIS-CLIENT AND COURIER PROTOCOL INPUT SETTINGS (e.g. with sendmail milter)
 #   (used with amavis helper clients like amavis-milter.c and amavis.c,
 #   NOT needed for Postfix and Exim  or dual-sendmail - keep it undefined.)
 #$unix_socketname = "/var/lib/amavis/amavisd.sock"; # amavis helper protocol socket
-$unix_socketname = undef;         # disable listening on a unix socket
+#$unix_socketname = "/var/lib/courier/filters/amavisd"; # courier socket
                                   # (default is undef, i.e. disabled)

+$unix_socket_protocol = 'amavis'; # either 'amavis' for milter or the amavis
+                                  # client or 'courier' for courier
+
 # Do we receive quoted or raw addresses from the helper program?
 # (does not apply to SMTP;  defaults to true)
@@ -390,5 +398,5 @@
 #   With D_REJECT, MTA may reject original SMTP, or send DSN (delivery status
 #            notification, colloquially called 'bounce') - depending on MTA;
-#            Best suited for sendmail milter, especially for spam.
+#            Best suited for sendmail milter and courier, especially for spam.
 #   With D_BOUNCE, amavisd-new (not MTA) sends DSN (can better explain the
 #            reason for mail non-delivery, but unable to reject the original
@@ -412,5 +420,5 @@
 #   and bouncing only increases the network cost of viruses for everyone
 # - use D_PASS (or virus_lovers) and $warnvirussender=1 to deliver viruses;
-# - use D_REJECT instead of D_BOUNCE if using milter and under heavy
+# - use D_REJECT instead of D_BOUNCE if using milter or courier and under heavy
 #   virus storm;
 #
--- amavisd.ori	Tue Jun 29 18:14:54 2004
+++ amavisd	Tue Jun 29 18:15:07 2004
@@ -84,4 +84,5 @@
 #  Amavis::In::AMCL
 #  Amavis::In::SMTP
+#  Amavis::In::Courier
 #  Amavis::AV
 #  Amavis::SpamControl
@@ -158,5 +159,6 @@
 	    $warnvirusrecip $warnbannedrecip
 	    $log_templ
-	    $unix_socketname $inet_socket_port $inet_socket_bind @inet_acl
+	    $unix_socketname $unix_socket_protocol
+	    $inet_socket_port $inet_socket_bind @inet_acl
 	    $myhostname $localhost_name
 	    $insert_received_line
@@ -363,4 +365,5 @@
 
 # $unix_socketname = '/var/amavis/amavisd.sock'; # traditional amavis client protocol
+# $unix_socket_protocol = 'amavis'; # 'amavis' or 'courier'
 # $inet_socket_port = 10024;      # accept SMTP on this TCP port
 # $inet_socket_port = [10024,10026,10027];  # ...possibly on more than one
@@ -4962,5 +4965,5 @@
 
 use vars qw($extra_code_sql $extra_code_ldap
-	    $extra_code_in_amcl $extra_code_in_smtp
+	    $extra_code_in_amcl $extra_code_in_smtp $extra_code_in_courier
 	    $extra_code_antivirus $extra_code_antispam);
 
@@ -4999,5 +49102,6 @@
 	    @banned_filename @bad_headers);
 
-use vars qw($amcl_in_obj $smtp_in_obj); # Amavis::In::AMCL and In::SMTP objects
+use vars qw($amcl_in_obj $smtp_in_obj $courier_in_obj);
+# Amavis::In::AMCL, In::SMTP and In::Courier objects
 use vars qw($sql_policy $sql_wblist);   # Amavis::Lookup::SQL objects
 
@@ -5179,5 +5183,11 @@
 	$conn->proto($sock->NS_proto);
 
-	if ($sock->NS_proto eq 'UNIX') {      # traditional amavis client
+	if ($sock->NS_proto eq 'UNIX' && $unix_socket_protocol eq 'courier') {
+	    # courierfilter client
+	    $courier_in_obj = Amavis::In::Courier->new  if !$courier_in_obj;
+	    $courier_in_obj->process_courier_request(
+		$sock, $conn, \&check_mail);
+	    do_log(2, Amavis::Timing::report());  # report elapsed times
+	} elsif ($sock->NS_proto eq 'UNIX') {      # traditional amavis client
 	    $amcl_in_obj = Amavis::In::AMCL->new  if !$amcl_in_obj;
 	    $amcl_in_obj->process_amavis_client_request(
@@ -5250,4 +5260,6 @@
     $smtp_in_obj = undef;  # calls Amavis::In::SMTP::DESTROY
     $amcl_in_obj = undef;  # (currently does nothing for Amavis::In::AMCL)
+    $courier_in_obj = undef;  # calls Amavis::In::Courier::DESTROY
+    $courier_in_obj = undef;  # calls Amavis::In::Courier::DESTROY
 }
 
@@ -6496,5 +6508,5 @@
     map { chomp($_ = <Amavis::DATA>) }
 	($extra_code_sql, $extra_code_ldap,
-	 $extra_code_in_amcl, $extra_code_in_smtp,
+	 $extra_code_in_amcl, $extra_code_in_smtp, $extra_code_in_courier,
 	 $extra_code_antivirus, $extra_code_antispam,
 	 $log_templ,
@@ -6545,8 +6557,15 @@
 }
 
-if ($unix_socketname eq '') { $extra_code_in_amcl = undef }
-else {
+if ($unix_socketname eq '') {
+    $extra_code_in_amcl = undef;
+    $extra_code_in_courier = undef;
+} elsif ($unix_socket_protocol eq 'courier') {
+    eval $extra_code_in_courier or die "Problem in the In::Courier code: $@";
+    $extra_code_in_courier = 1;   # release memory occupied by the source code
+    $extra_code_in_amcl = undef;
+} else {
     eval $extra_code_in_amcl or die "Problem in the In::AMCL code: $@";
     $extra_code_in_amcl = 1;   # release memory occupied by the source code
+    $extra_code_in_courier = undef;
 }
 if ($inet_socket_port eq '' || ref $inet_socket_port && !@$inet_socket_port) {
@@ -6646,4 +6665,5 @@
 do_log(1, "AMCL-in protocol code ".($extra_code_in_amcl?'':" NOT")." loaded");
 do_log(1, "SMTP-in protocol code ".($extra_code_in_smtp?'':" NOT")." loaded");
+do_log(1, "Courier-in protocol code ".($extra_code_in_courier?'':" NOT")." loaded");
 do_log(1, "ANTI-VIRUS code       ".($extra_code_antivirus?'':" NOT")." loaded");
 do_log(1, "ANTI-SPAM  code       ".($extra_code_antispam?'':" NOT")." loaded");
@@ -6774,4 +6794,7 @@
 
 $0 = 'amavisd (master)';
+if ($unix_socket_protocol eq 'courier') { # Allow courier to write to the socket
+    umask(007);
+}
 $server->run;  # transfer control to Net::Server
 
@@ -7956,4 +7979,210 @@
     }
 }
+
+1;
+
+__DATA__
+#
+package Amavis::In::Courier;
+use strict;
+
+BEGIN {
+    use Exporter ();
+    use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
+    $VERSION = '1.15';
+    @ISA = qw(Exporter);
+}
+
+use IO::File;
+use POSIX qw(strftime);
+use Errno qw(ENOENT);
+
+BEGIN {
+    import Amavis::Conf qw(:platform :confvars);
+    import Amavis::Util qw(do_log am_id debug_oneshot rmdir_recursively
+	strip_tempdir);
+    import Amavis::Lookup qw(lookup);
+    import Amavis::Timing qw(section_time);
+    import Amavis::In::Message;
+}
+
+sub new($) {
+    my($class) = @_;
+    my($self) = bless {}, $class;
+    $self->{tempdir_pers} = undef;
+    $self->{tempdir_empty} = 1;
+    $self->{preserve} = 0;
+    return $self;
+}
+
+# Remove the temporary directory, unless we've been asked to preserve it
+sub DESTROY {
+    my($self) = @_;
+    my($errn) = $self->{tempdir_pers} eq '' ? ENOENT
+		    : (stat($self->{tempdir_pers}) ? 0 : 0+$!);
+    if (defined $self->{tempdir_pers} && $errn != ENOENT) {
+	# this will not be included in the TIMING report,
+	# but it only occurs infrequently and doesn't take that long
+	if ($self->preserve_evidence && !$self->{tempdir_empty}) {
+	    do_log(0, "tempdir is to be PRESERVED: ".$self->{tempdir_pers});
+	} else {
+	    do_log(2, "tempdir being removed: ".$self->{tempdir_pers});
+	    rmdir_recursively($self->{tempdir_pers});
+	}
+    }
+}
+
+# Accept a single request for virus scanning from courierfilter
+sub process_courier_request($$$) {
+    my($self, $sock, $conn, $check_mail) = @_;
+    # $sock:       connected socket from Net::Server
+    # $conn:       information about client connection
+    # $check_mail: subroutine ref to be called with file handle
+    
+    my($msginfo) = Amavis::In::Message->new;
+    my($fh, $smtp_resp);
+    my($which_section) = "initialization";
+    
+    am_id("$$-$Amavis::child_invocation_count");
+    
+    eval {
+	local $/ = "\n";  # just make sure
+	
+	# Get the path to the data file
+	$which_section = "RX_datapath";
+	my($datapath) = scalar(<$sock>);
+	die "$!"  unless defined($datapath);
+	chomp $datapath;
+	
+	# Get the control files which contain sender and recipients
+	$which_section = "RX_controlfiles";
+	my(@recips, $sender);
+	while (<$sock>) {
+	    chomp;
+	    # courier indicates end of control files by sending a blank line
+	    last  unless $_;
+	    $sender ||= read_control_file($_, \@recips);
+	    debug_oneshot(1)  if lookup($sender, \@debug_sender_acl);
+	}
+	$msginfo->sender($sender);
+	$msginfo->recips(\@recips);
+	$msginfo->rx_time(time);
+	
+	# Open the data file
+	$which_section = "opening_mail_file";
+	$fh = IO::File->new($datapath, 'r')
+	    or die "Can't open $datapath: $!";
+	binmode($fh, ":bytes")
+	    or die "Can't cancel :utf8 mode: $!"  if $unicode_aware;
+	$msginfo->mail_text($fh);
+	section_time('got data');
+	do_log(1, sprintf("Courier <%s> -> %s", $sender,
+			  join(',', map{"<$_>"}@recips)));
+    };
+    
+    if ($@ ne '') { # something went wrong
+	chomp($@);
+	do_log(0, "$which_section FAILED, retry: $@");
+	$fh->close  if $fh;
+	$fh = undef;
+	$msginfo->mail_text(undef);
+	$smtp_resp = '451 Virus checking error';
+    } else {
+	# Get a temporary directory - check_mail needs one
+	$self->prepare_tempdir();
+	
+	# Do the work
+	$self->{tempdir_empty} = 0;
+	my($exit_code, $preserve_evidence);
+	($smtp_resp, $exit_code, $preserve_evidence) =
+	    &$check_mail($conn, $msginfo, 0, $self->{tempdir_pers});
+	if ($preserve_evidence) { $self->preserve_evidence(1) }
+	$fh->close or die "Can't close temp file: $!"  if $fh;
+	$fh = undef;
+	$msginfo->mail_text(undef);
+	
+	# Tidy up
+	if ($self->preserve_evidence) { # Move onto a new temporary directory
+	    do_log(0, "PRESERVING EVIDENCE in $self->{tempdir_pers}");
+	    $self->{tempdir_pers} = undef;
+	} else { # Clean out the present one and re-use it
+	    strip_tempdir($self->{tempdir_pers});
+	}
+	$self->{tempdir_empty} = 1;
+	$self->preserve_evidence(0);
+
+	if ($forward_method eq '' && $smtp_resp =~ /^25/) {
+	    # when forwarding is left for MTA on the input side to do,
+	    # warn if there is anything that should be done, but MTA is not
+	    # capable of doing (or a helper program can not pass the request)
+	    my($any_deletes);
+	    for my $r (@{$msginfo->per_recip_data}) {
+		my($addr,$newaddr) = ($r->recip_addr, $r->recip_final_addr);
+		if ($r->recip_done) {
+		    do_log(0, "WARN: recip addr <$addr> should be removed, but MTA can't do it");
+		    $any_deletes++;
+		} elsif ($newaddr ne $addr) {
+		    do_log(0, "WARN: recip addr <$addr> should be replaced with <$newaddr>, but MTA can't do it");
+		}
+	    }
+	    if ($any_deletes) {
+		do_log(0, "WARN: REJECT THE WHOLE MESSAGE, MTA-in can't do the recips deletion");
+		$smtp_resp = '550 Redirection failed';
+	    }
+	}
+    }
+
+    do_log(3, "mail checking ended: $smtp_resp");
+    send($sock, $smtp_resp, 0);
+}
+
+# Read the recipients from one control file and pushes them onto the array
+# referenced by the second argument
+# Returns the sender specified by this control file (if any)
+sub read_control_file($$) {
+    my($path, $recips) = @_;
+    my($sender);
+    
+    my($fh) = IO::File->new($path, 'r')
+	or die "Can't open control file $path: $!";
+    binmode($fh, ":bytes")
+	or die "Can't cancel :utf8 mode: $!"  if $unicode_aware;
+
+    # Parse the control file - also untaints the addresses
+    while (<$fh>) {
+	chomp;
+	/^ s ( .*? \@ (?:  \[  (?: \\. | [^\[\]\\] )*  \]
+		       |  [^@"<>\[\]\\\s] )* )
+	 $(?!\n)/xs && ($sender = $1);
+	/^ r ( .*? \@ (?:  \[  (?: \\. | [^\[\]\\] )*  \]
+		       |  [^@"<>\[\]\\\s] )* )
+	 $(?!\n)/xs && push(@$recips, $1);
+    }
+    
+    $fh->close or die "Can't close control file $path: $!";
+    
+    return $sender;
+}
+
+# create ourselves a temporary directory
+sub prepare_tempdir($) {
+    my($self) = @_;
+    if (! defined $self->{tempdir_pers} ) {
+	# invent a name for a temporary directory for this child, and create it
+	my($now_iso8601) = strftime("%Y%m%dT%H%M%S", localtime);
+	$self->{tempdir_pers} = sprintf("%s/amavis-%s-%05d",
+					$TEMPBASE, $now_iso8601, $$);
+    }
+    my($errn) = stat($self->{tempdir_pers}) ? 0 : 0+$!;
+    if ($errn == ENOENT || ! -d _) {
+	mkdir($self->{tempdir_pers}, 0750)
+	    or die "Can't create directory $self->{tempdir_pers}: $!";
+	$self->{tempdir_empty} = 1;
+	section_time('mkdir tempdir');
+    }
+}
+
+sub preserve_evidence  # try to preserve temporary files etc in case of trouble
+  { my($self)=shift; !@_ ? $self->{preserve} : ($self->{preserve}=shift) }
 
 1;

