Here are my patches to (for the most part) tidy up the logging and
modernise some of the plugins. A couple of bugfixes fell out of the
tidyup for free ! :-) My manager asked only that the company be credited
for these, hence my posting from my work address.
I have endeavoured to use the log levels as follows:
LOGDEBUG: things you only need to know while debugging
LOGINFO: normal things happening during normal operation
LOGNOTICE: normal mail rejection during normal operation (RBLs etc)
LOGWARN: something minor has gone wrong that won't make mail be rejected.
LOGERROR: something has gone badly wrong though the server is still
able to accept mail, but it may not go through any checks.
LOGCRIT: something wrong with the server config
There is still a little work to do yet, but the aim is that you can run
the server on LOGINFO if you like positive assurance that all is working,
or LOGNOTICE to just get a summary of successful mail yet fuller details
of reasons for rejection. LOGWARN is for those who don't care about why
they reject things (or who have other logging) !
Check_badmailfromto did not work, it would block mail if any
lefthandside and any righthandside in its config file matched the current
sender/recipient. I have altered this to what I assume was the intent,
to match both addresses on the same config line.
Greylisting I do not use, but updated the plugin to use $self->log
anyway; likewise the spamassassin plugin.
Log-deny.patch keeps a note of any recipient addresses which were
rejected; we use this in our custom logging plugin. I also have ideas
that this count could be useful to blacklist senders who do dictionary
attacks, though that's in the future.
Require-resolvable-fromhost was rejecting at MAIL FROM, we have discussed
before that some MTAs ignore errors at this stage so I moved the
rejection to RCPT TO.
None of these patches should significantly change the behaviour of
qpsmtpd. I have some other changes which do, those I'm still evaluating
as they aren't all in production yet. These will do for 0.29 though !
Nick
--
Critical Software Support Team
Technical Support: (International) +44 1782 714 122 (UK) 01782 714 122--- plugins/check_badmailfrom.orig Sat Feb 26 21:10:37 2005
+++ plugins/check_badmailfrom Mon Feb 28 10:48:56 2005
@@ -42,9 +42,12 @@
$bad =~ s/^\s*(\S+).*/$1/;
next unless $bad;
$bad = lc $bad;
- warn "Bad badmailfrom config: No \@ sign in $bad\n" and next unless $bad =~ m/\@/;
- $transaction->notes('badmailfrom', "sorry, your envelope sender is in my badmailfrom list")
- if ($bad eq $from) || (substr($bad,0,1) eq '@' && $bad eq "[EMAIL PROTECTED]");
+ $self->log(LOGERROR, "Bad badmailfrom config: No \@ sign in $bad") and next unless $bad =~ m/\@/;
+ if ($bad eq $from || $bad eq "[EMAIL PROTECTED]") {
+ $transaction->notes('badmailfrom', "Local policy rejects this mail");
+ $self->log(LOGNOTICE, "From address matches $bad, rejecting");
+ last;
+ }
}
return (DECLINED);
}
--- plugins/check_badmailfromto.orig Fri Jan 28 03:30:50 2005
+++ plugins/check_badmailfromto Mon Feb 28 11:29:21 2005
@@ -12,52 +12,40 @@
to not give the impression that the sender is blocked (good for cases of
harassment).
-Based heavily on check_badmailfrom.
-
=cut
sub register {
my ($self, $qp) = @_;
- $self->register_hook("mail", "mail_handler");
$self->register_hook("rcpt", "rcpt_handler");
}
-sub mail_handler {
- my ($self, $transaction, $sender) = @_;
+sub rcpt_handler {
+ my ($self, $transaction, $rcpt) = @_;
my @badmailfromto = $self->qp->config("badmailfromto")
or return (DECLINED);
+ my $sender = $transaction->sender;
return (DECLINED) unless ($sender->format ne "<>"
and $sender->host && $sender->user);
- my $host = lc $sender->host;
- my $from = lc($sender->user) . '@' . $host;
+ my $shost = lc $sender->host;
+ my $saddr = lc($sender->user) . '@' . $shost;
- for my $bad (@badmailfromto) {
- $bad =~ s/^\s*(\S+).*/$1/;
- next unless $bad;
- $bad = lc $bad;
- warn "Bad badmailfromto config: No \@ sign in $bad\n" and next unless $bad =~ m/\@/;
- $transaction->notes('badmailfromto', "$bad")
- if ($bad eq $from)
- || (substr($bad,0,1) eq '@' && $bad eq "[EMAIL PROTECTED]");
- }
- return (DECLINED);
-}
-
-sub rcpt_handler {
- my ($self, $transaction, $rcpt) = @_;
- my $recipient = lc($rcpt->user) . '@' . lc($rcpt->host);
- my $sender = $transaction->notes('badmailfromto');
- if ($sender) {
- my @badmailfromto = $self->qp->config("badmailfromto")
- or return (DECLINED);
+ my $rhost = lc $rcpt->host;
+ my $recipient = lc($rcpt->user) . '@' . $rhost;
- foreach (@badmailfromto) {
- my ($from, $to) = m/^\s*(\S+)\t(\S+).*/;
+ foreach (@badmailfromto) {
+ my ($from, $to) = m/^\s*(\S+)\t(\S+)\s*$/;
+ $self->log(LOGERROR, "Bad badmailfromto config: $_"), next
+ unless defined($from) && defined($to) && $from =~ /\@/ && $to =~ /\@/;
+ $from = lc $from; $to = lc $to;
+ $self->log(LOGDEBUG, "Checking $from against [EMAIL PROTECTED] and $saddr");
+ $self->log(LOGDEBUG, "Checking $to against [EMAIL PROTECTED] and $recipient");
+ if ( ($from eq "[EMAIL PROTECTED]" || $from eq $saddr)
+ && ($to eq "[EMAIL PROTECTED]" || $to eq $recipient) ) {
+ $self->log(LOGNOTICE, "From address matches $from and To matches $to, rejecting");
return (DENY, "mail to $recipient not accepted here")
- if lc($from) eq $sender and lc($to) eq $recipient;
}
}
return (DECLINED);
--- plugins/dnsbl.orig Fri Jan 28 03:30:50 2005
+++ plugins/dnsbl Mon Feb 28 12:27:08 2005
@@ -150,6 +150,7 @@
my $result = $ENV{'RBLSMTPD'};
my $remote_ip = $self->qp->connection->remote_ip;
$result =~ s/%IP%/$remote_ip/g;
+ $self->log(LOGNOTICE, "Rejected by RBLSMTPD: $result");
return (DENY, join(" ", $self->qp->config('dnsbl_rejectmsg'), $result));
}
@@ -157,12 +158,13 @@
my $whitelist = $self->qp->connection->notes('whitelisthost');
if ( $note ) {
if ( $rcpt->user =~ /^(?:postmaster|abuse|mailer-daemon|root)$/i ) {
- $self->log(2, "Don't blacklist special account: ".$rcpt->user);
+ $self->log(LOGDEBUG, "Don't blacklist special account: ".$rcpt->user);
}
elsif ( $whitelist ) {
- $self->log(2, "Whitelist overrode blacklist: $whitelist");
+ $self->log(LOGDEBUG, "Whitelist overrode blacklist: $whitelist");
}
else {
+ $self->log(LOGNOTICE, "Sending IP blacklisted: $note");
return (DENY, $note);
}
}
--- plugins/greylisting.orig Tue Feb 15 21:42:52 2005
+++ plugins/greylisting Mon Feb 28 23:29:35 2005
@@ -1,3 +1,4 @@
+#! perl
=head1 NAME
denysoft_greylist
@@ -129,7 +130,7 @@
map { split /\s+/, $_, 2 } $self->qp->config('denysoft_greylist'),
%arg };
if (my @bad = grep { ! exists $ARGS{$_} } sort keys %$config) {
- $self->log(1, "invalid parameter(s): " . join(',',@bad));
+ $self->log(LOGCRIT, "invalid parameter(s): " . join(',',@bad));
}
$self->{_greylist_config} = $config;
unless ($config->{recipient} || $config->{per_recipient}) {
@@ -173,7 +174,7 @@
return DECLINED unless $note;
# Decline if ALL recipients are whitelisted
if (($transaction->notes('whitelistrcpt')||0) == scalar($transaction->recipients)) {
- $self->log(4,"all recipients whitelisted - skipping");
+ $self->log(LOGINFO,"all recipients whitelisted - skipping");
return DECLINED;
}
return DENYSOFT, $note;
@@ -182,7 +183,7 @@
sub denysoft_greylist {
my ($self, $transaction, $sender, $rcpt, $config) = @_;
$config ||= $self->{_greylist_config};
- $self->log(7, "config: " . join(',',map { $_ . '=' . $config->{$_} } sort keys %$config));
+ $self->log(DEBUG, "config: " . join(',',map { $_ . '=' . $config->{$_} } sort keys %$config));
# Always allow relayclients and whitelisted hosts/senders
return DECLINED if exists $ENV{RELAYCLIENT};
@@ -194,24 +195,24 @@
if $config->{per_recipient_db};
$dbdir ||= -d "$QPHOME/var/db" ? "$QPHOME/var/db" : "$QPHOME/config";
my $db = "$dbdir/$DB";
- $self->log(6,"using $db as greylisting database");
+ $self->log(LOGDEBUG,"using $db as greylisting database");
my $remote_ip = $self->qp->connection->remote_ip;
my $fmt = "%s:%d:%d:%d";
# Check denysoft db
unless (open LOCK, ">$db.lock") {
- $self->log(2, "opening lockfile failed: $!");
+ $self->log(LOGERROR, "opening lockfile failed: $!");
return DECLINED;
}
unless (flock LOCK, LOCK_EX) {
- $self->log(2, "flock of lockfile failed: $!");
+ $self->log(LOGERROR, "flock of lockfile failed: $!");
close LOCK;
return DECLINED;
}
my %db = ();
unless (tie %db, 'AnyDBM_File', $db, O_CREAT|O_RDWR, 0600) {
- $self->log(2, "tie to database $db failed: $!");
+ $self->log(LOGERROR, "tie to database $db failed: $!");
close LOCK;
return DECLINED;
}
@@ -223,12 +224,12 @@
my ($ts, $new, $black, $white) = (0,0,0,0);
if ($db{$key}) {
($ts, $new, $black, $white) = split /:/, $db{$key};
- $self->log(3, "ts: " . localtime($ts) . ", now: " . localtime);
+ $self->log(LOGDEBUG, "ts: " . localtime($ts) . ", now: " . localtime);
if (! $white) {
# Black IP - deny, but don't update timestamp
if (time - $ts < $config->{black_timeout}) {
$db{$key} = sprintf $fmt, $ts, $new, ++$black, 0;
- $self->log(2, "key $key black DENYSOFT - $black failed connections");
+ $self->log(LOGNOTICE, "key $key black DENYSOFT - $black failed connections");
untie %db;
close LOCK;
return $config->{mode} eq 'testonly' ? DECLINED : DENYSOFT, $DENYMSG;
@@ -236,33 +237,33 @@
# Grey IP - accept unless timed out
elsif (time - $ts < $config->{grey_timeout}) {
$db{$key} = sprintf $fmt, time, $new, $black, 1;
- $self->log(2, "key $key updated grey->white");
+ $self->log(LOGNOTICE, "key $key updated grey->white");
untie %db;
close LOCK;
return DECLINED;
}
else {
- $self->log(3, "key $key has timed out (grey)");
+ $self->log(LOGNOTICE, "key $key has timed out (grey)");
}
}
# White IP - accept unless timed out
else {
if (time - $ts < $config->{white_timeout}) {
$db{$key} = sprintf $fmt, time, $new, $black, ++$white;
- $self->log(2, "key $key is white, $white deliveries");
+ $self->log(LOGINFO, "key $key is white, $white deliveries");
untie %db;
close LOCK;
return DECLINED;
}
else {
- $self->log(3, "key $key has timed out (white)");
+ $self->log(LOGNOTICE, "key $key has timed out (white)");
}
}
}
# New ip or entry timed out - record new and return DENYSOFT
$db{$key} = sprintf $fmt, time, ++$new, $black, 0;
- $self->log(2, "key $key initial DENYSOFT, unknown");
+ $self->log(LOGNOTICE, "key $key initial DENYSOFT, unknown");
untie %db;
close LOCK;
return $config->{mode} eq 'testonly' ? DECLINED : DENYSOFT, $DENYMSG;
--- lib/Qpsmtpd/TcpServer.pm.orig Sun Jan 30 05:40:24 2005
+++ lib/Qpsmtpd/TcpServer.pm Thu Feb 24 15:09:28 2005
@@ -74,7 +74,7 @@
my ($self, $code, @messages) = @_;
while (my $msg = shift @messages) {
my $line = $code . (@messages?"-":" ").$msg;
- $self->log(LOGDEBUG, $line);
+ $self->log( ($code >= 400 ? LOGNOTICE : LOGINFO), $line);
print "$line\r\n" or ($self->log(LOGERROR, "Could not print [$line]: $!"), return 0);
}
return 1;
--- lib/Qpsmtpd.pm.orig Thu Feb 24 12:47:53 2005
+++ lib/Qpsmtpd.pm Sat Feb 26 13:02:53 2005
@@ -129,7 +129,7 @@
my @plugins = $self->config('plugins');
my $dir = $self->plugin_dir;
- $self->log(LOGNOTICE, "loading plugins from $dir");
+ $self->log(LOGDEBUG, "loading plugins from $dir");
@plugins = $self->_load_plugins($dir, @plugins);
@@ -206,7 +206,7 @@
if ($hooks->{$hook}) {
my @r;
for my $code (@{$hooks->{$hook}}) {
- $self->log(LOGINFO, "running plugin ($hook):", $code->{name});
+ $self->log(LOGDEBUG, "running plugin ($hook):", $code->{name});
eval { (@r) = $code->{code}->($self, $self->transaction, @_); };
$@ and $self->log(LOGCRIT, "FATAL PLUGIN ERROR: ", $@) and next;
--- plugins/spamassassin.orig Tue Nov 30 11:48:20 2004
+++ plugins/spamassassin Mon Feb 28 12:05:19 2005
@@ -138,18 +138,18 @@
# or CHECK or REPORT or SYMBOLS
print SPAMD "X-Envelope-From: ", $transaction->sender->format, CRLF
- or warn "Could not print to spamd: $!";
+ or $self->log(LOGERROR, "Could not print to spamd: $!");
print SPAMD join CRLF, split /\n/, $transaction->header->as_string
- or warn "Could not print to spamd: $!";
+ or $self->log(LOGERROR, "Could not print to spamd: $!");
print SPAMD CRLF
- or warn "Could not print to spamd: $!";
+ or $self->log(LOGERROR, "Could not print to spamd: $!");
while (my $line = $transaction->body_getline) {
chomp $line;
print SPAMD $line, CRLF
- or warn "Could not print to spamd: $!";
+ or $self->log(LOGERROR, "Could not print to spamd: $!");
}
print SPAMD CRLF;
@@ -178,7 +178,6 @@
my ($flag, $hits, $required);
while (<SPAMD>) {
$self->log(LOGDEBUG, "check_spam: spamd: $_");
- #warn "GOT FROM SPAMD1: $_";
last unless m/\S/;
if (m{Spam: (True|False) ; (-?\d+\.\d) / (-?\d+\.\d)}) {
($flag, $hits, $required) = ($1, $2, $3);
@@ -213,7 +212,7 @@
$transaction->header->add('X-Spam-Status',
"$flag, hits=$hits required=$required\n" .
"\ttests=$tests", 0);
- $self->log(5, "check_spam: $flag, hits=$hits, required=$required, " .
+ $self->log(LOGINFO, "check_spam: $flag, hits=$hits, required=$required, " .
"tests=$tests");
return (DECLINED);
--- lib/Qpsmtpd/SMTP.pm.orig Tue Nov 23 16:37:40 2004
+++ lib/Qpsmtpd/SMTP.pm Tue Nov 23 16:58:33 2004
@@ -287,6 +287,7 @@
$self->log(LOGWARN, "$$ to email address : [$rcpt]");
$rcpt = (Qpsmtpd::Address->parse($rcpt))[0];
+ $self->transaction->add_badrcpt($rcpt);
return $self->respond(501, "could not parse recipient") unless $rcpt;
my ($rc, $msg) = $self->run_hooks("rcpt", $rcpt);
--- lib/Qpsmtpd/Transaction.pm.orig Wed Oct 20 11:58:48 2004
+++ lib/Qpsmtpd/Transaction.pm Tue Nov 23 16:59:29 2004
@@ -32,6 +32,18 @@
($self->{_recipients} ? @{$self->{_recipients}} : ());
}
+sub add_badrcpt {
+ my $self = shift;
+ @_ and push @{$self->{_badrcpts}}, shift;
+}
+
+sub badrcpts {
+ my $self = shift;
+ ($self->{_badrcpts} ? @{$self->{_badrcpts}} : ());
+}
+
+
+
sub sender {
my $self = shift;
@_ and $self->{_sender} = shift;
--- plugins/require_resolvable_fromhost.orig Fri Jan 28 03:30:50 2005
+++ plugins/require_resolvable_fromhost Thu Mar 3 10:34:56 2005
@@ -3,6 +3,7 @@
sub register {
my ($self, $qp) = @_;
$self->register_hook("mail", "mail_handler");
+ $self->register_hook("rcpt", "rcpt_handler");
}
sub mail_handler {
@@ -13,19 +14,24 @@
$sender->format ne "<>"
and $self->qp->config("require_resolvable_fromhost")
- and !check_dns($sender->host)
- and return (DENYSOFT,
- ($sender->host
- ? "Could not resolve ". $sender->host
- : "FQDN required in the envelope sender"));
-
- return DECLINED;
+ and !$self->check_dns($sender->host)
+ and $transaction->notes('fromhost' ,$sender->host
+ ? "Could not resolve sender domain ". $sender->host
+ : "FQDN required in the envelope sender");
+ return DECLINED;
}
+sub rcpt_handler {
+ my ($self, $transaction, $rcpt) = @_;
+ my $note = $transaction->notes('fromhost');
+ return (DENYSOFT, $note) if $note and !$transaction->notes('goodmailfrom');
+ return (DECLINED);
+}
+
sub check_dns {
- my $host = shift;
+ my ($self, $host) = @_;
# for stuff where we can't even parse a hostname out of the address
return 0 unless $host;
@@ -43,7 +49,7 @@
}
}
else {
- warn "$$ query for $host failed: ", $res->errorstring, "\n"
+ $self->log(LOGNOTICE, "query for $host failed: ", $res->errorstring)
unless $res->errorstring eq "NXDOMAIN";
}
return 0;