Postfix as a SASL authenticated, TLS enabled Relay

Postfix is a well established, open source mail transfer agent (MTA) that routes and delivers email. One of its main strengths compared to other MTAs like Sendmail, is its ease of operation and fairly straight forward, plain text configuration.

Postfix routes mail only from clients in trusted networks, from clients that have authenticated with SASL, or to domains that are configured as authorized relay destinations.[1]

In this post we are going to configure Postfix as a SMTP relay to Mailgun's edge SMTP API (although this will work to any other SMTP endpoint, given the correct credentials) using Simple Authentication Security Layer (SASL) on Ubuntu Linux.

First, let's install some prerequisites and the postfix server:


File: gistfile1.txt
-------------------

$ apt install libsasl2-modules sasl2-bin postfix

To verify what SASL plugins the Postfix installation supports, run:


File: gistfile1.txt
-------------------

$ postconf -a
cyrus
dovecot
$ postconf -A
cyrus

From the above output we can see that the server side of Postfix supports both Cyrus and Dovecot SASL, where the client side supports only Cyrus SASL, at least in this prepackaged version of Postfix.

There are two important configuration files that drive the Postfix server - master.cf and main.cf.

The master.cf configures all of Postfix subsystems like smtpd, the queue, relay, cleaned etc. This is where we specify if the Postfix subsystem will run in a jailed environments, if we desire verbose logging and on what ports (besides port 25) we would like the SMTP server to listen to.

Here's an example showing SMTP running in a chroot jail using verbose logging and listening on port 25 AND 2525:


File: gistfile1.txt
-------------------

$ head -13 /etc/postfix/master.cf
#
# Postfix master process configuration file.  For details on the format
# of the file, see the master(5) manual page (command: "man 5 master" or
# on-line: http://www.postfix.org/master.5.html).
#
# Do not forget to execute "postfix reload" after editing this file.
#
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (no)    (never) (100)
# ==========================================================================
smtp      inet  n       -       y       -       1000       smtpd -v
2525      inet  n       -       y       -       1000       smtpd

The main.cf configuration file specifies a small subset of all configuration options that control most of Postfix . Parameters not explicitly specified are left at their default values [2].

Bellow is a working configuration of Postfix as a Relay, using TLS and SASL for authentication, with some tuning parameters as an example:


File: gistfile1.txt
-------------------

$ cat /etc/postfix/main.cf

# General
smtpd_banner = SMTP at Your Company
biff = no
append_dot_mydomain = no
readme_directory = no

# TLS parameters
smtpd_tls_cert_file=/etc/ssl/certs/yourcompany.org.crt
smtpd_tls_key_file=/etc/ssl/certs/yourcompany.org.key
smtpd_use_tls=yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# Server
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = postfix-server.yourcompany.org
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
mydestination = $myhostname, postfix-server.yourcompany.org, localhost.yourcompany.org, $mydomain, localhost
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = ipv4

# Client
relayhost = [smtp.mailgun.org]
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_sasl_tls_security_options = noanonymous
smtpd_tls_auth_only = no
header_size_limit = 4096000
broken_sasl_auth_clients = yes
smtpd_sasl_auth_enable = yes

Please see [2] for detailed explanation on what all of the above configuration options do, but for the purpose of this post let's focus on the Client side of the config, responsible for authenticating Postfix with the upstream SMTP server to which it will relay mail.

First, we specify the upstream server we want our Postfix server to relay to (line 30). Next, we enable SASL and specify the credential file that will be used to pass to the relay host (lines 31 and 32).

The plain text sasl_passwd file looks like the following:


File: sasl_passwd
-----------------

$ cat /etc/postfix/sasl_passwd
[smtp.mailgun.org] user@yourdomain.org:credentials_for_domain_from_mailgun
[smtp.other.org] user2@yourdomain.org:credentials_for_user_2
[smtp1.other.org] user2@yourdomain.org:credentials_for_user_2

The first column contains the destination SMTP server to which Postfix will relay mail to. The second column specifies the credentials for the user domain (e.g. user@yourdomain.org).

To generate a hashed version of the file run:


File: gistfile1.txt
-------------------

$ chmod 600 /etc/postfix/sasl_passwd
$ postmap /etc/postfix/sasl_passwd
$ ls -la /etc/postfix/sasl_passwd*
-rw------- 1 root root   362 Mar 19 21:19 /etc/postfix/sasl_passwd
-rw------- 1 root root 12288 Mar 19 21:23 /etc/postfix/sasl_passwd.db

The file should be secured and only read by root (Postfix reads that file before it chroots and drops privileges).

Next, let's configure SASL on the server side of our Postfix installation - this will be authenticating SMTP request coming from the sending clients.

SASL is implemented separately from Postfix. Postfix uses client libraries to communicate with SASL enabled backends (Cyrus SASL's libsasl). There are various SASL implementations like Cyrus SASL and Dovecot, authentication backends and mechanisms, some requiring just a static file, others LDAP, or database access, and yet others connecting to a SASL password verification service, such as saslauthd.

To keep things simple we are going to use the Cyrus SASL with the sasldb auxiliary property plugin (auxprop), which is a static file on disk that does not require the use of the saslauthd service [3].

The configuration for the plugin looks like the following:


File: smtpd.conf
----------------

$ cat /etc/postfix/sasl/smtpd.conf
pwcheck_method: auxprop
auxprop_plugin: sasldb
mech_list: PLAIN LOGIN

The third line specifies the authentication mechanisms that the plugin will expose and allow.

Next, we need to create the credentials for a client that will be allowed to connect to the Postfix server:


File: sasldb2
-------------

$ saslpasswd2 -c -u yourdomain.org user
$ sasldblistusers2
user@yourdomain.org: userPassword
$ cp /etc/sasldb2 /var/spool/postfix/etc/
$ chown postfix:sasl /var/spool/postfix/etc/sasldb2
$ chmod 660 /var/spool/postfix/etc/sasldb2

Because the packaged Postfix on Ubuntu runs in a chroot environment, we need to copy the password database so that Postfix can read it and adjust permissions (lines 5 and 6) and restart Postfix.

To test, connect to Postfix using telnet, netcat, swaks, or your favorite tool, and you should be able to see the enabled authentication methods:


File: gistfile1.txt
-------------------

./swaks --auth --server postfix-server.yourcompany.org --port 25 --au user@yourdomain.org --ap password_token_or_api_key --from someuser@yourcompany.org --to otheruser@mailgun.com --h-Subject: "Testing SMTP relay" --body 'Testing Postfix SASL relay'
=== Trying postfix-server.yourcompany.org:25...
=== Connected to postfix-server.yourcompany.org.
<-  220 SMTP at Your Company
 -> EHLO user
<-  250-AUTH PLAIN LOGIN
<-  250-SIZE 52428800
<-  250-8BITMIME
<-  250-ENHANCEDSTATUSCODES
<-  250-SMTPUTF8
<-  250 STARTTLS
 -> AUTH LOGIN
<-  334 VXNlcm5hbWU6
 -> a29uc3RsdfhbnRpbkBwbdfsf3N0Z3VusdfLmNvbQ==
<-  334 UGFzc3dvcmQ6
 -> ZWI2M2Y0sdNzc1ZTRkYzZjMWUsdf2YWUwYjhkNDZhZWI2ZmYtYjljMTVmNGMtOffDgwNTk5NDE=
<-  235 2.0.0 OK
 -> MAIL FROM:<someuser@yourcompany.org>
<-  250 Sender address accepted
 -> RCPT TO:<otheruser@mailgun.com>
<-  250 Recipient address accepted
 -> DATA
<-  354 Continue
 -> Date: Thu, 19 Mar 2020 15:55:33 -0500
 -> To: otheruser@mailgun.com
 -> From: someuser@yourcompany.org
 -> Subject: Testing SMTP relay
 -> X-Mailer: swaks v20130209.0 jetmore.org/john/code/swaks/
 ->
 -> Testing Postfix SASL relay
 ->
 -> .
<-  250 Great success
 -> QUIT
<-  221 See you later.
=== Connection closed with remote host.

This minimal setup should be enough to create a TLS, SASL enabled Postfix relay.

If you prefer to use more scalable authentication backend such as LDAP or Postgres, you can use many of the available auxprop plugins, for example:


File: saslauthd
---------------

$ cat /etc/postfix/sasl/smtpd.conf
pwcheck_method: auxprop
auxprop_plugin: sql
mech_list: PLAIN LOGIN CRAM-MD5 DIGEST-MD5 NTLM
sql_engine: pgsql
sql_hostnames: 127.0.0.1, 192.0.2.1
sql_user: username
sql_passwd: secret
sql_database: dbname
sql_select: SELECT password FROM users WHERE user = '%u@%r'

To leverage the saslauthd Cyrus SASL password verification service, we need the following configuration:


File: saslauthd
---------------

$ cat /etc/default/saslauthd
START=yes
PWDIR="/var/spool/postfix/var/run/saslauthd"
PARAMS="-m ${PWDIR}"
PIDFILE="${PWDIR}/saslauthd.pid"
DESC="SASL Authentication Daemon"
NAME="saslauthd"
MECHANISMS="sasldb"
MECH_OPTIONS=""
THREADS=5
OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd"

In the above configuration the saslauthd process will use the sasldb file on disk as its authentication mechanism. In this case communication between the Postfix SMTP server - the Cyrus SASL's libsasl - and the saslauthd server happens over a UNIX-domain socket.

On Ubuntu, due to the fact that Postfix runs in a chroot environment we need to ensure it can talk to the saslauthd service:


File: gistfile1.txt
-------------------

$ dpkg-statoverride --force --update --add root sasl 755 /var/spool/postfix/var/run/saslauthd
$ ln -s /etc/default/saslauthd /etc/saslauthd
$ systemctl start saslauthd
$ systemctl status saslauthd
● saslauthd.service - LSB: saslauthd startup script
   Loaded: loaded (/etc/init.d/saslauthd; bad; vendor preset: enabled)
   Active: active (running) since Wed 2020-03-18 19:31:33 UTC; 4 days ago
     Docs: man:systemd-sysv-generator(8)
  Process: 1333 ExecStart=/etc/init.d/saslauthd start (code=exited, status=0/SUCCESS)
    Tasks: 5
   Memory: 5.0M
      CPU: 30ms
   CGroup: /system.slice/saslauthd.service
           ├─1463 /usr/sbin/saslauthd -a sasldb -c -m /var/spool/postfix/var/run/saslauthd -n 5
           ├─1464 /usr/sbin/saslauthd -a sasldb -c -m /var/spool/postfix/var/run/saslauthd -n 5
           ├─1465 /usr/sbin/saslauthd -a sasldb -c -m /var/spool/postfix/var/run/saslauthd -n 5
           ├─1466 /usr/sbin/saslauthd -a sasldb -c -m /var/spool/postfix/var/run/saslauthd -n 5
           └─1467 /usr/sbin/saslauthd -a sasldb -c -m /var/spool/postfix/var/run/saslauthd -n 5

Mar 18 19:31:33 postfix-server systemd[1]: Starting LSB: saslauthd startup script...
Mar 18 19:31:33 postfix-server saslauthd[1333]:  * Starting SASL Authentication Daemon saslauthd
Mar 18 19:31:33 postfix-server saslauthd[1463]: detach_tty      : master pid is: 1463
Mar 18 19:31:33 postfix-server saslauthd[1463]: ipc_init        : listening on socket: /var/spool/postfix/var/run/saslauthd/mux
Mar 18 19:31:33 postfix-server saslauthd[1333]:    ...done.
Mar 18 19:31:33 postfix-server systemd[1]: Started LSB: saslauthd startup script.
$

To examine the complete configuration, run:


File: saslfinger
----------------

$ saslfinger -s
saslfinger - postfix Cyrus sasl configuration Mon Mar 23 16:23:13 UTC 2020
version: 1.0.4
mode: server-side SMTP AUTH

-- basics --
Postfix: 3.1.0
System: Ubuntu 16.04.3 LTS \n \l

-- smtpd is linked to --
 libsasl2.so.2 => /usr/lib/x86_64-linux-gnu/libsasl2.so.2 (0x00007f3c9db22000)

-- active SMTP AUTH and TLS parameters for smtpd --
broken_sasl_auth_clients = yes
smtpd_sasl_auth_enable = yes
smtpd_tls_auth_only = no
smtpd_tls_cert_file = /etc/ssl/certs/yourcompany.org.crt
smtpd_tls_key_file = /etc/ssl/certs/yourcompany.org.key
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtpd_use_tls = yes

-- listing of /usr/lib/sasl2 --
total 16
drwxr-xr-x  2 root root 4096 Mar 17 14:17 .
drwxr-xr-x 71 root root 4096 Mar 18 06:52 ..
-rw-r--r--  1 root root    4 Mar 17 14:17 berkeley_db.active
-rw-r--r--  1 root root    4 Jan 28 11:32 berkeley_db.txt

-- listing of /etc/postfix/sasl --
total 12
drwxr-xr-x 2 root root 4096 Mar 18 13:58 .
drwxr-xr-x 3 root root 4096 Mar 23 14:16 ..
-rw-r--r-- 1 root root   70 Mar 18 13:58 smtpd.conf

-- content of /etc/postfix/sasl/smtpd.conf --
pwcheck_method: auxprop
auxprop_plugin: sasldb
mech_list: PLAIN LOGIN

-- active services in /etc/postfix/master.cf --
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (no)    (never) (100)
smtp      inet  n       -       y       -       1000       smtpd -v
2525      inet  n       -       y       -       1000       smtpd
pickup    unix  n       -       y       60      1       pickup
cleanup   unix  n       -       y       -       0       cleanup
qmgr      unix  n       -       n       300     1       qmgr
tlsmgr    unix  -       -       y       1000?   1       tlsmgr
rewrite   unix  -       -       y       -       -       trivial-rewrite
bounce    unix  -       -       y       -       0       bounce
defer     unix  -       -       y       -       0       bounce
trace     unix  -       -       y       -       0       bounce
verify    unix  -       -       y       -       1       verify
flush     unix  n       -       y       1000?   0       flush
proxymap  unix  -       -       n       -       -       proxymap
proxywrite unix -       -       n       -       1       proxymap
smtp      unix  -       -       y       -       -       smtp
relay     unix  -       -       y       -       -       smtp
showq     unix  n       -       y       -       -       showq
error     unix  -       -       y       -       -       error
retry     unix  -       -       y       -       -       error
discard   unix  -       -       y       -       -       discard
local     unix  -       n       n       -       -       local
virtual   unix  -       n       n       -       -       virtual
lmtp      unix  -       -       y       -       -       lmtp
anvil     unix  -       -       y       -       1       anvil
scache    unix  -       -       y       -       1       scache
maildrop  unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
uucp      unix  -       n       n       -       -       pipe
  flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
ifmail    unix  -       n       n       -       -       pipe
  flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp     unix  -       n       n       -       -       pipe
  flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
scalemail-backend unix - n n - 2 pipe
  flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
mailman   unix  -       n       n       -       -       pipe
  flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
  ${nexthop} ${user}

-- mechanisms on localhost --
250-AUTH PLAIN LOGIN
250-AUTH=PLAIN LOGIN

-- end of saslfinger output --

[1]. http://www.postfix.org/SMTPD_ACCESS_README.html
[2]. http://www.postfix.org/postconf.5.html
[3]. https://www.cyrusimap.org/sasl/sasl/developer/plugprog.html#auxiliary-property-auxprop-plugins