How to silently drop spam with Postfix

intro:
How to drop spam using Postfix, SpamAssassin and a perl script

subject:
postfix, spamassassin, perl, spam

content:
Background
Spam (either variety) really annoys me.
Unfortunately, until recently I've always had to deliver spam to a mail box somewhere, or use Procmail to delete it. This is acceptable in some situations, but if my machine is just acting as a virtual mail domain, rewriting mails which then get delivered onto someone else, I can easily end up forwarding a lot of spam (and needed a way of dropping it)

One way around this is to build upon the standard Postfix setup for dealing with spam, which is briefly summarised below, and changing it to what is also below.

Initial Postfix + Spam Assassin configuration

Edit /etc/postfix/master.cf and change the smtp definition to :

smtp inet n - - - - smtpd
-o content_filter=spam_filter:
(Previously it would have lacked the -o content_filter=spam_filter: part)

Now we need to define a "spam_filter" service, also in master.cf, like as follows :

spam_filter unix - n n - - pipe flags=Rq user=assassin
argv=/usr/local/sbin/postfixfilter -f ${sender} -- ${recipient}

Having set this up, all mail received over smtp (i.e. not locally sent) will pass through our filter script. This filter script is then responsible for resending the mail locally with any modifications made (e.g. addition of spam assassin headers etc).

The /usr/local/sbin/postfixfilter script can look as follows :

#!/bin/bash
#/usr/bin/spamc | /usr/sbin/sendmail -i "$@"
/usr/bin/spamc | /usr/local/sbin/spam_nuke.pl "$@"
exit $?

The original line is commented out - this will work, but spammy mail won't be dropped, it will continue to be delivered - so users will need to create filters to hide / delete it.

The other line using spam_nuke.pl undertakes a similar function to the first (commented out one), but checks the mail for a set header (e.g. X-Spam-Status: Y) and if it's found it exits, without sending it to sendmail. In this way the spam infected mail is silently dropped.

Here's the magic script (spam_nuke.pl)

#!/usr/bin/perl -w
#
# This reads in input on stdin, sees if it contains X-Spam-Status: Y,
# and if it does it prints out nothing, and exit(0)'s.
# Else it prints out the mail message (for local delivery
# through sendmail)

#debug ("Spam Check starting..");
undef $/; # change line seperator
$_ = <STDIN>; # read standard input (i.e. what's being piped in)

if ($_ =~ /^X-Spam-Status: Y/m ) {
# debug("spam detected, skipping ") ;
exit 0;
}

# debug("Mail ok : $_");
my $prog = "/usr/sbin/sendmail -i @ARGV";
# debug("Program set to : $prog");
open PROG, "|$prog" or die "Cannot spawn |$prog: $!";
print PROG $_;
close PROG;

Having achieved the above (with thanks to David Chan), I'm now quite happy :)

This filtering would be most useful on gateway servers, to stop spam reaching the "real" mail server. It obviously relies upon SpamAssassin being kept up to date, and modifications to it should be trivial if you wish to send a "sorry you're mail is spam" message to the sender (whose address will probably be forged anyway).

Technorati Tags:

Comments

When uncommenting the debug

When uncommenting the debug lines in
spam_nuke.pl i get errors:
Undefined subroutine &main::debug

How do i get the debuginfo in my /etc/maillog ?

debug

Hi,

debug is a subroutine, and is ommitted from the above for clarity's sake.

If you include something like the below you'll find the undefined subroutine message will go away.

sub debug {
    my $message = shift; 
    chomp $message; # remove line ending if there is one
    # open /etc/maillog in append mode, if we can't warn and return.
    open(LOG, ">>/etc/maillog") or warn ("Debug Failed: $message") and  return;
    print LOG $message . "\n";
    close(LOG);
}

Thanks! I put in the

Thanks! I put in the subroutine and uncommented the debug messages. Now i get nothing at all - no error or warnings. Where can i look for messages? I don't know much about perl scripts.

log messages

Hi,

With regards to what I copied in, log messages should go to the file /etc/maillog (not a very standard place for log messages, but it's what i think you asked for!).

You'll need to make sure the script is writable by probably the user "assassin" (as specified in the postfix master.cf line :

spam_filter unix - n n - - pipe flags=Rq user=assassin  
    argv=/usr/local/sbin/postfixfilter -f ${sender} -- ${recipient}

Once done, run a tail -f /var/log/mail.log and see what the server says when you send mail to yourself.

thanks

You saved me a bit of think work. didn't want to use scripts, but spamassassin refuses to discard mails. thanks

works, here an addition (that might not work ;-) )

Thanks,

this does work great.
Since it's perl I added this to keep spam, just in case there is a false positive. This needs /var/lib/spamgoeshere/ to be writable
by user id that script is running at, and it will filter spam above
5 as a score.

if ($_ =~ /^X-Spam-Status: Yes, score=([0-9\.]+)/m ) {
my $score = $1;
if ($score > 5 ){
my $user = $ARGV[3];
if ($user eq ""){
$user = "undefineduser";
}
my $spamstoredir = "/var/lib/spamgoeshere/$user";
if (! -d "$spamstoredir"){
system ("mkdir -p $spamstoredir");
}
my $from = "$ARGV[1]";
if ($from eq ""){
$from = "undefinedsource";
}
my $outfile = "$spamstoredir/$from";
open (OUT, ">>$outfile") or die "Cannot write to $outfile $!";
print OUT $_ ;
close (OUT);
exit 0;
}
}

Thanks a lot

I had to do a lot of searching on this subject and finally came across your site - it worked like a charm!

There's a problem with this

There's a problem with this script. It allows for special characters in e-mail addresses to affect sendmail calls. Try for example an address with apostrophe (valid in local part per RFC822) - the message will not be delivered, because the arguments in the sendmail call will be processed by the shell, and apostrophe is a special shell character.

Solution: rewrite the script to disallow shell escapes. Perl Cookbook 19.6 provides the recipe:

(Replace "my $prog..." until the end with this.)

my $prog = "/usr/sbin/sendmail";
# debug("Program set to : $prog");
$pid = open(PROG, "|-");

if ($pid) { # parent
print PROG $_;
close(PROG);
} else { # child
exec($prog, "-i", @ARGV);
}

thanks

Hi,

Thanks for that!

thanks, exactley what i've

thanks, exactley what i've searched for.

Hi, I just did everything on

Hi,

I just did everything on this site but i get the following error:

Apr 23 11:48:28 pipe[1460]: fatal: pipe_comand: execvp /usr/local/sbin/postfixfilter : No such file or directory

it has chmod 755 and it's there.

Can you tell me what i'm doing wrong???

hmm

I can only suggest it's a case sensitivity issue or something else equally annoying.

Try copy and pasting the file name into master.cf to make sure you've not got an annoying typo somehow?

Does the file have #!/bin/bash as it's first line?

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <img>
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
We don't take kindly to automated nonsensible adverts around here.