Server and OS, Spam Fighting

Spamilter and SpamAssassin Working Together

In my Killing Spam(mers) entry, I discussed getting both SpamAssassin and Neal Horman’s spamilter installed and working on joker.  Doing so made a significant impact on the amount of spam I was seeing.  However, they were working independently of one another, and what I really wanted was some sort of feedback loop.  Should a message get through spamilter and be classified as spam by SpamAssassin, I wanted it to tell spamilter about that.  I’ll attempt to explain how I got them working together in this article.

Realtime BlackLists, aka DNS BlackLists (RBL or DNSBL)

One of the resources spamilter uses to determine if it should tell an incoming MTA to go forth and self-fornicate is the collection of publicly available RBLs or DNSBLs.  These are blacklists provided by folks such as SpamHaus, SpamCop, etc.  The way they all work is that you (or an application) make a DNS A-record query for the MTA’s IP address, but it’s almost in the form of an IN-ADDR lookup.  You query the spam fighting DNS servers for that A-record, and if you get an answer of 127.0.0.1, .2, or .3, then you have a hit.  That MTA is blacklisted.

For instance, let’s say you get a connection in from the MTA at IP: 162.253.67.28.  If you want to ask SpamHaus whether that IP address is blacklisted or not, you’d do this:

joker$ nslookup 28.67.253.162.zen.spamhaus.org
Server:		127.0.0.1
Address:	127.0.0.1#53

Non-authoritative answer:
Name:	28.67.253.162.zen.spamhaus.org
Address: 127.0.0.3

The query looks like you’re doing an IN-ADDR: you reverse the IP address and then attach the rest of the blacklist provider’s FQDN. In this example, that’s SpamHaus’ zen.spamhaus.org.  The key point here: it was a hit.  Apparently SpamHaus thinks IP 162.253.67.28 is a bad guy.

Creating Your Own RBL

As comprehensive as the public RBLs are, there are still poisoned MTAs out there that none of the RBLs have picked up yet.  Fortunately, it’s not terribly difficult to create your own blacklist and have spamilter query it, too.  Further, Neal has provided tools to update blacklists as needed.  I’ll get into the update part momentarily; first we have to create the list.

You do that with your local DNS server.  I’ve mentioned my main server joker: it’s my primary incoming SMTP server as well as my primary DNS server.  On it, I added a new blacklist zone in the /usr/local/etc/namedb/named.conf file:

zone "bl.mydomain.net" {
        type master;
        file "db.bl.mydomain.net";
        allow-update {
                127.0.0.1;
                ::1;
        };
};

The key section is the allow-update one, which tells the bind daemon that the following hosts (or networks) are allowed to make updates to the zone. In my case, it was just joker, so localhost was enough.

Next I created the start of the blacklist file.  For the purposes of this document, I’ll start the file with the previous bad guy: 162.253.67.28.  In the db.bl.mydomain.net file:

$ORIGIN .
$TTL 3600       ; 1 hour
bl.mydomain.net         IN SOA  ns.mydomain.net. hostmaster.mydomain.net. (
                                2015111700 ; serial
                                1800       ; refresh (30 minutes)
                                900        ; retry (15 minutes)
                                86400      ; expire (1 day)
                                3600       ; minimum (1 hour)
                                )
                        NS      ns.mydomain.net.

28.67.253.162          IN            A           127.0.0.1

I restarted the bind process:

service named restart

And I had an RBL that I could update.

Updating the RBL

Neal’s spamilter source also builds a command called dnsblupd which ends up in /usr/local/bin by default.  It can, with a series of arguments, add a new IP address into your RBL.  For instance, to add IP 65.18.113.108 to my RBL, I’d:

joker# /usr/local/bin/dnsblupd -zbl.mydomain.net -i65.18.113.108
Insert request for 108.113.18.65.bl.mydomain.net
Update request completed
A record for 108.113.18.65.bl.mydomain.net exists.

And to verify that, I can use nslookup:

joker# nslookup 108.113.18.65.bl.mydomain.net
Server:		127.0.0.1
Address:	127.0.0.1#53

Name:	108.113.18.65.bl.mydomain.net
Address: 127.0.0.1

Telling Spamilter About New RBLs

With the new RBL created, I had to convince spamilter to use it. Fortunately Neal made that very easy.  The file in question is /var/db/spamilter/db.rdnsbl.  In it, you’ll see all of the RBLs that spamilter will query when a new MTA tries to connect.  I added this to the bottom of my file:

# Locally defined
bl.mydomain.net                 |http://fuck.you.spammer.com            |Reject |Rcpt

Since spamilter references those db files with each new connection, it wasn’t necessary to restart the daemon. It started using it immediately. Of course, I didn’t have much in it to begin with, but Neal provides a way for spamilter to update the RBL. Further, I now had a tool for SpamAssassin to use to arm the RBL if need be.

Spamilter and RBL Feedback

In the scripts directory of the spamilter source, Neal includes a shell script called blupd.  It’s pretty simple:

#!/bin/sh

# This script is for use as an Exec action of spamilter.
# It takes the ip address passed to it, and requests an A RR be inserted into a DNS zone
# ie. Dynamic RDNSBL creation. Now expiring the entry is a whole other matter, then again, maybe not.

/usr/local/bin/dnsblupd -zrdnsbl.somedomain.com -i$1

#exit 0	# Accept action
exit 1	# Reject action

I copied this to /usr/local/bin, and edited the dnsblupd line to call my bl.somedomain.net RBL. The exit codes are important, and by default it’s 1. This matters, because spamilter will be calling this script on every one of the incoming domains that I have blocked in /var/db/spamilter/db.sndr. If you remember my last spamilter entry, I added bunch of new TLDs to the file like so:

.click                  |               |  |Reject
.website                        |               |  |Reject
.xyg                    |               |  |Reject
.xyz                    |               |  |Reject

With this new tool, I can have spamilter add each of the MTAs trying to deliver mail for one of those TLDs into my RBL and then either accept or reject the mail. The exit code of whatever spamilter execs determines whether it should accept (0) the mail, or reject (1) it. I wanted it to reject the mail, so I left the exit code as 1. The new db.sndr entries:

.click                  |               |Exec   |/usr/local/bin/blupd
.website                        |               |Exec   |/usr/local/bin/blupd
.xyg                    |               |Exec   |/usr/local/bin/blupd
.xyz                    |               |Exec   |/usr/local/bin/blupd

And with that, spamilter was feeding back data to the RBL.

What About SpamAssassin?

Recall from my last spam fighting entry that I’d written an interesting set of procmail rules so that it and SpamAssassin could work together.  With the RBL I just created, it was time to let SpamAssassin populate it with MTAs that might slip through spamilter.  This took some creative .procmailrc editing.  At the top in the variables section, I added two more:

DNSBLUPD=/usr/local/bin/dnsblupd
BLACKLIST=bl.mydomain.net

And then I had to change each of the rules which formerly just sent mail to /dev/null.  I figured I still wanted the mail going to /dev/null, but I wanted to RBL the MTA that delivered that mail, too.  A bit Draconian, but my thought was: If your MTA has delivered a piece of trash to me, I don’t want to talk to it any longer. If we look at my first .procmailrc rule, it took anything with 8 *s and just trashed it in /dev/null. Well, I changed that entry:

#
# Anything 8 stars or more is trashed after MTA is blacklisted.
:0
* ^X-Spam-Level: \*\*\*\*\*\*\*\*.*
{
        :0 c
        |$DNSBLUPD -z $BLACKLIST -i `grep "Received: from" - | head -1 | awk '{print $5}' | sed s/.// | sed s/\]\)//` >/dev/null

        :0
        /dev/null
}

This combined entry does a couple of things. First, the call to dnsblupd uses a few other CLI commands to pull the MTA’s IP address out of the message. At this point in the delivery cycle, neither SpamAssassin nor procmail have any idea who sent the message, so I had to pluck that out. The second entry then sends the message where it belongs: /dev/null.

I added the same sort of target to each of the other rules that formerly trashed the message.  And with that, SpamAssassin was providing real time feedback to spamilter via the RBL.

More to Come

There’s more to tell in this little spam fighting saga.  You’ll note that in my Putting the Jails to Work entry, I created a riddler jail for outgoing SMTP.  My goal was to eventually make him an incoming SMTP agent as well, but it would require some changes to spamilter and ipfwmtad at the source code level.  Stay tuned.

Leave a Reply