LINUXOR.SK ... open source notes ...

Email 07 - Dovecot authentication

category: solutionz · date: 2019-12-31 · updated: 2026-10-02 · author: LALA

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:

mermaid
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:

ini
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:

ini
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:

mermaid
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:

ini
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:

HopProtection
Client to Postfix, port 25smtpd_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 465smtpd_tls_wrappermode=yes: TLS before the first SMTP byte
Postfix to DovecotUNIX socket on the same host
Dovecot to the domain controllersldaps:// 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:

DatabaseLDAP fileSearch baseUsed for
passdb 1dovecot-ldap.conf.usersou=users_stdverify the password of a person
passdb 2dovecot-ldap.conf.devicesou=msx_svc,ou=users_svcverify the password of a device account
userdb 1dovecot-ldap.conf.usersou=users_stdmailbox location of a person
userdb 2dovecot-ldap.conf.devicesou=msx_svc,ou=users_svcmailbox 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:

ini
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):

bash
$ 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.

FileStateLocal settings
dovecot.confchangedprotocols = imap lmtp, listen = *
conf.d/10-auth.confchangedplaintext allowed, plain login, LDAP instead of system accounts
conf.d/10-logging.confchangedsyslog, facility mail, auth_verbose = yes
conf.d/10-mail.confchangedMaildir, vmail, uid range 501 to 501
conf.d/10-master.confchangedIMAP port, no imaps listener, the two sockets
conf.d/10-ssl.confchangedssl = no, certificate paths
conf.d/auth-ldap.conf.extrewrittentwo passdb, two userdb
dovecot-ldap.conf.users, dovecot-ldap.conf.devicesnewnot part of the package
10-director, 15-lda, 15-mailboxes, 20-imap, 20-lmtp, 20-pop3, 90-acl, 90-plugin, 90-quotastockno marker, no active line beyond the empty stock blocks and the stock special-use mailboxes
the other auth-*.conf.extstocknot 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:

FileDC2-A-VCMSX001DC2-B-VCMSX001 and DC2-A-VCMSX002
10-ssl.confssl_cert and ssl_key activethe same two lines commented out
10-logging.conffour debug switches commented outthe same four set to no
stock filesnewer package revision, three .rpmnewolder revision
dovecot-ldap.conf.*empty line at the endnone 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

← solutionz