Flexible mail relaying control for sendmail

See the Change History section for information on updates and changes to these rulesets.

At the University of Oregon, we want to prevent general use of our mail systems to relay mail from outside users to other outside users. But we also have complicating factors that make most of the methods currently proposed unsuitable for our purposes. Many of our users want to relay mail through a few well-known server hosts to other users on campus at different hosts. Many users use local ISPs to avoid our congested modem pool, come from clients with many different domain names and IP addresses not in our own network, and (as our support staff assure me) are not particularly willing to change their client SMTP server settings depending on where they dial in from. We also want the flexibility to add or remove permissions for particular domains or IP addresses without having to edit the sendmail configuration directly or restart sendmail.

The rulesets below are my attempt to address these requirements. They also have an uncommon feature: they handle more legal address formats than many commonly-suggested check_rcpt implementations, especially "source route" mail addresses. The user%host@relay and @relay:user@host formats are blessed by the RFCs and handled by sendmail. Many relay-prevention rulesets I've looked at don't even try to handle these address forms, and it's only a matter of time before spammers figure out that they can get around many relaying blocks by using addresses like spam%victim@your.domain. The only other implementation I have seen that attempts to deal thoroughly with such addresses was presented by Robert Harker in his paper "Selectively Rejecting Spam Using Sendmail" in the LISA '97 proceedings. My implementation was influenced by some of the techniques in Harker's paper.

Note that these rulesets are not designed for blocking spam mail to users at your site, although limiting mail relaying will help prevent your systems from being exploited by spammers. A variety of techniques for blocking spam mail to your own systems are presented in references at the end of this page.

The behavior of these rulesets is primarily controlled by four special database maps:

relay_to_domain
A recipient address that matches a domain name in this map will be accepted, no matter who is sending the mail.
relay_to_ip
A recipient address of the form user@[1.2.3.4] will be accepted if the numeric IP address in brackets matches an item in this map. Note that recipient domain names do not have their IP addresses looked up for matching in this map; only addresses using the bracketed IP address format are checked using this map.
relay_from_domain
A client whose domain name matches an item in this map can relay mail through you to any recipient.
relay_from_ip
A client whose IP address matches an item in this map can relay mail through you to any recipient.

All of the database maps are optional, in that if they do not exist, they are treated as empty. You can safely leave out the maps that you don't need. To provide increased performance and some insurance against missing or trashed maps, these rules also check sendmail classes w (all the hosts you accept mail for) and R (domains that you are willing to relay to). You will probably want to put your own domain, and any other critical domains, in class R. An option is also provided to let you accept mail from any client in a host or domain in class R.

In the domain maps, domain names are matched on the right -- uoregon.edu matches darkwing.uoregon.edu, cs.uoregon.edu, asterix.cs.uoregon.edu, etc. In the IP maps, IP addresses of the form 1.2.3.4 are matched on the left -- 128.223 matches all addresses of the form 128.223.*.* and 198.68.17 matches all addresses of the form 198.68.17.*. Note that the rulesets won't match pure top-level domains (com, net, org, uk, etc.). They also won't match "class A" single-octet IP addresses by default, but you can optionally enable such matching.

An example relay_to_domain source file might look like this:

uoregon.edu        *OK*
tzadkiel.efn.org   *OK*

And a relay_from_ip source file might look like this:

128.223            *OK*
198.68.17          *OK*
204.214.99.68	   *OK*

All maps use *OK* as the thing that has to go on the right-hand side (RHS) of the database mapping to avoid the possibility of confusion with valid host names and user names, since the RHS value is substituted into a match. You can also make exceptions for individual hosts by making an entry for that host and using something other than *OK* for the RHS.

The general logic of this check_rcpt implementation is:

  1. Check the client's host name against class w, maybe class R, and the domains in the relay_from_domain map. If found, the address is accepted because you permit that client to relay through you to anywhere else.
  2. Check the client's IP address against the relay_from_ip map. If it is found, then again the address is accepted.
  3. At this point, the client has been established as one you're not willing to permit unlimited relaying for. Check the RCPT TO: address for a domain name or bracketed IP address that you will permit relaying to. Each element of a source route address is checked. If the given address is completely routable through the domains you want to permit relaying to then the address is accepted.
  4. If none of the checks above succeed, then the RCPT TO: is denied with an error message and a permanent error code.

Much of the work is done by the Relay_to ruleset. First the address is passed through ruleset 3, which puts it in a canonical format that's easier to check and places the next recipient domain in a "focus" delimited by <@...>. The domain in the focus is then checked against class w (synonyms for this host), class R (a list of hosts or domains you want to permit relaying to), and finally the database maps that list the domains or bracketed IP addresses you're also willing to relay mail to. If the domain in the focus is permitted, then the focus is removed and Relay_to is invoked on the remaining part of the address. Eventually either the address is stripped down to just a local user part, and Relay_to returns *OK* to indicate the RCPT TO: should be permitted, or a domain that you won't relay to is in the focus, and Relay_to returns *NOTOK*.

This version includes support for FEATURE(mailertable) and for UUCP. If your configuration uses a mailertable, then a recipient domain is looked up in it, and permitted if the domain is found. Recipient domains are also checked against any UUCP relays you have specified in your configuration via the SITECONFIG macro.

While this ruleset is in production use at our site and a couple of others, you are advised to use it at your own risk. I'm not guaranteeing its fitness or effectiveness, but I'm also not placing any restrictions on the use or distribution of this code, other than that you are not to represent it as your own.

Change History

December 18, 1997
Initial release of m4 macros.
December 23, 1997
Bugfix for mailertable handling.
January 12, 1998
Fixed bug in Relay_to rule selected when HACK(rdomains) is not included (pointed out by Jacques Distler <distler@golem.ph.utexas.edu>).
January 22, 1998
Fixed problem with mailertable handling (pointed out by Christopher Masto <chris@netmonger.net>). This fix changes the way mailertable lookups are handled by these rulesets. Previously my assumption was that the right-hand side of a mailertable entry would specify a mailer:domain pair, which is wrong for many possible mailers. Now rather than attempting to check the right-hand side of a mailertable entry, a domain is permitted as a recipient if it exists on the left-hand side of a mailertable entry.
June 23, 1998
Updated instructions to address the frequently-asked question "why does makemap complain about 'hash'?", with the recommendation to either use the "dbm" map type or install Berkeley DB (pointers provided).

Installation Guide

If you are a sendmail novice then you should proceed with caution. These instructions assume you have some familiarity with configuring and operating sendmail.

  1. If you are running sendmail 8.9, then you don't need to install these rules at all, as the "access_db" feature in 8.9 provides nearly all the same functionality. I have not attempted to install or test these rulesets under 8.9.
  2. Download relaycontrol.tar. Change to your sendmail-8.8.x/cf directory and extract it to add these files to sendmail's m4 packages:
  3. In the simplest case, just add the line "HACK(relaycontrol)" to the end of your sendmail configuration .mc file, which I'll call "your.mc" for the rest of this discussion. There are several customization options discussed in README.relaycontrol; if you use these, add them before the "HACK(relaycontrol)" line. In particular, by default these rules use the "hash" database type which depends on having compiled and linked sendmail and makemap with the Berkeley DB library. If your system doesn't have that library, you can use the "dbm" map type, whose routines are available on nearly all UNIX systems in their standard C libraries, or get DB 1.86 from SleepyCat software. Note that for sendmail 8.8.x, you need to use DB 1.85 or 1.86, not DB 2.x.
  4. If, for some reason, you don't want to use the m4 package version or you need to edit this directly into sendmail.cf, you can use this raw sendmail.cf code. Note that this code is not currently maintained or updated with features from the m4 package; if you're bold enough to edit sendmail.cf directly, hopefully you're also smart enough to read the m4 files to get newer features.
  5. Build the configuration with make -f Makefile.dist your.cf. Don't install it yet, though.
  6. If you haven't yet, build the makemap program in sendmail-8.8.x/makemap and install it, as you'll be needing it to build the databases.
  7. Create the desired database source files, and compile them with makemap hash relay_db <relay_db (or use "dbm" instead of "hash", if you don't have Berkeley DB installed). A Makefile can simplify the process of building the databases. Also create your sendmail.cR file, if you specified it above.
  8. Before installing your.cf as your new sendmail.cf, try running it in test mode with sendmail -bt -Cyour.cf. You can check the behavior of the Relay_to ruleset by typing Relay_to address at the test mode prompt. Hopefully addresses for domains you want to relay to will return "OK", and others will report errors. If your new configuration has errors (i.e. missing files, bad tabification of rulesets, etc.), these will be reported when you load it in test mode.
  9. If things look good in sendmail test mode, then you can back up your existing sendmail.cf, copy your.cf to /etc/sendmail.cf, and restart sendmail. It's also a good idea to watch your mail logs for a bit after installing the new configuration to see if mail delivery is still happening properly.

Other check_* rulesets and spam-blocking hints

Eric Allman has his own set of experimental anti-spam measures for sendmail 8.8. My ruleset is largely compatible with the one presented there, including the use of class R to list relay hosts, except that mine is tolerant of more different address formats and uses database maps to extend the set of addresses permitted for relaying.

A variety of other sendmail check_* rulesets (including many other implementations of check_rcpt) are available at Claus Aßmann's Using check_* in sendmail 8.8 page. Some (but not all) of his available check_rcpt rules attempt to check source route addresses.

Chip Rosenthal's MAPS Transport Security Initiative site has more links on relay limitation, including information on how to implement it for other MTA software.

Thanks also go to Bob Jones <bj@oregon.uoregon.edu>, Jacques Distler <distler@golem.ph.utexas.edu>, and Christopher Masto <chris@netmonger.net> for spotting bugs in these rulesets.

Please feel free to contact me with questions or bug reports at the address below.


Steve VanDevender <stevev@hexadecimal.uoregon.edu>
Last modified: Wed Feb 16 14:01:22 PST 2000