Securing a Postfix Smtp Server

I must start this post with the acknowledgement that I know only what I’ve experienced on this topic.

I recently set up my own mail server for the fun of it. I figured it was something I’d never done, so why not, right?

Well, one day later, spammers discovered my server and began using it to send out spam mail (curse you spammers!). I didn’t notice this until I received a notification from my hosting provider that my network IO was over the threshold I had set. I promptly logged in, tailed the mail logs and discovered unbelievable amounts of mail being rejected by Google, Yahoo, Aol, and Hotmail. Why? Spam.

With that, I spent the next day figuring out how to better secure my smtp server. I’d like to detail some of the exploits that the spammers used to get in to my server, how I failed in configuring my server properly, and how I fixed it.

Leaving an Open Relay

An open relay is basically an smtp server that requires no authentication and/or allows connections from outside ip addresses, so anyone can send emails from anywhere to anywhere. The settings in question specific to this issue in my configuration were the following:

smtpd_recipient_restrictions = permit_mynetworks, check_relay_domains
...
mynetworks = 0.0.0.0/0 127.0.0.0/8 [::fff:127.0.0.0]/104 [::1]/128

Basically that is an open relay. Here’s why.

  • Firstly, smtpd_recipient_restrictions = permit_mynetworks allows any email to be sent without any restrictions as long as the email originated from a box in the IP ranges specified in the mynetworks variable.

  • Secondly, mynetworks = 0.0.0.0/0 allows emails to be sent through my smtp server from any client within the ip range of 0.0.0.0-255.255.255.255. This is bad because any computer can try to send emails through my smtp server and succeed because of the permit_mynetworks restriction (or lack therof).

Specifying Incorrect Configuration Parameters

One of my first mistakes when configuring Postfix was misspelling some smtpd parameters using smtp_ instead of smtpd_ to prefix them. As it turns out, if you do this, Postfix ignores your attempted configuration without a peep. This one went on for a long time before I noticed that two of my smtpd_ fields were missing the d. As soon as I put those in there, everything started working as it should, albeit still insecure, but at least it was following the specifications of my config file.

Not Specifying a Correct smtpd_sasl_path

This one took me a while. The smtpd_sasl_path is a path to the socket file for your SASL server. In my case, this is Dovecot.

As it turns out, Postfix defaults to running in chroot mode which makes its root directory /var/spool/postfix/. This was my first mistake. I was specifying

smtpd_sasl_path = /var/spool/postfix/private/auth-client

and it was not starting up because it couldn’t find the socket file. This was because it was looking for the file at /var/spool/postfix/var/spool/postfix/private/auth-client a path which clearly does not exist. The solution to this is to simply specify a relative path.

smtpd_sasl_path = private/auth-client

I decided that I would get smart though and shave off some text from the field value by configuring Dovecot to place the socket file at /var/spool/postfix/auth-client rather than at /var/spool/postfix/private/auth-client (speaking in absolute terms despite running in chroot mode). This returned the following error

warning: when SASL type is "dovecot", SASL path "auth-client" should be a socket pathname

As it turns out, postfix won’t operate with the SASL socket file path outside of the private directory. So with that, I placed my auth-client file back in the private directory and Postfix started up fine.

Not Specifying the Allowed Senders File

Even if you do have authentication required, you still need to specify which users can send email with what addresses. This was a bit of a surprise to me initially because I was under the impression that a password is associated with an email address, not an email address(s) associated with a username and password. To keep users from being able to send email as addresses that are not theirs (specifically randomly generated addresses in my case), you need to create a mapping file that maps usernames to the addresses they are authorized to send mail as. In my case, this is a one to one relationship (one address per username). Before my example I’d like to note that the filename is not required to be the one I use (though my filename is the one used in the Postfix setup documentation).

Okay. Let’s create the map file. To do this, open up and edit /etc/postfix/controlled_envelope_senders (this file likely doesn’t exist yet)

vim /etc/postfix/controlled_envelope_senders

Once you’ve got that open, you simply need to put the maps in there.

# envelope sender owners jcricket@example0.com jimminey

Now that we’ve done that, we need to turn it into a binary. Run the following command and it will generate a <filename>.db binary map file in the same directory as the original file.

postmap /etc/postfix/controlled_envelope_senders

Presto! Now the user jimminey can send email as jcricket@example0.com. However, so can everyone else…​still.

Now that we have our controlled envelope senders file, we need to reference it in our postfix main.cf and set postfix up to restrict access to the maps specified in that file. Crack er open in your favorite editor and put the following line in somewhere after smtpd_sasl_auth_enable

smtpd_sasl_auth_enable = yes
...
# This line specifies our map file for use by postfix
# Note that this does NOT reference controlled_envelope_senders.db
smtpd_sender_login_maps = hash:/etc/postfix/controlled_envelope_senders
# This line sets postfix to reject anyone who authenticates but tries to send email as an address they aren't permitted to use
smtpd_recipient_restrictions = reject_sender_login_mismatch, permit_sasl_authenticated, reject_unauth_destination

So what we’ve just done is tell Postfix where our map file is (smtpd_sender_login_maps). After that, we tell Postfix to reject any users that have been authenticated but are trying to send with an address they aren’t authorized to send with in our map file (smtpd_recipient_restrictions). Please note that reject_sender_login_mismatch comes at the beginning of the smtpd_recipient_strictions field. This is key. It is so key in fact, that I missed it (I only miss the key stuff of course thanks Murphy). This was the forth exploit attempt that got me.

Misordering smtpd_recipient_restrictions

This one is the final bit that let the spammers in (so far at least).

The smtpd_recipient_restrictions are restrictions that you can place on the users and their emails based on various things. In my case, I had the following restrictions string

smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_sender_login_mismatch, reject_unauth_destination

Postfix applies these restrictions in the order in which they are specified. As they put it <blockquote>Restrictions are applied in the order as specified; the first restriction that matches wins.</blockquote> As soon as one restriction matches, then the ones that follow don’t get applied. This was very problematic because in my case permit_mynetworks is first. So that I can log in from my cell phone which has an IP address that changes, I set

mynetworks = 0.0.0.0/0 127.0.0.0/8 [::fff:127.0.0.0]/104 [::1]/128

which allows any IP address to connect to my SMTP server. Since Postfix takes the first match and goes no further and any IP address is in 0.0.0.0/0, anyone can send mail through my SMTP server. This = bad.

What you should do is start your restrictions with the the most strict restrictions followed by the less strict. In my case, that looks like

smtpd_recipient_restrictions = reject_sender_login_mismatch, permit_sasl_authenticated, reject_unauth_destination

In the event someone tries to send an email, first they must login. If they don’t log in, they are rejected due to reject_sender_login_mismatch (we can’t do a match if we don’t have a sender username). Secondly, once logged in, the user must be authorized to use the address they are trying to send as as specified in the smtpd_sender_login_maps line. Finally, once the user has been authenticated and they have permissions to use the address they are trying to send as, their email is not rejected. It follows that they are then filtered through permit_sasl_authenticated. This basically runs a check to see if they are authenticated (which we know they already are because of the previous filter) and since they are, they are permitted and Postfix stops looking for more matches because it’s found one that permits the user to perform their requested action.

As chef Elzar says, "Bam!"