You all have persevered through quite a bit to get to this point: we have a functional and secure e-mail server that does a good job at ignoring or dumping off spam before it hits your inbox. We've got all the right pieces in place to ensure that the mail we send gets delivered; we've got OpenDKIM operational, and we've got DNS properly configured (including reverse lookups!).

We could stop here and declare success. After all, you can plug your mail account name and password into your mail app or your smartphone and send and receive e-mail. You can easily add new accounts. Even better, you can easily add aliases, so creating "idonttrustyounottospamme@mydomain.com" and using it at a skeevy website that requires e-mail registration and then deleting the alias takes only a few seconds.

But are there any extra steps we can take to increase security? What about one-time passwords or two-factor authentication? What about bolting on a webmail front end so we can access our e-mail from a browser? What about push notifications or calendaring? What about letting users set and change their own e-mail passwords? What else can we bolt onto our server, and what other configuration paths might we take to do things differently?

We're going to answer all of these questions in this final installment.

Postscreen and additional filtering

The Postfix configuration we set up in parts 1-3 is pretty aggressive. We're refusing to communicate with other e-mail servers that don't do things exactly right; further, if you've implemented the Sieve filter rules I laid out in part 2, we're also discarding mail without properly formed message IDs. However, we haven't specifically implemented DNS-based black hole list checking. We're going to do that now.

DNS black holes lists—which you'll see referred to as DNSRBLs, for "DNS real-time black hole lists"—are lists maintained by various entities, some companies, and some individuals, filled with IP addresses or ranges of IP addresses from which the list maintainer believes spam has originated. The various RBLs all have their own criteria for listing an address, and many of them are notoriously difficult to get off of once you're listed. Much of the work we did in parts 2 and 3 was so that our own servers would behave properly and not end up on one or more of these blacklists.

SpamAssassin (which we configured in part 3) already checks many DNSRBLs as part of its default configuration and scores e-mails appropriately if the sender address is on one or more of them, but we're going to go a step further here and have Postfix reject messages entirely based on the lists. We'll do it by turning on a Postfix feature called postscreen.

Postscreen actually does a whole bunch of other stuff, too, all with the intent of separating legitimate mail senders from spammers. Out of the box with very minimal effort, postscreen implements a number of checks on incoming e-mail to make sure the remote servers are configured in a normal, non-spammy way; it also offers deeper checks, but we're going to leave those alone, because they can delay receipt of e-mail (or deny it all together).

Postscreen as a whole involves a number of additional Postfix-spawned processes working on incoming connections before the sptmd process accepts that connection. This means that to get postscreen working, we need to first make changes in master.cf , because that configuration file controls how the Postfix master process wrangles all of its separate daemons and utilities. Open up master.cf ; at the beginning of the file, you should see this:

smtp inet n - - - - smtpd #smtp inet n - - - 1 postscreen #smtpd pass - - - - - smtpd #dnsblog unix - - - - 0 dnsblog #tlsproxy unix - - - - 0 tlsproxy submission inet n - - - - smtpd

We want to comment out the initial smtp line, and then un-comment all four of the commented-out lines, so that we wind up with this:

#smtp inet n - - - - smtpd smtp inet n - - - 1 postscreen smtpd pass - - - - - smtpd dnsblog unix - - - - 0 dnsblog tlsproxy unix - - - - 0 tlsproxy submission inet n - - - - smtpd

If those lines are missing, then you'll want to add them in near the top of master.cf . These lines open the appropriate local ports and create the appropriate unix sockets for the postscreen process, which will involve TLS connection handling, DNS lookups, and remote blacklist checking.

After this, we also need to make some additions to main.cf to tell Postfix to actually use postscreen, as well as which remote DNS blacklists we want to check and which senders will be excluded from postscreen checking. So, open up main.cf for editing and add the following at the bottom:

postscreen_greet_action = enforce postscreen_dnsbl_action = enforce postscreen_access_list = permit_mynetworks postscreen_dnsbl_sites = zen.spamhaus.org, b.barracudacentral.org, bl.spamcop.net

The first two parameters implement the screening part of postscreen's functionality, telling it that we want to enforce non-spammy connection behavior (and not accept connections from clients that try to short-circuit the process to deliver spam faster) and to turn on DNS blacklist checking. The next parameter excludes your mynetworks variable contents from postscreen checking (which will keep LAN clients from getting postscreen'd). Finally, the last parameter adds three DNSRBLs to postscreen's check list.

You can specify as many or as few RBLs as you'd like; there are lots to choose from. Additionally, you can use the postscreen_dnsbl_threshold parameter to control how many lists a sender has to be on in order to get postscreen to reject the e-mail; if you want to get really fancy, you can also assign different weights to your blocklists so that being on one "counts" more than being on another. The Postfix documentation has more info on how to set this up.

Restart Postfix with sudo service postfix restart to make your changes live. And that's it—postscreen is now active. If you want to watch it in action, send yourself an e-mail from an external account and watch your /var/log/mail.log file:

One more security tip: rate limiting with iptables

Postfix will automatically rate-limit connection attempts—that's what the postfix anvil process is for. However, rather than modify more default configuration, we can lean on a much more powerful and flexible tool to do our rate limiting: iptables.

Iptables lets you set rules that will affect the behavior of the operating system's kernel firewall. It's got an extraordinarily flexible (and extraordinarily complex) level of configurability, and so rather than give an exhaustive iptables tutorial, we're going to very quickly run through how to use it to drop connections from spammy spammers who spam too hard.

Note that when implementing iptables here on top of postscreen, it is a lot like wearing a belt with suspenders: this might be a bit of overkill. However, if you're in a situation where you can't (or don't want to) implement postscreen or another rate-limiting solution, iptables can easily fit the bill. It's very, very good at what it does.

What it's not, though, is friendly to inexperienced users. The first thing we need to do is install a quick program to automatically save your running iptables configuration at shutdown and re-apply it at startup (iptables doesn't do this by itself). So, run the following command:

aptitude install iptables-persistent

As part of the quick iptables-persistent setup, you'll be asked if you want to save your current set of iptables rules; you probably don't have any, but say yes anyway.

Next, navigate to /etc/iptables . This is where iptables-persistent keeps the saved copies of your firewall rules. There should be a file named rules.v4 in the directory, containing your IPv4-specific firewall rules (there's probably also a rules.v6 file, which we can ignore).

Your rules.v4 file should have very little in it. If you haven't explicitly defined any iptables rules, it ought to look something like this:

*filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] COMMIT

If it doesn't, and instead contains one or more rules, then you should probably skip this section unless you know exactly what the existing rules are for and why they're there. If you're comfortable with iptables' syntax, you can graft our configuration onto your existing rules, but it will take a bit of rejiggering; if you're not sure exactly how to do it already, you should stop and move on to the next section.

If your rules are indeed empty, with only three "accept" chains defined, then we can proceed. Delete the file's contents and replace it with the following:

*filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] :LOG_AND_DROP - [0:0] -A INPUT -s 192.168.0.0/24 -i eth0 -j ACCEPT -A INPUT -p tcp -m tcp --dport 993 -m state --state NEW -m recent --set --name imapssl --rsource -A INPUT -p tcp -m tcp --dport 993 -m state --state NEW -m recent --update --seconds 10 --hitcount 20 --name imapssl --rsource -j LOG_AND_DROP -A INPUT -p tcp -m tcp --dport 993 -j ACCEPT -A INPUT -p tcp -m tcp --dport 587 -m state --state NEW -m recent --set --name imap --rsource -A INPUT -p tcp -m tcp --dport 587 -m state --state NEW -m recent --update --seconds 10 --hitcount 20 --name imap --rsource -j LOG_AND_DROP -A INPUT -p tcp -m tcp --dport 587 -j ACCEPT -A INPUT -p tcp -m tcp --dport 465 -m state --state NEW -m recent --set --name smtps --rsource -A INPUT -p tcp -m tcp --dport 465 -m state --state NEW -m recent --update --seconds 10 --hitcount 20 --name smtps --rsource -j LOG_AND_DROP -A INPUT -p tcp -m tcp --dport 465 -j ACCEPT -A INPUT -p tcp -m tcp --dport 25 -m state --state NEW -m recent --set --name smtp --rsource -A INPUT -p tcp -m tcp --dport 25 -m state --state NEW -m recent --update --seconds 10 --hitcount 20 --name smtp --rsource -j LOG_AND_DROP -A INPUT -p tcp -m tcp --dport 25 -j ACCEPT -A LOG_AND_DROP -j LOG --log-prefix "iptables deny: " --log-level 7 -A LOG_AND_DROP -j DROP COMMIT

Be sure to modify "192.168.0.0/24" to point to your LAN subnet (or just to your own server's IP address if you're on EC2 or other shared hosting). We're creating a new "LOG_AND_DROP" chain, and directing iptables to keep track of incoming connections on TCP ports 993, 587, 465, and 25. If a client hits those ports more than 20 times in 10 seconds, iptables will firewall off that client and drop all traffic from it until it stops sending.

The 20 connections and 10 seconds limits can be adjusted to suit your tastes, though counting more connections might require modifying some kernel parameters.

After you've saved and exited out of rules.v4 , you can make the rules active with the iptables-restore command, like this:

iptables-restore rules.v4

To see a list of active iptables rules, you can run iptables -L .

Now, on to webmail!