6. Mail Transfer Agents

i will describe three different MTA's in this section, Sendmail, Postfix and Qmail. These are three MTA's that can be configured to use Ldap for the retrieval of information. From personal experience, I must say that Postfix is much easier to set up than sendmail, but this may change in the future, as the ldap support in sendmail develops to a more mature state. I have not used qmail myself.

6.1. Sendmail

6.1.1. Ldap support in sendmail

Sendmail has support for ldap since somewhere around version 8.8.x using the map type ldapx.From version 8.10 and up an ldap database type ldap is supported. Please note that the ldap map support is not enabled per default in the RedHat package. Debian versions 2.2 and later do have ldap support in their sendmail, I am told. If you have to compile it yourself, please read the file sendmail/README from the sendmail sources. It contains valuable information about how to compile in the ldap support.

Both the old and the new ldap map type have the ability to look up entries in an ldap database. There is one thing that must be noted however. When a search is being done, only one result should be returned. If more results are found, only the first is used. Additionally, is multiple return values for that result are found, only the first is returned. Let's take a look at the following example ldif file:

dn: cn=mailuser1,ou=mail,dc=company,dc=com
objectclass: top
objectclass: foo
cn: mailuser1
mail: [email protected]
mail: [email protected]

If a search is performed with a simple search filter like cn=mailuser1, and the attribute that is asked for is mail, only [email protected], is returned. To get both results, they should we stored in a single-valued attribute with comma-separated values, like this:

mail: [email protected], [email protected]

An email message containing information related to this subject can be found at the LIH home.

6.1.2. System layout.

When ldap maps are available, almost anything can be looked up in an ldap database. What we would like to do is to simplify the configuration of the following setup.

Let's say we have a medium-sized or large network where we recieve for many domains. We have two mailhosts and two or three fallback hosts. This setup will normally have four places where three types of information are stored.

  • The mailhosts both need a file , or traditionally sendmail.cw, to store the domains for which they should recieve mail. The fallback hosts keep the same information in the access file, but they use it to list the domains for which they should relay any incoming mail.

  • The mailhosts both have a virtusers file, which maps multiple adresses (or entire domains) to a single virtual or local user.

  • The mailhosts both have an aliases file, which maps virtual users to one or more mail addresses or local users.

If this information is stored in a single database, each host will read its configuration from that database, which improves both the manageability and the scaleability of the network. One could even think of a single host which carries all data, mapped to with nfs. In such a case it makes no difference anymore to which host we are connecting. To a user, they appear to be all the same.

6.1.3. Sendmail configuration file

To understand how this information is read from an ldap database instead of the regular files a little background knowledge of the sendmail.cf file is neccesary. The information we're dealing with here is stored in two different ways. The local-host-names file is read into a class (class w, to be exactly, hence the old extension cw), while the virtusers file is used through a simple map. The aliases file is also a map, but it is defined in a different manner and used internally, instead of being referred to in rules.

When information is retrieved from a ldap database, it always ends up being in a map. This is somewhat problematic with the information stored in the local-host-names file, because this used to be a class. I have been unable to fill the class with the information from the map or something alike. That would be the easy way, but I think it's not possible. (If I'm incorrect about this, please let me know). Therefore, I had to define a new map, and insert rules in the sendmail configuration to make sure that (almost) every time when a value is looked up in the class, the new map is searched for the value too.

For the maps, the change of configuration is easy. A map is normally defined with a name and database type, and some database-specific options, (like the location of the file, for the normally used newdb database types). So for maps, it suffices to change the definition of the map and voila, we're done. Ldap maps have a few more options, some of which can be predefined. They are explained in the following list (largely taken from Booker Bense's document):

Ldap-specific map options in sendmail.cf

-h

Defines a space-separated list with hostnames of ldap servers. All machines will be queried in this order, until a result is found. This can be configured globally.

-b

Defines the ldap search base, e.g. the ldap directory you are going to search in. This can be configured globally.

-k

Defines the ldap search filter. It is a "sprintf" style string that defines how the map takes it's input value and constructs an ldap search. It has the form of an ordinary ldap search filter, with %s replaced by the value that is searched for. To learn more about ldap search filters please see RFC 2254. The search filter and the search base used above should define a search that returns at most one entry. The ldap map will only use the first entry it recieves.

-v

Defines the ldap attribute of which the value will be returned by the map lookup. Explained in detail later.

PLease note that all ldap options must be double-quoted and must immediately follow the sendmail option. Here is an example:

Kldapexamplemap ldap -h"localhost ldap.myorg.com" -b"ou=mail,dc=myorg,dc=com" -k"(mailstuff)(uid=%s))" -v"mailaddress"

6.1.4. Schema

For this particular setup I have defined a subtree mail in the ldap directory, under which all mail-related information will be stored. I t would have been possible to store some of the user-related mail-information in the ou=Users subtree, but I have specificalle chosen not to do this. When using a separate subtree, all information for sendmail is stored in a single place, and, when having many users, searches may be faster, because not the entire ou=Users subtree needs to be searched, but only the ou=mail subtree.

In this subtree two kinds of records will appear.

  1. Entries that hold mappings from the virtuser file and aliases file, for a single virtual user. I have chosen to store both mappings in a single entry, because this clarifies the effect and configuration used. For this I have defined an objectclass inetmailrecipient, and three attributes, mailid, mailacceptinggeneralid and maildrop.

    inetmailrecipient

    This is a classification that tells us the entry is some form of mapping from one or more real mail addresses and/or mail domains to one or more real users.

    mailid

    This describes the mail addresses that this virtual user recieves mail for. Can be in the form of normail addresses, like [email protected], or complete domains like @my2nd.org. Multiple of these attributes may be present, but they should all contain only one value. For each of these id's mail is sent to mailacceptinggeneralid.

    Here you'll put what you used to put at the left side in the virtusers file.

    mailacceptinggeneralid

    Defines a virtual user. This is in fact the link between the virtusers and aliases file. In each entry, one attribute of this type must be present, but no more than one may be present, and the attribute may contain only one value. This can either be a local username, or a virtual user. In the second case, a maildrop attribute must be present, in the first case, it does not.

    Here you'll put what you used to put in the virtusers file, and at the left side in the aliases file.

    maildrop

    Defines the addresses and/or users that mail received for should be delivered to. Only one attribute of this type may be present, but it may contain a comma-separated list of addresses and/or users. If the value of mailacceptinggeneralid is a virtual user, this attribute must be present. If the value is a real user, this entry may be omitted.

    Here you'll put what you used to put at the right side in the aliases file.

    In general, one could say that the mailid and mailacceptinggeneralid together provide the functionality of the virtusers file, and mailacceptinggeneralid and maildrop together provide the functionality of the aliases file.

  2. One or more entries that hold the domain names which would normally apear in the sendmail.cw and access files. For this I have defined an objectclass inetmaildomain, and three attributes, maildomain, sendmailislokalkey and sendmailaccesskey.

    inetmaildomain

    This is a classification that tells us the entry lists mail domains which belong to our system, and either should be delivered locally, or be relayed to another host.

    maildomain

    Defines the mail domain. Multiple of these attributes may be presentin a single entry. The value should be the domain without the "@".

    For each of the dmain entries in the local-host-names file, one of these attributes should be present.

    sendmailislocalkey

    This defines a simple key that is used in the sendmail rules to determine if a domain is local. It can be anything really, but is must be the exact string you will use in your sendmail rules. For now I have used <LDAPLOCAL>. One attribute of this type must be present, and no more than one may be present in each entry.

    sendmailaccesskey

    This defines the key that is used in the sendmail rules to determine what action needs t be taken for the specified domain. It can be one of RELAY, OK, REJECT, DISCARD or an error indicator. (For detailed information see the cf/README file from the sendmail sources.)

    Note: PLease note that in this particular setup I will only be using entire domains in the access file. That means that in this case I can use the maildomain attribute for both the information used in the access file and the information used in the local-host-names. If you want a finer control over your access list, you should define a separate entry to hold that information, instead of using the maildomain attribute.

6.1.5. More information.

Here are a few sources of information that might be useful:

  • Booker Bense has written a document about the usage of ldap with sendmail 8.9.3. He says it's the wrong place to start when learning about using sendmail and ldap, but it has been of great help to me.

  • A short howto about how to setup sendmail with ldap, written by Jason Christopher Radford

  • New articles about ldap and sendmail are being published on sendmail.net, written by Michael Donnelly, originating from ldapmap.org, which has also a lot of general interesting ldap-related information.

6.2. Postfix

6.2.1. Support

Postfix has native ldap support. A lot of options in postfix can be configured using maps which can be of various types. One of those types is ldap. For each ldap map a couple of options can be configured. (See Section 6.2.2.)

The process of having postfix look up certain data in an ldap database is pretty straightforward. The most common use (so i think) is to have postfix look up virtual users in the ldap database. Together with the above explaind nss_ldap feature, this allows you to have all your email users in an ldap database. But other things can be configured too, like the domains postfix is allowed to relay mail for, or relay mail from, or for which is should act as a backup mail server.

6.2.2. Configuration

The description of the configuration options is completely taken from LDAP_README in the postfix docs from version 20001217.

server_host

The name of the host running the LDAP server, e.g.

ldapsource_server_host = ldap.your.com

It should be possible with all the libraries mentioned above to specify multiple servers separated by spaces, with the libraries trying them in order should the first one fail. It should also be possible to give each server in the list a different port, by naming them like "ldap.your.com:1444".

server_port (389)

The port the LDAP server listens on, e.g.

ldapsource_server_port = 778

search_base (No default; you must configure this.)

The base at which to conduct the search, e.g.

ldapsource_search_base = dc=your, dc=com

timeout (10 seconds)

The number of seconds a search can take before timing out, e.g.

ldapsource_timeout = 5

query_filter (mailacceptinggeneralid=%s)

The RFC2254 filter used to search the directory, where %s is a substitute for the address Postfix is trying to resolve, e.g.

ldapsource_query_filter = (%s)(paid_up=true))

domain (No default; you must configure this.)

This is a list of domain names, paths to files, or dictionaries. If specified, only lookups ending in a domain on this list will be searched. This can significantly reduce the query load on the LDAP server.

ldapsource_domain = postfix.org, hash:/etc/postfix/searchdomains

result_attribute (maildrop)

The attribute(s) Postfix will read from any directory entries returned by the lookup, to be resolved to an email address.

ldapsource_result_attribute = mailbox,maildrop

special_result_attribute (No default)

The attribute(s) of directory entries that can contain DNs or URLs. If found, a recursive subsequent search is done using their values.

ldapsource_special_result_attribute = member

scope (sub)

The LDAP search scope: sub, base, or one. These translate into LDAP_SCOPE_SUBTREE, LDAP_SCOPE_BASE, and LDAP_SCOPE_ONELEVEL.

bind (yes)

Whether or not to bind to the LDAP server. Newer LDAP implementations don't require clients to bind, which saves time. Example:

ldapsource_bind = no

If you do need to bind, you might consider configuring Postfix to connect to the local machine on a port that's an SSL tunnel to your LDAP server. If your LDAP server doesn't natively support SSL, put a tunnel (wrapper, proxy, whatever you want to call it) on that system too. This should prevent the password from traversing the network in the clear.

bind_dn ("")

If you do have to bind, do it with this distinguished name. Example:

ldapsource_bind_dn = uid=postfix, dc=your, dc=com

bind_pw ("")

The password for the distinguished name above. If you have to use this, you probably want to make main.cf readable only by the Postfix user. Example:

ldapsource_bind_pw = postfixpw

cache (no)

Whether to use a client-side cache for the LDAP connection. See ldap_enable_cache(3). It's off by default.

cache_expiry (30 seconds)

If the client-side cache is enabled, cached results will expire after this many seconds.

cache_size (32768 bytes)

If the client-side cache is enabled, this is its size in bytes.

dereference (0)

When to dereference LDAP aliases. (Note that this has nothing do with Postfix aliases.) The permitted values are those legal for the OpenLDAP/UM LDAP implementations:

0never
1when searching
2when locating the base object for the search
3always

6.2.3. Example setup

If you want a virtual domain (say foo.virtualdomain.com) and you want to store email addresses in Ldap for this domain, youwould need the following piece in your main.cf.

virtual_maps = ldap:ldapvirtual
ldapvirtual_search_base = ou=mail,o=YourOrg,c=nl
ldapvirtual_query_filter = (mailacceptinggeneralid=%s)
ldapvirtual_domain = foo.virtualdomain.com
ldapvirtual_result_attribute = maildrop
ldapvirtual_bind = no
ldapvirtual_scope = one

With this setting, if postfix receives mail for a user with a domain part "foo.virtualdomain.com", to do a search in the database for entries that have an attribute mailacceptinggeneralid that matches "[email protected]". If such an entry is found, the values of all available maildrop attributes are returned, and to these values the mail is delivered. If "[email protected]" is not found, another query is performed, that tries to match the catchall user, "@foo.virtualdomain.com". If this is again not found, the message will be bounced.

6.3. Qmail

Qmail itself does not have any ldap support. However, there is a patch by Andre Oppermann that provides ldap support. This package, including the documentation for it, can be found at his site.