Postfix and SASL

Motivation

Postfix and SMTP authentication can be a very time intensive issue, I had to cope with twice. The second time I had to reread all the documentation, because there were almost 5 years between the first and second attempt. This is what motivated me to write this documentation.

Introduction

Given a running postfix mail server, we do not want unauthorized users to send mails through our server, which is why we need authentication mechanisms to protect the mail server. Within the mail server context, SASL (Simple Authentication and Security Layer) has become a quasi standard. This framework is defined in RFC4422. There are several SASL implementations (e.g. the cyrus SASL implementation). Since setting up SASL for Postfix can be a really huge pain in the a**, this document should help you getting over most of the traps and issues.

There are two variants that I will address:

  1. Postfix – SASL – PAM – MySQL (the hard one)
  2. Postfix – Dovecot – MySQL

Overview

postfix_sasl

Preconditions

This document has the following preconditions:

  • Installed debian wheezy. On other OSes, some of the config files might be in different locations
  • Installed and preconfigured postfix (running with basic operation)
  • Postfix running in a chrooted environment
  • Installed and configured MySQL server with databases and tables for virtual mailboxing

Packages

  1. V1: libsasl2-2, sasl2-bin, libpam-mysql
  2. V2: dovecot-common, dovecot-mysql

Configuration (V1)

The combination of Postfix, Cyrus, PAM and MySQL really is a pain in the a**, took me several days to setup. Even the fact that it finally worked did not really satisfy me, because it felt very unstable in the end. The reason for that is that so many parts are part of the whole process. I will start from bottom up.

MySQL

Nothing special about the actual MySQL server configuration.

Concerning ERM and structure, I have a user called postfix and a database called postfix.

You need to have at least a table which contains a field for the user (i.e. user@domain.tld) and the associated password.

PAM

Pluggable Authentication Modules (PAM) on Linux provide common authentication mechanisms on Linux. One of those modules is a MySQL-Module (pam_mysql.so), which we will use to fetch credentials from a database.

Edit the file /etc/pam.d/smtp as follows:

auth required pam_mysql.so user=postfix passwd=postfixpassword host=127.0.0.1 db=postfix table=mailbox usercolumn=username passwdcolumn=password crypt=3
 
account sufficient pam_mysql.so user=postfix passwd=postfixpassword host=127.0.0.1 db=postfix table=mailbox usercolumn=username passwdcolumn=password crypt=3

Note that there are the two fields username and password I mentioned earlier. Replace the postfixpassword with your actual MySQL password for the user postfix.
The parameter crypt is somewhat crutial, because it describes how passwords are stored in the database.
According to the documentation of PAM-MySQL, the following values are available:

  • 0 (or “plain”) = No encryption. Passwords stored in plaintext. HIGHLY DISCOURAGED.
  • 1 (or “Y”) = Use crypt(3) function
  • 2 (or “mysql”) = Use MySQL PASSWORD() function. It is possible that the encryption function used by pam-mysql is different from that of the MySQL server, as pam-mysql uses the function defined in MySQL’s C-client API instead of using PASSWORD() SQL function in the query.
  • 3 (or “md5”) = Use MySQL MD5() function

SASL Auth Daemon

You may wonder, why the hell do we need PAM to do all the database queries, saslauthd has a thing called auxprop, which already provides SQL mechanisms.
Well, that’s right, but unfortunately, those mechanisms require passwords to be stored in plaintext, which I highly disadvise (see this link).
Edit the file /etc/default/saslauthd:

START=yes
MECHANISMS="pam"
OPTIONS="-c -r -m /var/spool/postfix/var/run/saslauthd"

Note the long path /var/spool/postfix/var/run/saslauthd. This is necessary, if postfix is running in a chroot environment (which is the default for debian). To check, if your postfix is running chrooted, have a look at /etc/postfix/master.cf. There should be a line like the following:

smtp      inet  n       -       -       -       -       smtpd

We need to create the socket directory for saslauthd within the chroot environment:

# mkdir -p /var/spool/postfix/var/run/saslauthd

Unfortunately, other tools (e.g. testsaslauthd) will expect the saslauth socket to reside in /var/run/saslauthd, which is why we need to redirect this via symlink:

# rm -rf /var/run/saslauthd
# ln -s /var/spool/postfix/var/run/saslauthd /var/run/saslauthd

There are some permissions that need to be changed:

# chgrp sasl /etc/pam.d/smtp
# chmod 640 /etc/pam.d/smtp

At this point, we shall be able to test the saslauthd:

# /etc/init.d/saslauthd restart
# testsaslauthd -s smtp -u user@domain.tld -p newpassword
0: OK "Success."

If this doesn’t work, it is most probably because some permissions are not right. Check the permissions of the socket /var/spool/postfix/var/run/saslauthd.

Postfix

The SASL implementation that postfix will use can be altered with the parameter smtpd_sasl_type. There are two available values to that parameter: cyrus and dovecot. For now, we will keep focusing on the cyrus variant.
Cyrus SASL will place a configuration file in the postfix configuration.
Edit /etc/postfix/sasl/smtpd:

pwcheck_method: saslauthd
mech_list: PLAIN LOGIN
log_level: 5

According to this documentation, no other mechanisms than PLAIN and LOGIN are available when using saslauthd.
Again, there are some permissions that need to be fixed. The postfix user needs to be member of the sasl group:

# adduser postfix sasl
# chgrp postfix /etc/postfix/sasl/smtpd.conf
# chmod 640 /etc/postfix/sasl/smtpd.conf

Now, all that postfix needs to know, is that users which are authenticated via SASL are allowed to send mails.
To do this, edit the postfix main config /etc/postfix/main.cf:

smtpd_sasl_auth_enable = yes
broken_sasl_auth_clients = yes
smtpd_sender_restrictions = 
    ...
    permit_sasl_authenticated
    ...

Finally, restart postfix:

# /etc/init.d/postfix restart

Configuration (V2)

Dovecot made my life so much easier. The online documentation as well as the configuration structure is very well and intuitive.
I had to install dovecot anyway, because I wanted to use it as IMAP server.

Postfix

We need to tell postfix to use dovecot as SASL implementation. So edit the file /etc/postfix/main.cf as follows:

smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth

Dovecot

First of all, we need to enable the MySQL authentication module of dovecot by editing the file /etc/dovecot/conf.d/10-auth.conf:

!include auth-sql.conf.ext

Now, we will have to create/enable a smtp authentication service in /etc/dovecot/conf.d/10-master.conf:

service auth {
# Postfix smtp-auth
  unix_listener /var/spool/postfix/private/auth {
    mode = 0660
    user = postfix
    group = postfix
  }
}

You might recognize the part private/auth, this is the relative path to the socket that dovecot will create for postfix.
We need to tell dovecot, where to receive the passwords from. To do that, edit the file /etc/dovecot/dovecot-sql.conf.ext:

connect = host=/var/run/mysqld/mysqld.sock dbname=postfix user=postfix password=postfixpassword
...
default_pass_scheme = CRAM-MD5
...
password_query = \
  SELECT username, domain, password \
  FROM mailbox WHERE username = '%u' AND domain = '%d'
...
user_query = \
  SELECT username, password, 5000 as uid, 5000 as gid, '/home/vmail/%d/%n' as home \
  FROM mailbox WHERE username='%u'

You might want to change some of those SQL queries to fit your personal requirements, especially if you have a more or less complex database/table structure.

A note on password storage

For me, password storage in this context turned out to be a very complex topic, although storing a simple MD5/SHA1 hash in a database is technically a very simple task to do. Personally, I do not want to store passwords of users in plain text in any kind of file or database on my server, even if TLS/SSL is used. For the V1 method, I used to store the passwords with the MD5() function of MySQL, which used to work fine. Unfortunately, this doesn’t work with the V2 method. A method that worked for me is to store the actual CRAM-MD5 hash, generated by a dovecot binary called doveadm:

# doveadm pw
Enter new password: 
Retype new password: 
{CRAM-MD5}a7cb902940b3f6662c48ace840a4e3e410241e875d720cb45b2d95a3e1ddfc8b

The downside of that method is that these hashes will most likely not work with other MTAs.