Enterprise mail routing with Postfix and LDAP

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.113730.3.2.3
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 john.smith@mail1.internal, 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):

  • sample.user@example.com
  • sample_user@example.com
  • sampleuser@example.com
  • sample@example.com
  • john.smith@example.com

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 john.smith@mail1.internal, and so, john.smith@mail1.internal is the real address where messages sent to John Smith, using any of the previous e-mail addresses, must be routed to. Thus, john.smith@mail1.internal 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 john.smith@mail1.internal 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: sample.user@example.com
mail: sample_user@example.com
mail: sampleuser@example.com
mail: sample@example.com
mail: john.smith@example.com
objectClass: top
objectClass: mailRecipient
uid: john.smith
mailRoutingAddress: john.smith@mail1.internal

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.

Configuring Postfix

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 /etc/postfix/ldap-aliases.cf:

# 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 = no tells Postfix to perform an unauthenticated (anonymous) BIND against the LDAP directory.
  • size_limit = 1 tells 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 scope search scope.

    The search scope is one of: sub, one, base.

  • server_host defines one or several LDAP hosts, trying them in order should the first one fail.
  • query_filter defines 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 sample.user@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)(mail=sample.user@example.com))" 
mailRoutingAddress
# extended LDIF
#
# LDAPv3
# base  with scope sub
# filter: (&(objectclass=mailRecipient)(mail=sample.user@example.com))
# requesting: mailRoutingAddress
#

dn: uid=john.smith,ou=People, dc=example, dc=com
mailRoutingAddress: john.smith@mail1.internal

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

Thus, Postfix will route any message to sample.user@example.com to a mailbox named john.smith at mailhost mail1.internal.

Advertisements

9 thoughts on “Enterprise mail routing with Postfix and LDAP

  1. Hello there! First of all, very good article.
    I found it searching for a solution for my problem.

    Do you use cyrus as your mail backend store?

    And what about virtual domains? Several of them!

    I have a similiar setup, but since I’m setting the cyrus virtdomains to on or userid and unixhierarchysep to yes, this has gave me a major problem since, now, I cannot route mail like i used to. Ie, previously we did it just like you do, username_domain_tld@backend, now with the new cyrus setup, the mailbox is not called username_domain_tld anymore, it’s now called as username@domain.tld; We added the domains cuz there might be 2 mailboxes with the same name but in diferent mailboxes, for example, the postmaster@domain1 and postmaster@domain2

    This brings an RFC problem username@domain.tld@backend, ie, the 2 @’s. Now we can’t get the mail to be routed. If our mailRoutingAddress is set to username@domain.tld@backend, the alias won’t even be found.

    You seem to have enough experience, at least to point me into some direction.

    Please!

  2. Pingback: miniGeek.org » openLDAP, Postfix & CourierIMAP

  3. Keep on going and the chances are you will stumble on something, perhaps when you are least expecting it. I have never heard of anyone stumbling on something sitting down.

  4. hi,

    I was facing one problem which is having quiet similar messaging solution.

    We are having our mail server in redundancy mode were as in redhat cluster suite in active passive mode. Openldap server is acting as a backend for storing user information.

    Since more than 5000 users are there we are planning to distribute the mail service load by keeping two mailbox server and one smtp server.

    our smtp server will be integrated with openldap for authentication and whenever the users logs in , our smtp server needs to verify LDAP server and query the mailhost attribute for the respective mailbox server.

    Will the above mentioned solution will work or not? What is the purpose of mailhost attribute in openldap. Will it was used for the same purpose which i was mention?

  5. Together with almost everything which seems to be building within this subject matter, all your opinions are rather stimulating. Nonetheless, I appologize, but I can not subscribe to your entire plan, all be it stimulating none the less. It looks to us that your opinions are generally not entirely validated and in fact you are generally yourself not really totally certain of your point. In any case I did appreciate reading through it.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s