amavisd-new-qmqpqq.patch [plain text]
--- amavisd.ori Tue Nov 2 22:14:31 2004
+++ amavisd Tue Nov 2 22:20:27 2004
@@ -92,4 +92,5 @@
# Amavis::In::AMCL
# Amavis::In::SMTP
+# Amavis::In::QMQPqq
# Amavis::AV
# Amavis::SpamControl
@@ -1788,4 +1789,5 @@
: sprintf(" (%s [%s])", $myhostname, $conn->socket_ip) ),
($conn->socket_port eq '' ? 'unix socket' : "port ".$conn->socket_port) );
+ # must not use proto name QMQPqq in 'with'
$s .= "\n with $smtp_proto" if $smtp_proto=~/^(ES|S|L)MTPS?A?\z/i; # rfc3848
$s .= "\n id $id" if $id ne '';
@@ -5339,5 +5341,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_qmqpqq
$extra_code_antivirus $extra_code_antispam $extra_code_unpackers);
@@ -5359,4 +5361,5 @@
use vars qw($amcl_in_obj $smtp_in_obj); # Amavis::In::AMCL and In::SMTP objects
+use vars qw($qmqpqq_in_obj); # Amavis::In::QMQPqq object
use vars qw($sql_policy $sql_wblist); # Amavis::Lookup::SQL objects
use vars qw($ldap_policy); # Amavis::Lookup::LDAP objects
@@ -5407,4 +5410,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,"QMQPqq-in protocol code ".($extra_code_in_qmqpqq?'':" NOT")." loaded");
do_log(0,"ANTI-VIRUS code ".($extra_code_antivirus?'':" NOT")." loaded");
do_log(0,"ANTI-SPAM code ".($extra_code_antispam ?'':" NOT")." loaded");
@@ -5880,4 +5884,10 @@
$amcl_in_obj = Amavis::In::AMCL->new if !$amcl_in_obj;
$amcl_in_obj->process_policy_request($sock, $conn, \&check_mail, 0);
+ } elsif ($suggested_protocol eq 'QMQPqq') {
+ if (!$extra_code_in_qmqpqq) {
+ die "incoming TCP connection, but dynamic QMQPqq code not loaded";
+ }
+ $qmqpqq_in_obj = Amavis::In::QMQPqq->new if !$qmqpqq_in_obj;
+ $qmqpqq_in_obj->process_qmqpqq_request($sock,$conn,\&check_mail);
} else { # defaults to SMTP or LMTP
if (!$extra_code_in_smtp) {
@@ -5948,4 +5958,5 @@
do_log(5,"child_finish_hook: invoking DESTROY methods");
$smtp_in_obj = undef; # calls Amavis::In::SMTP::DESTROY
+ $qmqpqq_in_obj = undef; # calls Amavis::In::QMQPqq::DESTROY
$amcl_in_obj = undef; # (currently does nothing for Amavis::In::AMCL)
$sql_wblist = undef; # calls Amavis::Lookup::SQL::DESTROY
@@ -5961,4 +5972,5 @@
do_log(5,"at the END handler: invoking DESTROY methods");
$smtp_in_obj = undef; # at end calls Amavis::In::SMTP::DESTROY
+ $qmqpqq_in_obj = undef; # at end calls Amavis::In::QMQPqq::DESTROY
$amcl_in_obj = undef; # (currently does nothing for Amavis::In::AMCL)
$sql_wblist = undef; # at end calls Amavis::Lookup::SQL::DESTROY
@@ -7611,5 +7623,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_qmqpqq,
$extra_code_antivirus, $extra_code_antispam, $extra_code_unpackers,
$Amavis::Conf::log_templ, $Amavis::Conf::log_recip_templ);
@@ -7739,5 +7751,7 @@
if (c('protocol') eq 'QMQPqq') { # simpleminded, not checking all policy banks
- die "In::QMQPqq code not available";
+ eval $extra_code_in_qmqpqq or die "Problem in the In::QMQPqq code: $@";
+ $extra_code_in_qmqpqq = 1; # release memory occupied by the source code
+ $extra_code_in_smtp = undef;
} elsif (c('protocol') =~ /^(SMTP|LMTP)\z/ ||
$inet_socket_port ne '' &&
@@ -7745,6 +7759,8 @@
eval $extra_code_in_smtp or die "Problem in the In::SMTP code: $@";
$extra_code_in_smtp = 1; # release memory occupied by the source code
+ $extra_code_in_qmqpqq = undef;
} else {
$extra_code_in_smtp = undef;
+ $extra_code_in_qmqpqq = undef;
}
@@ -10019,4 +10035,331 @@
$stat or die "Error writing a SMTP response to the socket: $!";
}
+}
+
+1;
+
+__DATA__
+#
+package Amavis::In::QMQPqq;
+use strict;
+# use re 'taint'; # (is this module ready for this yet?)
+
+BEGIN {
+ use Exporter ();
+ use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
+ $VERSION = '1.17';
+ @ISA = qw(Exporter);
+}
+use POSIX qw(strftime);
+use Errno qw(ENOENT);
+
+BEGIN {
+ import Amavis::Conf qw(:platform :confvars :dynamic_confvars c cr ca);
+ import Amavis::Util qw(do_log am_id prolong_timer debug_oneshot
+ untaint sanitize_str strip_tempdir rmdir_recursively);
+ import Amavis::Lookup qw(lookup);
+ import Amavis::Timing qw(section_time);
+ import Amavis::rfc2821_2822_Tools;
+ import Amavis::In::Message;
+ import Amavis::In::Connection;
+}
+
+sub new($) {
+ my($class) = @_;
+ my($self) = bless {}, $class;
+ $self->{fh_pers} = undef; # persistent file handle for email.txt
+ $self->{tempdir_pers} = undef; # temporary directory for check_mail
+ $self->{preserve} = undef; # don't delete tempdir on exit
+ $self->{tempdir_empty} = 1; # anything of interest in tempdir?
+ $self->{bytesleft} = undef; # bytes left for whole package
+ $self->{len} = undef; # set by getlen() method
+ $self->{sock} = undef; # connected socket
+ $self->{proto} = undef; # protocol
+ $self->{session_closed_normally} = undef; # closed properly? (waited for K/Z/D)
+ $self;
+}
+
+sub preserve_evidence # try to preserve temporary files etc in case of trouble
+ { my($self)=shift; !@_ ? $self->{preserve} : ($self->{preserve}=shift) }
+
+sub DESTROY {
+ my($self) = shift;
+# do_log(0, "Amavis::In::QMQPqq::DESTROY called");
+ $self->{fh_pers}->close
+ or die "Can't close temp file: $!" if $self->{fh_pers};
+ 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});
+ }
+ }
+ if (! $self->{session_closed_normally}) {
+ $self->qmqpqq_resp("Z","Service shutting down, closing channel");
+ }
+}
+
+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');
+ }
+ # prepare temporary file for writing (and reading later)
+ my($fname) = $self->{tempdir_pers} . "/email.txt";
+ my($errn) = stat($fname) ? 0 : 0+$!;
+ if ($self->{fh_pers} && !$errn && -f _) {
+ $self->{fh_pers}->seek(0,0) or die "Can't rewind mail file: $!";
+ $self->{fh_pers}->truncate(0) or die "Can't truncate mail file: $!";
+ } else {
+ $self->{fh_pers} = IO::File->new($fname, 'w+', 0640)
+ or die "Can't create file $fname: $!";
+ section_time('create email.txt');
+ }
+}
+
+
+# get byte, die if no bytes left
+sub getbyte($) {
+my($self) = shift;
+if(!$self->{bytesleft}--) {
+ die("No bytes left");
+ }
+if(defined($_ = $self->{sock}->getc)) {
+ return($_);
+ }
+die("EOF on socket");
+}
+
+sub getlen($) {
+my($self) = shift;
+my($ch,$len);
+
+for(;;) {
+ $ch = $self->getbyte;
+ if($ch eq ':') {
+ return($self->{len} = $len);
+ }
+ if($ch !~ /^\d$/) {
+ die("Char '$ch' is not a number while determining length");
+ }
+ $len .= $ch;
+ }
+}
+
+sub getcomma($) {
+my($self) = shift;
+if($self->getbyte ne ',') {
+ die("Comma expected, found '$_'");
+ }
+}
+
+sub getnetstring($$) {
+my($self) = shift;
+($self->{sock}->read($_[0],$self->getlen) == $self->{len}) ||
+ die("EOF on socket");
+$self->{bytesleft} -= $self->{len};
+$self->getcomma;
+}
+
+
+# Accept a QMQPqq connect
+# and call content checking for the message received
+#
+sub process_qmqpqq_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
+
+$self->{proto} = "QMQPqq";
+$self->{sock} = $sock; # store $sock info for getbyte() method
+$self->{bytesleft} = 20; # initial bytesleft value, there should
+ # NEVER EVER be longer email than 10^20 (approximately)
+ # bytes but increase if needed ;)
+$self->{len} = undef;
+
+my($msginfo);
+
+my($sender,@recips);
+
+my($len);
+
+$conn->smtp_proto("QMQPqq"); # the name of the method is too specific
+eval {
+ # get length of whole package
+ $self->{bytesleft} = $self->getlen;
+
+ # get length of 'email'
+ $len = $self->getlen;
+ section_time('initial length determination');
+
+ am_id(sprintf("%05d-%02d",$$,$Amavis::child_invocation_count));
+
+ # prepare tempdir
+ $self->prepare_tempdir;
+ $msginfo = Amavis::In::Message->new;
+ $msginfo->rx_time(time);
+ $msginfo->delivery_method(c('forward_method'));
+
+ # get 'email'
+ $self->{tempdir_empty} = 0;
+ my $size = 16384;
+ while(($len > 0) && ($sock->read($_,($len >= $size ? $size : $size = $len)) == $size)) {
+ (print {$self->{fh_pers}} $_) ||
+ die("Can't write to mail file: $!");
+ $len -= $size;
+ }
+ if($len > 0) {
+ die("EOF on socket");
+ }
+ $self->{fh_pers}->flush || die("Can't flush mail file: $!");
+ $self->{fh_pers}->seek(0,1) || die("Can't seek on file: $!");
+ $self->{bytesleft} -= $self->{len};
+ section_time('email receiving');
+ # comma has to follow
+ $self->getcomma;
+
+ # get sender
+ $self->getnetstring($sender);
+ section_time('sender receiving');
+
+ # get recips
+ my $i = 0;
+ while($self->{bytesleft}) {
+ $self->getnetstring($recips[$i++]);
+ }
+ section_time('recips receiving');
+
+ # final comma has to follow
+ $self->{bytesleft} = 1;
+ $self->getcomma;
+
+ $msginfo->sender($sender);
+ $msginfo->recips(\@recips);
+
+ do_log(1, sprintf("%s:%s:%s %s: <%s> -> %s Received: %s",
+ $self->{proto},$conn->socket_ip eq $inet_socket_bind ?
+ '' : '['.$conn->socket_ip.']',
+ $conn->socket_port, $self->{tempdir_pers},
+ $sender, join(',', map{"<$_>"}@recips),
+ join(' ',
+ ($msginfo->msg_size eq '' ? ()
+ : 'SIZE='.$msginfo->msg_size),
+ ($msginfo->body_type eq '' ? ()
+ : 'BODY='.$msginfo->body_type),
+ received_line($conn,$msginfo,am_id(),0) )
+ ));
+
+ $msginfo->mail_tempdir($self->{tempdir_pers});
+ $msginfo->mail_text_fn($self->{tempdir_pers} . '/email.txt');
+ $msginfo->mail_text($self->{fh_pers});
+
+ my($smtp_resp,$exit_code,$preserve_evidence) =
+ &$check_mail($conn,$msginfo,0,$self->{tempdir_pers});
+
+ if ($preserve_evidence) {
+ $self->preserve_evidence(1);
+ }
+ if ($smtp_resp !~ /^4/ &&
+ grep { !$_->recip_done } @{$msginfo->per_recip_data}) {
+ die("TROUBLE/MISCONFIG: not all recipients done, ".
+ "\$forward_method is \"$forward_method\"");
+ }
+
+ # all ok
+ if($smtp_resp =~ /^2/) {
+ $self->qmqpqq_resp("K",$smtp_resp);
+ }
+ # permanent reject
+ elsif($smtp_resp =~ /^5/) {
+ $self->qmqpqq_resp("D",$smtp_resp);
+ }
+ # temporary reject (or other error if !~ /^4/)
+ else {
+ $self->qmqpqq_resp("Z",$smtp_resp);
+ }
+ };
+
+alarm(0); do_log(5,"timer stopped after QMQPqq eval");
+
+if($@ ne '') {
+ chomp($@);
+
+ do_log(0,"QMQPqq: NOTICE: $@");
+ $self->qmqpqq_resp("Z","Service shutting down, $@");
+ }
+
+if ($self->preserve_evidence && !$self->{tempdir_empty}) {
+ # keep evidence in case of trouble
+ do_log(0,"PRESERVING EVIDENCE in ".$self->{tempdir_pers});
+ $self->{fh_pers}->close or die "Can't close mail file: $!";
+ $self->{fh_pers} = undef; $self->{tempdir_pers} = undef;
+ $self->{tempdir_empty} = 1;
+ }
+
+# cleanup, but leave directory (and file handle
+# if possible) for reuse
+if ($self->{fh_pers} && !$can_truncate) {
+ # truncate is not standard across all Unix variants,
+ # it is not Posix, but is XPG4-UNIX.
+ # So if we can't truncate a file and leave it open,
+ # we have to create it anew later, at some cost.
+ #
+ $self->{fh_pers}->close or die "Can't close mail file: $!";
+ $self->{fh_pers} = undef;
+ unlink($self->{tempdir_pers}."/email.txt")
+ or die "Can't delete file ".
+ $self->{tempdir_pers}."/email.txt: $!";
+ section_time('delete email.txt');
+ }
+
+if (defined $self->{tempdir_pers}) { # prepare for the next one
+ strip_tempdir($self->{tempdir_pers});
+ $self->{tempdir_empty} = 1;
+ }
+
+$self->preserve_evidence(0); # reset
+# report elapsed times by section for each transaction
+do_log(2, Amavis::Timing::report());
+
+$self->{session_closed_normally} = 1;
+# closes connection after child_finish_hook
+}
+
+# sends a QMQPqq response consisting of K/D/Z code and an optional message;
+# slow down evil clients by delaying response on permanent errors
+sub qmqpqq_resp($$$;$$) {
+my($self,$code,$resp,$penalize,$line) = @_;
+if($code !~ /^(K|Z|D)$/) {
+ die("Internal error(2): bad QMQPqq response code: '$code'");
+ }
+if($penalize) {
+ do_log(0,"QMQPqq: $resp; PENALIZE: $line");
+ sleep 5;
+ section_time('QMQPqq penalty wait');
+ }
+$resp = sanitize_str($resp,1);
+do_log(4,"QMQPqq> $resp");
+print($self->netstring($code . $resp));
+}
+
+sub netstring($$) {
+my($self,$string) = @_;
+return(sprintf("%d:%s,",length($string),$string));
}
--- amavisd.conf.ori Tue Nov 2 22:14:36 2004
+++ amavisd.conf Tue Nov 2 22:20:27 2004
@@ -42,5 +42,6 @@
$enable_global_cache = 1; # enable use of libdb-based cache if $enable_db=1
-$inet_socket_port = 10024; # listen on this local TCP port(s) (see $protocol)
+$protocol = 'QMQPqq'; # suggested protocol to use on all input sockets
+$inet_socket_port = 10628; # accept connections on this local TCP port(s)
# $unix_socketname = "$MYHOME/amavisd.sock"; # when using sendmail milter
--- amavisd.conf-sample.ori Tue Nov 2 22:14:52 2004
+++ amavisd.conf-sample Tue Nov 2 22:20:27 2004
@@ -213,8 +213,11 @@
# SMTP SERVER (INPUT) PROTOCOL SETTINGS (e.g. with Postfix, Exim v4, ...)
# (used when MTA is configured to pass mail to amavisd via SMTP or LMTP)
-$inet_socket_port = 10024; # accept SMTP on this local TCP port
+#$inet_socket_port = 10024; # accept connection on this local TCP port
# (default is undef, i.e. disabled)
# multiple ports may be provided: $inet_socket_port = [10024, 10026, 10028];
+$protocol = 'QMQPqq'; # suggested protocol to use on all input sockets
+$inet_socket_port = 10628; # accept connections on this local TCP port(s)
+
# SMTP SERVER (INPUT) access control
# - do not allow free access to the amavisd SMTP port !!!
@@ -1970,4 +1973,5 @@
# ],
# };
+#
# NOTE: the use of policy banks for changing protocol on the input socket is
@@ -1979,4 +1983,8 @@
# protocol=>'AM.PDP', # Amavis policy delegation protocol (new milter helper)
# };
+# $policy_bank{'QMQPqq'} = {
+# log_level => 3,
+# protocol=>'QMQPqq', # possible with patch amavisd-new-qmqpqq.patch applied
+# };
## the name 'MYNETS' has special semantics: this policy bank gets loaded
@@ -2001,4 +2009,5 @@
# $interface_policy{'10026'} = 'ALT';
+# $interface_policy{'10628'} = 'QMQPqq';
# $interface_policy{'9998'} = 'AM.PDP';
# $interface_policy{'SOCK'} = 'AM.PDP';