home * bincancel-vorerhebungen * hacks * (anti)spam * oesterreich.* * crash * remailer-stats * vorträge * verschiedenes * abmahnung * english


The boring basics

I've been hardening my mail server against spam for a long time, and I've built a SpamAssassin-based antispam appliance for customers. The basics of anti-spam configuration can be found in my talks on the subject.

Newer stuff (that doesn't really work)

I've also experimented with greylisting and SPF. The problem with both is that they interfere too much with the delivery of legitimate mail -- "standard" greylisting is applied to all mail, and therefore delays delivery of legitimate messages, while SPF has all kinds of well-known problems with forwarding, people that send mails from all over the world, and spammers creating SPF records for their domains.

Selective Greylisting

So in the end, after the umpteenth usenet discussion ("greylisting is evil because it clogs my servers queues" vs "greylisting is so effective"), I implemented a postfix policy daemon for "Selective Greylisting".

Basically, it means that messages that look OK are immediately whitelisted, while messages that look "suspect" in one way or another are subject to greylisting.

Interestingly enough, it seems to work quite well, and that's why I'm creating this page.

(I'll try to extract some statistics from the logfiles and add some numbers to this page RSN)


Selective Greylisting, as "classic" greylisting, takes place at the RCPT stage in the SMTP dialog. A list of checks is done, and a "suspectness" score is generated as the sum of the failed tests. If the suspectness is zero, the combination of client IP address, HELO string and MAIL FROM domain is whitelisted, a non-zero suspectness results in greylisting (for a period of time depending on the score [I'm not sure this detail has any beneficial effect]).

The checks that I've implemented are:

Reverse DNS lookup

I never liked the idea of blocking clients with no reverse lookup, but in this context it seems acceptable.

HELO name is an IP address or non-FQDN

This, again, is a test that on its own doesn't say much about a host's legitimacy.

HELO name exists in DNS

Here, I test whether the HELO name has an A or MX record.

Client is on a dialup-DNSBL

This is again something on which other people have been blocking mail for ages, which I considered too harsh.

SPF status is either "pass" or "none"

This seems like a good use of SPF to me: if everything is fine, fine. If not, greylist. I'm using the "guess" mode of Mail::SPF::Query here, which, in the absence of SPF DNS records, tries to guess them from the sender domain's DNS data.

The tuple that is grey- or whitelisted is (client IP address, HELO name, sender domain) -- this is in contrast to "standard" greylisting, where (client IP address, sender address, recipient address) is used. I think that my approach has a few benefits:

Last, but not least, an important feature: I'm keeping a manually- maintained list of forwarders, i.e. machines where an email address is being forwarded to my mail server. Machines on that list are automatically whitelisted, so they don't have to go thru the greylist cycle for every new sender domain that happens to be forwarded to me, plus they're immune from DNS misconfigurations etc.


My implementation of the concept is a policy delegation daemon for postfix. Postfix' smtpd sends all available data to the daemon when the RCPT command is received.

Add the following to /etc/postfix/master.cf:

policy-meta     unix -  n       n       -       -       spawn
        user=nobody argv=/path/to/meta-greylist -d

Add this somewhere appropriate in smtpd_recipient_restrictions in main.cf:

        check_policy_service unix:private/policy-meta,

And create the directory for the database; /var/spool/postfix/db/greylist/ is what is defined in the source. This directory must be writeable by the "nobody" account.

Install the prerequisite Perl modules:

Finally: download the code.

And don't forget to fill the "forwarders" table when the sqlite DB has been created:

$ sqlite /var/spool/postfix/db/greylist/greylist.sqlite
SQLite version 2.8.15
Enter ".help" for instructions
sqlite> insert into forwarders values('forwarder1.example.com');


There's no expiry of database entries. The data structures are there, I just couldn't be bothered to implement a cleanup function...
christian mock, last modified: Tue Dec 6 16:24:21 2005, impressum