A LDAP directory offers a single, logically centralized, hierarchical store to keep data, like information about users, groups, mailboxes, services, etc. There are a number of applications out there than can leverage this LDAP store. For example, PAM can authenticate by checking against an LDAP directory, Cyrus-IMAP can check whether a certain user has a IMAP/POP mailbox available, and Postfix can contact an LDAP directory in order to route an incoming mail to its corresponding mail host.
LDAP is flexible enough to describe abstract concepts, like persons, by using object classes. Each object class is built upon attributes. An attribute is the minimal unit of information and tells about a property, such as the name of a person, its postal address or e-mail address. The minimal storage unit is an LDAP entry, however, which is an instance of one ore several object classes. The collection of attributes and object classes is called schema, while the collection of LDAP entries is called Directory Information Tree (DIT).
There are several RFC documents out there describing most of the standard LDAP object classes for the LDAPv3 protocol. There are other object classes which are either propietary or non-standard. For example, Fedora Directory Server defines an object class named mailRecipient. This legacy object class was used by the Netscape Messaging Server 4 to define a mailbox and is defined as:
objectclasses: ( 2.16.840.1.1137184.108.40.206 NAME 'mailRecipient' DESC '' SUP top AUXILIARY MUST ( objectClass ) MAY ( cn $ mail $ mailAlternateAddress $ mailHost $ mailRoutingAddress $ mailAccessDomain $ mailAutoReplyMode $ mailAutoReplyText $ mailDeliveryOption $ mailForwardingAddress $ mailMessageStore $ mailProgramDeliveryInfo $ mailQuota $ multiLineDescription $ uid $ userPassword ) X-ORIGIN 'Netscape Messaging Server 4.x' )
This object class is particularly simple and interesting since it defines a few concepts we can use when routing mail, like the idea of multiple mail aliases for a single mailbox.
Describing our purpouses
Take this scenario: we have a real user, John Smith which owns a mailbox which is stored at host mail1.internal. The mailbox for John Smith is firstname.lastname@example.org, since it’s stored in host mail1.internal. Let’s say this real user has the following e-mail addresses assigned to him (assigned to its mailbox):
These are the e-mail addresses. The last e-mail address is, in fact, the real e-mail address that anyone could expect John Smith to have. The other e-mail addresses are in fact aliases.
Whenever a Postfix MTA in the enterprise receives a mail for any of these e-mail addresses, we want it to be delivered to a mailbox named john.smith in host mail1.internal, that is, delivered to email@example.com, and so, firstname.lastname@example.org is the real address where messages sent to John Smith, using any of the previous e-mail addresses, must be routed to. Thus, email@example.com is what we will call the mail routing address.
We will configure Postfix to use an LDAP-based virtual alias map. Each time a mail is sent to Postfix, it will perform an LDAP query for that e-mail address and will try to guess its mail routing address. If one is found, Postfix will deliver that mail to it:
- If the domain part (the address at the right of the
@sign) of the mail routing address equals the FQDN of the Postfix host:
Postfix will try to deliver the message locally, usually using an MDA, like procmail or cyrus-imapd/deliver (Cyrus-IMAP local delivery agent used to deliver messages to a IMAP mailbox).
For example, let be firstname.lastname@example.org the mail routing address and mail1.internal Postfix’s hostname FQDN. In this case, Postfix will deliver the message locally using a MDA.
- If the domain part (the address at the right of the
@sign) of the mail routing address does not equal the FQDN of the Postfix host:
Postfix will trigger the message routing process again in order to deliver it to its final destination, checking querying the LDAP directory again if necessary.
To store mail routing addresses and it’s corresponding mail aliases, we will use the mailRecipient object class. The multi-valued mail attribute will hold all mail aliases and mailRoutingAddress will the real, final destination for any of them.
A sample entry, exemplifying the John Smith user we described before, in LDIF syntax is:
dn: uid=john.smith,ou=People, dc=example, dc=com givenName: John sn: Smith mail: email@example.com mail: firstname.lastname@example.org mail: email@example.com mail: firstname.lastname@example.org mail: email@example.com objectClass: top objectClass: mailRecipient uid: john.smith mailRoutingAddress: firstname.lastname@example.org
Note the absence of the posixAccount object class. This means John Smith is not a regular UNIX user and thus, this mailbox cannot be used to log in through PAM, for example. Note the absence of the inetOrgPerson object class too, which could mean this mailbox is not assigned to a real user.
The first thing that is required is creating a configuration file with details about how the LDAP directory server should be queried and contacted. The name of this file is not relevant, but I decided to name it
# cat /etc/postfix/ldap-aliases.cf bind = no version = 3 timeout = 20 ## set the size_limit to 1 since we only ## want to find one email address match size_limit = 1 expansion_limit = 0 start_tls = no #tls_require_cert = no server_host = ldap://ldap1.internal/ ldap://ldap2.internal/ search_base = dc=example,dc=com scope = sub query_filter = (&(objectclass=mailRecipient)(mail=%s)) result_attribute = mailRoutingAddress special_result_filter = %s@%d
The configuration options are described in detail in the Postfix ldap_table(5) manual page. A brief description lies hereafter:
bind = notells Postfix to perform an unauthenticated (anonymous) BIND against the LDAP directory.
size_limit = 1tells Postfix to request one, and only one, LDAP entry matching the
query_filter, starting the search operation at the LDAP entry whose DN is specified by
search_base, and using a
The search scope is one of: sub, one, base.
server_hostdefines one or several LDAP hosts, trying them in order should the first one fail.
query_filterdefines the LDAP search that Postfix will use in order to retrieve the mail routing address given a mail address.
In the previous configuration file, the LDAP search filter will look for entries belonging to the mailRecipient object class whose mail attribute matches the recipient mail address of the incoming message.
If one is found (at most only one entry will be retrieved since
size_limit = 1), the mailRoutingAddress attribute, defined to by
result_attribute, points to the final destination for the message, that is the mail routing address.
Finally, we will add the
virtual_alias_maps directive to Postfix’s
/etc/postfix/main.cf configuration file:
# tail -1 /etc/postfix/main.cf virtual_alias_maps = ldap:/etc/postfix/ldap-aliases.cf
For example, when receiving a message for email@example.com, Postfix will first look to see if this mail address is an alias or a real address. The LDAP query can be tested by running:
$ ldapsearch -x -b"dc=example,dc=coml" "(&(objectclass=mailRecipient)(firstname.lastname@example.org))" mailRoutingAddress # extended LDIF # # LDAPv3 # base with scope sub # filter: (&(objectclass=mailRecipient)(email@example.com)) # requesting: mailRoutingAddress # dn: uid=john.smith,ou=People, dc=example, dc=com mailRoutingAddress: firstname.lastname@example.org # search result search: 2 result: 0 Success # numResponses: 2 # numEntries: 1
Thus, Postfix will route any message to email@example.com to a mailbox named john.smith at mailhost mail1.internal.