The Perfect Mailserver: LDAP with Postfix and Dovecot

A three-part series on configuring LDAP, mail, and merging the two. We're starting with the end. I'll be writing about gluing together LDAP and your mail daemons.

The Perfect Mailserver: LDAP with Postfix and Dovecot

Welcome to a three-part series on configuring LDAP, mail, and merging the two. We're starting with the end. I'll be writing about gluing together LDAP and your mail daemons.

I'm going to assume you've already got working mailserver with Postfix and Dovecot, and got a LDAP server working. If not, please check out my articles on these by travelling into the future (stay tuned for links).

In the below templates, xf is the name of my LDAP trust root and cn=mail,ou=groups,dc=xf is the distinguished name (DN) of the groups giving access to email services (we'll use the same one for both incoming and outgoing). The account we bind to in order to retrieve passwords is cn=passdn,ou=admin,dc=xf, as LDAP best practice is to not make hashed passwords available publicly, but rather have a dedicated account for retrieving them. Substitute in your own values for these, you will also probably have two dc entries, such as dc=google,dc=com. I went with one for minimalism, and because I had control over all the DNS, so I could make xf. actually resolve to something, which is a requirement in LDAP.

We'll start with the main configuration:

uris           = ldaps://xf:636
ldap_version    = 3
auth_bind       = yes
dn              = cn=passdn,ou=admin,dc=xf
dnpass          = hunter2
base            = ou=users,dc=xf
scope           = subtree
deref           = never

user_filter = (&(memberOf=cn=mail,ou=groups,dc=xf)(uid=%u))
pass_attrs = uid=user,userPassword=password
pass_filter = (&(memberOf=cn=mail,ou=groups,dc=xf)(uid=%u))
default_pass_scheme = SSHA
user_attrs = uid=user
/etc/dovecot/dovecot-ldap.conf.ext

This assumes that uid is the username, and userPassword is the hashed password.

Let's protect that password with chmod 0640 /etc/dovecot/dovecot-ldap.conf.ext.

You can see that in the user and pass filters, we're specifying an additional filter, for the users to be in the mail group, which is referenced by its full DN. You'll need the memberOf overlay to get this to work. If you don't want to look up a group (or couldn't get memberOf to work), change it like so:

user_filter = (uid=%u)
pass_filter = (uid=%u)

Next, we'll set up the main bit:

mbox_write_locks = fcntl
mmap_disable = yes
auth_username_format=%Ln

passdb {
  driver = ldap
  args = /etc/dovecot/dovecot-ldap.conf.ext
}
userdb {
  driver = ldap
  args = /etc/dovecot/dovecot-ldap.conf.ext
  default_fields = home=/srv/mail/%u
}

We'll probably want mailboxes to be created if they don't exist for new users logging in:

lda_mailbox_autocreate = yes
/etc/dovecot/conf.d/15-lda.conf

Be sure you also have something like this:

mail_uid = 6600
mail_gid = 6600
first_valid_uid = 6600
first_valid_gid = 6600
/etc/dovecot/conf.d/10-mail.conf

Now, moving onto Postfix, we just have a few files to change:

virtual_mailbox_domains = zm.is, rkveideman.is, opennic.cyb
virtual_minimum_uid = 6600
virtual_alias_maps = hash:/etc/postfix/virtual
	             ldap:/etc/postfix/maps
smtpd_sender_login_maps = ldap:/etc/postfix/senders
/etc/postfix/main.cf

This part is if you want to set up any catch-alls, here I've set that up for two of my domains:

@zm.is m 
@rkveideman.is m 
/etc/postfix/virtual
server_host      = ldaps://xf
server_port      = 636
version          = 3
bind             = no
start_tls        = no
bind_dn          = cn=passdn,ou=admin,dc=xf
bind_pw          = hunter2
search_base      = ou=users,dc=xf
scope            = sub
query_filter     = (&(memberOf=cn=mail,ou=groups,dc=xf)(mail=%s))
result_attribute = uid
/etc/postfix/maps

Keep it safe: chmod 0640 /etc/postfix/maps.

Youn may already be seeing a problem. We're logging into Dovecot with our uid, but need to receive mail with our mail attribute, so we need some glue between these:

server_host      = ldaps://xf      
server_port      = 636
version          = 3
bind             = no
start_tls        = no
search_base      = ou=users,dc=xf      
scope            = sub
query_filter     = (&(memberOf=cn=mail,ou=groups,dc=xf)(uid=%s))
result_attribute = mail

No password needed here since it's simply looking up the uid and returning the mail attribute. Easy.

Go ahead and restart any relevant services, and log in with your new credentials.

If it doesn't work, stay tuned for the mailserver instalment in this series, where I will demistify the time-old overcomplicated topic of running your own mailserver.