amavisd-new-courier.patch [plain text]
--- amavisd.ori Tue Nov 2 22:14:31 2004
+++ amavisd Tue Nov 2 22:16:44 2004
@@ -92,4 +92,5 @@
# Amavis::In::AMCL
# Amavis::In::SMTP
+# Amavis::In::Courier
# Amavis::AV
# Amavis::SpamControl
@@ -123,5 +124,5 @@
fetch_modules('REQUIRED BASIC MODULES', 1, qw(
Exporter POSIX Fcntl Socket Errno Carp Time::HiRes
- IO::Handle IO::File IO::Socket IO::Socket::UNIX IO::Socket::INET
+ IO::Handle IO::File IO::Select IO::Socket IO::Socket::UNIX IO::Socket::INET
IO::Wrap IO::Stringy Digest::MD5 Unix::Syslog File::Basename File::Copy
Mail::Field Mail::Address Mail::Header Mail::Internet
@@ -231,5 +232,5 @@
$myversion $myhostname
$MYHOME $TEMPBASE $QUARANTINEDIR
- $daemonize $pid_file $lock_file $db_home
+ $daemonize $courierfilter_shutdown $pid_file $lock_file $db_home
$enable_db $enable_global_cache
$daemon_user $daemon_group $daemon_chroot_dir $path
@@ -405,4 +406,7 @@
$child_timeout = 8*60; # abort child if it does not complete each task in n sec
+# Assume STDIN is a courierfilter pipe and shutdown when it becomes readable
+$courierfilter_shutdown = 0;
+
# Can file be truncated?
# Set to 1 if 'truncate' works (it is XPG4-UNIX standard feature,
@@ -5300,4 +5304,5 @@
use Errno qw(ENOENT);
use IO::File ();
+use IO::Select;
# body digest for caching, either SHA1 or MD5
#use Digest::SHA1;
@@ -5339,5 +5344,5 @@
$extra_code_db $extra_code_cache
$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 $extra_code_unpackers);
@@ -5358,5 +5363,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
use vars qw($ldap_policy); # Amavis::Lookup::LDAP objects
@@ -5407,4 +5413,5 @@
do_log(0,"AMCL-in protocol code ".($extra_code_in_amcl?'':" NOT")." loaded");
do_log(0,"SMTP-in protocol code ".($extra_code_in_smtp?'':" NOT")." loaded");
+ do_log(0,"Courier protocol code ".($extra_code_in_courier?'':" NOT")." loaded");
do_log(0,"ANTI-VIRUS code ".($extra_code_antivirus?'':" NOT")." loaded");
do_log(0,"ANTI-SPAM code ".($extra_code_antispam ?'':" NOT")." loaded");
@@ -5426,4 +5433,5 @@
# Subroutine will be called in scalar context with no arguments.
# It may return a scalar string (or undef), or an array reference.
+
%builtins = (
'.' => undef,
@@ -5592,7 +5600,35 @@
### Net::Server hook
+### This hook takes place immediately after the "->run()" method is called.
+### This hook allows for setting up the object before any built in configuration
+### takes place. This allows for custom configurability.
+sub configure_hook {
+ my($self) = @_;
+ if ($courierfilter_shutdown) {
+ # Duplicate the courierfilter pipe to another fd since STDIN is closed if we
+ # daemonize
+ $self->{courierfilter_pipe} = IO::File->new('<&STDIN')
+ or die "Can't duplicate courierfilter shutdown pipe: $!";
+ $self->{courierfilter_select} = IO::Select->new($self->{courierfilter_pipe});
+ }
+}
+
+### Net::Server hook
+### This hook occurs just after the bind process and just before any
+### chrooting, change of user, or change of group occurs. At this point
+### the process will still be running as the user who started the server.
+sub post_bind_hook {
+ my ($self) = @_;
+ if (c('protocol') eq 'COURIER') {
+ # Allow courier to write to the socket
+ chmod(0660, $unix_socketname);
+ }
+}
+
+### Net::Server hook
### This hook occurs after chroot, change of user, and change of group has
### occured. It allows for preparation before looping begins.
sub pre_loop_hook {
+ my ($self) = @_;
my($self) = @_;
local $SIG{CHLD} = 'DEFAULT';
@@ -5631,4 +5667,15 @@
}
Amavis::SpamControl::init() if $extra_code_antispam;
+ if ($courierfilter_shutdown) {
+ # Tell courierfilter we have finished initialisation by closing fd 3
+ # But make sure it's a pipe (and not the courierfilter shutdown pipe)
+ # first: if we have been started using filterctl (i.e. not when
+ # courierfilter itself starts) then there is no initial pipe on fd 3 so
+ # it could be assigned to another file
+ open(my $fh3, '<&3');
+ if (-p $fh3 && $self->{courierfilter_pipe}->fileno() != 3) {
+ POSIX::close(3);
+ }
+ }
};
if ($@ ne '') {
@@ -5861,5 +5908,7 @@
if ($sock->NS_proto eq 'UNIX') { # traditional amavis helper program
if ($suggested_protocol eq 'COURIER') {
- die "unavailable support for protocol: $suggested_protocol";
+ # courierfilter client
+ $courier_in_obj = Amavis::In::Courier->new if !$courier_in_obj;
+ $courier_in_obj->process_courier_request($sock, $conn, \&check_mail);
} elsif ($suggested_protocol eq 'AM.PDP') {
$amcl_in_obj = Amavis::In::AMCL->new if !$amcl_in_obj;
@@ -5936,4 +5985,14 @@
}
+### Net::Server::PreForkSimple hook
+### Is run by the master process every 10 seconds if $courierfilter_shutdown is set
+sub run_dequeue {
+ my($self) = @_;
+ if ($self->{courierfilter_select}->can_read(0)) {
+ do_log(0, "Instructed by courierfilter to shutdown");
+ $self->server_close();
+ }
+}
+
### Child is about to be terminated
### user customizable Net::Server hook
@@ -5948,4 +6007,5 @@
do_log(5,"child_finish_hook: invoking DESTROY methods");
$smtp_in_obj = undef; # calls Amavis::In::SMTP::DESTROY
+ $courier_in_obj = undef; # calls Amavis::In::Courier::DESTROY
$amcl_in_obj = undef; # (currently does nothing for Amavis::In::AMCL)
$sql_wblist = undef; # calls Amavis::Lookup::SQL::DESTROY
@@ -5961,4 +6021,5 @@
do_log(5,"at the END handler: invoking DESTROY methods");
$smtp_in_obj = undef; # at end calls Amavis::In::SMTP::DESTROY
+ $courier_in_obj = undef; # at end calls Amavis::In::Courier::DESTROY
$amcl_in_obj = undef; # (currently does nothing for Amavis::In::AMCL)
$sql_wblist = undef; # at end calls Amavis::Lookup::SQL::DESTROY
@@ -7611,5 +7672,5 @@
$extra_code_db, $extra_code_cache,
$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, $extra_code_unpackers,
$Amavis::Conf::log_templ, $Amavis::Conf::log_recip_templ);
@@ -7730,10 +7791,14 @@
if (c('protocol') eq 'COURIER') {
- die "In::Courier code not available";
+ 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;
} elsif (c('protocol') eq 'AM.PDP' || $unix_socketname ne '') {
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;
} else {
$extra_code_in_amcl = undef;
+ $extra_code_in_courier = undef;
}
@@ -7861,4 +7926,9 @@
chroot => $daemon_chroot_dir ne '' ? $daemon_chroot_dir : undef,
no_close_by_child => 1,
+
+ # 9 to ensure it runs EVERY 10 seconds
+ # (Net::Server::PreForkSimple only checks every 10 seconds)
+ check_for_dequeue => $courierfilter_shutdown ? 9 : undef,
+ max_dequeue => $courierfilter_shutdown ? 1 : undef,
# controls log level for Net::Server internal log messages:
@@ -10020,4 +10090,223 @@
}
}
+
+1;
+
+__DATA__
+#
+package Amavis::In::Courier;
+use strict;
+use re 'taint';
+
+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 c cr ca);
+ import Amavis::Util qw(do_log am_id debug_oneshot rmdir_recursively
+ strip_tempdir untaint);
+ 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_msgpath";
+ my($msgpath) = scalar(<$sock>);
+ die "$!" unless defined($msgpath);
+ chomp $msgpath;
+ $msgpath = untaint($msgpath) if $msgpath =~ m{^[A-Za-z0-9/._=+-]+\z};
+
+ # Get the control files which contain sender and recipients
+ $which_section = "RX_controlfiles";
+ my(@recips, $sender, $msgid, $ip, $name, $helo);
+ while (<$sock>) {
+ chomp;
+ # courier indicates end of control files by sending a blank line
+ last unless $_;
+ ($sender, $msgid, $ip, $name, $helo) = read_control_file($_, \@recips);
+ debug_oneshot(1) if lookup(0, $sender, @{ca('debug_sender_maps')});
+ }
+ $msginfo->sender($sender);
+ $msginfo->recips(\@recips);
+ $msginfo->rx_time(time);
+ $msginfo->client_addr($ip);
+ $msginfo->client_name($name);
+ $msginfo->client_helo($helo);
+ $msginfo->queue_id($msgid);
+
+ # Open the data file
+ $which_section = "opening_mail_file";
+ $fh = IO::File->new($msgpath, 'r')
+ or die "Can't open $msgpath: $!";
+ binmode($fh, ":bytes")
+ or die "Can't cancel :utf8 mode: $!" if $unicode_aware;
+ $msginfo->mail_text($fh);
+ $msginfo->mail_text_fn($msgpath);
+ $msginfo->mail_tempdir($TEMPBASE); # defaults to $TEMPBASE !?
+ 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 (c('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,$queue_id,$ip,$name,$helo);
+
+ 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
+ while (<$fh>) {
+ chomp;
+ /^ s ( .*? \@ (?: \[ (?: \\. | [^\[\]\\] )* \]
+ | [^@"<>\[\]\\\s] )* )
+ \z/xs && ($sender = $1);
+ /^ r ( .*? \@ (?: \[ (?: \\. | [^\[\]\\] )* \]
+ | [^@"<>\[\]\\\s] )* )
+ \z/xs && push(@$recips, $1);
+ /^ M ( [0-9a-fA-F]+ \. [0-9a-fA-F]+ \. [0-9a-fA-F]+ )
+ \z/xs && ($queue_id = $1);
+ /^ f .*? ; \s* ( [A-Za-z0-9\.-]* ) \s* \( ( [A-Za-z0-9\.-]* )
+ \s* \[ ( [0-9A-Fa-f\.:]+ ) \] \)
+ \z/xs && (($helo, $name, $ip) = ($1, $2, $3));
+ }
+
+ $fh->close or die "Can't close control file $path: $!";
+ return ($sender,$queue_id,$ip,$name,$helo);
+}
+
+# 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;
--- amavisd.conf-sample.ori Tue Nov 2 22:14:52 2004
+++ amavisd.conf-sample Tue Nov 2 22:15:06 2004
@@ -139,4 +139,11 @@
#$notify_method = $forward_method;
+# COURIER using courierfilter
+#$forward_method = undef; # no explicit forwarding, Courier does it itself
+#$notify_method = 'pipe:flags=q argv=perl -e $pid=fork();if($pid==-1){exit(75)}elsif($pid==0){exec(@ARGV)}else{exit(0)} /usr/sbin/sendmail -f ${sender} -- ${recipient}';
+# Only set $courierfilter_shutdown to 1 if you are using courierfilter to
+# control the startup and shutdown of amavis
+#$courierfilter_shutdown = 1; # (default 0)
+
# prefer to collect mail for forwarding as BSMTP files?
#$forward_method = "bsmtp:$MYHOME/out-%i-%n.bsmtp";
@@ -203,8 +210,10 @@
# (default is true)
-# 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 or Exim or dual-sendmail - keep it undefined.
$unix_socketname = "$MYHOME/amavisd.sock"; # amavis helper protocol socket
+#$unix_socketname = "/var/lib/courier/allfilters/amavisd"; # Courier socket
+#$protocol = 'COURIER'; # uncomment if using Courier
#$unix_socketname = undef; # disable listening on a unix socket
# (default is undef, i.e. disabled)
@@ -433,5 +442,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 or even suppress DSN, but unable
@@ -439,5 +448,5 @@
# viruses, and for Postfix and other dual-MTA setups, which can't
# reject original client SMTP session, as the mail has already
-# been enqueued.
+# been enqueued. Do not use with Courier.
$final_virus_destiny = D_BOUNCE; # (defaults to D_DISCARD)
@@ -453,5 +462,5 @@
# D_BOUNCE is preferred for viruses, but consider:
# - use D_PASS (or virus_lovers) 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 Courier or milter and under heavy
# virus storm;
#
@@ -941,7 +950,7 @@
# picture above), and infected mail (if passed) gets additional header:
# X-AMaViS-Alert: INFECTED, message contains virus: ...
-# (header not inserted with milter interface!)
+# (header not inserted with Courier or milter interface!)
#
-# NOTE (milter interface only): in case of multiple recipients,
+# NOTE (Courier and milter interface only): in case of multiple recipients,
# it is only possible to drop or accept the message in its entirety - for all
# recipients. If all of them are virus lovers, we'll accept mail, but if