Routing
and Alias Management with OpenLDAP and Sendmail
John D'Emic
LDAP and Sendmail offer sys admins considerable advantages for
dynamic mail routing and centralized alias management. A common
requirement, as an organization grows, is to support geographically
dispersed mailservers. While this can be achieved by using subdomaining
(i.e., bill@nyc.acme.com, jane@dublin.acme.com), it is generally
preferable to route the mail dynamically from a single address (jane@acme.com).
I'll explore how this can be accomplished using Sendmail in conjunction
with OpenLDAP.
A similar problem faced by administrators is dealing with growing
and disparate "/etc/aliases" databases. These files are often maintained
individually on a per system basis and, in the case of load balanced
mail servers, need to be kept in sync manually. An alternative to
individually maintaining each file is to migrate the alias data
into an LDAP directory. I'll examine how to configure Sendmail to
use OpenLDAP to accomplish this.
Finally, LDAP APIs exist for practically every common development
platform. This gives the sys admin the ability to implement Web
front-ends, command-line scripts, etc. to manage routing entries
and aliases. It also provides developers an easy entry point for
integrating application functionality with the organization's mail
systems in a standard way.
Installing the Software
To implement this solution, you'll need recent versions of OpenLDAP
and Sendmail. If your system has an OpenLDAP package, that is probably
sufficient. If you choose to build OpenLDAP yourself, however, you
can obtain stable sources from:
http://www.openldap.org
The ./configure script offers options for supporting SASL, TLS, etc.
You won't need these to store mail data in the directory, but you
may want to build OpenLDAP with them anyway (particularly TLS) if
your needs change or if you plan to store sensitive data in the directory.
Your system should have a packaged version of Sendmail available
(and probably installed). You'll need to make sure that it has been
built with LDAP support. You can do this by running the following
command:
# sendmail -d0.1
If you see "LDAPMAP" somewhere after "Compiled With:" you should be
ok. If not, you'll need to configure Sendmail with LDAP support. The
source can be obtained from:
http://www.sendmail.org
Build instructions can be found in the INSTALL folder in the root
of the Sendmail tarball. You'll need to add the following two lines
to the site.config.m4 file:
APPENDDEF(`confMAPDEF', `-DLDAPMAP')dnl APPENDDEF(`confLIBS',
`-lldap -llber')dnl
After you've built and installed Sendmail, run "sendmail -d0.1" again
and make sure the LDAPMAP string appears.
Configuring OpenLDAP
Once OpenLDAP has been installed, you'll need to configure it
to load the necessary schemas to support routing and alias data.
Assuming your OpenLDAP installation has been installed under root,
look in /etc/openldap/schema for a file called "sendmail.schema".
If the file isn't there, you'll need to grab it from the Sendmail
tarball. Copy it from the "cf" subdir in the tarball to /etc/openldap/schema.
Once it's there, edit your slapd.conf file (in /etc/openldap) and
add the following lines to the include lines at the top of the file:
include /etc/openldap/schema/sendmail.schema
include /etc/openldap/schema/misc.schema
Within these schema files, we will be making use of the inetLocalMailRecipient
objectClass and SendmailMTA objectClasses and attributes.
Next we need to configure the suffix. The "suffix" keyword indicates
the base dn of the directory that slapd will be serving. For purposes
of this example, it will be "dc=acme,dc=com".
Following suffix is the rootdn. This is the equivalent of a system
superuser account and is what we'll use to bind and add entries
to the directory. The rootdn line looks like this:
rootdn "cn=Manager,dc=acme,dc=com"
Now we can set a password for the rootdn. This is accomplished with
the "rootpw" directive. Although you can use a plaintext password
here, you're encouraged to use the slappasswd command to generate
a crypted password. Running slappasswd will prompt you for
a password twice then output an encrypted version. Cut and paste this
into slapd.conf as follows:
rootpw {SSHA}SjHPdR/DxUWaUu1iGzhhMVzg2urKqSiR
You can now start the slapd daemon using your system's service facilities
(i.e., /sbin/service) or by manually running "slapd" as root.
Populating the Directory
Assuming you're starting with an empty LDAP directory, the first
step is to set up a base dn for your organization. The base dn for
our fictional organization looks like this (and is the same value
we defined for the suffix field in slapd.conf):
"dc=acme,dc=com"
This will be the root for all other entries in our directory. We'll
create two organizational units now. One will be called "people" and
will contain the mail routing data. (Note that this ou could also
potentially contain posix account information, contact information,
etc.) We will also create an ou called "aliases" that will contain
the alias data.
We can add these to the directory like this:
# ldapadd -x -D "cn=Manager,dc=acme,dc=com" -h ldap.acme.com -W
Enter LDAP Password: <rootdn password typed here>
dn: dc=acme,dc=com
objectClass: dcObject
objectClass: organization
o: acme
dc: acme
<cntrl-d>
dn: ou=people,dc=acme,dc=com
objectClass: organizationalUnit
ou: people
<cntrl-d>
dn: ou=aliases,dc=acme,dc=com
objectClass: organizationalUnit
ou: people
<cntrl-c>
Run the following to confirm the entries have been added:
# ldapsearch -x -b "dc=acme,dc=com"
You should now see the new entries.
Adding Routing Records
We for our fictional acme.com organization will have two locations
-- our corporate headquarters located in Manhattan and an office
in Dublin. We have mailservers in each location called mail.nyc.acme.com
and mail.dub.acme.com. mail.nyc.acme.com is performing primary MX
duties for the acme.com domain and as such is accepting all mail.
We will add routing entries to deliver mail bound for users in New
York onto mail.nyc.acme.com and mail bound for users in Dublin to
mail.dub.acme.com.
Let's assume we have two users, Mike Jones and Jane Jones. Mike
works in New York, and Jane is in Dublin. Mike's Unix username is
mjones, and Jane's is jjones. Their "external" email addresses are
"mjones@acme.com" and "jjones@acme.com". The following LDAP entries
are needed to set up routing for them:
dn: cn=mjones,ou=people,dc=acme,dc=com
objectClass: inetOrgPerson
objectClass: inetLocalMailRecipient
cn: mjones
sn: Jones
mailLocalAddress: mjones@acme.com
mailRoutingAddress: mike@mail.nyc.acme.com
dn: cn=jjones,ou=people,dc=acme,dc=com
objectClass: inetOrgPerson
objectClass: inetLocalMailRecipient
cn: jjones
sn: Jones
mailLocalAddress: jjones@acme.com
mailRoutingAddress: jjones@mail.dub.acme.com
Looking at the LDIF, "inetOrgPerson" is the structural object class
for the entry and is required for OpenLDAP versions 2.1 and up. "inetLocalMailRecipient"
is defined in the misc.schema and provides the "mailLocalAddress"
and "mailRoutingAddress" attributes. "mailLocalAddress" is the user's
email address as it appears inbound to the primary MX server (mail.nyc.acme.com).
mailRoutingAddress defines the address to which we want to route the
mail. In Mike's case, it will go to mail.nyc.acme.com, and in Jane's
case it's going to get directed to mail.dub.acme.com in Dublin.
You can add these entries using the same syntax we used previously
to create the base dn. Alternatively, you can put the entries into
a text file and supply the "-f" flag to "ldapadd".
Maintaining mail routing information like this is extremely flexible.
Let's say Mike needs to work in Dublin for a few weeks and wants
to pull his mail down from the mail.dub.acme.com while there. We
can use "ldapmodify" to change his routing address:
# ldapmodify -x -D "cn=Manager,dc=acme,dc=com" -h ldap.acme.com -W
Enter LDAP Password: <rootdn password typed here>
dn: cn=mjones,ou=people,dc=acme,dc=com
changetype: modify
replace: mailRoutingAddress
mailRoutingAddress: mjones@mail.dub.acme.com
Now Mike's mail will be routed to mail.dub.acme.com. Note that this
happens on the fly. A restart of Sendmail or OpenLDAP is not required.
This is obviously beneficial when maintaining more then a few mail
servers.
Configuring Sendmail for Routing
Now that the directory is populated, we can look at configuring
Sendmail. To begin, you'll need to generate a sendmail.cf. Find
the appropriate sendmail.mc for your system. These are located in
the Sendmail tarball in the "cf/cf" subdirectory. If you're using
your system's Sendmail installation, the mc files are usually in
/usr/share/sendmail or something similiar. You'll want to add the
following lines:
define(`confLDAP_DEFAULT_SPEC', ` -h ldap.acme.com -s sub -b
"dc=acme,dc=com"')dnl
FEATURE(`ldap_routing') dnl
LDAPROUTE_DOMAIN(`acme.com')dnl
Build and install new cf file like this:
# m4 ../m4/cf.m4 sendmail.mc > /etc/mail/sendmail.cf.new
# cd /etc/mail
# cp sendmail.cf sendmail.cf.old
# cp sendmail.cf.new sendmail.cf
Then restart Sendmail and watch the appropriate log files (i.e., /var/log/messages
and /var/log/maillog) for errors.
We'll now enter Sendmail in test mode to confirm everything is
okay:
# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked) Enter
<ruleset> <address> > /map ldapmra mjones@acme.com map_lookup:
ldapmra (mjones@acme.com) returns
mjones@mail.nyc.acme.com (0)
> /map ldapmra jjones@acme.com map_lookup: ldapmra (jjones@acme.com)
returns
jjones@mail.dub.acme.com (0)
Sending some test messages should confirm mail is routing properly.
If you're having issues double-check your /etc/mail/access and /etc/mail/local-host-names
and make sure Sendmail can relay for your domain.
Having the routing set up this way gives us some interesting architectural
options. Let's say we're running a large mailing list server on
mail.nyc.acme.com. As the list membership grows, we decide we want
to dedicate a server for mailing list processing. Instead of changing
the mailing list addresses or jumping through hoops with aliases
or procmail, we can add routing entries for each list and route
it to the dedicated list server.
Routing in LDAP also gives you the ability to segment relay and
delivery. You could potentially have load-balanced relay servers,
each consulting a central LDAP directory for routing decisions.
These relay servers would do no local delivery, but instead relay
each message to the appropriate delivery server. The delivery servers
themselves could be clustered for high availability (and their alias
data all centrally shared via LDAP, as I'll show next.)
Migrating Aliases into LDAP
Now that we have routing data in LDAP, we can move the "/etc/aliases"
database into the directory. We'll take every alias in /etc/aliases
(or /etc/mail/aliases depending on your Sendmail installation) and
represent them like this:
dn: sendmailMTAKey=postmaster,ou=alias,dc=acme,dc=com
objectClass: sendmailMTA
objectClass: sendmailMTAAlias
objectClass: sendmailMTAAliasObject
sendmailMTAAliasGrouping: aliases
sendmailMTACluster: acme.com
sendmailMTAKey: postmaster
sendmailMTAAliasValue: mjones@acme.com
dn: sendmailMTAKey=webmaster,ou=alias,dc=acme,dc=com
objectClass: sendmailMTA
objectClass: sendmailMTAAlias
objectClass: sendmailMTAAliasObject
sendmailMTAAliasGrouping: aliases
sendmailMTACluster: acme.com
sendmailMTAKey: postmaster
sendmailMTAAliasValue: mjones@acme.com, jjones@acme.com
"sendmailMTACluster" will be referenced in the LDIFs as
I'll show next. "sendmailMTAKey" defines the left-hand side of the
alias. "sendmailMTAAliasValue" defines the right-hand side of the
alias. Note that multiple values for the right-hand side of the alias
are comma delimited.
Once this file is populated, you can add it using ldapadd's "-f"
flag. Use ldapsearch to confirm the entries are present.
Configuring Sendmail to read Aliases from LDAP
Now that the aliases have been entered, we can configure Sendmail
to read them. Open the mc file used previously and add the following:
define(`confLDAP_CLUSTER', `acme.com')dnl
define(`ALIAS_FILE',`ldap:')dnl
Take a look at "confLDAP_CLUSTER". This is what we were referencing
with the "sendmailMTACluster" attribute in the alias LDIF. Once the
definitions have been added to the sendmail.mc, you can build and
install a new sendmail.cf.
Test the aliases are working by running the following:
# sendmail -bv postmaster@acme.com mjones@acme.com... deliverable:
mailer esmtp, host
acme.com., user mjones@acme.com
# sendmail -v webmaster@acme.com jjones@acme.com... deliverable:
mailer esmtp, host
acme.com., user john@acme.com
Now we can send some test messages and confirm that alias expansion
is working.
Writing Some Scripts
As mentioned before, LDAP APIs are available for practically every
commonly used programming language. We'll take a look now at a pair
of very simple Perl scripts that illustrate some of the basic concepts
of LDAP programming. The first script, ldap_alias_view.pl (Listing
1), will display all the aliases present in the directory. The second
script, ldap_alias_add.pl (Listing 2), will allow us to add new
aliases. These can be used as a basis for developing a suite of
tools to manage routes and aliases in LDAP.
We'll be using the Perl-LDAP tools in the script. You can install
these from CPAN by executing the following:
perl -MCPAN -e "install Net::LDAP"
ldap_alias_view.pl
Digging into this script after the use directives, lines 7 through
10 define the LDAP variables we'll be using. The fun starts on lines
11 through 13 where it opens a connection to the LDAP server. Next,
it performs a search operation against the directory limiting the
results to objects of the "sendmailMTAAlias" class. Finally, it
iterates through the results and prints them using the "get_value"
method of $entry.
ldap_alias_add.pl
This script takes two arguments, the left-hand side value of an
alias and the right-hand side value of the alias. The script begins
just like ldap_alias.view.pl. The difference is that we must supply
a bind dn and password to be able to add the aliases to the directory.
(Although the topic is beyond the scope of this article, OpenLDAP
supports GSSAPI as an authorization mechanism. This can potentially
eliminate the need to store plaintext passwords in a script like
this.) By the time we reach line 26, we are ready to add the entry
to the directory. Note how the hash corresponds to the LDIF entries
we manually added before. In production, this script would likely
perform validation of the email addresses before committing them
to the directory. (Email::Valid from CPAN is particularly useful
for this.)
Conclusion
In this article, I've provided a basic introduction to the flexibility
gained by integrating OpenLDAP with Sendmail. Some topics you might
want to investigate once you're comfortable with the basics include
GSSAPI authentication, directory replication using slurpd, and mailHost
routing attributes.
John D'Emic has been splitting time between systems administration
and development for the past eight years. He has a computer science
degree from St. John's University and is currently employed by Opsource,
Inc. He can be contacted at: jdemic@opsource.net. |