Spam-Resistant Postfix


One of the challenges of running an email server is the vast number of spam emails you receive. There have been lots of studies trying to figure out exactly how many spam emails are sent, what percentage of total emails they are, etc. From personal experience, I’ve seen up to 80% of emails arriving being spam. Luckily, there are several options available that can be used to stem the tide of spam, and even keep them from making it to our inbox in the first place.

The changes and modifications below shouldn’t affect the ability of legitimate and properly configured servers from sending you email messages. However, be warned that there are lots of mis-configured email servers out there (in my experience, more than 90% are Microsoft Exchange…) which will get tripped up. However, usually it is a simple fix on their end and you just need to inform the server’s administrator of the issue. Most of the time they aren’t even aware of their problem.

I personally use Postfix running on Debian for my email server, so the instructions below will be specific to it. However, apart from the Postfix-specific configuration options, all the information below should also be applicable to other email servers.

Postfix configuration

The easiest change we can make is to the Postfix configuration itself, since it requires no other programs. In the main.cf file, make sure there exists a line like so:

smtpd_helo_restrictions = ... reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_helo_hostname ...

You may have other restrictions as well, depending on your needs. In a nutshell, these three options will reject servers which HELO/EHLO with an invalid hostname, a non-fully qualified domain name or non-bracketed IP address, and non-existent domain names. For more information, see the official documentation.

As I mentioned above, mis-configured email servers can be blocked by these changes. By far the most frequent issue that I have seen happens when reject_unknown_helo_hostname is added to the Postfix configuration. Microsoft Exchange servers seem to default to HELO’ing with “.local”. Of course, outside of the server’s network, this domain name is unresolvable, causing messages to be rejected. However, this is an easy fix on the sender’s side once they are notified about it.

Greylisting

The second step we can take to limit the delivery of spam messages is to employ a technique called greylisting. Greylisting is a process where, upon receiving an email, the server looks to see if the (sending IP address, sender address, recipient address) tuple has been seen before. If it has not, a temporarily rejection is returned (ie, SMTP 4xx). After a given delay, typically a few minutes, if the same tuple is seen again, it is immediately allowed to be delivered. This targets spammers who blast messages without queuing messages which have temporary rejections. A legitimate server (and admittedly some spammers) will follow the SMTP RFCs and retry delivery of the message after a short while. For further details, see the Wikipedia article.

It is easy to configure Postfix to employ greylisting. First, you need to install the postgrey package:

sudo apt-get install postgrey

The postgrey package has a default configuration located at /etc/default/postgrey. You can configure some options; see the man page for more information. For example, to change the port that postgrey listens on to 10023, use:

POSTGREY_OPTS="--inet=10023"

Next, add the appropriate check in main.cf:

smtpd_recipient_restrictions = ... check_policy_service inet:127.0.0.1:10023 ...

Finally, reload both the postfix and postgrey services:

sudo /etc/init.d/postgrey/restart
sudo /etc/init.d/postfix/restart

SPF (Sender Policy Framework)

These next two modifications move from being reactive in nature to proactive instead. They depend on email server administrators stating which servers are authorized to send email messages for a given domain and cryptographically signing each message to confirm authenticity, respectively. We’ll start with SPF, since it’s slightly simpler to setup.

Sender Policy Framework, or SPF, was designed as a way for administrators to explicitly list which servers are authorized to send emails for a given domain, and in the process make it impossible to perform email spoofing. There are two steps: first, decide on a policy and publish it via DNS; second, check for and enforce SPF on incoming messages.

SPF looks for policy information in special DNS records of either TXT or SPF type, based on the domain name of the incoming email. The format of the policy has many options – look at the OpenSPF’s policy page for an overview. Here is the policy for my domain:

calenhad.com. IN TXT "v=spf1 mx -all"

Briefly put, this states that using SPF version 1, any server listed as a mail server for the domain may send messages, and all others are forbidden.

Now that the policy has been published, other servers which have implemented SPF will be able to use it to accept or reject messages claiming to be from your domain. Next, we will configure Postfix to check SPF itself.

Install the postfix-policyd-spf-{perl,python} package, depending on your language preference. :)

sudo apt-get install postfix-policyd-spf-perl

Add a new service in master.cf:

policy-spf unix - n n - - spawn user=nobody argv=/usr/sbin/postfix-policyd-spf-perl

Then add a check in main.cf:

smtpd_recipient_restrictions = ... check_policy_service unix:private/policy-spf ...

Once again, reload Postfix to make the changes active. You can test that Postfix is now checking SPF by sending a message to an account on your server. It should append a header to the message indicating the status of the SPF check. If you don’t see the headers or have problems, check the mail logs.

For more information about SPF, I’d recommend the OpenSPF’s website and Wikipedia article.

DKIM (DomainKeys Identified Mail)

Our next step is to configure DomainKeys Identified Mail, or DKIM, for Postfix. DKIM is a way to verify the origin domain of a message by computing a signed hash of various header fields using a public key for the domain name from which it was sent. Once again, Wikipedia has an article for further information, and you can also visit the DKIM homepage.

Before we configure DKIM, we need to determine what the domain we are sending messages for is, and choose what is called a selector which will be used to pick the correct public key later on. The domain should be obvious, and the selector can technically be anything you want; I just use the remainder of my server’s domain name for this. So for my setup, the domain is calenhad.com, and the selector is mail.

Start by installing the opendkim package:

sudo apt-get install opendkim

We need to generate the public / private key pair that the server will use for signing. Luckily, we can use the opendkim-genkey command to easily create the key pair:

/usr/sbin/opendkim-genkey -d <domain> -s <selector>

Then opendkim needs to be configured. The configuration file is located at /etc/opendkim.conf. There are several options, but the four important ones are the following:

Domain calenhad.com
KeyFile /etc/mail/dkim/key.pem
Selector mail
InternalHosts /etc/mail/dkim/trusted-hosts

key.pem is the location of the private key we just generated. (The public key is published in a DNS record below.) The domain is your domain, and the selector is used to allow different DKIM keys for the same domain. InternalHosts specifies a file which contains a list of hosts whose email should be signed instead of being verified. Make sure to list your server’s IP address here, otherwise messages sent from it may not be signed.

Next, tell opendkim what local port to listen on by editing /etc/default/opendkim (port 10024 in my case):

SOCKET="inet:10024@localhost"

Add the needed configuration to Postfix’s main.cf:

milter_default_action = accept
smtpd_milters = inet:localhost:10024
non_smtpd_milters = inet:localhost:10024

Then restart both the opendkim and postfix services to make the changes active.

The last step is publishing our public key so other servers can verify our DKIM signatures. The general syntax is as follows (the Wikipedia article describes several of the options for the entry):

<selector>._domainkey.<domain>. IN TXT v=DKIM1; g=*; k=rsa; p=<public key contents>

For my server, it looks like this:

mail._domainkey.calenhad.com. IN TXT v=DKIM1; g=*; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNAD... [snip]

After adding the needed DNS entry, try sending and receiving messages from your sever. The email headers should now include mention of a DKIM signature and verification. If they are missing or you encounter problems, look at the log files and try enabling the LogWhy option in the opendkim configuration for more verbose messages.

SpamAssassin

Finally, the last weapon in our arsenal for those spam messages which make it past the defenses: SpamAssassin. Since there’s signficantly more documentation about configuring SpamAssassin and Postfix than the preceding topics, I won’t go into great detail. The high-level overview is to install the spamassassin package, and then tell Postfix how to use it by defining a policy service in the master.cf file and telling the smtp service to use it:

smtp inet n - - - - smtpd -o content_filter=spamassassin
spamassassin unix - n n - - pipe user=spamd argv=/usr/bin/spamc -f -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}

Revision History