I have my own handcrafted mail filter, which uses Mail::Audit, a module not without faults but with some very convenient plugins, like Mail::Audit::List among others.

I wanted to extend the mail filter by also using Mail::SpamAssassin, which claims to be pluggable into Mail::Audit, but that is wrong since some versions of Mail::SpamAssassin.

SpamAssassin offers no method of easily plugging in custom code to create custom checks, but my mail filter does some elaborate content filtering on MIME encoded message parts which I did not want to do away with (like filtering on bogus HTML tags, filtering on attached executables, filtering on content-type charsets), so I wrote some ugly code to have a message looked at by both, Mail::Audit and Mail::SpamAssassin.

#!/usr/bin/perl -w use strict; use lib '/home/corion/bin/lib'; use POSIX qw(strftime); use Mail::Audit qw(List Attach Bounce); use Mail::SpamAssassin; use Mail::SpamAssassin::NoMailAudit; our $DEBUG = 0; # Start and cache SpamAssassin for later use with PPerl our $spamassassin; BEGIN { $spamassassin = Mail::SpamAssassin->new(); }; # Slurp the mail my @raw_mail = <>; my $mail = Mail::Audit->new( data => [@raw_mail] ); # First, make a copy of the mail in the archive, # whether we keep it or not : my $timestamp = strftime("%Y%m%d",localtime); do { $mail->noexit(1); $mail->accept("~/mail/archive/mail.$timestamp"); $mail->noexit(0); } unless $DEBUG; my $reason = ""; # why did we ignore this mail ? # Now, run the mail through SpamAssassin so it can have # its say as well: my $sa_mail = Mail::SpamAssassin::NoMailAudit ->new( data => \@raw_mail ); my $spam_status = $spamassassin->check( $sa_mail ); if ($spam_status->is_spam) { $reason .= sprintf "Rejected by SpamAssassin: %s\n", $spam_status->get_names_of_tests_hit; }; $spam_status->finish; # ... other, Mail::Audit based MIME checking follows

Replies are listed 'Best First'.
Re: Using Mail::Audit and Mail::SpamAssassin together
by kvale (Monsignor) on Apr 26, 2004 at 20:11 UTC
    I find that Mail::Audit and Mail::SpamAssassin work well together. Here is my filter code (for a kmail client):
    #!/usr/bin/perl # # A cutsom filter to process my email use Mail::Audit; open LOG, ">>/home/kvale/.audit_log"; print LOG localtime() . ": "; my $item = Mail::Audit->new( nomime => 1 ); my $subject = $item->subject(); if ($item->subject =~ /\[SPAM\s+\?\]/i) { print LOG "phy spam: $subject\n"; $item->print_out(); } use Mail::SpamAssassin; my $mail = Mail::SpamAssassin->new(); my $status = $mail->check( $item ); if ($status->is_spam()) { $item->replace_header( subject => '***SPAM***' . $subject); my @report = split /\n/, $status->get_report(); shift @report until $report[0] =~ /^Content analysis/; print LOG "SA Spam: $subject\n", join "\n", @report, "\n"; $item->print_out(); } print LOG "OK: $subject\n"; $item->print_out();
    Note that M::SA methods work nicely with M::A objects.

    It is not true that new tests cannot be written into SpamAssassin. Look in the rules directory for info, write some yourself, and put them in your .cf file. If your rules need more than the builtin methods can offer, it is pretty simple add your own module with the custom code. If you don't want to do that much hacking, SA 3.0.0 will have a plugin framework for these sorts of expansion needs.

    -Mark

      I first thought that as well, but using the nomime => 1 option to Mail::Audit removes all possibility of checking the MIME attachments of a mail, and also removes the implicit capability of easily checking Base64-encoded HTML content - which is one major misfeature of SpamAssassin, as it can't detect HTML tags such as <oprah><spam><filler> as invalid.

      Writing my own rules via a .cf file is good as long as I am content with what SpamAssassin has to offer within its framework, that is, content checking on non-decoded or already-cleaned content, with all HTML tags removed.

      I did not find an easy way to simply supply a module that injected a function into the SpamAssassin::NoMailAudit namespace (or wherever else) and then to use that function as a test from within a .cf rule.