From 77e63e92ae1ba572206f7d4ffe717f7d8ff00b95 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Wed, 27 Jun 2012 19:17:01 -0400 Subject: [PATCH 01/25] drop the check_ prefix from the last 3 plugins --- Changes | 8 ++++---- MANIFEST | 12 ++++++------ config.sample/plugins | 2 +- docs/hooks.pod | 2 +- plugins/async/{check_earlytalker => earlytalker} | 2 +- plugins/{check_bogus_bounce => bogus_bounce} | 2 +- plugins/{check_earlytalker => earlytalker} | 6 +++--- plugins/{check_loop => loop} | 2 +- t/config/plugins | 2 +- 9 files changed, 19 insertions(+), 19 deletions(-) rename plugins/async/{check_earlytalker => earlytalker} (97%) rename plugins/{check_bogus_bounce => bogus_bounce} (97%) rename plugins/{check_earlytalker => earlytalker} (97%) rename plugins/{check_loop => loop} (97%) diff --git a/Changes b/Changes index 4cba6eb..be8d88f 100644 --- a/Changes +++ b/Changes @@ -570,7 +570,7 @@ Next Version no longer exists for that sender (great for harassment cases). (John Peacock) - check_earlytalker and resolvable_fromhost - short circuit test if + earlytalker and resolvable_fromhost - short circuit test if whitelistclient is set. (Michael Toren) check_badmailfrom - Do not say why a given message is denied. @@ -642,7 +642,7 @@ Next Version Add a plugin hook for the DATA command - check_earlytalker - + earlytalker - + optionally react to an earlytalker by denying all MAIL-FROM commands rather than issuing a 4xx/5xx greeting and disconnecting. (Mark Powell) @@ -728,7 +728,7 @@ Next Version Use $ENV{QMAIL} to override /var/qmail for where to find the control/ directory. - Enable "check_earlytalker" in the default plugins config + Enable "earlytalker" in the default plugins config Added a milter plugin to allow use of sendmail milters @@ -792,7 +792,7 @@ Next Version unrecognized_command hook and a count_unrecognized_commands plugin. (Rasjid Wilcox) - check_earlytalker plugin. Deny the connection if the client talks + earlytalker plugin. Deny the connection if the client talks before we show our SMTP banner. (From Devin Carraway) Patch Qpsmtpd::SMTP to allow connect plugins to give DENY and diff --git a/MANIFEST b/MANIFEST index b9d30ca..991ffdd 100644 --- a/MANIFEST +++ b/MANIFEST @@ -59,7 +59,7 @@ Makefile.PL MANIFEST This list of files MANIFEST.SKIP META.yml Module meta-data (added by MakeMaker) -plugins/async/check_earlytalker +plugins/async/earlytalker plugins/async/dns_whitelist_soft plugins/async/dnsbl plugins/async/queue/smtp-forward @@ -77,9 +77,9 @@ plugins/auth/authdeny plugins/badmailfrom plugins/badmailfromto plugins/badrcptto -plugins/check_bogus_bounce -plugins/check_earlytalker -plugins/check_loop +plugins/bogus_bounce +plugins/earlytalker +plugins/loop plugins/connection_time plugins/content_log plugins/count_unrecognized_commands @@ -172,9 +172,9 @@ t/plugin_tests/auth/auth_vpopmaild t/plugin_tests/auth/authdeny t/plugin_tests/auth/authnull t/plugin_tests/badmailfrom -t/plugin_tests/check_badmailfromto +t/plugin_tests/badmailfromto t/plugin_tests/badrcptto -t/plugin_tests/check_earlytalker +t/plugin_tests/earlytalker t/plugin_tests/count_unrecognized_commands t/plugin_tests/dnsbl t/plugin_tests/dspam diff --git a/config.sample/plugins b/config.sample/plugins index 887a022..5fb03f8 100644 --- a/config.sample/plugins +++ b/config.sample/plugins @@ -30,7 +30,7 @@ dont_require_anglebrackets quit_fortune # tls should load before count_unrecognized_commands #tls -check_earlytalker +earlytalker count_unrecognized_commands 4 relay diff --git a/docs/hooks.pod b/docs/hooks.pod index 6423fc6..3dd7b5a 100644 --- a/docs/hooks.pod +++ b/docs/hooks.pod @@ -293,7 +293,7 @@ was sent, this hook is called. B This hook, like B, B, B, B, is an endpoint of a pipelined command group (see RFC 1854) and may be used to -detect ``early talkers''. Since svn revision 758 the F +detect ``early talkers''. Since svn revision 758 the F plugin may be configured to check at this hook for ``early talkers''. Allowed return codes are diff --git a/plugins/async/check_earlytalker b/plugins/async/earlytalker similarity index 97% rename from plugins/async/check_earlytalker rename to plugins/async/earlytalker index fa0266d..9e3fb22 100644 --- a/plugins/async/check_earlytalker +++ b/plugins/async/earlytalker @@ -2,7 +2,7 @@ =head1 NAME -check_earlytalker - Check that the client doesn't talk before we send the SMTP banner +earlytalker - Check that the client doesn't talk before we send the SMTP banner =head1 DESCRIPTION diff --git a/plugins/check_bogus_bounce b/plugins/bogus_bounce similarity index 97% rename from plugins/check_bogus_bounce rename to plugins/bogus_bounce index 70e5de0..2a97472 100644 --- a/plugins/check_bogus_bounce +++ b/plugins/bogus_bounce @@ -2,7 +2,7 @@ =head1 NAME -check_bogus_bounce - Check that a bounce message isn't bogus +bogus_bounce - Check that a bounce message isn't bogus =head1 DESCRIPTION diff --git a/plugins/check_earlytalker b/plugins/earlytalker similarity index 97% rename from plugins/check_earlytalker rename to plugins/earlytalker index 5a8ef3d..f75c8fe 100644 --- a/plugins/check_earlytalker +++ b/plugins/earlytalker @@ -2,7 +2,7 @@ =head1 NAME -check_earlytalker - Check that the client doesn't talk before we send the SMTP banner +earlytalker - Check that the client doesn't talk before we send the SMTP banner =head1 DESCRIPTION @@ -30,7 +30,7 @@ must also be allowed for. Do we reject/deny connections to early talkers? - check_earlytalker reject [ 0 | 1 ] + earlytalker reject [ 0 | 1 ] Default: I @@ -48,7 +48,7 @@ issued a deny or denysoft (depending on the value of I). The defaul is to react at the SMTP greeting stage by issuing the apropriate response code and terminating the SMTP connection. - check_earlytalker defer-reject [ 0 | 1 ] + earlytalker defer-reject [ 0 | 1 ] =head2 check-at [ CONNECT | DATA ] diff --git a/plugins/check_loop b/plugins/loop similarity index 97% rename from plugins/check_loop rename to plugins/loop index 634c126..1a3d264 100644 --- a/plugins/check_loop +++ b/plugins/loop @@ -2,7 +2,7 @@ =head1 NAME -check_loop - Detect mail loops +loop - Detect mail loops =head1 DESCRIPTION diff --git a/t/config/plugins b/t/config/plugins index 44bbe28..c4f25d6 100644 --- a/t/config/plugins +++ b/t/config/plugins @@ -30,7 +30,7 @@ parse_addr_withhelo quit_fortune # tls should load before count_unrecognized_commands #tls -check_earlytalker +earlytalker count_unrecognized_commands 4 relay From 8fd04a2621a1ce085f177ae0018185e4a15c2749 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Wed, 27 Jun 2012 19:36:58 -0400 Subject: [PATCH 02/25] SPF: more logging additions --- plugins/sender_permitted_from | 56 ++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/plugins/sender_permitted_from b/plugins/sender_permitted_from index dabad55..d888701 100644 --- a/plugins/sender_permitted_from +++ b/plugins/sender_permitted_from @@ -143,28 +143,18 @@ sub mail_handler { }; # SPF result codes: pass fail softfail neutral none error permerror temperror + return $self->handle_code_none($reject, $why) if $code eq 'none'; + return $self->handle_code_fail($reject, $why) if $code eq 'fail'; + return $self->handle_code_softfail($reject, $why) if $code eq 'softfail'; + if ( $code eq 'pass' ) { $self->log(LOGINFO, "pass, $code: $why" ); return (DECLINED); } - elsif ( $code eq 'fail' ) { - $self->log(LOGINFO, "fail, $why" ); - return (DENY, "SPF - forgery: $why") if $reject >= 3; - return (DENYSOFT, "SPF - $code: $why") if $reject >= 2; - } - elsif ( $code eq 'softfail' ) { - $self->log(LOGINFO, "fail, $why" ); - return (DENY, "SPF - $code: $why") if $reject >= 4; - return (DENYSOFT, "SPF - $code: $why") if $reject >= 3; - } elsif ( $code eq 'neutral' ) { $self->log(LOGINFO, "fail, $code, $why" ); return (DENY, "SPF - $code: $why") if $reject >= 5; } - elsif ( $code eq 'none' ) { - $self->log(LOGINFO, "fail, $code, $why" ); - return (DENY, "SPF - $code: $why") if $reject >= 6; - } elsif ( $code eq 'error' ) { $self->log(LOGINFO, "fail, $code, $why" ); return (DENY, "SPF - $code: $why") if $reject >= 6; @@ -184,6 +174,44 @@ sub mail_handler { return (DECLINED); } +sub handle_code_none { + my ($self, $reject, $why ) = @_; + + if ( $reject >= 6 ) { + $self->log(LOGINFO, "fail, none, $why" ); + return (DENY, "SPF - none: $why"); + }; + + $self->log(LOGINFO, "pass, none, $why" ); + return DECLINED; +}; + +sub handle_code_fail { + my ($self, $reject, $why ) = @_; + + if ( $reject >= 2 ) { + $self->log(LOGINFO, "fail, $why" ); + return (DENY, "SPF - forgery: $why") if $reject >= 3; + return (DENYSOFT, "SPF - fail: $why") + }; + + $self->log(LOGINFO, "pass, fail tolerated, $why" ); + return DECLINED; +}; + +sub handle_code_softfail { + my ($self, $reject, $why ) = @_; + + if ( $reject >= 3 ) { + $self->log(LOGINFO, "fail, soft, $why" ); + return (DENY, "SPF - fail: $why") if $reject >= 4; + return (DENYSOFT, "SPF - fail: $why") if $reject >= 3; + }; + + $self->log(LOGINFO, "pass, softfail tolerated, $why" ); + return DECLINED; +}; + sub data_post_handler { my ($self, $transaction) = @_; From 0ca16d61a774a83703643eaced3792405e21eb78 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Wed, 27 Jun 2012 20:15:13 -0400 Subject: [PATCH 03/25] summarize: check more locations to discover QP dir --- log/summarize.pl | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/log/summarize.pl b/log/summarize.pl index 04784cc..b506d82 100755 --- a/log/summarize.pl +++ b/log/summarize.pl @@ -3,6 +3,7 @@ use strict; use warnings; +use Cwd; use Data::Dumper; use File::Tail; @@ -276,16 +277,20 @@ sub show_symbol { sub get_qp_dir { foreach my $user ( qw/ qpsmtpd smtpd / ) { - my ($homedir) = (getpwnam( $user ))[7] or next; if ( -d "$homedir/plugins" ) { return "$homedir"; }; - if ( -d "$homedir/smtpd/plugins" ) { - return "$homedir/smtpd"; + foreach my $s ( qw/ smtpd qpsmtpd qpsmtpd-dev / ) { + if ( -d "$homedir/smtpd/plugins" ) { + return "$homedir/smtpd"; + }; }; }; + if ( -d "./plugins" ) { + return Cwd::getcwd(); + }; }; sub populate_plugins_from_registry { From ba854c471fca704cfd7c40a62b7e661070070866 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Wed, 27 Jun 2012 20:16:11 -0400 Subject: [PATCH 04/25] log/run: removed spurious space --- log/run | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/log/run b/log/run index 5b3b4b6..e3a630c 100755 --- a/log/run +++ b/log/run @@ -1,4 +1,4 @@ -#! /bin/sh +#!/bin/sh export LOGDIR=./main mkdir -p $LOGDIR exec multilog t s10000000 n20 $LOGDIR From 6f34fbb6cdc34ebfd7aa92d6d9f45511e22724d6 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Wed, 27 Jun 2012 20:16:34 -0400 Subject: [PATCH 05/25] dspam: better error message if dspam_bin is not found --- plugins/dspam | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dspam b/plugins/dspam index d92da7f..d133dd8 100644 --- a/plugins/dspam +++ b/plugins/dspam @@ -216,7 +216,7 @@ sub register { $self->{_args}{dspam_bin} ||= '/usr/local/bin/dspam'; if ( ! -x $self->{_args}{dspam_bin} ) { - $self->log(LOGERROR, "dspam not found: "); + $self->log(LOGERROR, "dspam CLI binary not found: install dspam and/or set dspam_bin"); return DECLINED; }; From e3fcd08706778a084e39858b30f81e407a444856 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Wed, 27 Jun 2012 20:17:00 -0400 Subject: [PATCH 06/25] qmail_deliverable: test variable if defined before accessing --- plugins/qmail_deliverable | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/qmail_deliverable b/plugins/qmail_deliverable index 0704b06..b22d221 100755 --- a/plugins/qmail_deliverable +++ b/plugins/qmail_deliverable @@ -77,7 +77,7 @@ sub register { $self->log(LOGWARN, "Odd number of arguments, using default config"); } else { my %args = @args; - if ($args{server} =~ /^smtproutes:/) { + if ($args{server} && $args{server} =~ /^smtproutes:/) { my ($fallback, $port) = $args{server} =~ /:(?:(.*?):?)(\d+)/; From c5fb92e64989c733886a8903c79d4f1676135159 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Wed, 27 Jun 2012 20:18:16 -0400 Subject: [PATCH 07/25] spamassassin: further log message refinement --- plugins/spamassassin | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/spamassassin b/plugins/spamassassin index 7070d7f..3c6b0f9 100644 --- a/plugins/spamassassin +++ b/plugins/spamassassin @@ -369,11 +369,12 @@ sub reject { my ($self, $transaction) = @_; my $sa_results = $self->get_spam_results($transaction) or do { - $self->log(LOGNOTICE, "skip, no results"); + $self->log(LOGNOTICE, "error, no results"); return DECLINED; }; - my $score = $sa_results->{score} or do { - $self->log(LOGERROR, "skip, error getting score"); + my $score = $sa_results->{score}; + if ( ! defined $score ) { + $self->log(LOGERROR, "error, error getting score"); return DECLINED; }; @@ -385,7 +386,7 @@ sub reject { }; my $reject = $self->{_args}{reject} or do { - $self->log(LOGERROR, "skip, reject disabled ($status, $learn)"); + $self->log(LOGERROR, "pass, reject disabled ($status, $learn)"); return DECLINED; }; @@ -400,7 +401,7 @@ sub reject { } } - $self->connection->notes('karma', $self->connection->notes('karma') - 1); + $self->connection->notes('karma', ($self->connection->notes('karma') - 1)); # default of media_unsupported is DENY, so just change the message $self->log(LOGINFO, "fail, $status, > $reject, $learn"); return ($self->get_reject_type(), "spam score exceeded threshold"); From db206898875a188c33093a063a71bf11c1ab1e6a Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Fri, 29 Jun 2012 20:20:58 -0400 Subject: [PATCH 08/25] registry: added auth_ prefixes, relay aliases --- plugins/registry.txt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/plugins/registry.txt b/plugins/registry.txt index cedcd91..0ecfb3a 100644 --- a/plugins/registry.txt +++ b/plugins/registry.txt @@ -9,7 +9,7 @@ 3 ident::p0f p0f p0f 5 karma krm karma 6 dnsbl dbl dnsbl -7 relay rly relay +7 relay rly relay check_relay,check_norelay,relay_only 9 earlytalker ear early check_earlytalker 15 helo hlo helo check_spamhelo 16 tls tls tls @@ -22,13 +22,14 @@ # # Authentication # -30 auth::vpopmail_sql aut vpsql -31 auth::vpopmaild vpd vpopd -32 auth::vpopmail vpo vpop -33 auth::checkpasswd ckp chkpw -34 auth::cvs_unix_local cvs cvsul -35 auth::flat_file flt aflat -36 auth::ldap_bind ldp aldap +30 auth::auth_vpopmail_sql aut vpsql +31 auth::auth_vpopmaild vpd vpopd +32 auth::auth_vpopmail vpo vpop +33 auth::auth_checkpasswd ckp chkpw +34 auth::auth_cvs_unix_local cvs cvsul +35 auth::auth_flat_file flt aflat +36 auth::auth_ldap_bind ldp aldap +37 auth::authdeny dny adeny # # Sender / From # From d239f394e9983e03062f91f473f056382c7511d7 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Fri, 29 Jun 2012 20:28:38 -0400 Subject: [PATCH 09/25] summarize: recognize tcpserver log entries --- log/summarize.pl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/log/summarize.pl b/log/summarize.pl index b506d82..c4616ff 100755 --- a/log/summarize.pl +++ b/log/summarize.pl @@ -70,7 +70,7 @@ while ( defined (my $line = $fh->read) ) { next if ! $line; my ( $type, $pid, $hook, $plugin, $message ) = parse_line( $line ); next if ! $type; - next if $type =~ /info|unknown|response/; + next if $type =~ /^(info|unknown|response|tcpserver)$/; next if $type eq 'init'; # doesn't occur in all deployment models if ( ! $pids{$pid} ) { # haven't seen this pid @@ -151,6 +151,7 @@ sub parse_line { return parse_line_plugin( $line ) if substr($message, 0, 1) eq '('; return ( 'dispatch', $pid, undef, undef, $message ) if substr($message, 0, 12) eq 'dispatching '; return ( 'response', $pid, undef, undef, $message ) if $message =~ /^[2|3]\d\d/; + return ( 'tcpserver', $pid, undef, undef, undef ) if substr($pid, 0, 10) eq 'tcpserver:'; # lines seen about once per connection return ( 'init', $pid, undef, undef, $message ) if substr($message, 0, 19) eq 'Accepted connection'; From 5825c2c3c8c4378af2d181978a943ac0876730aa Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Fri, 29 Jun 2012 20:29:04 -0400 Subject: [PATCH 10/25] clamdscan: default is scan always, even authenticated --- plugins/virus/clamdscan | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/virus/clamdscan b/plugins/virus/clamdscan index 0af2929..72e64ea 100644 --- a/plugins/virus/clamdscan +++ b/plugins/virus/clamdscan @@ -140,7 +140,7 @@ sub data_post_handler { my $filename = $self->get_filename( $transaction ) or return DECLINED; - return (DECLINED) if $self->is_immune(); + #return (DECLINED) if $self->is_immune(); return (DECLINED) if $self->is_too_big( $transaction ); return (DECLINED) if $self->is_not_multipart( $transaction ); From c723c40670fbb1de813c5e9914a5e37d36b59e82 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Fri, 29 Jun 2012 20:30:06 -0400 Subject: [PATCH 11/25] run: define PORT variable --- run | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/run b/run index 22c6029..0e2ff84 100755 --- a/run +++ b/run @@ -11,6 +11,7 @@ PERL=/usr/bin/perl QMAILDUID=`id -u $QPUSER` NOFILESGID=`id -g $QPUSER` IP=`head -1 config/IP` +PORT=25 LANG=C # Remove the comments between the and tags to choose a @@ -19,7 +20,7 @@ LANG=C # exec $BIN/softlimit -m $MAXRAM \ $BIN/tcpserver -c 10 -v -R -p \ - -u $QMAILDUID -g $NOFILESGID $IP smtp \ + -u $QMAILDUID -g $NOFILESGID $IP $PORT \ ./qpsmtpd 2>&1 # @@ -30,7 +31,7 @@ exec $BIN/softlimit -m $MAXRAM \ # exec $BIN/softlimit -m $MAXRAM \ # $PERL -T ./qpsmtpd-forkserver \ # --listen-address $IP \ -# --port 25 \ +# --port $PORT \ # --limit-connections 15 \ # --max-from-ip 5 \ # --user $QPUSER From 3a50137b34f8a4b72752a6b9073de6faa8f0e049 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Fri, 29 Jun 2012 20:39:44 -0400 Subject: [PATCH 12/25] logs: improve ability to find logs --- log/summarize.pl | 4 ++-- log/watch.pl | 22 ++++++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/log/summarize.pl b/log/summarize.pl index c4616ff..1201aa0 100755 --- a/log/summarize.pl +++ b/log/summarize.pl @@ -284,8 +284,8 @@ sub get_qp_dir { return "$homedir"; }; foreach my $s ( qw/ smtpd qpsmtpd qpsmtpd-dev / ) { - if ( -d "$homedir/smtpd/plugins" ) { - return "$homedir/smtpd"; + if ( -d "$homedir/$s/plugins" ) { + return "$homedir/$s"; }; }; }; diff --git a/log/watch.pl b/log/watch.pl index b93ff6e..0514a3d 100755 --- a/log/watch.pl +++ b/log/watch.pl @@ -3,11 +3,12 @@ use strict; use warnings; +use Cwd; use Data::Dumper; use File::Tail; -my $dir = find_qp_log_dir() or die "unable to find QP home dir"; -my $file = "$dir/main/current"; +my $dir = get_qp_dir() or die "unable to find QP home dir"; +my $file = "$dir/log/main/current"; my $fh = File::Tail->new(name=>$file, interval=>1, maxinterval=>1, debug =>1, tail =>100 ); while ( defined (my $line = $fh->read) ) { @@ -15,16 +16,21 @@ while ( defined (my $line = $fh->read) ) { print $line; }; -sub find_qp_log_dir { +sub get_qp_dir { foreach my $user ( qw/ qpsmtpd smtpd / ) { - my ($homedir) = (getpwnam( $user ))[7] or next; - if ( -d "$homedir/log" ) { - return "$homedir/log"; + if ( -d "$homedir/plugins" ) { + return "$homedir"; }; - if ( -d "$homedir/smtpd/log" ) { - return "$homedir/smtpd/log"; + foreach my $s ( qw/ smtpd qpsmtpd qpsmtpd-dev / ) { + if ( -d "$homedir/$s/plugins" ) { + return "$homedir/$s"; + }; }; }; + if ( -d "./plugins" ) { + return Cwd::getcwd(); + }; }; + From 5bc212b890008ac6646148faff7add9ac2bfee0f Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Sat, 30 Jun 2012 14:18:16 -0400 Subject: [PATCH 13/25] helo: added is_plain_ip to lenient checks there's no excuse for a client to ever send a raw IP, and I have yet to see a valid client do it --- plugins/helo | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/plugins/helo b/plugins/helo index 58748c7..10ee6b3 100644 --- a/plugins/helo +++ b/plugins/helo @@ -106,25 +106,25 @@ Default: lenient =head3 lenient -Reject failures of the following tests: is_in_badhelo, invalid_localhost, and -is_forged_literal. +Reject failures of the following tests: is_in_badhelo, invalid_localhost, +is_forged_literal, and is_plain_ip. This setting is lenient enough not to cause problems for your Windows users. It is comparable to running check_spamhelo, but with the addition of regexp -support and the prevention of forged localhost and forged IP literals. +support, the prevention of forged localhost, forged IP literals, and plain +IPs. =head3 rfc Per RFC 2821, the HELO hostname is the FQDN of the sending server or an address literal. When I is selected, all the lenient checks and -the following are enforced: is_plain_ip, is_not_fqdn, no_forward_dns, and -no_reverse_dns. +the following are enforced: is_not_fqdn, no_forward_dns, and no_reverse_dns. If you have Windows users that send mail via your server, do not choose -I without I and the B plugin. Windows -users often send unqualified HELO names and will have trouble sending mail. - can defer the rejection, and if the user subsequently authenticates, -the rejection will be cancelled. +I without settings I and using the B +plugin. Windows PCs often send unqualified HELO names and will have trouble +sending mail. The B plugin defers the rejection, and if the user +subsequently authenticates, the rejection is be cancelled. =head3 strict @@ -259,11 +259,10 @@ sub populate_tests { my $self = shift; my $policy = $self->{_args}{policy}; - @{ $self->{_helo_tests} } = qw/ is_in_badhelo invalid_localhost is_forged_literal /; + @{ $self->{_helo_tests} } = qw/ is_in_badhelo invalid_localhost is_forged_literal is_plain_ip /; if ( $policy eq 'rfc' || $policy eq 'strict' ) { - push @{ $self->{_helo_tests} }, qw/ is_plain_ip is_not_fqdn - no_forward_dns no_reverse_dns /; + push @{ $self->{_helo_tests} }, qw/ is_not_fqdn no_forward_dns no_reverse_dns /; }; if ( $policy eq 'strict' ) { From 7d5edacf9be732c79f705d791500b7dfeb75d15d Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Sat, 30 Jun 2012 15:37:25 -0400 Subject: [PATCH 14/25] karma: added adjust_karma method makes it easier to set karma in plugins --- lib/Qpsmtpd/Plugin.pm | 9 ++ plugins/badmailfrom | 2 +- plugins/dspam | 9 +- plugins/earlytalker | 2 +- plugins/helo | 2 +- plugins/karma | 16 +-- plugins/qmail_deliverable | 4 +- plugins/spamassassin | 2 +- plugins/whitelist | 223 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 248 insertions(+), 21 deletions(-) create mode 100644 plugins/whitelist diff --git a/lib/Qpsmtpd/Plugin.pm b/lib/Qpsmtpd/Plugin.pm index 6b063b4..3086c20 100644 --- a/lib/Qpsmtpd/Plugin.pm +++ b/lib/Qpsmtpd/Plugin.pm @@ -282,6 +282,15 @@ sub is_immune { return; }; +sub adjust_karma { + my ( $self, $value ) = @_; + + my $karma = $self->connection->notes('karma') || 0 + $karma += $value; + $self->connection->notes('karma', $value); + return $value; +}; + sub _register_standard_hooks { my ($plugin, $qp) = @_; diff --git a/plugins/badmailfrom b/plugins/badmailfrom index 47aa425..1d1f36f 100644 --- a/plugins/badmailfrom +++ b/plugins/badmailfrom @@ -85,7 +85,7 @@ sub hook_mail { next unless $bad; next unless $self->is_match( $from, $bad, $host ); $reason ||= "Your envelope sender is in my badmailfrom list"; - $self->connection->notes('karma', ($self->connection->notes('karma') || 0) - 1); + $self->adjust_karma( -1 ); return $self->get_reject( $reason ); } diff --git a/plugins/dspam b/plugins/dspam index d133dd8..72aba48 100644 --- a/plugins/dspam +++ b/plugins/dspam @@ -478,9 +478,7 @@ sub reject_agree { if ( $d->{class} eq 'Spam' ) { if ( $sa->{is_spam} eq 'Yes' ) { - if ( defined $self->connection->notes('karma') ) { - $self->connection->notes('karma', $self->connection->notes('karma') - 2); - }; + $self->adjust_karma( -2 ); $self->log(LOGINFO, "fail, agree, $status"); my $reject = $self->get_reject_type(); return ($reject, 'we agree, no spam please'); @@ -493,9 +491,7 @@ sub reject_agree { if ( $d->{class} eq 'Innocent' ) { if ( $sa->{is_spam} eq 'No' ) { if ( $d->{confidence} > .9 ) { - if ( defined $self->connection->notes('karma') ) { - $self->connection->notes('karma', ( $self->connection->notes('karma') + 2) ); - }; + $self->adjust_karma( 2 ); }; $self->log(LOGINFO, "pass, agree, $status"); return DECLINED; @@ -591,6 +587,7 @@ sub autolearn { defined $self->{_args}{autolearn} or return; + # only train once. $self->autolearn_naughty( $response, $transaction ) and return; $self->autolearn_karma( $response, $transaction ) and return; $self->autolearn_spamassassin( $response, $transaction ) and return; diff --git a/plugins/earlytalker b/plugins/earlytalker index f75c8fe..f7d38b2 100644 --- a/plugins/earlytalker +++ b/plugins/earlytalker @@ -173,7 +173,7 @@ sub connect_handler { }; $self->connection->notes('earlytalker', 1); - $self->connection->notes('karma', -1); + $self->adjust_karma( -1 ); return DECLINED; } diff --git a/plugins/helo b/plugins/helo index 10ee6b3..29a3633 100644 --- a/plugins/helo +++ b/plugins/helo @@ -430,7 +430,7 @@ sub no_matching_dns { if ( $self->connection->notes('helo_forward_match') && $self->connection->notes('helo_reverse_match') ) { $self->log( LOGDEBUG, "foward and reverse match" ); -# TODO: consider adding some karma here + $self->adjust_karma( 1 ); # whoppee, a match! return; }; diff --git a/plugins/karma b/plugins/karma index e46fdfb..18fc768 100644 --- a/plugins/karma +++ b/plugins/karma @@ -177,14 +177,14 @@ those senders haven't sent us any ham. As such, it's much safer to use. This plugin sets the connection note I. Your plugin can use the senders karma to be more gracious or rude to senders. The value of -I is the number the nice connections minus naughty +I is the number of nice connections minus naughty ones. The higher the number, the better you should treat the sender. -When I is set and a naughty sender is encountered, most -plugins should skip processing. However, if you wish to toy with spammers by -teergrubing, extending banner delays, limiting connections, limiting -recipients, random disconnects, handoffs to rblsmtpd, and other fun tricks, -then connections with the I note set are for you! +To alter a connections karma based on its behavior, do this: + + $self->adjust_karma( -1 ); # lower karma (naughty) + $self->adjust_karma( 1 ); # raise karma (good) + =head1 EFFECTIVENESS @@ -194,7 +194,7 @@ connections. This plugins effectiveness results from the propensity of naughty senders to be repeat offenders. Limiting them to a single offense per day(s) greatly -reduces the number of useless tokens miscreants add to our Bayes databases. +reduces the resources they can waste. Of the connections that had previously passed all other checks and were caught only by spamassassin and/or dspam, B rejected 31 percent. Since @@ -207,7 +207,7 @@ Connection summaries are stored in a database. The database key is the int form of the remote IP. The value is a : delimited list containing a penalty box start time (if the server is/was on timeout) and the count of naughty, nice, and total connections. The database can be listed and searched with the -karma_dump.pl script. +karma_tool script. =head1 BUGS & LIMITATIONS diff --git a/plugins/qmail_deliverable b/plugins/qmail_deliverable index b22d221..04cf5aa 100755 --- a/plugins/qmail_deliverable +++ b/plugins/qmail_deliverable @@ -138,9 +138,7 @@ sub rcpt_handler { return DECLINED if $rv; - if ( defined $self->connection->notes('karma') ) { - $self->connection->notes('karma', ($self->connection->notes('karma') - 1)); - }; + $self->adjust_karma( -1 ); return (DENY, "fail, no mailbox by that name. qd (#5.1.1)" ); } diff --git a/plugins/spamassassin b/plugins/spamassassin index 3c6b0f9..6e81c7e 100644 --- a/plugins/spamassassin +++ b/plugins/spamassassin @@ -401,7 +401,7 @@ sub reject { } } - $self->connection->notes('karma', ($self->connection->notes('karma') - 1)); + $self->adjust_karma( -1 ); # default of media_unsupported is DENY, so just change the message $self->log(LOGINFO, "fail, $status, > $reject, $learn"); return ($self->get_reject_type(), "spam score exceeded threshold"); diff --git a/plugins/whitelist b/plugins/whitelist new file mode 100644 index 0000000..2e0ccb7 --- /dev/null +++ b/plugins/whitelist @@ -0,0 +1,223 @@ + +=head1 NAME + +whitelist - whitelist override for other qpsmtpd plugins + + +=head1 DESCRIPTION + +The B plugin allows selected hosts or senders or recipients +to be whitelisted as exceptions to later plugin processing. It is a more +conservative variant of Devin Carraway's 'whitelist' plugin. + + +=head1 CONFIGURATION + +To enable the plugin, add it to the qpsmtpd/config/plugins file as usual. +It should precede any plugins you might wish to whitelist for. + +Several configuration files are supported, corresponding to different +parts of the SMTP conversation: + +=over 4 + +=item whitelisthosts + +Any IP address (or start-anchored fragment thereof) listed in the +whitelisthosts file is exempted from any further validation during +'connect', and can be selectively exempted at other stages by +plugins testing for a 'whitelisthost' connection note. + +Similarly, if the environment variable $WHITELISTCLIENT is set +(which can be done by tcpserver), the connection will be exempt from +further 'connect' validation, and the host can be selectively +exempted by other plugins testing for a 'whitelistclient' connection +note. + +=item whitelisthelo + +Any host that issues a HELO matching an entry in whitelisthelo will +be exempted from further validation at the 'helo' stage. Subsequent +plugins can test for a 'whitelisthelo' connection note. Note that +this does not actually amount to an authentication in any meaningful +sense. + +=item whitelistsenders + +If the envelope sender of a mail (that which is sent as the MAIL FROM) +matches an entry in whitelistsenders, or if the hostname component +matches, the mail will be exempted from any further validation within +the 'mail' stage. Subsequent plugins can test for this exemption as a +'whitelistsender' transaction note. + +=item whitelistrcpt + +If any recipient of a mail (that sent as the RCPT TO) matches an +entry from whitelistrcpt, or if the hostname component matches, no +further validation will be required for this recipient. Subsequent +plugins can test for this exemption using a 'whitelistrcpt' +transaction note, which holds the count of whitelisted recipients. + +=back + +whitelist_soft also supports per-recipient whitelisting when using +the per_user_config plugin. To enable the per-recipient behaviour +(delaying all whitelisting until the rcpt part of the smtp +conversation, and using per-recipient whitelist configs, if +available), pass a true 'per_recipient' argument in the +config/plugins invocation i.e. + + whitelist_soft per_recipient 1 + +By default global and per-recipient whitelists are merged; to turn off +the merge behaviour pass a false 'merge' argument in the config/plugins +invocation i.e. + + whitelist_soft per_recipient 1 merge 0 + + +=head1 BUGS + +Whitelist lookups are all O(n) linear scans of configuration files, even +though they're all associative lookups. Something should be done about +this when CDB/DB/GDBM configs are supported. + + +=head1 AUTHOR + +Based on the 'whitelist' plugin by Devin Carraway . + +Modified by Gavin Carr to not inherit +whitelisting across hooks, but use per-hook whitelist notes instead. +This is a more conservative approach e.g. whitelisting an IP will not +automatically allow relaying from that IP. + +=cut + +my $VERSION = 0.02; + +# Default is to merge whitelists in per_recipient mode +my %MERGE = (merge => 1); + +sub register { + my ($self, $qp, %arg) = @_; + + $self->{_per_recipient} = 1 if $arg{per_recipient}; + $MERGE{merge} = $arg{merge} if defined $arg{merge}; + + # Normal mode - whitelist per hook + unless ($arg{per_recipient}) { + $self->register_hook("connect", "check_host"); + $self->register_hook("helo", "check_helo"); + $self->register_hook("ehlo", "check_helo"); + $self->register_hook("mail", "check_sender"); + $self->register_hook("rcpt", "check_rcpt"); + } + + # Per recipient mode - defer all whitelisting to rcpt hook + else { + $self->register_hook("rcpt", "check_host"); + $self->register_hook("helo", "helo_helper"); + $self->register_hook("ehlo", "helo_helper"); + $self->register_hook("rcpt", "check_helo"); + $self->register_hook("rcpt", "check_sender"); + $self->register_hook("rcpt", "check_rcpt"); + } +} + +sub check_host { + my ($self, $transaction, $rcpt) = @_; + my $ip = $self->qp->connection->remote_ip || return (DECLINED); + + # From tcpserver + if (exists $ENV{WHITELISTCLIENT}) { + $self->qp->connection->notes('whitelistclient', 1); + $self->log(2, "host $ip is a whitelisted client"); + return OK; + } + + my $config_arg = $self->{_per_recipient} ? {rcpt => $rcpt, %MERGE} : {}; + for my $h ($self->qp->config('whitelisthosts', $config_arg)) { + if ($h eq $ip or $ip =~ /^\Q$h\E/) { + $self->qp->connection->notes('whitelisthost', 1); + $self->log(2, "host $ip is a whitelisted host"); + return OK; + } + } + return DECLINED; +} + +sub helo_helper { + my ($self, $transaction, $helo) = @_; + $self->{_whitelist_soft_helo} = $helo; + return DECLINED; +} + +sub check_helo { + my ($self, $transaction, $helo) = @_; + + # If per_recipient will be rcpt hook, and helo actually rcpt + my $config_arg = {}; + if ($self->{_per_recipient}) { + $config_arg = {rcpt => $helo, %MERGE}; + $helo = $self->{_whitelist_soft_helo}; + } + + for my $h ($self->qp->config('whitelisthelo', $config_arg)) { + if ($helo and lc $h eq lc $helo) { + $self->qp->connection->notes('whitelisthelo', 1); + $self->log(2, "helo host $helo in whitelisthelo"); + return OK; + } + } + return DECLINED; +} + +sub check_sender { + my ($self, $transaction, $sender) = @_; + + # If per_recipient will be rcpt hook, and sender actually rcpt + my $config_arg = {}; + if ($self->{_per_recipient}) { + $config_arg = {rcpt => $sender, %MERGE}; + $sender = $transaction->sender; + } + + return DECLINED if $sender->format eq '<>'; + my $addr = lc $sender->address or return DECLINED; + my $host = lc $sender->host or return DECLINED; + + for my $h ($self->qp->config('whitelistsenders', $config_arg)) { + next unless $h; + $h = lc $h; + + if ($addr eq $h or $host eq $h) { + $transaction->notes('whitelistsender', 1); + $self->log(2, "envelope sender $addr in whitelistsenders"); + return OK; + } + } + return DECLINED; +} + +sub check_rcpt { + my ($self, $transaction, $rcpt) = @_; + + my $addr = lc $rcpt->address or return DECLINED; + my $host = lc $rcpt->host or return DECLINED; + + my $config_arg = $self->{_per_recipient} ? {rcpt => $rcpt, %MERGE} : {}; + for my $h ($self->qp->config('whitelistrcpt', $config_arg)) { + next unless $h; + $h = lc $h; + + if ($addr eq $h or $host eq $h) { + my $note = $transaction->notes('whitelistrcpt'); + $transaction->notes('whitelistrcpt', ++$note); + $self->log(2, "recipient $addr in whitelistrcpt"); + return OK; + } + } + return DECLINED; +} + From ad56587e798fc96df0178e4b8a15873e7fa5020b Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Sat, 30 Jun 2012 15:40:24 -0400 Subject: [PATCH 15/25] added log/show_message, dropped .pl suffix to be consistent with other QP scripts --- log/{log2sql.pl => log2sql} | 0 log/show_message | 72 +++++++++++++++++++++++++++++++++ log/{summarize.pl => summarize} | 0 log/{watch.pl => watch} | 0 4 files changed, 72 insertions(+) rename log/{log2sql.pl => log2sql} (100%) create mode 100755 log/show_message rename log/{summarize.pl => summarize} (100%) rename log/{watch.pl => watch} (100%) diff --git a/log/log2sql.pl b/log/log2sql similarity index 100% rename from log/log2sql.pl rename to log/log2sql diff --git a/log/show_message b/log/show_message new file mode 100755 index 0000000..932726a --- /dev/null +++ b/log/show_message @@ -0,0 +1,72 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Data::Dumper; + +my $QPDIR = '/usr/home/qpsmtpd/smtpd'; +my $logfile = "$QPDIR/log/main/current"; + +my $is_ip = 0; +my $search = $ARGV[0]; + +if ( ! $search ) { + die "\nusage: $0 [ ip_address | PID ]\n\n"; +}; + +if ( $search =~ /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/ ) { + #print "it's an IP\n"; + $is_ip++; +}; + +open my $LOG, '<', $logfile; + +if ( $is_ip ) { # look for the connection start message for the IP + my $ip_matches; + while ( defined (my $line = <$LOG>) ) { + next if ! $line; + my ( $tai, $pid, $mess ) = split /\s/, $line, 3; + if ( 'Connection from ' eq substr( $mess, 0, 16 ) ) { + my ( $ip ) = (split /\s+/, $mess)[-1]; # IP is last word + $ip = substr $ip, 1, -1; # trim off brackets + if ( $ip eq $search ) { + $ip_matches++; + $search = $pid; + $is_ip = 0; + }; + }; + }; + seek $LOG, 0, 0; + die "no pid found for ip $search\n" if $is_ip; + print "showing the last of $ip_matches connnections from $ARGV[0]\n"; +}; + +print "showing QP message PID $search\n"; + +while ( defined (my $line = <$LOG>) ) { + next if ! $line; + my ( $tai, $pid, $mess ) = split /\s/, $line, 3; + next if ! $pid; + print $mess if ( $pid eq $search ); +}; +close $LOG; + + +sub get_qp_dir { + foreach my $user ( qw/ qpsmtpd smtpd / ) { + my ($homedir) = (getpwnam( $user ))[7] or next; + + if ( -d "$homedir/plugins" ) { + return "$homedir"; + }; + foreach my $s ( qw/ smtpd qpsmtpd qpsmtpd-dev / ) { + if ( -d "$homedir/$s/plugins" ) { + return "$homedir/$s"; + }; + }; + }; + if ( -d "./plugins" ) { + return Cwd::getcwd(); + }; +}; diff --git a/log/summarize.pl b/log/summarize similarity index 100% rename from log/summarize.pl rename to log/summarize diff --git a/log/watch.pl b/log/watch similarity index 100% rename from log/watch.pl rename to log/watch From 45da124f9ffd5d371814072f76258b9f8af12de2 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Sat, 30 Jun 2012 15:58:21 -0400 Subject: [PATCH 16/25] config: replace domainkeys with dkim dkim is the heir apparent the Mail::DomainKeys perl module is deprecated (per it's author) --- config.sample/plugins | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.sample/plugins b/config.sample/plugins index 5fb03f8..25cf8bb 100644 --- a/config.sample/plugins +++ b/config.sample/plugins @@ -56,7 +56,7 @@ auth/authdeny rcpt_ok headers days 5 reject_type temp require From,Date -domainkeys +dkim # content filters #uribl From 2a85f3b8fbc88395e158ebcb894a2845f63d6a6e Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Sat, 30 Jun 2012 16:11:54 -0400 Subject: [PATCH 17/25] karma: added error keyword to error log messages --- plugins/karma | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/karma b/plugins/karma index 18fc768..b5a3a33 100644 --- a/plugins/karma +++ b/plugins/karma @@ -383,7 +383,7 @@ sub get_db_tie { my ( $self, $db, $lock ) = @_; tie( my %db, 'AnyDBM_File', $db, O_CREAT|O_RDWR, 0600) or do { - $self->log(LOGCRIT, "tie to database $db failed: $!"); + $self->log(LOGCRIT, "error, tie to database $db failed: $!"); close $lock; return; }; @@ -416,12 +416,12 @@ sub get_db_lock { # Check denysoft db open( my $lock, ">$db.lock" ) or do { - $self->log(LOGCRIT, "opening lockfile failed: $!"); + $self->log(LOGCRIT, "error, opening lockfile failed: $!"); return; }; flock( $lock, LOCK_EX ) or do { - $self->log(LOGCRIT, "flock of lockfile failed: $!"); + $self->log(LOGCRIT, "error, flock of lockfile failed: $!"); close $lock; return; }; @@ -441,12 +441,12 @@ sub get_db_lock_nfs { blocking_timeout => 10, # 10 sec stale_lock_timeout => 30 * 60, # 30 min } or do { - $self->log(LOGCRIT, "nfs lockfile failed: $!"); + $self->log(LOGCRIT, "error, nfs lockfile failed: $!"); return; }; open( my $lock, "+<$db.lock") or do { - $self->log(LOGCRIT, "opening nfs lockfile failed: $!"); + $self->log(LOGCRIT, "error, opening nfs lockfile failed: $!"); return; }; From 1d2192ba17b965e4f114880731cb69817437805c Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Sat, 30 Jun 2012 16:12:42 -0400 Subject: [PATCH 18/25] registry: renamed clamd abb3 from cad to clm --- plugins/registry.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/registry.txt b/plugins/registry.txt index 0ecfb3a..8d6f1ae 100644 --- a/plugins/registry.txt +++ b/plugins/registry.txt @@ -64,7 +64,7 @@ 70 virus::aveclient ave avirs 71 virus::bitdefender bit bitdf 72 virus::clamav cav clamv -73 virus::clamdscan cad clamd +73 virus::clamdscan clm clamd 74 virus::hbedv hbv hbedv 75 virus::kavscanner kav kavsc 76 virus::klez_filter klz vklez From c6b7b504bb98ecdc0273aa479d45623d1fe048f1 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Sat, 30 Jun 2012 16:28:54 -0400 Subject: [PATCH 19/25] added missing semicolon --- lib/Qpsmtpd/Plugin.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Qpsmtpd/Plugin.pm b/lib/Qpsmtpd/Plugin.pm index 3086c20..3bb4b73 100644 --- a/lib/Qpsmtpd/Plugin.pm +++ b/lib/Qpsmtpd/Plugin.pm @@ -285,7 +285,7 @@ sub is_immune { sub adjust_karma { my ( $self, $value ) = @_; - my $karma = $self->connection->notes('karma') || 0 + my $karma = $self->connection->notes('karma') || 0; $karma += $value; $self->connection->notes('karma', $value); return $value; From dc02055c96350b03248b08e8ab327b76cc745300 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Sat, 30 Jun 2012 16:40:51 -0400 Subject: [PATCH 20/25] log/summarize: added auth formats --- log/summarize | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/log/summarize b/log/summarize index 1201aa0..f1cf174 100755 --- a/log/summarize +++ b/log/summarize @@ -38,6 +38,10 @@ my %formats = ( check_earlytalker => "%-3.3s", helo => "%-3.3s", tls => "%-3.3s", + 'auth::auth_vpopmail' => "%-3.3s", + 'auth::auth_vpopmaild' => "%-3.3s", + 'auth::auth_vpopmail_sql' => "%-3.3s", + 'auth::auth_checkpassword' => "%-3.3s", badmailfrom => "%-3.3s", check_badmailfrom => "%-3.3s", sender_permitted_from => "%-3.3s", From ee7ae800b2eeb60baa24a80cccacedb7522881c5 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Sat, 30 Jun 2012 16:43:15 -0400 Subject: [PATCH 21/25] config/plugins: better defaults, additional entries --- config.sample/plugins | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/config.sample/plugins b/config.sample/plugins index 25cf8bb..7f19860 100644 --- a/config.sample/plugins +++ b/config.sample/plugins @@ -28,6 +28,8 @@ dont_require_anglebrackets # parse_addr_withhelo quit_fortune +#karma penalty_box 1 reject naughty + # tls should load before count_unrecognized_commands #tls earlytalker @@ -37,10 +39,10 @@ relay resolvable_fromhost rhsbl -dnsbl +dnsbl reject naughty reject_type disconnect badmailfrom badrcptto -helo +helo policy lenient # sender_permitted_from # greylisting p0f genre,windows @@ -65,18 +67,21 @@ virus/klez_filter # You can run the spamassassin plugin with options. See perldoc # plugins/spamassassin for details. # -spamassassin +spamassassin reject 12 # rejects mails with a SA score higher than 20 and munges the subject # of the score is higher than 10. # -# spamassassin reject_threshold 20 munge_subject_threshold 10 +# spamassassin reject 20 munge_subject_threshold 10 # dspam must run after spamassassin for the learn_from_sa feature to work dspam learn_from_sa 7 reject 1 # run the clamav virus checking plugin # virus/clamav +# virus/clamdscan deny_viruses yes scan_all 1 + +naughty reject data # You must enable a queue plugin - see the options in plugins/queue/ - for example: From 45a2116e8ea8b6ef0f7393b9bde3976301f2b9a5 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Sat, 30 Jun 2012 17:51:59 -0400 Subject: [PATCH 22/25] log/summarize: narrower column when no geoip city data present --- log/summarize | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/log/summarize b/log/summarize index f1cf174..b203cca 100755 --- a/log/summarize +++ b/log/summarize @@ -25,7 +25,6 @@ my %formats = ( ip => "%-15.15s", hostname => "%-20.20s", distance => "%5.5s", - 'ident::geoip' => "%-20.20s", 'ident::p0f' => "%-10.10s", count_unrecognized_commands => "%-5.5s", @@ -109,10 +108,16 @@ while ( defined (my $line = $fh->read) ) { }; if ( $plugin eq 'ident::geoip' ) { - my ($gip, $distance) = $message =~ /(.*?),\s+([\d]+)\skm/; - if ( $distance ) { - $pids{$pid}{$plugin} = $gip; - $pids{$pid}{distance} = $distance; + if ( length $message < 3 ) { + $formats{'ident::geoip'} = "%-3.3s"; + $formats3{'ident::geoip'} = "%-3.3s"; + } + else { + my ($gip, $distance) = $message =~ /(.*?),\s+([\d]+)\skm/; + if ( $distance ) { + $pids{$pid}{$plugin} = $gip; + $pids{$pid}{distance} = $distance; + }; }; }; } @@ -234,12 +239,12 @@ sub print_auto_format { if ( defined $pids{$pid}{helo_host} && $plugin =~ /helo/ ) { $format .= " %-18.18s"; - push @values, delete $pids{$pid}{helo_host}; + push @values, substr( delete $pids{$pid}{helo_host}, -18, 18); push @headers, 'HELO'; } elsif ( defined $pids{$pid}{from} && $plugin =~ /from/ ) { $format .= " %-20.20s"; - push @values, delete $pids{$pid}{from}; + push @values, substr( delete $pids{$pid}{from}, -20, 20); push @headers, 'MAIL FROM'; } elsif ( defined $pids{$pid}{to} && $plugin =~ /to|rcpt|recipient/ ) { From d0e9a010da9bc2fa5d7cd6a40aea941362584ae7 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Sat, 30 Jun 2012 19:04:42 -0400 Subject: [PATCH 23/25] log/show_message: fixed QP dir detection --- log/show_message | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/log/show_message b/log/show_message index 932726a..9ee2ef1 100755 --- a/log/show_message +++ b/log/show_message @@ -5,7 +5,7 @@ use warnings; use Data::Dumper; -my $QPDIR = '/usr/home/qpsmtpd/smtpd'; +my $QPDIR = get_qp_dir(); my $logfile = "$QPDIR/log/main/current"; my $is_ip = 0; @@ -20,7 +20,7 @@ if ( $search =~ /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/ ) { $is_ip++; }; -open my $LOG, '<', $logfile; +open my $LOG, '<', $logfile or die "unable to open $logfile\n"; if ( $is_ip ) { # look for the connection start message for the IP my $ip_matches; From f5021a6d554ff2a78e2a0b6d9169913670a147f0 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Sat, 30 Jun 2012 19:10:14 -0400 Subject: [PATCH 24/25] resolvable_fromhost: additional logging --- plugins/resolvable_fromhost | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/resolvable_fromhost b/plugins/resolvable_fromhost index d65bece..3181470 100644 --- a/plugins/resolvable_fromhost +++ b/plugins/resolvable_fromhost @@ -68,6 +68,7 @@ Default: temp (temporary, aka soft, aka 4xx). use strict; use warnings; +use lib 'lib'; use Qpsmtpd::Constants; use Qpsmtpd::DSN; use Qpsmtpd::TcpServer; @@ -114,13 +115,14 @@ sub hook_mail { }; my $result = $transaction->notes('resolvable_fromhost') or do { + $self->log(LOGINFO, 'error, missing result' ); return Qpsmtpd::DSN->temp_resolver_failed( $self->get_reject_type(), '' ); }; return DECLINED if $result =~ /^(?:a|ip|mx)$/; # success return DECLINED if $result =~ /^(?:whitelist|null|naughty)$/; # immunity - $self->log(LOGINFO, $result ); # log error + $self->log(LOGINFO, "fail, $result" ); # log error return Qpsmtpd::DSN->addr_bad_from_system( $self->get_reject_type(), "FQDN required in the envelope sender"); From a8e793e0af92f889a86dd06b7966198b842dd67b Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Sat, 30 Jun 2012 22:35:17 -0400 Subject: [PATCH 25/25] earlytalker: lower karma for earlytalkers --- plugins/earlytalker | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/earlytalker b/plugins/earlytalker index f7d38b2..bcbad95 100644 --- a/plugins/earlytalker +++ b/plugins/earlytalker @@ -205,6 +205,7 @@ sub log_and_deny { my $ip = $self->qp->connection->remote_ip || 'remote host'; $self->connection->notes('earlytalker', 1); + $self->adjust_karma( -1 ); my $log_mess = "$ip started talking before we said hello"; my $smtp_msg = 'Connecting host started transmitting before SMTP greeting';