Address rewriting with sendmail genericstable

Paul Heinlein
First published on January 19, 2006
Last updated on January 25, 2006

In mid-2005, Galois, Inc., my former employer, took over the care and feeding of cvs.haskell.org, the public code repository for several significant open-source Haskell projects. All active developers have accounts on the machine, so there are several dozen local accounts.

Whenever a developer submits a change to the repository, a backend script formats and sends an e-mail message detailing the change for other developers interested in that section of code. Since the commit scripts run under each developer’s local user account, the automated e-mail messages were addressed From: <username@cvs.haskell.org>.

The problem is that neither Galois nor the developers wanted to maintain a separate cvs.haskell.org e-mail server. Galois didn’t want the hassle of maintaining a separate mail domain, and the developers were leary of being assigned yet another e-mail address.

The ideal solution

Ideally, outbound messages would appear with a From header that reflected each developer’s e-mail address of choice. Similarly, any message delivered to a local account, such as an error message from a cron job, would be sent off to the same remote address.

Aliases

The easy part of the final solution was to use aliases to ensure that mail addressed to the developers’ local accounts would be delivered to their preferred remote e-mail address. This would ensure that error messages from cron jobs or automated scripts would actually get read. The aliases database, typically found at /etc/mail/aliases, is well known. It’s included and populated on nearly every system that ships with sendmail as the main mail transport agent.

Simply, each line includes the local mail alias, a colon, and the real destination for mail addressed to that local alias. The real destination may be a local account, a program (like Mailman), or, in this case, a remote e-mail address.

bob:  robert.smith@hisdomain.net
kate: kate@school.ac.uk

After editing the aliases file, you need to run newaliases to translate your aliases into the binary database format sendmail can read.

Generics table

The tougher issue was masquerading locally generated messages, especially CVS commits, so they appeared to be from a developer’s remote e-mail address. Sendmail has long been able to masquerade local users as all coming from a single domain, but I’d never had to masquerade each local account from its own domain.

The solution lay in sendmail’s genericstable feature. The sendmail documentation says, “This feature will cause unqualified addresses (i.e., without a domain) and addresses with a domain listed in class {G} to be looked up in a map and turned into another (“generic”) form, which can change both the domain name and the user name.”

Unlike the aliases feature, however, getting the genericstable feature to work will likely mean that you’ll have to modify your sendmail.cf configuration file. For most of us, that means editing the m4 source file (typically, but not always, /etc/mail/sendmail.mc). In my case, the additions were minor:

FEATURE(`genericstable')dnl
GENERICS_DOMAIN(`localhost.localdomain')dnl

The format of the genericstable file is, annoyingly, just slightly different from that used by aliases. In this case, there are two whitespace-separated columns, the first containing the local username, the second the remote address.

bob     robert.smith@hisdomain.net
kate    kate@school.ac.uk

After populating /etc/mail/genericstable, it’s necessary to build the binary representation sendmail will use:

makemap hash /etc/mail/genericstable < /etc/mail/genericstable

Any e-mail messages generated by local username bob will have their From header rewritten to appear to be from <robert.smith@hisdomain.net>.

All done!

Once the aliases and genericstable databases are in place, sendmail is able to deliver mail addressed to local accounts to their proper remote mailboxes and masquerade locally generated messages as being from those same mailboxes. Handy, eh?