We will set up Postfix and Dovecot using an OpenLDAP backend with a custom LDAP schema. Our schema uses a dedicated organization unit called mail which acts as a container for all of our hosted domains and users. The virtual mail users will use their primary e-mail address to login to the server and each user can utilize multiple aliases.
Both Postfix and Dovecot will read all of their user attributes from our LDAP backend and we will be able to change these attributes dynamically without the need of rebuilding external lookup databases for Postfix.
Schema
Setup OpenLDAP
Our directory will be stored on a new logical volume to make sure we can grow disk space dynamically. Recent OpenLDAP versions store their configuration in the LDAP directory tree. Therefore changes to the configuration are applied by using LDIF (LDAP Data Interchange Format) files, which is an ASCII-based file format. We will be using the ldap{add,modify} utilities for this job.
Let’s first create a volume to store the databases and define its mount parameters in the file system table.
lvcreate -L 1G -n ldap vg0 mkfs.ext4 -L "ldap" /dev/mapper/vg0-ldap
/dev/mapper/vg0-ldap /var/lib/ldap ext4 defaults 0 2
We need to make sure the volume is mounted before we start the installation of slapd.
apt install slapd ldap-utils
Once the installation completes we’ll instruct the server to listen on local interfaces only. We will therefore modify the defaults and reload the slapd daemon.
SLAPD_SERVICES="ldap://127.0.0.1:389/ ldap://[::1]:389 ldapi:///"
systemctl force-reload slapd
Create administrative account
Next we should create an administrator for our directory. We will use slappaswd to create a password hash for this user. The hashed output will be copied to an LDIF file that will create our administrative account. We need to authenticate ourselves using the database root password that was set during installation in order to apply this change.
slappasswd
New password:
Re-enter new password:
{SSHA}...
dn: cn=admin,dc=example,dc=com changetype: add objectClass: simpleSecurityObject objectClass: organizationalRole cn: admin description: LDAP administrator userPassword: {SSHA}...
ldapmodify -W -D cn=admin,dc=example,dc=com -H ldapi:/// -f slapd_admin.ldif
Enter LDAP Password:
adding new entry "cn=admin,dc=example,dc=com"
Now that our new administrator has been added to the directory, we need to verify we can search our directory using this account.
ldapsearch -W -D cn=admin,dc=example,dc=com -b dc=example,dc=com -H ldapi:///
Allow configuration using TCP
So far our LDAP configuration relies on local root access to commit database changes. Administrative privileges to the configuration are granted using the SASL mechanism EXTERNAL via Unix sockets -H ldapi:///
.
To allow management inside our network (even locally in our case), we will create a password to enable configuration via TCP. TLS will be added later on…
slappasswd
New password:
Re-enter new password:
{SSHA}...
We will populate our hashed password through another LDIF update. Take note that we will use a different encryption scheme for our passwords later as well. All existing passwords will need to be revisited. This includes our administrator cn=admin,dc=example,dc=com created before.
dn: olcDatabase={0}config,cn=config changetype: modify replace: olcRootPW olcRootPW: {SSHA}...
ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f slapd_config_password.ldif
modifying entry "olcDatabase={0}config,cn=config"
ldapsearch -W -D cn=admin,cn=config -b cn=config
Access control
Per default, unauthenticated users are able to read the Directory Information Tree, base DN and schemas. We will allow this for authenticated users only.
ldapsearch -LLL -W -D cn=admin,cn=config -b cn=config '(olcAccess=*)' olcAccess
The defaults look like this:
dn: olcDatabase={-1}frontend,cn=config olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external ,cn=auth manage by * break olcAccess: {1}to dn.exact=""by * read olcAccess: {2}to dn.base="cn=Subschema"by * read dn: olcDatabase={0}config,cn=config olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external ,cn=auth manage by * break dn: olcDatabase={1}mdb,cn=config olcAccess: {0}to attrs=userPassword by self write by anonymous auth by * none olcAccess: {1}to attrs=shadowLastChange by self write by * read olcAccess: {2}to *by * read
We will now improve our directory’s security by restricting queries to authenticated users.
dn: olcDatabase={-1}frontend,cn=config changetype: modify delete: olcAccess dn: olcDatabase={-1}frontend,cn=config changetype: modify add: olcAccess olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external ,cn=auth manage by * break olcAccess: {1}to dn.exact="" by users read olcAccess: {2}to dn.base="cn=Subschema" by users read dn: olcDatabase={1}mdb,cn=config changetype: modify delete: olcAccess dn: olcDatabase={1}mdb,cn=config changetype: modify add: olcAccess olcAccess: {0}to attrs=userPassword,shadowLastChange by self write by anonymou s auth by * none olcAccess: {1}to dn.base="" by users read olcAccess: {2}to * by users read
ldapmodify -W -D cn=admin,cn=config -f slapd_config_user_ACL.ldif
Improve password security
Next we will be changing our password format to SHA-512 using a 16 character salt. We did say we won’t stick with SSHA remember?
dn: cn=config changetype: modify add: olcPasswordCryptSaltFormat olcPasswordCryptSaltFormat: $6$%.16s - add: olcPasswordHash olcPasswordHash: {CRYPT}
ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f slapd_config_sha-512.ldif
We will need to update our configuration password after this as only newly created or changed passwords will be hashed by this password scheme. This also applies to RFC 3062 extended operation aware tools only, like ldappasswd. We will therefore perform our updates as completely hashed strings as these will be written to the userPasswd attribute unaltered.
slappasswd -c '$6$%.16s'
The password can then be changed using our newly hashed password for the olcRootPW attribute in the slapd_config_pwassword.ldif
example we used before. We will also update the administrative password of our database.
ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:/// -b cn=config "(olcRootDN=cn=admin,dc=example,dc=com)" dn olcRootDN olcRootPW
dn: olcDatabase={1}mdb,cn=config
olcRootDN: cn=admin,dc=example,dc=com
olcRootPW: {SSHA}...
dn: olcDatabase={1}mdb,cn=config changetype: modify replace: olcRootPW olcRootPW: {CRYPT}...
ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f slapd_database_password.ldif
ldappasswd -W -S -D cn=admin,dc=example,dc=com -H ldapi:///
TLS configuration
Our server will provide Transport Layer Security using Let’s Encrypt Certificates. Naturally other certification authorities can be used for the same purpose. The private key will need to be copied to our LDAP configuration directory or made readable by our system’s openldap account. We will copy it to the LDAP etc directory.
ls -l /etc/ldap/tls/ total 4 -r-------- 1 openldap openldap 3243 Oct 29 09:45 ldap_example_com_ACME.key
Like the other configuration settings the certificate files will need to be added to the LDAP directory itself.
dn: cn=config changetype: modify add: olcTLSCertificateFile olcTLSCertificateFile: /etc/ssl/certs/ldap_example_com_ACME.pem add: olcTLSCertificateKeyFile olcTLSCertificateKeyFile: /etc/ldap/tls/ldap_example_com_ACME.key
ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f slapd_config_TLS.ldif
We need to add well-known root certification authorities to our configuration, otherwise our LDAP tools will refuse to connect to our server.
TLS_CACERT /etc/ssl/certs/ca-certificates.crt
ldapsearch -LLL -ZZ -W -D cn=admin,cn=config -H ldap://ldap.example.com -b cn=config
Enforce TLS encryption
We will first add 256-Bit encryption to our database configuration.
dn: olcDatabase={1}mdb,cn=config changetype: modify add: olcSecurity olcSecurity: tls=256
ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f slapd_config_enforce_TLS.ldif
Once we have successfully tested the enforced setting, we will apply our configuration globally.
ldapsearch -H ldap://ldap.example.com
ldap_bind: Confidentiality required (13)
additional info: TLS confidentiality required
We are now adding this to the configuration context. Going forward our server will reject unsecured communication.
dn: cn=config changetype: modify add: olcSecurity olcSecurity: tls=256
ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f slapd_config_enforce_TLS.ldif
ldapsearch -LLL -W -D cn=admin,cn=config -H ldap://ldap.example.com -b cn=config
Enter LDAP Password:
ldap_bind: Confidentiality required (13)
additional info: TLS confidentiality required
Setup mail users
We will use a custom schema to store our user data virtualMail.ldif. The schema is available on GitHub. It contains all the attributes for our virtual mail users:
- User Login (e-mail address)
- Password
- Aliases of this user
- UID/GID used for mailbox files
- Mail Home Directory
- Quota
dn: cn=virtualMail,cn=schema,cn=config cn: virtualMail objectClass: olcSchemaConfig olcAttributeTypes: {0}( 1.3.6.1.4.1.53373.2.1.100 NAME 'mailHomeDirectory' D ESC 'The absolute path to the mail user home directory' EQUALITY caseExactI A5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) olcAttributeTypes: {1}( 1.3.6.1.4.1.53373.2.1.101 NAME 'mailAlias' DESC 'RFC 822 Mailbox - mail alias' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5S ubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} ) olcAttributeTypes: {2}( 1.3.6.1.4.1.53373.2.1.102 NAME 'mailDrop' DESC 'Post fix mail final destination attribute' EQUALITY caseIgnoreIA5Match SUBSTR ca seIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} ) olcAttributeTypes: {3}( 1.3.6.1.4.1.53373.2.1.103 NAME 'mailUidNumber' DESC 'UID required to access the mailbox' EQUALITY integerMatch SYNTAX 1.3.6.1.4 .1.1466.115.121.1.27 SINGLE-VALUE ) olcAttributeTypes: {4}( 1.3.6.1.4.1.53373.2.1.104 NAME 'mailGidNumber' DESC 'GID required to access the mailbox' EQUALITY integerMatch SYNTAX 1.3.6.1.4 .1.1466.115.121.1.27 SINGLE-VALUE ) olcAttributeTypes: {5}( 1.3.6.1.4.1.53373.2.1.105 NAME 'mailEnabled' DESC 'T RUE to enable, FALSE to disable account' EQUALITY booleanMatch SYNTAX 1.3.6 .1.4.1.1466.115.121.1.7 SINGLE-VALUE ) olcAttributeTypes: {6}( 1.3.6.1.4.1.53373.2.1.106 NAME 'mailQuota' DESC 'Mai l quota limit in kilobytes' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1 466.115.121.1.26 ) olcAttributeTypes: {7}( 1.3.6.1.4.1.53373.2.1.107 NAME 'mailGroupACL' DESC ' ACL group attribute' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115 .121.1.26 SINGLE-VALUE ) olcAttributeTypes: {8}( 1.3.6.1.4.1.53373.2.1.108 NAME 'mailExpungeTrash' DE SC 'Time to automatically expunge Trash mailbox' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) olcAttributeTypes: {9}( 1.3.6.1.4.1.53373.2.1.109 NAME 'mailAlternate' DESC 'M ailbox hosted externally' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5S ubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} ) olcObjectClasses: {0}( 1.3.6.1.4.1.53373.2.2.100 NAME 'mailUser' DESC 'Hoste d mail user class' SUP top AUXILIARY MAY ( mailHomeDirectory $ mailAlias $ mailDrop $ mailUidNumber $ mailGidNumber $ mailEnabled $ mailQuota $ mailGr oupACL $ mailExpungeTrash $ mailAlternate ) )
ldapadd -ZZ -W -D cn=admin,cn=config -H ldap://ldap.example.com -f virtualMail.ldif
adding new entry "cn=virtualMail,cn=schema,cn=config"
Add indexes to the mix
The user login/mailbox and mail aliases should be indexed to improve the query/search performance.
dn: olcDatabase={1}mdb,cn=config add: olcdbindex olcdbindex: mailAlias eq,sub add: olcdbindex olcdbindex: mailDrop eq
ldapmodify -ZZ -W -D cn=admin,cn=config -H ldap://ldap.example.com -f virtualMail_indexes.ldif
Mail OUs
Next we will add an organizational unit or container for our mail elements. Have a look at our diagram again at the top of the page.
dn: ou=Mail,dc=example,dc=com ou: Mail description: Mail objectClass: organizationalUnit
ldapadd -ZZ -W -D cn=admin,dc=example,dc=com -H ldap://ldap.example.com -f mail_OU.ldif
And another organizational unit for our services. Some accounts to query mail users and more privileged accounts for user management/user attribute changes will reside in it.
dn: ou=Services,dc=example,dc=com ou: Services description: Service accounts objectClass: organizationalUnit
ldapadd -ZZ -W -D cn=admin,dc=example,dc=com -H ldap://ldap.example.com -f services_OU.ldif
Virtual domains
We will now add an organizational unit that will hold our hosted domains.
dn: ou=Domains,ou=Mail,dc=example,dc=com ou: Domains description: Mail domains objectClass: organizationalUnit
ldapadd -ZZ -W -D cn=admin,dc=example,dc=com -H ldap://ldap.example.com -f domains_OU.ldif
We can then add domain objects to this OU. These entries will describe the virtual domains that we host and will allow Postfix to query our directory in order to relay mail.
dn: dc=example.com,ou=Domains,ou=Mail,dc=example,dc=com dc: example.com objectClass: dNSDomain
ldapadd -ZZ -W -D cn=admin,dc=example,dc=com -H ldap://ldap.example.com -f domains.ldif
We should index our domain objects as well.
dn: olcDatabase={1}mdb,cn=config changetype: modify add: olcdbindex olcdbindex: dc eq
ldapmodify -ZZ -W -D cn=admin,cn=config -H ldap://ldap.example.com -f virtuaDomains_indexes.ldif
Service account
Postfix and Dovecot will use this account to bind to our LDAP server and perform their queries. First we will create a new password. Then we will be adding the account to its OU:
mkpasswd --rounds 5000 -m sha-512 --salt $(head -c 40 /dev/urandom | base64 | sed -e 's/+/./g' | cut -b 10-25)
dn: cn=mail,ou=Services,dc=example,dc=com cn: mail objectClass: simpleSecurityObject objectClass: organizationalRole userpassword: {CRYPT}...
ldapadd -ZZ -W -D cn=admin,dc=example,dc=com -H ldap://ldap.example.com -f mailServicesAccount.ldif
Mail users
We will finally add an OU for our mail users and create our first user.
dn: ou=Users,ou=Mail,dc=example,dc=com ou: Users description: Mail users objectClass: organizationalUnit
ldapadd -ZZ -W -D cn=admin,dc=example,dc=com -H ldap://ldap.example.com -f Users_OU.ldif
dn: uid=vmail10001,ou=Users,ou=Mail,dc=example,dc=com mailHomeDirectory: /var/vmail/domains/example.com/user mailAlias: user@example.com mailAlias: alias@example.com mailDrop: user@example.com objectClass: account objectClass: simpleSecurityObject objectClass: mailUser mailUidNumber: 5000 mailGidNumber: 5000 mailEnabled: TRUE mailQuota: 1G uid: vmail10001 userPassword: {CRYPT}...
ldapadd -ZZ -W -D cn=admin,dc=example,dc=com -H ldap://ldap.example.com -f mailUsers.ldif
Postfix LDAP configuration
Migrating an existing Postfix environment to LDAP entails changing the tables used for its virtual domains and aliases. This can be done by adding new queries to the configuration or by replacing existing ones. A feasible approach probably involves testing the queries first – using postmap – and implementing them after successful testing.
virtual_mailbox_domains = ldap:/etc/postfix/ldap/virtual_domains.cf virtual_alias_maps = ldap:/etc/postfix/ldap/virtual_aliases.cf
There is nothing exiting about verifying our relay domains. Either we have them or we don’t…
server_host = ldap://ldap.example.com search_base = ou=Domains,ou=Mail,dc=example,dc=com version = 3 start_tls = yes bind = yes bind_dn = cn=mail,ou=Services,dc=example,dc=com bind_pw = <secret_password> query_filter = (&(ObjectClass=dNSDomain)(dc=%s)) result_attribute = dc
Aliases are a bit more thrilling in this regard. Remember we are using the primary alias/e-mail address as login. Therefore each user needs to have one alias (maiAlias) that matches its login/mailbox attribute (mailDrop). Now for any mail we will simply query objects of type (mailUser) and we will try to find the requested (mailAlias) and return the corresponding mailbox.
Unless… well unless the user has been disabled (!(mailEnabled=FALSE).
server_host = ldap://ldap.example.com search_base = ou=Users,ou=Mail,dc=example,dc=com version = 3 start_tls = yes bind = yes bind_dn = cn=mail,ou=Services,dc=example,dc=com bind_pw = <secret_password> query_filter = (&(objectClass=mailUser)(mailAlias=%s)(!(mailEnabled=FALSE))) result_attribute = mailDrop
To ensure each account can only utilize its own aliases, we will be using the same construct to control SASL logins: smtpd_sender_login_maps = ldap:/etc/postfix/ldap/virtual_aliases.cf
.
Dovecot LDAP configuration
For Dovecot we will use two separate queries for userdb and passdb. This will allow asynchronous lookups.
passdb { driver = ldap args = /etc/dovecot/dovecot-ldap-passdb.conf } userdb { driver = ldap args = /etc/dovecot/dovecot-ldap-userdb.conf }
server_host = ldap://ldap.example.com uris = ldap://ldap.example.com tls = yes debug_level = 0 dn = cn=mail,ou=Services,dc=example,dc=com dnpass = '<secret_password>' auth_bind = yes ldap_version = 3 base = ou=Users,ou=Mail,dc=example,dc=com pass_attrs = mailDrop=user, userPassword=password pass_filter = (&(objectClass=mailUser)(mailDrop=%u)(!(mailEnabled=FALSE)))
server_host = ldap://ldap.example.com uris = ldap://ldap.example.com tls = yes debug_level = 0 dn = cn=mail,ou=Services,dc=example,dc=com dnpass = '<secret_password>' auth_bind = yes ldap_version = 3 base = ou=Users,ou=Mail,dc=example,dc=com user_attrs = mailHomeDirectory=home,mailUidNumber=uid,mailGidNumber=gid,mailQuota=quota_rule=*:storage=%$,mailGroupACL=acl_groups,mailExpungeTrash=namespace/inbox/mailbox/Trash/autoexpunge user_filter = (&(objectClass=mailUser)(mailDrop=%u)(!(mailEnabled=FALSE))) iterate_attrs = mailDrop=user iterate_filter = (objectClass=mailUser)
Restricted account updates
LDAP ACLs come in handy to grant some accounts the privilege of updating any mail account’s password. This will be used to reset user passwords while also limiting access to our mail-subtree only:
olcAccess: {0}to dn.subtree="ou=Mail,dc=example,dc=com" attrs=userPassword,s hadowLastChange by self write by dn.children="ou=Admins,ou=Mail,dc=example, dc=com" write by anonymous auth by * none olcAccess: {1}to attrs=userPassword,shadowLastChange by self write by anonym ous auth by * none olcAccess: {2}to dn.base="" by users read olcAccess: {3}to * by users read
Since this can become a bit complex using LDIF updates, we will use the ldapvi editor for this task:
ldapvi -Z -D cn=admin,cn=config -h ldap://host.example.com -b cn=config
We will also introduce a new attribute for external e-mail addresses. This way our users may receive instructions on how to reset their passwords using an alternate e-mail (the attribute is already part of the schema hosted at GitHub):
olcAttributeTypes: {9}( 1.3.6.1.4.1.53373.2.1.109 NAME 'mailAlternate' DESC 'M ailbox hosted externally' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5S ubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} ) olcObjectClasses: {0}( 1.3.6.1.4.1.53373.2.2.100 NAME 'mailUser' DESC 'Hoste d mail user class' SUP top AUXILIARY MAY ( mailHomeDirectory $ mailAlias $ mailDrop $ mailUidNumber $ mailGidNumber $ mailEnabled $ mailQuota $ mailGr oupACL $ mailExpungeTrash $ mailAlternate ) )
With this configuration we will be restricting password updates to our mail user subtree. We will specify the account which can update all attributes of mail users. Using the scope dn.subtree for filtering requests, we will broadly match all of the accounts accessing this subtree. We will append a break statement therefore. Break will continue to evaluate succeeding ACL definitions.
olcAccess: {0}to dn.subtree="ou=Users,ou=Mail,dc=example,dc=com" by dn.exact ="cn=mailAccounts,ou=Admins,ou=Mail,dc=example,dc=com" write by * break olcAccess: {1}to dn.subtree="ou=Users,ou=Mail,dc=example,dc=com" attrs=userP assword,shadowLastChange by self write by dn.children="ou=Admins,ou=Mail,dc =example,dc=com" write by anonymous auth by * none olcAccess: {2}to attrs=userPassword,shadowLastChange by self write by anonym ous auth by * none olcAccess: {3}to dn.base="" by users read olcAccess: {4}to * by users read
Once this ACL is implemented we can use two special accounts to update attributes (we also need to create a new organization unit for these admins):
cn=mailAccounts,ou=Admins,ou=Mail,dc=example,dc=com
mailAccounts can edit all attributes in the mail sub-tree while
cn=mailPasswords,ou=Admins,ou=Mail,dc=example,dc=com
mailPasswords can only write userPasswords.
Password security 2.0
Let’s add the Argon2 password scheme to our configuration. We will need to install an additional module for this.
apt install slapd-contrib
slappasswd -h {ARGON2} -o module-load=pw-argon2
New password:
Re-enter new password:
{ARGON2}$argon2i$v=19$m=4096,t=3,p=1$j9mVESA9ZjLo1YyWlgIBQA$dsqtAnVmVJkMnPOOvPE8/imdsYGnCd+IPbN+Q8Y/P20
dn: cn=module{0},cn=config changetype: modify add: olcModuleLoad olcModuleLoad: pw-argon2
Note that starting with OpenLDAP 2.5 Debian has moved the Argon2 module to the core package. This is a breaking change as the configuration has to use olcModuleload: argon2 going forward.
ldapmodify -ZZ -W -D cn=admin,cn=config -H ldap://ldap.example.com -f slapd_config_argon2.ldif
We will now apply an Argon2 hashed password to a user:
dn: uid=vmail10001,ou=Users,ou=Mail,dc=example,dc=com changetype: modify replace: userPassword userPassword: {ARGON2}…
ldapmodify -ZZ -W -D cn=mailPasswords,ou=Admins,ou=Mail,dc=example,dc=com -H ldap://ldap.example.com -f slapd_user_pw.ldif
Enter LDAP Password:
modifying entry "uid=vmail10001,ou=Users,ou=Mail,dc=example,dc=com"
Migrating existing passwords
Migrating our users without them changing their passwords requires a little trick. We will run a script to rehash their passwords once they login.
For this we will define a new service imap-postlogin
which we need to reference in Dovecot’s main imap service, service imap { executable = imap imap-postlogin }
.
service imap { executable = imap imap-postlogin unix_listener imap-master { user = dovecot } } service imap-postlogin { executable = script-login /var/vmail/conf.d/scripts/postlogin.sh unix_listener imap-postlogin { } }
#!/bin/sh # Split out domain part from $USER user@domain hash_prefix='{ARGON2}' mail_alias=${USER%@*} mail_domain=${USER#*@} migrate_domain='example.com' case "$mail_domain" in $migrate_domain) dovecot_new_pw=$(printf "$PLAIN_PASS" | argon2 $(head -c 40 /dev/urandom | base64 | sed -e 's/+/./g' | cut -b 10-25) -e) printf "user: $USER\n" >> /tmp/login.log printf "$hash_prefix$dovecot_new_pw\n" >> /tmp/login.log ;; esac exec "$@"
Besides activating the imap-postlogin service we will need to pass the plain password to our script. For this we need to add default_fields = plain_pass=%w
to our userdb stanza in Dovecot.
userdb { args = /etc/dovecot/dovecot-ldap-userdb.conf default_fields = plain_pass=%w driver = ldap }
Useful commands
Find highest user ID:
ldapsearch -LLL -ZZ -D cn=admin,dc=example,dc=com -W -H ldap://ldap.example.com -b ou=Users,ou=Mail,dc=example,dc=com objectClass=mailUser uid | awk '{if(max<$2){max=$2;uid=$2}}END{print uid}'
Self update user password:
ldappasswd -ZZ -W -A -S -D uid=vmail10001,ou=Users,ou=Mail,dc=example,dc=com -H ldap://ldap.example.com
Extract all mailAliases of a given user:
mail=alias@example.com; \ ldapsearch -LLL -ZZ -D cn=admin,dc=example,dc=com -W -H ldap://ldap.example.com -b ou=Users,ou=Mail,dc=example,dc=com \ "(&(objectClass=mailUser)(mailDrop=$mail))" mailAlias | sed -n 's/^mailAlias:\s\(.*\)/\1/p'
A more practical use case:
mail=alias@example.com;timestamp=$(date +%Y%m%d%H%M%S.%3N) \ ldapsearch -LLL -ZZ -D cn=admin,dc=example,dc=com -W -H ldap://ldap.example.com -b ou=Users,ou=Mail,dc=example,dc=com \ "(&(objectClass=mailUser)(mailDrop=$mail))" mailAlias | awk -v ts="$timestamp" '/mailAlias/{ print $2 "\tHOLD Planned maintenance " ts }' >>$maint_aliases
Backup and restore
We will dump the configuration (0) and our database (1) and move it to /home/backups. After cleaning up the etc path we will import the exported configuration again.
slapcat -n 0 -l /tmp/config.ldif slapcat -n 1 -l /tmp/data.ldif systemctl stop slapd mv /etc/ldap/slapd.d/ /home/backups/ mkdir /etc/ldap/slapd.d/ slapadd -n 0 -F /etc/ldap/slapd.d -l /tmp/config.ldif _#################### 100.00% eta none elapsed none fast! Closing DB... chown -R openldap: /etc/ldap/slapd.d/
In the second step the exported database is restored.
mv /var/lib/ldap/*.mdb /home/backups/ slapadd -n 1 -F /etc/ldap/slapd.d -l /tmp/data.ldif -#################### 100.00% eta none elapsed spd 11.7 k/s Closing DB... chown openldap: /var/lib/ldap/*.mdb systemctl start slapd