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;
}

Reply via email to