package RT::Interface::Email::Filter::SpamAssassin;
our $VERSION = .95;
our $statERR;
BEGIN{
if( $RT::SpamAssassinClient == 0 ){
require Mail::Field;
require Mail::Header;
$statERR = 'There were no X-Spam fields.';
*_score = sub {
my $msg = Mail::Header->new( [split(/\n/, $_[0])] );
#Make long headers like X-Spam-Status approx. single-lines.
$msg->fold(1024);
my $status;
my $report = Mail::Field->extract('X-Spam-Status', $msg);
$report &&= $report->stringify;
$status->{score} = $1 if $report =~ /score=(\d+(?:\.\d+)?)/;
$status->{threshold} = $1 if $report =~ /required=(\d+(?:\.\d+)?)/;
local $_ = Mail::Field->extract('X-Spam-Flag', $msg);
$status->{isspam} = $_->stringify if $_;
return $status;
};
}
elsif( $RT::SpamAssassinClient > 0 ){
require Mail::SpamAssassin::Client;
$statERR = 'Is spamd running?';
*_score = sub{
my $client = Mail::SpamAssassin::Client->new({
host=>($RT::SpamAssassinClient eq '1' ?
'localhost' : $RT::SpamAssassinClient),
port=>($RT::SpamAssassinPort || 783)
});
$client->check( $_[0] );
};
}
else{
require Mail::SpamAssassin;
$statERR = 'A problem doing it ourselves';
*_score = sub{
my $spamcheck = Mail::SpamAssassin->new();
my $return = $spamcheck->check( $spamcheck->parse($_[0]) );
# make a compatible return value
my $status;
$status->{isspam} = 'True' if $return->is_spam();
$status->{score} = $return->get_score();
$status->{threshold} = $return->get_required_score();
return $status;
};
}
}
sub GetCurrentUser {
my %args = @_;
my $status = _score( ${$args{RawMessageRef}} );
unless( $status ){
$RT::Logger &&
$RT::Logger->error("SpamAssassin returned undef. $statERR");
return ( $args{'CurrentUser'}, $args{'AuthLevel'} );
}
my $msgfrom = $args{'Message'}->head->get('From');
$msgfrom =~ s/\s{2,}|\n//;
my $msginfo = "score " . $status->{score} . " from [$msgfrom]";
$RT::Logger && $RT::Logger->debug("SpamAssassin returned $msginfo");
# add the new header... so a scrip can deal with it later
if( $RT::SpamAssassinClient != 0 ){
$args{'Message'}->head->delete('X-Spam-Score');
$args{'Message'}->head->add('X-Spam-Score', $status->{score});
}
return ( $args{'CurrentUser'}, $args{'AuthLevel'} )
unless $status && _spamdbool($status->{isspam});
# punt especially spammy messages
if( $status->{score} > $status->{threshold}*($RT::SpamAssassinMax||1.5) ){
$RT::Logger && $RT::Logger->info("SpamAssassin thinks $msginfo is very spammy, punting");
return ( $args{'CurrentUser'}, -1 );
}
if( $RT::SpamAssassinQueue ){
$RT::Logger && $RT::Logger->info("SpamAssassin rerouting $msginfo to queue " . $RT::SpamAssassinQueue);
$args{'Queue'}->Load( $RT::SpamAssassinQueue );
}
else{
$RT::Logger && $RT::Logger->debug("SpamAssassin ignoring $msginfo, because SpamAssassinQueue not set.");
}
return ( $args{'CurrentUser'}, $args{'AuthLevel'} );
}
sub _spamdbool {
my ($bool) = @_;
# in case someone changes things, as they always seem to
return ($bool eq 'True' ||
$bool eq 'YES' ||
$bool eq '1' ||
$bool eq 'T');
}
=head1 NAME
RT::Interface::Email::Filter::SpamAssassin - Spamassassin filter for RT
=head1 SYNOPSIS
F<RT_SiteConfig.pm>
#Enable spam filtering, but make sure you authenticate users first!
Set(@MailPlugins, 'Filter::SpamAssassin', ... 'Auth::MailFrom');
#Enable per-message calls to SpamAssassin on localhost
Set($RT::SpamAssassinClient, 'mail.example.org')
#Refile suspect messages
#Set($RT::SpamAssassinQueue, 'SPAM');
#Conservative punting of messages
Set($RT::SpamAssassinMax, 4);
=head1 DESCRIPTION
This plugin checks to see if an incoming mail is spam, optionally running it
through SpamAssassin. If the mail is very definitely spam, then it is punted.
Otherwise, it may be passed on as normal or directed to a special queue if
suspect.
=over
=item B<$RT:SpamAssassinClient>
Whether or not to (re)check messages.
=over
=item I<hostname>
Connect to I<hostname> with L<Mail::SpamAssassin::Client> to process messsage.
=item I<-1>
Process message with L<Mail::SpamAssassin::PerMsgStatus>. This is very slow.
=item I<0>
Rely on existing headers. This is very fast.
=item I<1>
Equivalent to I<localhost>.
=back
=item B<$RT::SpamAssassinPort>
The port to connect to if B<$RT::SpamAssassinClient> is true. Default is 783.
=item B<$RT::SpamAssassinMax>
If the message's score is at least this many times greather than SpamAssassin's
threshold, discard the message.
The default is 1.5, so messages scoring > 1.5*threshold are discarded.
=item B<$RT::SpamAssassinQueue>
If set, messages meeting the local threshold will be shunted to the
queue specified in the variable. This can be a safe way to avoid
excess notifications to your AdminCCs, without dropping messages.
=back
=head1 AUTHOR
=over
=item Jerrad Pierce
VERSION 0.95 (20081010)
=item Erik Aronesty
VERSION: 0.9 (20080807)
=item Best Practical
=back
=head1 LICENSE
GPL 2
=cut
eval "require RT::Interface::Email::Filter::SpamAssassin_Vendor";
die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email/Filter/SpamAssassin_Vendor.pm});
eval "require RT::Interface::Email::Filter::SpamAssassin_Local";
die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email/Filter/SpamAssassin_Local.pm});
1;