SpamAssassin filter for RT 3.0.10

I got SpamAssassin working via the mail filter in
RT::Interface::email::Filter::SpamAssassin. The filter needed a
complete renovation; perhaps it came from some much earlier RT version?

The appended patch works with SpamAssassin 2.63, only tested on RT 3.0.10/
Solaris 9/MySQL/Perl5.8.3/FastCGI/etc.

There was another tricky bug: apparently SpamAssassin munges the $_
global variable when a message rings the spam bell, such that its value
in the Gateway function’s loop around @RT::MailPlugins gets unset. This
clears the plugin entry so it breaks on the next incoming email. I know
the functions/operators that set $_ are supposed have local effects but
apparently something doesn’t. Switching to an explicit loop variable
fixed the symptom, so that patch is included.

I also added a couple of new features:

  • It logs the message-id header of a message it drops on the floor at
    the “info” level so you can match it with MTA logs to diagnose cases
    of vanished mail.
  • Adds the “X-RT-SpamAssassin-Score” header so a scrip can sort the
    spammiest messages into a separate queue.

I know the spam filter could be implemented in the MTA or in procmail
driving the rt-mailgate, but this looked easier to maintain. If there aren’t
any objections I’ll submit it as a patch.

-- Larry

*** lib/RT/Interface/Email.pm.orig Fri Jan 2 17:55:55 2004
— lib/RT/Interface/Email.pm Mon May 3 19:52:05 2004
*** 500,521 ****

  # Since this needs loading, no matter what

! for (@RT::MailPlugins) {
my $Code;
my $NewAuthStat;
! if ( ref($) eq “CODE” ) {
! $Code = $
;
}
else {
! $_ = “RT::Interface::email::blush:" unless /^RT::Interface::email::/;
! eval "require $
;”;
if ($@) {
! die ("Couldn’t load module $: $@");
next;
}
no strict ‘refs’;
! if ( !defined( $Code = *{ $
. “::GetCurrentUser” }{CODE} ) ) {
! die (“No GetCurrentUser code found in $_ module”);
next;
}
}
— 500,522 ----

  # Since this needs loading, no matter what

! my $pi;
! foreach $pi (@RT::MailPlugins) {
my $Code;
my $NewAuthStat;
! if ( ref($pi) eq “CODE” ) {
! $Code = $pi;
}
else {
! $pi = “RT::Interface::email::$pi” unless $pi =~ m/^RT::Interface::email::/;
! eval “require $pi;”;
if ($@) {
! die (“Couldn’t load module $pi: $@”);
next;
}
no strict ‘refs’;
! if ( !defined( $Code = *{ $pi . “::GetCurrentUser” }{CODE} ) ) {
! die (“No GetCurrentUser code found in $pi module”);
next;
}
}
*** lib/RT/Interface/Email/Filter/SpamAssassin.pm.orig Fri Jan 2 17:55:55 2004
— lib/RT/Interface/Email/Filter/SpamAssassin.pm Mon May 3 20:15:40 2004
*** 27,41 ****
my $spamtest = Mail::SpamAssassin->new();

sub GetCurrentUser {
! my $item = shift;
! my $status = $spamtest->check ($item);
! return (undef, 0) unless $status->is_spam();
! eval { $status->rewrite_mail() };
! if ($status->get_hits > $status->get_required_hits()*1.5) {
# Spammy indeed
! return (undef, -1);
}
! return (undef, 0);
}

=head1 NAME
— 27,48 ----
my $spamtest = Mail::SpamAssassin->new();

sub GetCurrentUser {
! my $result = 0;
! my %args = ( Message => undef,
! @_ );
! my $status = $spamtest->check_message_text($args{‘Message’}->as_string());
! if ($status->is_spam() &&
! $status->get_hits() > $status->get_required_hits()*1.5) {
# Spammy indeed
! my $mid = $args{‘Message’}->head()->get(‘Message-Id’);
! my $hits = $status->get_hits();
! $RT::Logger->info(“Filter::SpamAssassin: Dropping message $mid because spam score = $hits”);
! $result = -1;
! } else {
! $args{‘Message’}->head()->replace(‘X-RT-SpamAssassin-Score’, sprintf("%.3f", $status->get_hits()));
}
! $status->finish();
! return (undef, $result);
}

=head1 NAME

There was another tricky bug: apparently SpamAssassin munges the $_
global variable when a message rings the spam bell, such that its value
in the Gateway function’s loop around @RT::MailPlugins gets unset. This
clears the plugin entry so it breaks on the next incoming email. I know
the functions/operators that set $_ are supposed have local effects but
apparently something doesn’t. Switching to an explicit loop variable
fixed the symptom, so that patch is included.

Here’s an example to share a legit use of local:

$_=3;
{
 local $_;
 something_that_modifies_dollar_underbar();
}
# $_ == 3