Here's a rewritten spamassassin plugin that has no issues with SpamAssassin 2.50. I ripped out all the attempt at speaking the SA network protocol and just used spamc, which already knows how. This is very slightly slower than the old way, but much more future-resistant, and a great deal faster than using Mail::SpamAssassin each time.
Since there wasn't really any way to retain it, all the filter configurability is gone from qpsmtpd's side. I don't think this is really a problem -- spamd will operate according to the SA configuration specified by the user, so all you need to do is edit the .spamassassin/user_prefs file in qpsmtpd's homedir. I did support configurability insofar as it's germane to running spamc, so support for remote spamd, user selection, etc., is available. -- Devin \ aqua(at)devin.com, 1024D/E9ABFCD2; http://www.devin.com Carraway \ IRC: Requiem GCS/CC/L s-:--- !a !tv C++++$ ULB+++$ O+@ P L+++
=head1 NAME spamassassin - SpamAssassin integration for qpsmtpd =head1 DESCRIPTION Plugin that checks if the mail is spam by using the "spamc" client application from SpamAssassin F<http://www.spamassassin.org>. =head1 CONFIGURATION Configuration as to spamassassin's actual scoring should be done in the spamassassin user_prefs file. The spamassassin plugin supplies configuration options relevant to executing spamc and directing it to the proper spamd host. The format (from qpsmtpd's F<config/plugins>) consists of pairs of option names and their values: spamassassin [option value] [...] Options being those listed below and the values being parameters to the options. Confused yet? :-) =over 4 =item host I<hostname> Host to which spamc should connect to reach a spamd. By default spamc will connect to localhost. Equates to spamc -u. =item port I<port> Port on which spamc should connect to spamd. By default spamc uses port 783. Equates to spamc -p. =item user I<user> User as which spamd should run when scoring. By default spamd will run as the user running spamc (e.g. qmaild). Equates to spamc -u. =item timeout I<seconds> Timeout duration when spamc should abort the connection. Mail will not be rejected because of timeouts. =head1 AUTHOR The spamassassin plugin was originally written by Ask Bjorn Hansen; this rewrite by Devin Carraway. =head1 SEE ALSO spamc(1), spamd(1) =cut use IPC::Open2; sub register { my ($self, $qp, @args) = @_; $self->register_hook("data_post", "check_spam"); $self->log(0, "Bad parameters for the spamassassin plugin") if @_ % 2; %{$self->{_args}} = @args; } sub check_spam { my ($self, $transaction) = @_; my $pid = open2(\*RD, \*WR, '/usr/bin/spamc', (($self->{_args}->{host}) ? ('-d',$self->{_args}->{host}) : ()), (($self->{_args}->{port}) ? ('-p',$self->{_args}->{port}) : ()), (($self->{_args}->{ssl}) ? ('-S') : ()), (($self->{_args}->{max_size}) ? ('-s',$self->{_args}->{max_size}) : ()), (($self->{_args}->{user}) ? ('-u',$self->{_args}->{user}) : ()), (($self->{_args}->{timeout}) ? ('-t',$self->{_args}->{timeout}) : ()), ); $pid or return DECLINED; print WR $transaction->header->as_string, "\n"; my $line; print WR $line while $line = $transaction->body_getline; $transaction->body_resetpos; close WR; my $h = new Mail::Header; $h->read(\*RD); while (<RD>) { chomp; $self->log(10,"spamc returned body: [$_]"); } close RD; waitpid $pid, 0; my $status = $h->get('X-Spam-Status'); unless ($status) { $self->log(1, "spamc didn't return an X-Spam-Status header; ". "doing nothing"); return DECLINED; } $transaction->header($h); if ($status =~ /hits=(-?[0-9.]+)\s+required=(-?[0-9.]+)/) { $self->log(1,"mail earned score $1, needed $2"); if ($1 >= $2) { $self->log(2,"report: $status"); return (DENY, 'spam score exceeded'); } } return DECLINED; }
