Debian Postfix SASL Configuration HOWTO

Written by Bob Proulx <bob@proulx.com>

License

Copyright (C) 2008, 2009, 2013 Bob Proulx

This document is free; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This document is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You can get a copy of the GNU GPL at at <http://www.gnu.org/copyleft/gpl.html>.

The purpose of this document

Setting up two systems to use authenticated email transfer is very useful but not immediately intuitive. The purpose of this document is to describe the steps needed to install and configure SASL.

Installing Required Packages

Install these packages:

  $ sudo apt-get install libsasl2-2 libsasl2-modules sasl2-bin

Creating a Password

There are many different possible ways to configure SMTP AUTH SASL to authenticate. (One such alternative is using the saslauthd.) The way that I think is simplest is to use the default /etc/sasldb2 and simply set up the accounts. Pick an account name for your client to use to authenticate. Pick a password. Since only the machines are going to be using it makes sense to make it long. I like using pwgen to generate random passwords.

  $ pwgen -snc 32 1
  o79fIklxU3USPiTGDQ3L5Wfd8oz2wr0H

Generating 32 characters of random data should provide a good strong password. This is a password used between the machines and not a password that is ever typed in by a human.

Server Configuration

Obviously I am already using my laptop with this configuration and therefore I have already done the setup on the server. But here are the step by step items needed to set up the server end of the connection.

On the server set up the /etc/sasldb2 file with an account and password. Here is the synopsis form of the command:

  sudo saslpasswd2 -c -u SERVER-FQDN REMOTE-ACCOUNTNAME

Where:

  -c create an entry for the user
  -u domain / realm and should be the local FQDN

The examples in the previously stated references generally use 'postconf -h myhostname' to get the fully qualified domain name of the server. In my case this is joseki.proulx.com but for generality I will continue the tradition of using the postconf output.

I chose to use the hostname "discord" for the account name that the laptop uses to connect. I cut and pasted in the randomly generated password that was created just a moment ago.

  $ postconf -h myhostname
  joseki.proulx.com

  $ sudo saslpasswd2 -c -u `postconf -h myhostname` mfal6
  Password:
  Again (for verification):

The result (but not the password) can be seen with sasldblistusers2:

  $ sudo sasldblistusers2
  mfal6@joseki.proulx.com: userPassword

Hint: You can delete the account with the saslpasswd2 -d option.

At this time the /etc/sasldb2 file should be group owned by the sasl group.

  $ ls -l /etc/sasldb2
  -rw-rw---- 1 root sasl 12288 Oct 11 07:01 /etc/sasldb2

To enable postfix to read this file you must add postfix to the "sasl" group. The adduser command takes the username to be operated upon as the first program argument and the group to add to it as the second argument.

  $ sudo adduser postfix sasl

On server edit /etc/postfix/master.cf and add submission as a copy of smtp. This causes postfix to listen on port 587. The "submission" port 587 shouldn't be blocked by ISPs trying to block virus spam. It is used only between two previously configured systems to submit email.

  smtp      inet  n       -       -       -       -       smtpd
  submission inet n       -       -       -       -       smtpd

On server edit /etc/postfix/main.cf and enable sasl with:

  smtpd_sasl_auth_enable = yes
  smtpd_sasl_authenticated_header = yes

And assuming that you are using Postfix address masquerading then you need the following too to allow SASL clients to be hidden. Otherwise their localhost hostnames bleed through.

  local_header_rewrite_clients = permit_inet_interfaces,
          permit_sasl_authenticated

I haven't decided if I want to continue with using configuration smtpd_sasl_authenticated_header = yes or not. It leaves the login account in the headers. This probably isn't a big deal for me so I have left it there so far because it is somewhat useful. But eventually I will probably remove it since it does provide information to the outside world that a) isn't necessary b) may be confusing and c) may provide an attacker with local information.

Add permit_sasl_authenticated to smtpd_recipient_restrictions:

  smtpd_recipient_restrictions =
          permit_mynetworks,
          permit_sasl_authenticated,
          reject_unauth_destination

Lastly disable plain text and anonymous logins. By default Postfix already prevents anonymous logins. Additionally we want to prevent plaintext and so add the following list.

  smtpd_sasl_security_options = noplaintext, noanonymous

The default client configuration that we are going to set up will list noplaintext and noanonymous and so the client will avoid using those methods, avoid sending passwords in the clear, and will fall through to the secure DIGEST-MD5 authentication mode. But it is good if both sides do this.

The default Debian configuration for Postfix configures it to run in a chroot for added security. At /etc/init.d/postfix time it copies various files from outside the chroot to inside it. It will also need access to the /etc/sasldb2 file that has just been created. Add it to the list. This next description of the action you need to take is a little hacky because the /etc/init.d/postfix file needs to be edited and currently edited by hand. This really needs to be fixed in the Debian package but unfortunately won't make it in for Lenny. Oh well. Edit carefully. Compare with my copy on joseki if you want to be sure.

Add etc/sasldb2 to FILES in /etc/init.d/postfix:
  -                FILES="etc/localtime etc/services etc/resolv.conf etc/hosts \
  +                FILES="etc/sasldb2 etc/localtime etc/services etc/resolv.conf etc/hosts \

Then there is another important little hack that needs to be done to the /etc/init.d/postfix file. It copies this into the postfix chroot. Then the old code explicitly makes the files readable (with "chmod a+rX") but the sasl file has passwords so you don't want to have that readable. Hopefully this will get changed upstream but currently this needs the following modification. Assume that the original permissions are okay and keep them.

-                    if [ -f /${file} ]; then rm -f ${file} && cp /${file} ${file}; fi
-                    if [ -f  ${file} ]; then chmod a+rX ${file}; fi
+                    if [ -f /${file} ]; then rm -f ${file} && cp -p /${file} ${file}; fi

Then restart postfix through the newly modified init.d script to cause the script to copy the sasldb2 file into the chroot properly and to restart the postfix which will be in the sasl group which was also just added to it.

  $ sudo service postfix restart

Check the configuration:

  $ connect mail.proulx.com 587
  220 joseki.proulx.com ESMTP Postfix (Debian/GNU)
  EHLO dementia.proulx.com
  250-joseki.proulx.com
  250-PIPELINING
  250-SIZE 10240000
  250-ETRN
  250-AUTH DIGEST-MD5 NTLM CRAM-MD5 LOGIN
  250-ENHANCEDSTATUSCODES
  250-8BITMIME
  250 DSN
  QUIT
  221 2.0.0 Bye

Verify that the "AUTH" line exists and includes "DIGEST-MD5" as one of the options. Verify that "PLAIN" is not allowed.

Client Configuration

The client needs to be configured to route mail to the relay host. The "transport" table configures custom mail routes. If the destination matches the left hand side then perform the action on the right hand side. If the right hand side is empty then use the default action. The default action only needs to be overridden when routing mail to the "submission" port on the mail relay host.

Set Up The Transport File

On the client host add routes in /etc/postfix/transport to route mail to the server.


  localhost               :
  mfal7                   :
  mfal7.jerslash.net      :
  *                       :[mail.proulx.com]:submission

Update the hash. This creates the transport.db file that is used by Postfix.


  $ sudo postmap hash:/etc/postfix/transport

Update: The transport isn't needed. It might never have been needed or things may have changed so that it isn't needed now. In any case only the relayhost needs to be set. Only non-local mail is sent to relayhost. The transport map was previously used to avoid sending local mail to the relayhost. But now since local mail isn't delivered to relayhost this transport map configuration isn't needed.

  relayhost = [mail.proulx.com]:submission

The Postfix main.cf File

Add to the /etc/postfix/main.cf file:

  smtp_sasl_auth_enable = yes
  smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
  smtpd_sasl_auth_enable = yes
  smtpd_sasl_security_options = noplaintext, noanonymous
  transport_maps = hash:/etc/postfix/transport

The Password Map

The account and password information is stored on the remote client in /etc/postfix/sasl_passwd file. Create that file with the following content using the previously generated passwords:

  mail.proulx.com mfal6:o79fIklxU3USPiTGDQ3L5Wfd8oz2wr0H

Update the hashed database file:

  $ sudo postmap hash:/etc/postfix/sasl_passwd
  $ sudo chmod o-r /etc/postfix/sasl_passwd*

This will leave the files looking like this:

  -rw-r----- 1 root root   128 2008-07-14 02:29 sasl_passwd
  -rw-r----- 1 root root 12288 2008-07-14 02:29 sasl_passwd.db

Masquerading

If you are operating from a host with a dynamic IP address then you very likely have a hostname that isn't publicly known. In which case you want to masquerade all of your hosts as your domain name. That way those private machines won't be exposed and people will get your domain name instead. People can reply to your domain name.

  masquerade_domains = jerslash.net

Restart Postfix

Restart postfix to use this new configuration.

  $ sudo service postfix restart

At this point in the process everything should be set up and everything should be working. You could send a test message and verify that it either succeeds or fails.

Debugging

Send a test email. If it arrives at the right place then good. Look at the headers. If everything looks okay then great. But for me it failed and then I had to debug things to determine the problem. Here are some debugging tips.

While debugging you will assuredly queue up several mail messages that you won't care about. You can view the mail queue with the 'mailq' command.

  $ mailq
  -Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------
  D78ECCA048      310 Sun Nov 22 14:38:17  jer@mfal6.jerslash.net
  (host mail.proulx.com[216.17.153.58] said: 450 4.1.8 <jer@example.com>: Sender address rejected: Domain not found (in reply to RCPT TO command))
                                           bob@example.com

You can delete messages from the Postfix mail queue with the 'postsuper' command.

  $ postsuper -d D78ECCA048
  postsuper: D78ECCA048: removed
  postsuper: Deleted: 1 message

The main debugging technique is to log in manually on the server using the account that we have set up. In my case the failure was not setting up /etc/sasldb2 in the postfix chroot. This part of the process is rather involved to cover completely.

The smtp login uses a base64 encoded password. Using AUTH PLAIN would allow you to log in at the SMTP prompts manually. But you need the encoding. There are many different ways to produce this. The easiest is probably to use the base64 command.

  $ printf "test\0test\0testpass" | base64
  dGVzdAB0ZXN0AHRlc3RwYXNz

Note that for whatever reason the account name is repeated twice with null bytes separating them.

But you might not have base64 on your system. It is available in the Lenny/Testing coreutils but not in Etch or older. If you are running Etch you can snarf a copy from

  joseki.proulx.com:/usr/local/bin/base64

OR you can do this from the postfix docs:

  $ printf '\0username\0password' | mmencode

  $ perl -MMIME::Base64 -e 'print encode_base64("\0username\0password");'

Which says here that the first use of the user name is apparently optional. I don't know. I didn't leave it out in my case.

OR you can use one of these two scripts. Here is a perl script which will do the encoding. Blank lines were removed.

  #!/usr/bin/perl
  use strict;
  use MIME::Base64;
  if ($#ARGV != 1) {
      die "Usage: encode_sasl_plain.pl USERNAME PASSWORD\n";
  }
  print encode_base64("$ARGV[0]\0$ARGV[0]\0$ARGV[1]");
  exit 0;

Same thing as a ruby script. Just because I like ruby.

  #!/usr/bin/env ruby
  require 'base64'
  if ! ARGV[0] || ! ARGV[1]
    $stderr.print "Usage: encode_sasl_plain.pl USERNAME PASSWORD\n"
    exit 1
  end
  puts Base64.encode64("#{ARGV[0]}\0#{ARGV[0]}\0#{ARGV[1]}")
  exit 0

Then using these it works like this:

  $ encode_sasl_plain.pl test testpass
  dGVzdAB0ZXN0AHRlc3RwYXNz

This is shown from the postfix docs:

    $ telnet server.example.com 25
    . . .
    220 server.example.com ESMTP Postfix
    EHLO client.example.com
    250-server.example.com
    250-PIPELINING
    250-SIZE 10240000
    250-ETRN
    250-AUTH PLAIN DIGEST-MD5 NTLM CRAM-MD5 LOGIN
    250-ENHANCEDSTATUSCODES
    250-8BITMIME
    250 DSN
    AUTH PLAIN dGVzdAB0ZXN0AHRlc3RwYXNz
    235 Authentication successful

AUTH PLAIN is offered and so can be used. That would be insecure on the open internet. (Okay for local testing.) But on the remote client we have already set smtp_sasl_security_options = noplaintext, noanonymous in the above configuration. So the client will never use AUTH PLAIN. This keeps your client from being insecure. The next in the list is AUTH DIGEST-MD5 and that is a challenge response form and is safe to use over the open internet. DIGEST-MD5 is what we want it to normally use.

In any case, if you can log in with the above using AUTH PLAIN then things are basically working. If not then hopefully the log files will show the problem. For me I was stuck at this point for a long time until I figured out that /etc/sasldb2 needed to be copied into the postfix chroot at /var/spool/postfix/etc/sasldb2 and from that figured out that it needed to be added to the FILES= list in the startup script.

Resources

A problem with the references is that they spend a lot of time talking about how to build the needed parts from source and make them all work together. But of course on Debian this is already done for us. So while those are good references you must ignore most of them.

  /usr/share/doc/postfix/SASL_README.gz
  http://www.postfix.org/SASL_README.html
  http://postfix.state-of-mind.de/patrick.koetter/smtpauth/