diff --git a/Changes b/Changes index 8005a84..1cb12f7 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,8 @@ 0.20 - development + Add munge_subject_threshold and reject_threshold options to the + spamassassin plugin. Add documentation to the spamassassin plugin. + Add -p to mkdir in log/run (Rasjid Wilcox ) clamav plugin, thanks to Matt Sergeant, matt@sergeant.org. diff --git a/config.sample/plugins b/config.sample/plugins index 5ebabfb..19c8453 100644 --- a/config.sample/plugins +++ b/config.sample/plugins @@ -1,3 +1,11 @@ +# +# Example configuration file for plugins +# + +# enable this to get configuration via http; see perldoc +# plugins/http_config for details. +# http_config http://localhost/~smtpd/config/ http://www.example.com/smtp.pl?config= + quit_fortune require_resolvable_fromhost rhsbl @@ -10,7 +18,21 @@ check_relay # content filters klez_filter + + +# You can run the spamassassin plugin with options. See perldoc +# plugins/spamassassin for details. +# spamassassin +# 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 + +# run the clamav virus checking plugin +# clamav + +# queue the mail with qmail-queue queue/qmail-queue diff --git a/plugins/spamassassin b/plugins/spamassassin index 0c812e2..962285a 100644 --- a/plugins/spamassassin +++ b/plugins/spamassassin @@ -1,26 +1,77 @@ +=head1 NAME + +spamassassin + +=head1 DESCRIPTION + +Plugin that checks if the mail is spam by using the "spamd" daemon +from the SpamAssassin package. L + +SpamAssassin 2.40 or newer is required. + +=head1 CONFIG + +Configured in the plugins file without any parameters, the +spamassassin plugin will add relevant headers from the spamd +(X-Spam-Status etc). + +The format goes like + + spamassassin option value [option value] + +Options being those listed below and the values being parameters to +the options. Confused yet? :-) + +=over 4 + +=item reject_threshold [threshold] + +Set the threshold over which the plugin will reject the mail. Some +mail servers are so useless that they ignore 55x responses not coming +after RCPT TO, so they might just keep retrying and retrying and +retrying until the mail expires from their queue. + +I like to configure this with 15 or 20 as the threshold. + +The default is to never reject mail based on the SpamAssassin score. + +=item munge_subject_threshold [threshold] + +Set the threshold over which we will prefix the subject with +'***SPAM***'. A messed up subject is easier to filter on than the +other headers for many people with not so clever mail clients. You +might want to make another plugin that does this on a per user basis. + +The default is to never munge the subject based on the SpamAssassin score. + +=back + +With both options the configuration line will look like the following + + spamasssasin reject_threshold 18 munge_subject_threshold 8 + +=cut -# -# Requires the spamd patch attached to this spamassassin bug: -# http://bugzilla.spamassassin.org/show_bug.cgi?id=660 -# -# The patch is going to be included in SpamAssassin 2.40. -# -# ... or you can change REPORT_IFSPAM to REPORT below; but the headers -# will be a bit different than you are used to. -# -# use Socket qw(:DEFAULT :crlf); use IO::Handle; sub register { - my ($self, $qp) = @_; + my ($self, $qp, @args) = @_; $self->register_hook("data_post", "check_spam"); -} -#my $rv = check_spam(); -#die "failure!" unless defined $rv; -#print "rv: $rv\n"; + $self->log(0, "Bad parameters for the spamassassin plugin") + if @_ % 2; + + %{$self->{_args}} = @args; + + $self->register_hook("data_post", "check_spam_reject") + if $self->{_args}->{reject_threshold}; + + $self->register_hook("data_post", "check_spam_munge_subject") + if $self->{_args}->{munge_subject_threshold}; + +} sub check_spam { my ($self, $transaction) = @_; @@ -34,7 +85,7 @@ sub check_spam { my $iaddr = inet_aton($remote) or $self->log(1, "Could not resolve host: $remote") and return (DECLINED); my $paddr = sockaddr_in($port, $iaddr); - + my $proto = getprotobyname('tcp'); socket(SPAMD, PF_INET, SOCK_STREAM, $proto) or $self->log(1, "Could not open socket: $!") and return (DECLINED); @@ -49,21 +100,27 @@ sub check_spam { print SPAMD "REPORT_IFSPAM SPAMC/1.0" . CRLF; # or CHECK or REPORT or SYMBOLS - print SPAMD join CRLF, split /\n/, $transaction->header->as_string; - print SPAMD CRLF; + print SPAMD join CRLF, split /\n/, $transaction->header->as_string + or warn "Could not print to spamd: $!"; + + print SPAMD CRLF + or warn "Could not print to spamd: $!"; while (my $line = $transaction->body_getline) { chomp $line; - print SPAMD $line, CRLF; + print SPAMD $line, CRLF + or warn "Could not print to spamd: $!"; } + print SPAMD CRLF; shutdown(SPAMD, 1); my $line0 = ; # get the first protocol lines out if ($line0) { $transaction->header->add("X-Spam-Check-By", $self->qp->config('me')); } + while () { - warn "GOT FROM SPAMD1: $_"; + #warn "GOT FROM SPAMD1: $_"; next unless m/\S/; s/\r?\n$/\n/; my @h = split /: /, $_, 2; @@ -72,5 +129,37 @@ sub check_spam { last if $h[0] eq "Spam" and $h[1] =~ m/^False/; } + return (DECLINED); } + +sub check_spam_reject { + my ($self, $transaction) = @_; + + my $score = $self->get_spam_score($transaction) or return DECLINED; + + return (DENY, "spam score exceeded threshold") + if $score >= $self->{_args}->{reject_threshold}; + + return DECLINED; +} + + +sub check_spam_munge_subject { + my ($self, $transaction) = @_; + my $score = $self->get_spam_score($transaction) or return DECLINED; + + return DECLINED unless $score >= $self->{_args}->{munge_subject_threshold}; + + my $subject = $transaction->header->get('Subject') || ''; + $transaction->header->replace('Subject', "***SPAM*** $subject"); + + return DECLINED; +} + +sub get_spam_score { + my ($self, $transaction) = @_; + my $status = $transaction->header->get('X-Spam-Status') or return; + my ($score) = ($status =~ m/hits=(\d+\.\d+)/)[0]; + return $score; +}