Email 07 - Dovecot authentication
Email Solution · Previous: Active Directory integration · Next: TLS and certificates
Every client of the internal servers has to log in before it may send (requirement FR2), and Postfix cannot check a password by itself. It asks another program. In this system that program is Dovecot, installed on all three internal servers, and on two of them it does nothing else: on the internal pair there is no mailbox to deliver to and no IMAP client to serve, only the question whether a password belongs to an account.
Why Postfix needs a second program
SMTP authentication is defined in terms of SASL, a framework that separates the protocol that needs a login from the mechanism that performs it. The design carries the usual picture as ASCII art; redrawn:
flowchart TB smtp["SMTP"] ldap["LDAP"] xmpp["XMPP"] oth["Other protocols"] sasl["SASL abstraction layer"] ext["EXTERNAL"] gss["GSSAPI"] plain["PLAIN"] om["Other mechanisms"] smtp --> sasl ldap --> sasl xmpp --> sasl oth --> sasl sasl --> ext sasl --> gss sasl --> plain sasl --> om
Postfix implements the left half only. For the mechanisms and for the password check it uses one of two back ends, Cyrus SASL or Dovecot, and the design chose Dovecot. It gives no comparison; the reason that can be read from it is that Dovecot was needed anyway, as delivery agent and IMAP server on the mailbox server, and one configuration could then serve all three hosts. Its three roles in the design are authentication back end for Postfix, delivery to virtual mailboxes, and local IMAP server for the webmail. This Article is about the first; the other two are in Mailbox server.
On the Postfix side the whole integration is four lines of main.cf, the same on the three servers:
smtpd_sasl_type = dovecot smtpd_sasl_path = private/auth smtpd_sasl_auth_enable = yes smtpd_sasl_security_options = noanonymous
noanonymous is the mechanism property the design asks for: no mechanism that lets a client in without an identity. The whole file is a Config document: Postfix main.cf, internal servers.
The socket
Postfix and Dovecot can talk over TCP or over a UNIX-domain socket. The design takes the socket "for better privacy": nothing listens on the network and the file system decides who may connect. Dovecot creates the socket where Postfix looks for it, in 10-master.conf:
service auth {
unix_listener auth-userdb {
mode = 0666
user = vmail
group = vmail
}
unix_listener /var/spool/postfix/private/auth {
mode = 0666
user = postfix
group = postfix
}
}smtpd_sasl_path = private/auth is relative to the Postfix queue directory, /var/spool/postfix. Howtos usually call that directory the Postfix chroot, and a chrooted smtpd could indeed reach nothing outside it; here the chroot column of master.cf is n for every service, so the location is a convention and not a necessity. The mode 0666 is wider than it looks dangerous: the directory private belongs to postfix and is closed to everybody else, which is what really protects the socket. The second listener, auth-userdb, is for the delivery agent on the mailbox server. The whole file: Dovecot 10-master.conf.
A login then runs like this:
sequenceDiagram participant C as SMTP client participant P as Postfix smtpd participant D as Dovecot auth participant A as Domain controller C->>P: STARTTLS on port 25, or TLS from the start on 465 P->>C: AUTH PLAIN LOGIN offered C->>P: AUTH with account name and password P->>D: request over the socket private/auth D->>A: LDAPS bind as vmail_svc, search with pass_filter A->>D: DN of the account D->>A: LDAPS bind as that DN with the password of the client A->>D: bind result D->>P: OK or FAIL P->>C: 235 or 535
PLAIN and LOGIN, and why plaintext is acceptable
10-auth.conf has three local settings:
disable_plaintext_auth = no auth_mechanisms = plain login !include auth-ldap.conf.ext
Both mechanisms send the password itself, only base64-encoded. The design gives the reason in one sentence: a plaintext mechanism is supported by nearly every mail client in use, and the clients here are whatever a storage controller, a switch or a monitoring tool ships with (see Requirements and concept). LOGIN is the older, never standardised variant of the same thing, added to the stock plain for the clients that offer nothing else. There is a second reason that the design does not spell out: the password is verified by binding to Active Directory with it, and for that the server must hold the clear password for a moment. The shared-secret mechanisms (CRAM-MD5, DIGEST-MD5) need a secret stored on the server side, and a directory that only accepts binds cannot provide one.
Plaintext is acceptable only because it never travels in the clear:
| Hop | Protection |
|---|---|
| Client to Postfix, port 25 | smtpd_tls_security_level = encrypt: no mail command before STARTTLS; smtpd_tls_auth_only = yes: AUTH is not even offered before it |
| Client to Postfix, port 465 | smtpd_tls_wrappermode=yes: TLS before the first SMTP byte |
| Postfix to Dovecot | UNIX socket on the same host |
| Dovecot to the domain controllers | ldaps:// on port 636 |
The Postfix side is the subject of TLS and certificates. Dovecot's own safeguards are both switched off: disable_plaintext_auth = no, and ssl = no in 10-ssl.conf. That is consistent, since Dovecot has no TLS listener of its own here, but it means that the protection lives entirely in Postfix. For the one network listener Dovecot does have, IMAP on port 143, the stock comment above the setting says that connections from the local host count as secure anyway, so the webmail on the same machine would have worked with the default as well.
Two OUs, two files
Accounts of people are in OU=Users_STD; accounts of devices and applications, the <service-name>_mail accounts, are in OU=msx_SVC,OU=Users_SVC (see Active Directory integration). A Dovecot LDAP configuration file has one search base, so there are two files, and auth-ldap.conf.ext defines every database twice:
| Database | LDAP file | Search base | Used for |
|---|---|---|---|
passdb 1 | dovecot-ldap.conf.users | ou=users_std | verify the password of a person |
passdb 2 | dovecot-ldap.conf.devices | ou=msx_svc,ou=users_svc | verify the password of a device account |
userdb 1 | dovecot-ldap.conf.users | ou=users_std | mailbox location of a person |
userdb 2 | dovecot-ldap.conf.devices | ou=msx_svc,ou=users_svc | mailbox location of a device account |
They are tried in the order written, so the login of a device account first fails in the users' OU and then succeeds in its own. The two LDAP files differ in the base line and in nothing else. The part that matters:
uris = ldaps://10.12.16.209:636 ldaps://10.12.16.210:636 base = ou=users_std,DC=ad-dc2,DC=example,DC=net auth_bind = yes dn = cn=vmail_svc,ou=Users_svc,DC=ad-dc2,DC=example,DC=net dnpass = <LDAP_BIND_PASSWORD> pass_filter = (&(userPrincipalName=%u)(objectClass=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2))) pass_attrs = userPassword=password default_pass_scheme = CRYPT
The domain controllers are addressed by IPv4 address; the comment in the file says why: LDAP client libraries had problems on hosts with both IPv4 and IPv6. %u is the complete login name, so an account logs in with its userPrincipalName, account@ad-dc2.example.net, and accounts that are disabled in the directory are filtered out.
Dovecot knows two ways to check a password against LDAP. In a password lookup it reads a hash from the directory with its own bind account and compares it itself. In an authentication bind it searches the account and then binds as that account with the password the client sent. The files contain traces of both: auth_bind = yes selects the second, while pass_attrs and default_pass_scheme = CRYPT belong to the first, and the comments in auth-ldap.conf.ext say "Password lookup". What works is the bind. Active Directory does not hand out password hashes over LDAP, so the lookup lines are leftovers of a template and without effect. The service account vmail_svc is still needed, for the search that finds the DN to bind as.
One thing follows from the filters that is easy to miss. pass_filter contains no group: Dovecot will confirm the password of any enabled account in the two OUs. Whether that account may send, and where to, is decided afterwards by Postfix with the groups SMTP_ACCESS and ESMTP_ACCESS (see Internal servers: Postfix). The group IMAP_ACCESS appears in user_filter only, which is used for IMAP logins and for delivery and plays no part in SMTP authentication.
The Config documents: auth-ldap.conf.ext, dovecot-ldap.conf.users, dovecot-ldap.conf.devices, 10-auth.conf.
Logging
Dovecot logs to syslog with the facility mail, so its lines land in the same file as those of Postfix and a failed login can be read in one place: the smtpd line with the client address and, next to it, the Dovecot line with the reason. auth_verbose = yes is what produces the second; by default Dovecot does not log failed attempts. The password and debug switches are off. The file: 10-logging.conf.
Keepalived on the internal pair watches the dovecot process as well as the Postfix master (see High availability and key synchronization). The reason is this Article: a node whose Dovecot is gone still accepts connections and rejects every login.
Which files are local and which are stock
The install notes do not list the changes. They say that the configuration is "in attached configuration files", and the commands, run as root, replace the packaged tree wholesale (the Dovecot half of them):
$ yum install postfix dovecot $ systemctl stop dovecot $ mkdir -p /etc/dovecot-original $ cp -R /etc/dovecot/* /etc/dovecot-original/ $ rm -rf /etc/dovecot/*
So the archived trees have to speak for themselves. I filtered every file with grep -vE '^\s*(#|$)' and looked for my CFG-ON and CFG-OFF markers.
| File | State | Local settings |
|---|---|---|
dovecot.conf | changed | protocols = imap lmtp, listen = * |
conf.d/10-auth.conf | changed | plaintext allowed, plain login, LDAP instead of system accounts |
conf.d/10-logging.conf | changed | syslog, facility mail, auth_verbose = yes |
conf.d/10-mail.conf | changed | Maildir, vmail, uid range 501 to 501 |
conf.d/10-master.conf | changed | IMAP port, no imaps listener, the two sockets |
conf.d/10-ssl.conf | changed | ssl = no, certificate paths |
conf.d/auth-ldap.conf.ext | rewritten | two passdb, two userdb |
dovecot-ldap.conf.users, dovecot-ldap.conf.devices | new | not part of the package |
10-director, 15-lda, 15-mailboxes, 20-imap, 20-lmtp, 20-pop3, 90-acl, 90-plugin, 90-quota | stock | no marker, no active line beyond the empty stock blocks and the stock special-use mailboxes |
the other auth-*.conf.ext | stock | not included from 10-auth.conf |
The tree of DC2-A-VCMSX001 supports the split from another side. It contains 10-logging.conf.rpmnew, 10-mail.conf.rpmnew and 10-ssl.conf.rpmnew: a package update brought newer stock files, and RPM puts those beside a file that was changed by hand. A diff against them shows exactly the marked settings. On the same host 15-mailboxes, 20-imap, 20-lmtp, 20-pop3 and 90-quota are a newer revision than on the other two servers, different in comments only, which is what RPM does with files nobody touched.
What I cannot tell: the pristine package files (/etc/dovecot-original) were not archived, so for 10-director, 15-lda, 90-acl, 90-plugin and the unused auth-* files the verdict rests on the active lines alone. There are no .rpmnew files on the other two servers; whether they received the update later is not in the Source material. And the design's own table of "files modified from default" omits 10-logging.conf, which carries three markers.
Differences between the three servers
diff between the hosts shows a configuration that was copied, not adapted:
| File | DC2-A-VCMSX001 | DC2-B-VCMSX001 and DC2-A-VCMSX002 |
|---|---|---|
10-ssl.conf | ssl_cert and ssl_key active | the same two lines commented out |
10-logging.conf | four debug switches commented out | the same four set to no |
| stock files | newer package revision, three .rpmnew | older revision |
dovecot-ldap.conf.* | empty line at the end | none on DC2-B-VCMSX001 |
ssl = no is on all three. The certificate paths in 10-ssl.conf are /etc/pki/dovecot/certs/dc1-a-vcmsx001-4096.cer and the matching key: the name of the first internal server of site 1, from the first concept of 2017-2018, and a 4096-bit key where the certificates of these servers are 2048-bit and live under /etc/pki/tls. Nothing in the install notes of the site 2 servers creates those files. With ssl = no Dovecot does not load them, which is presumably why the active lines on DC2-A-VCMSX001 never caused an error. The file: 10-ssl.conf.
Everything else is identical, including the parts that only the mailbox server needs: mailbox paths under /data/vmail, the auth-userdb socket, the vmail account (the notes of the pair create it "not used on this server, for future use"), and the SELinux module (dovecot-local-1.1.te).
What I would do differently
- The pair runs services it does not need. dovecot.conf is the same on all three hosts, so the authentication-only servers also start IMAP on port 143, on every IPv4 address, and LMTP. The host firewall of the pair opens only SSH, 25 and 465, so IMAP is not reachable there, but a process that is not started needs no firewall rule.
- One tree for three hosts was convenient and left its marks: mailbox paths on servers without mailboxes, a certificate path of another site. A short auth-only configuration for the pair would have been easier to read than the full one with the unused half.
- The leftovers of the password lookup (
pass_attrs,default_pass_scheme, the comments) should have gone whenauth_bind = yescame in. They cost a reader the question which of the two is in use.