« All politics is local | Main | A semester too early »

Shades of grey

I spent some time yesterday implementing greylisting on our secondary MX. I found it fascinating, but if neither of the two key parts of that sentence (“greylisting” or “secondary MX”) mean anything to you, you may want to skip this. (If you understand one or the other, in the extended entry I’ll give a thumbnail explanation of both, before outlining my installation.)

This has become a bit of an epic. I’m not sure if I want everyone to skip it, because it’s hopelessly technical and geeky, or read it, because I put so much time into it…

Now Playing: I See Monsters from Love Is Hell by Ryan Adams

What is greylisting

Greylisting” is an anti-spam technique which, as the name implies, is neither “blacklisting” (never accept mail from this list of sites/addresses/hosts, but accept from all others) nor “whitelisting” (always accept mail from this list of sites/addresses/hosts, but reject from all others.) Greylisting says to all incoming mail, “Not just yet. Come back in five minutes and try again.” Legitimate mailers do, in fact, come back in five minutes, and are allowed in, because the mail transfer protocol has provisions for that sort of thing built in. (For example, a mail server could be too busy to accept new mail; the protocol allows for retries.) Spammers, however, tend to be using fire-and-forget bulk mailers which can’t deal with “Try again later.” So they don’t, and the mail doesn’t get delivered.

The actual implementation, of course, is more complicated (some implementations can eventually whitelist senders; they can recognize and track messages differently, etc.) but that’s the summary.

The hangup with greylisting is that it builds in a delay for legitimate mail. While this is transparent to the end user, some users have started thinking email is like Instant Messaging, and they don’t want five-minute (or fifteen-minute, or sixty-minute) delays on incoming mail. So we (ok, I) have opted to only implement greylisting on our secondary MX.

Primary and secondary MX

“MX” is short for “Mail Exchanger.” The “MX record” in DNS information indicates which host accepts incoming mail for a domain. Most hosts specify two or more MX hosts, and indicate priority: this is our primary mail server, but if you can’t reach it, try this secondary, etc. out to five or six servers if it’s a big site. The idea is that if your primary mail server goes down, hosts sending mail to you will instead deliver to your secondary mail server, which will hold it until the primary comes back up, then hand the mail along.

A common spammer tactic is to pass up the heavily spam-defended primary MX, and spew their slime at the secondary MX instead. Mail relayed through the secondary tends to be more trusted by the primary, and many mail admins (not this one, of course) don’t filter or defend their secondary MXes as well as their primaries. On the other hand, legitimate mail should be using the primary anyway, so we can actually defend the secondaries more aggressively—just like people are more wary of traveling salesmen, who come to the front door, not the back.

Preparing for Postgrey

Anyway: the secondary MX uses Postfix, so I opted to install Postgrey, which bills itself as a “Postfix greylisting policy server.” I had to upgrade Postfix from 2.0.x to 2.2.x in order to get the policy-server infrastructure that Postgrey uses. Also, since Postgrey is a Perl script, I needed to install a series of modules: Net::Server, IO::Multiplex, and the BerkeleyDB module. I also needed to install the Berkeley DB library itself.

Installation of Postgrey

Once I’d plowed through those installs, I had to figure out installing Postgrey itself, getting it running, and keeping it running. The installation documentation is pretty sparse, so I had to make several false starts before I got it running.

They suggest creating (another) dummy user to run Postgrey, so I created the user “postgrey” and a matching group. Later on, I (re)discovered that the default group is “nogroup”, so if you have that group already, there’s no need for a new group; just make the postgrey user a member of that group. I, instead, run postgrey with the --group=postgrey option flag, which overrides the default group.

A directory needs to be created to store the database files. The default location is /var/spool/postfix/postgrey/. This directory needs to belong to the postgrey user.

With the users and directories created, unpack the tarball:

 # tar -xzvf postgrey-1.18.tar.gz

…and look in the resulting directory. Note that the README file is fairly unhelpful; use perldoc postgrey instead to get help. Copy the postgrey file to an executable path; I suggest /usr/sbin/ but it’s not too important, as long as it’s in someone’s $PATH. Copy the whitelist files, postgrey_whitelist_clients and postgrey_whitelist_recipients, to /etc/postfix. That’s the installation.

Configuration

Postfix and Postgrey function like a client/server pair, so Postfix has to know to call Postgrey, and Postgrey has to be listening for the call. The first part is achieved by editing the /etc/postfix/main.cf configuration file, and adding something like check_policy_service inet:127.0.0.1:10023 at the end of your smtpd_recipient_restrictions = block. This is explained in the “Delegating SMTP Access Policy” page linked above; it means there’s something listening on the localhost port 10023 which will tell Postfix what to do with the mail. It goes at the end of the list of options because I want all my DNS Block Lists to have a crack at rejecting the mail outright before I bother greylisting it. Once we’ve got Postgrey listening on that port, we’ll reload Postfix to start it calling here.

Here’s what this block looks like on my main.cf file:

smtpd_recipient_restrictions = 
    permit_mynetworks,
    reject_unauth_destination,
    reject_invalid_hostname,
    reject_unauth_pipelining,
    reject_non_fqdn_sender,
    reject_unknown_sender_domain,
    reject_non_fqdn_recipient,
    reject_unknown_recipient_domain,
    check_recipient_access hash:/etc/postfix/bluebird_recipients,
    reject_rbl_client sbl-xbl.spamhaus.org,
    reject_rbl_client opm.blitzed.org,
    reject_rbl_client list.dsbl.org
    check_policy_service inet:127.0.0.1:10023
    permit
  policy_time_limit = 3600

Starting the daemon

Starting Postgrey is both simple and complex. It can be run from a simple command line:

 # postgrey --inet=10023 -d

…will set it up as a daemon listening on port 10023. (Note: I think this means that we could set up multiple mail servers to use a single Postgrey implementation this way.) I need to add the --group= flag as noted above, but it’s that simple. However, what if the server crashes? How do I make sure the service comes back up when the machine reboots? I need a script in /etc/rc.d/init.d/ to do that for me. I’m trying to write one; so far, it will shut down Postgrey properly, but I haven’t been able to make it start it up.

Anyway, as the Postgrey daemon is starting up, it will log errors in the /var/log/maillog file. The ones I still see are like this:

postgrey[25489]: fatal: Your vendor has not defined BerkeleyDB macro DB_AUTO_COMMIT, used at (eval 5) line 1 
postgrey[25489]: warning: Use of uninitialized value in bitwise or (|) at /usr/bin/postgrey line 495.

The first one appears to be something wrong with my BerkeleyDB installation. I looked for solutions, but found only this mailing list thread implying that I should just live with it. The second error is only a warning; I’m not quite sure why it comes up, but it doesn’t affect the continued functioning of Postgrey. So you can safely (I think) ignore these two errors.

You can check to make sure Postgrey is running like this:

 # ps -auxww | grep "postgrey"

Fitting the pieces together

If Postgrey is running, and you have modified your /etc/postfix/main.cf file to call it, you need to reload Postfix.

 # postfix reload

If you want to keep an eye on the incoming mail hitting the greylist for a little while, to be sure it’s working, try this:

# tail -f /var/log/maillog | grep "postgrey"

I hope someone finds this helpful. We’ll see what it does for my spam load here at work.

Comments

This was helpful to me; thanks! Now have postgrey successfully running.

thanks, you had a few of my missing pieces for getting postgrey working properly here.

To get something in /etc/init.d/ to run on startup, you need to (on Mandrake, Fedora or Redhat, and maybe others) run chkconfig. So, probably:

chkconfig postgrey on

And that should do it.

Post a comment