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.

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:

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).

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.

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.

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.

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

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.