Authenticated SMTP Submission (SMTP AUTH) with Postfix and saslauthd


So, you've got Postfix working and it's nice.

Except for one little thing.  Your users are roaming the earth with their laptops connecting to any old wifi hotspot or dormitory LAN.  And they need to relay their outbound mail through your Postfix, securely.  And half the places they plug into don't let them send anything on port 25.  They need relay control based on knowing a secret, not on their IP address.  And it can't be sent in the clear.  You need to get SMTP AUTH working.   There are, apparently, a gazillion really complicated ways to do it, and a bunch of howtos about those ways.  Let's keep things simple.

There are at least six ways the client can authenticate (prove its identity) to the server.  The DIGEST-MD5 and CRAM-MD5 methods use encryption, so the password isn't exposed to an eavesdropper.  But the messages themselves will still be exposed to anybody in the coffee shop who's got a tool like tcpdump.  PLAIN and LOGIN send in the clear.  If you allow those methods, or your users expect privacy, you'll have to protect the entire session with TLS.

I'm using Debian-3.1 "Sarge" and 4.0 "Etch." I built Postfix-2.4 from source on Sarge because I wanted some new features.  Postfix-2.3 on Etch is out-of-the-box.


Part 1.  SMTP AUTH

Most of my users get shells and use SSH+SFTP.  On GNU+Linux, those logins get authenticated through a set of subroutines called Pluggable Authentication Modules (PAM).  Some users prefer RSA or DSA key-pairs instead of passwords, and the sshd handles those itself instead of consulting PAM.  I don't want those credentials involved in sending mail.  So I created some pairs of user names and passwords just for SMTP AUTH.

The Postfix smtpd daemon knows how to do the SMTP part of SMTP AUTH.  But it doesn't know anything about user passwords or PAM or Kerberos or RADIUS or any other authentication scheme.  So the job of accepting or declining the user's credentials is outsourced to an saslauthd daemon.  saslauthd knows how to use PAM.  It knows how to look at /etc/passwd and /etc/shadow directly in case PAM isn't available.  It knows how to look in a Berkeley DB file that nothing else on the system knows or cares about.  That's what I'm using.  And it can deal with credentials that were presented in clear text, or encoded in CRAM-MD5 or DIGEST-MD5.  Clear text credentials might sound dangerous, but the whole SMTP session might be happening in a TLS or SSH tunnel, which would make them okay.

Install the Cyrus Simple Authentication and Security Layer daemon:

apt-get install sasl2-bin

The Debian package installs a file /etc/default/saslauthd which is a fragment of a shell script.  The start-stop script /etc/init.d/saslauthd sources it to set some variables.  Edit the file and uncomment the "START=yes" line.  Also, change the MECHANISMS line to read MECHANISMS="sasldb".  (Should you want to use your unix /etc/passwd and /etc/shadow files via PAM, instead of SASL-only credentials, it's MECHANISMS="pam".)

Postfix needs to be in saslauthd's group or it can't talk to it.

           adduser postfix sasl

And in Etch, Postfix runs chrooted in /var/spool/postfix, which means saslauthd needs to run in there too or Postfix can't see saslauthd's socket.  This is mentioned in a comment in /etc/default/saslauthd, and in /usr/share/doc/sasl2-bin/README.Debian but they don't really tell you what's going on.

mkdir -p /var/spool/postfix/var/run/saslauthd
dpkg-statoverride --add root sasl 710 /var/spool/postfix/var/run/saslauthd

Now it is time to create a credential for sending mail.  In SASL, that's a domain name, which is known as a "realm," a user name, and a password.  Let's say example.net, joeblow, and TheSecret.

saslpasswd2 -c -u example.net joeblow

You'll be prompted for that password.  This will cause a file /etc/sasl2db to appear.  Tell your user joeblow that his login name for the SMTP server is joeblow@example.net.  I suppose this looks like an email address so you can make it the same as his address and there's less to remember.

Now, Postfix.  Create a directory sasl in your postfix configuration directory, probably /etc/postfix.  Create a file in there called smtpd.conf.  Its contents are

pwcheck_method: saslauthd
mech_list: digest-md5 cram-md5

Now edit your master.cf file.  This file tells the master postfix daemon what daemons to run, whether to chroot them, and what arguments to give them.  Each line begins in column 1 with a word which could be a port number, or the name of that port number in the file /etc/services, or something you defined in main.cf.  The SMTP daemon we will use to accept mail for relaying is called a submission daemon, and generally belongs on port 587.  I added this "line" to master.cf:

submission inet n       -       n       -       -       smtpd
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_sasl_security_options=noanonymous
  -o smtpd_sasl_local_domain=myhost.example.org
  -o header_checks=
  -o body_checks=
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject_unauth_destination
  -o smtpd_sasl_security_options=noanonymous,noplaintext
 
-o smtpd_sasl_tls_security_options=noanonymous

The last two options define a policy where you'll only accept the encrypted authentications options unless the whole session uses TLS.  That gives your users the choice of encrypting the session or not.  If you're sure they all have modern TLS-capable clients, force them to use TLS with this:

    -o smtpd_tls_auth_only

Leading space in master.cf means the previous line continues.  This says to run an instance of Postfix smtpd listening to port 587, and don't chroot it.  It will start with the many compiled-in Postfix configuration defaults, then read any which apply to smtpd from main.cf, and then read the options given here.  Notice there are no spaces around the equals signs.  Notice I had to override the header and body checks.  I've got about a hundred regular expressions that stop a lot of junk before it gets to amavis-new.  I don't want outbound mail checked against those regexes.

Now start saslauthd and restart Postfix

/etc/init.d/saslauthd start
postfix reload

And check your new submission daemon to see what it offers.

$ telnet myhost.example.net submission
Trying 192.168.220.136...
Connected to myhost.example.net.
Escape character is '^]'.
220 myhost.example.net ESMTP Postfix
ehlo myclient.example.net
250-myhost.example.net
250-PIPELINING
250-SIZE 10000000
250-ETRN
250-AUTH NTLM LOGIN PLAIN DIGEST-MD5 CRAM-MD5
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
quit
221 2.0.0 Bye
Connection closed by foreign host.
$

Now open your email client and set up an outbound SMTP server that requires DIGEST-MD5 authentication.  Give it the user@realm and password in the sasl2db file.  Try sending through it.  Tail your postfix log while you're doing that.  On Debian that's

tail -f /var/log/mail.log

mm

Part 2.  Transport Layer Security TLS

First you need a Certificate from an Authority.  If you're an ISP, you need to buy one from a company who has managed to get its Root Certificate compiled into popular clients like Apple Mail and Outlook Express.  If you're just a do-it-yourselfer, you can be your own Certificate Authority and sign your own postfix Certificate.  The clients will complain (with scary popups) to your users until they figure out how to import your Certificate into their clients.

Here is how I make a self-signed Root Certificate.  You will need a secret passphrase.  Write it down.  Be able to type it twice without looking.  This recipe is from The Book of Postfix, except they generate their certs in /usr/local/ssl/misc and I do it in root's home directory.  Root's a user and I back him up.

mkdir /root/ssl
cd /root/ssl
locate CA.pl
/usr/lib/ssl/misc/CA.pl -newca
(answer the questions)
chmod go-rw demoCA/private/cakey.pem

That creates a directory /root/ssl/demoCA containing cacert.pem and private/cakey.pem.  Make sure nobody but root can read cakey.pem.

Now that you have a self-signed Root Certificate, you can generate a postfix certificate from it.

openssl req -new -nodes -keyout postfix_private_key.pem \
    -out postfix_private_key.pem -days 3650


and answer the questions.  That creates a file postfix_private_key.pem.  Finally we need to sign it.  You'll need that passphrase to validate your self-signed root cert:

openssl ca -policy policy_anything -out postfix_public_cert.pem \
  -infiles postfix_private_key.pem


and you answer the questions.  You'll need the passphrase you entered for your self-signed root cert.  That causes postfix_public_cert.pem to appear.  Now make a directory and copy them there.

chmod go-rw postfix_private_key.pem
mkdir /etc/postfix/certs

cp -p demoCA/private/cakey.pem /etc/postfix/certs
cp -p demoCA/cacert.pem /etc/postfix/certs
cp -p postfix_private_key.pem postfix_public_cert.pem /etc/postfix/certs

Now all the certificate files are in one place.  The next step is to verify that your Postfix installation was built with TLS.  If it was, then

postconf -d | grep tls

will show about 92 defaults with tls in their names.  If it was built with TLS, you're ready to configure Postfix.  (If not, you have to either find the version that was, in your GNU+X+Linux distribution, or compile a brand new Postfix from source code.  That's not very hard, because Postfix is a very well written work.  You can do it from the instructions that come with it.)  Edit master.cf and add an option to that submission line.

  -o smtpd_use_tls=yes

And edit main.cf, telling Postfix where the certs are.

smtpd_tls_key_file = /etc/postfix/certs/postfix_private_key.pem
smtpd_tls_cert_file = /etc/postfix/certs/postfix_public_cert.pem

/* is this true?
But that's not enough.  You need a collection of Certificate Authority Certificates so Postfix will be able to verify the certs presented by the clients.  Your operating system distribution probably came with Apache with mod_ssl, and for that to work it needs a CA cert collection.  Debian packages that collection separately.  So we'll use that.  Not only will it save you the trouble of collecting them yourself, but it will update them as long as your release's security support lasts.

apt-get install ca-certificates

Debian's installer will ask whether you want to trust all of them, or none, or some.  The answer to that is outside the scope of these notes.  Now you should have a bunch of .crt and .pem files in /usr/share/ca-certificates and some symlinks in /etc/ssl/certs pointing at them.
 */

Tell Postfix where to find that self-signed root cert, and where to look for more if it's not enough.  While you're at it, tell it what device node to read random numbers from.  Edit main.cf and add this:

smtpd_tls_CAfile = /root/ssl/cacert.pem
smtpd_tls_CApath = /etc/ssl/certs
tls_random_source = /dev/urandom

To verify that random generator works do this shell command:

dd if=/dev/urandom count=1 | od -x

You should get three lines of chatter from dd, and 32 lines of random hex numbers, and no errors.  At this point certs are ready so add this option to the submission line in master.cf.

  -o smtpd_use_tls=yes

Restart Postfix and test.



Part 3.  Things you should already have taken care of in Postfix.

You're not an open relay, because your smtpd_mumble_restrictions begins with permit_mynetworks and ends with reject.  Right?

You figured out why the postmaster address gets spam you would have blocked for any other user, right?  It's in the source code.
grep -n -B1 -A5 'XX 2821' src/smtpd/smtpd_check.c
It's otherwise not explicitly documented.  It was implicitly documented by the statement that Postfix complies with RFC2821.  That RFC defines two special recipients: postmaster@example.net and <>.  You're required to accept email for those, and Postfix enforces that whether you like it or not.