Chapter 3. Relaying

Introduction

A mail relay is a system that resends mail that it receives. When mail that should be delivered by some other host arrives at the mail relay host, the system decides whether it should relay the mail. If relaying is allowed, the relay host sends the mail on to the destination address. If relaying is denied, a “Relaying denied” error message is returned to the sender. This chapter contains recipes that control when relaying is allowed or denied and recipes to configure a system to make use of a mail relay.

Relaying is different from forwarding. Mail that is forwarded arrives at the system addressed to the local host; it is forwarded only if the host is instructed to do so by the aliases database or the .forward file. Mail that is relayed arrives at the system addressed to some other host; it is only relayed if sendmail is configured to allow relaying.

In the same way that sendmail must be configured to act as a mail relay, a system must be configured to use a mail relay. Any system running sendmail can directly deliver its own mail; sendmail does not depend on relays by default. However, there are a variety of different sendmail configurations that use relay servers:

Mail is sent to the mail relay host via the SMTP relay mailer. The configuration of the relay mailer can be changed with the m4 macros RELAY_MAILER_ARGS, RELAY_MAILER_FLAGS, RELAY_MAILER_QGRP, and RELAY_MAILER_MAXMSG. sendmail can even be configured to use a different mailer for relaying by specifying a different mailer name with the confRELAY_MAILER define. However, changing the mailer name or fiddling with the relay mailer configuration is generally a bad idea because it creates an unnecessarily complex configuration that must be maintained. It is better to configure a mail relay host that is capable of handling standard SMTP mail than it is to create a custom mailer for every system that uses the relay host for the simple reason that there are fewer systems to maintain.

By default, sendmail does not relay mail—thus, a default sendmail system does not consider itself a mail relay. There is a good reason for this: relaying opens a system to the possibility of being abused by spammers. Spammers love to find a system that they can relay through in order to hide the true source of the spam mail. Everything you do to create a relay weakens this security. Therefore, care must be taken to use only those configuration tools that you really need to get the job done.

Several features are available that turn a sendmail system into a mail relay:

promiscuous_relay

This feature tells sendmail to relay mail from any source to any destination. It should not be used on a system that is accessible from the Internet because it creates an open relay that will quickly be found and exploited by spammers.

relay_local_from

This causes sendmail to relay any mail in which the envelope sender address contains the name of a host in the local domain. Because the envelope sender address in mail is easily forged, spammers can exploit a relay that uses this feature. For this reason, the relay_local_from feature should be avoided.

relay_mail_from

This feature tells sendmail to relay mail if the envelope sender address of that mail contains the name of a host in any domain listed in the relay-domains file or listed as RELAY in the access database. Because the envelope sender address in mail is easily forged, spammers can exploit a relay that uses this feature if they can figure out the entries in the relay-domains file or the access database. For this reason, the relay_mail_from feature should be avoided.

relay_based_on_MX

This feature enables relaying for any host or domain that lists the local host as its mail exchanger. The disadvantage of this feature is that you lose direct control over which systems can use your system as a relay, and you place that control into the hands of others—the domain administrators. Domain administrators can simply place MX records in their domains to enable relaying through your system.

relay_entire_domain

When this feature is specified, sendmail relays mail for any host in a domain identified in class $=m. Class $=m contains the name of the local host’s domain, as determined by sendmail during startup. Thus, this feature enables relaying for hosts in the local domain.

relay_hosts_only

By default, values in the relay-domains file or the access database are interpreted as domain names, and relaying is granted to any of the hosts in those domains. This feature changes that. When relay_hosts_only is specified, the values in the relay-domains file and in the access database are interpreted as hostnames, and mail is only relayed for the specific hosts listed in those files.

Other than the relay_hosts_only feature, which works with the relay-domains file and the access database, the features listed above tend to reduce the amount of control the sendmail administrator has over relaying. Generally, a better way to enable relaying is by using the relay-domains file. Using the relay-domains file requires no special m4 configuration because sendmail reads this file by default. To use it, all you need to do is create a text file named relay-domains that contains a list of the domains for which relaying is allowed.

Entries in the relay-domains file enable relaying to or from the domains listed in the file. To have more control over the condition in which relaying is approved, use the access database. The access database is not designed specifically for relaying—it has broader security applications. However, it can be used to control relaying as demonstrated in Recipe 3.10.

For maximum security, use SMTP AUTH or STARTTLS to authenticate the hosts granted relay privileges. Chapter 7 and Chapter 8 cover these security protocols.

Because spammers may abuse a mail relay, special care should be taken to thoroughly test the relay configuration. If your server fails any of the tests, adjust the configuration to close the security hole. No tests are infallible, but they do provide clear indications of possible configuration problems.

Create a minimal sendmail configuration containing only an OSTYPE statement to specify the correct operating system and a FEATURE command to select the nullclient feature:

# cd /usr/local/src/sendmail-8.12.9/cf/cf
# cat > sendmail.mc
               VERSIONID(`Recipe 3.1 nullclient master configuration file.')
               dnl Select the correct operating system
               OSTYPE(`linux')
               dnl Select the nullclient feature and specify the relay server
               FEATURE(`nullclient', `smtp.wrotethebook.com')
               Ctrl-D

Build the new sendmail.cf file, copy it to the correct path, and restart sendmail. An example of building and installing a sendmail.cf file is shown in the last step of Recipe 1.8.

Discussion

Any Unix system running sendmail, even when the system is used as a single-user workstation, is fully capable of handling its own mail. Some workstations configure sendmail to handle both inbound and outbound mail—just like a mail server. Many Unix workstations, even those that depend on a mail server to collect their inbound mail, have a full sendmail configuration for handling outbound mail. Yet, it is also possible to create a very simple sendmail configuration on a workstation that relies on a mail server for both inbound and outbound mail service. When the nullclient feature is used, a system sends all of its mail through a mail relay host.

Most of the recipes in this book show configuration lines that are added to a pre-existing sendmail configuration. This recipe does not; the Solution section shows the complete configuration file. The nullclient configuration contains only two essential lines: the OSTYPE macro that identifies the client’s operating system and the FEATURE macro that configures the nullclient feature. The nullclient feature has two fields. The first is the feature name: nullclient. The second is the name of the relay host to which outbound mail is sent. The format of the second field is mailer:host., where mailer is the name of a mailer defined in the configuration and host is the domain name of the relay host. The basic nullclient configuration contains only the local and prog mailers, and the five SMTP mailers: smtp, esmtp, smtp8, dsmtp, and relay. mailer defaults to relay if no other mailer name is provided. This default is correct and should only be changed if you add other mailers to the configuration and have a specific reason for using another mailer. We allow mailer to default to relay in the sample configuration, and we recommend that you do the same.

The server value is assigned to the $S, $H, and $M macros in the sendmail.cf configuration file. These macros are, respectively, the smart host, mail hub, and masquerade macros. sendmail:

A few sendmail -bv tests show the impact of the nullclient configuration on mail delivery:

# sendmail -bv tyler@example.com
tyler@example.com... deliverable: mailer relay, host smtp.wrotethebook.com, user 
tyler@example.com
# sendmail -bv sara@crab
sara@crab... deliverable: mailer relay, host smtp.wrotethebook.com, user 
sara@crab.wrotethebook.com
# sendmail -bv craig
craig... deliverable: mailer relay, host smtp.wrotethebook.com, user 
craig@smtp.wrotethebook.com

The host value displayed by the first test shows that mail addressed to a user on an external host is sent through the mail relay host. In the generic configuration, mail addressed to an external system is sent directly to that system or to its MX server, using the esmtp mailer. On the nullclient system, the mail is sent to the relay host using the relay mailer. In this example, the client then relies on smtp.wrotethebook.com to relay the mail to tyler@example.com.

The second test is very similar to the first. In the second test, mail is addressed to another host within the local wrotethebook.com domain. Again, the mail is sent to the relay server instead of directly to the external host.

Probably the most interesting is the third test, which shows how mail is delivered to the local username craig. Normally, mail addressed in this manner would be handled by the local mailer and delivered directly to the local user’s mailbox. Under the nullclient configuration, this mail is sent to the relay host for delivery, even though the mail is addressed to a user who has an account directly on the local system.

Note that the -bv test is often the best way to view delivery information. In this particular case, a test using -bt could have given a confusing result. For example, assume you decided to run sendmail -bt and use the /parse command to examine the mail delivery triple. You would see something like the following:

# cat > special-test
               /parse tyler@science.foo.edu
               /parse sara@crab
               /parse craig
               CTRL-D
# sendmail -bt < special-test | grep '^mailer'
mailer relay, host smtp.wrotethebook.com, user tyler@science.foo.edu
mailer relay, host smtp.wrotethebook.com, user sara@crab.wrotethebook.com
mailer local, user craig

The first two results obtained by /parse provide the correct information. The third result, however, is potentially misleading. You might look at this and think that, even with the nullclient configuration in place, mail addressed to local users is handled by the local mailer instead of being forwarded to the relay server. The problem comes from assuming that parsing the delivery address is the end of the story. In this case, it isn’t. Because the mailer is the local mailer, the delivery address is processed through the aliases database. When sendmail finishes aliasing, it processes the localaddr ruleset (ruleset 5) if the local mailer has the F=5 flag set, which it does by default. It is the localaddr ruleset that then decides this mail needs to be sent to the mail relay, as the following -bt test shows:

# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> localaddr craig
localaddr          input: craig
MailerToTriple     input: < server . wrotethebook . com > craig < @ smtp . 
wrotethebook . com >
MailerToTriple   returns: $# relay $@ smtp . wrotethebook . com $: craig < @ smtp . 
wrotethebook . com >
localaddr        returns: $# relay $@ smtp . wrotethebook . com $: craig < @ smtp . 
wrotethebook . com >
> /quit

It can be confusing trying to remember when to call each ruleset. In general, it is easier to use -bv when all you want is information about the mail delivery.

In addition to sending all mail through the relay server, the nullclient configuration replaces the hostname of the client with the name of the server in the email sender address. A sendmail -bt test demonstrates this function of the nullclient configuration:

# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> /tryflags HS
> /try relay kathy@giant.wrotethebook.com
Trying header sender address kathy@giant.wrotethebook.com for mailer relay
canonify           input: kathy @ giant . wrotethebook . com
Canonify2          input: kathy < @ giant . wrotethebook . com >
Canonify2        returns: kathy < @ giant . wrotethebook . com . >
canonify         returns: kathy < @ giant . wrotethebook . com . >
1                  input: kathy < @ giant . wrotethebook . com . >
1                returns: kathy < @ giant . wrotethebook . com . >
HdrFromSMTP        input: kathy < @ giant . wrotethebook . com . >
PseudoToReal       input: kathy < @ giant . wrotethebook . com . >
PseudoToReal     returns: kathy < @ giant . wrotethebook . com . >
MasqSMTP           input: kathy < @ giant . wrotethebook . com . >
MasqSMTP         returns: kathy < @ giant . wrotethebook . com . >
MasqHdr            input: kathy < @ giant . wrotethebook . com . >
MasqHdr          returns: kathy < @ smtp . wrotethebook . com . >
HdrFromSMTP      returns: kathy < @ smtp . wrotethebook . com . >
final              input: kathy < @ smtp . wrotethebook . com . >
final            returns: kathy @ smtp . wrotethebook . com
Rcode = 0, addr = kathy@smtp.wrotethebook.com
 > /quit

The /tryflags command specifies the particular address we wish to process. In this case, we ask to see the processing of the header/sender (HS) address.[1] The /try command specifies the mailer for which the address should be processed (relay) and the email address that is to be processed (kathy@giant.wrotethebook.com). The result shows that the sender’s hostname (giant.wrotethebook.com) is replaced by the mail relay host’s name (smtp.wrotethebook.com). This example uses the sender’s fully qualified domain name, but it could have used any hostname alias found in class $=w. Any of them would be replaced by smtp.wrotethebook.com in this test.

The two line configuration shown in this recipe sends all mail that originates on the local host to the relay for processing. It also masquerades that mail so that it appears to originate from the mail relay host. The nullclient configuration is intended for systems that depend on the mail server for all email needs. The classic examples of systems that might use such a configuration are diskless clients that depend completely on a server. But the benefits of the nullclient configuration have far wider utility. Centralizing mail services can simplify queue management, mail policy control, security, and logging. Many sites use the nullclient configuration on desktop workstations.

Replacing the hostname in the sender address is called masquerading, and it is covered extensively in Chapter 4. The third edition of sendmail covers the nullclient configuration in 4.8.33. Recipe 3.2, Recipe 3.3, and Recipe 3.4 all provide configurations that use a mail relay host; evaluate those recipes if the nullclient feature does not match your needs.

3.2. Passing Outbound Mail to a Relay

Discussion

The SMART_HOST define sets a value for the sendmail.cf $S macro. The $S macro identifies the smart host. sendmail uses the smart host to relay mail to external systems. A few -bv tests show the impact of the SMART_HOST define:

# sendmail -bv sara@crab
sara@crab... deliverable: mailer relay, host smtp.wrotethebook.com, user sara@crab.
wrotethebook.com
# sendmail -bv tyler@example.com
tyler@example.com... deliverable: mailer relay, host smtp.wrotethebook.com, user 
tyler@example.com
# sendmail -bv craig
craig... deliverable: mailer local, user craig

The first two tests show that all mail addressed to an external host, whether the host is part of the local wrotethebook.com domain or part of some other domain, is sent to the smart host for delivery to the external host. The third test shows that all mail that can be delivered by the local mailer is kept on the local system and delivered by the local mailer.

This configuration differs from the nullclient configuration in two ways:

  1. Local mail remains on the local host.

  2. Mail that originates from the local host has the local host’s hostname in the sender address.

This creates a sendmail configuration that can handle its own local mail but must depend on a mail relay host for access to external systems. The concept of a smart host originally developed because many organizations had limited Internet connectivity. In those days, mail was relayed through the smart host because the smart host was the only host that could deliver the mail. These conditions still exist in some locations, but a more common reason that this configuration is popular is because network managers want to route all outbound mail through a single server for increased security, simplified queue management, and centralized logging.

Discussion

The MAIL_HUB define sets a value for the sendmail.cf $H macro. The $H macro identifies a mail hub that sendmail uses to deliver local mail. A few -bv tests show the impact of the MAIL_HUB define:

# sendmail -bv tyler@example.com
tyler@example.com... deliverable: mailer esmtp, host example.com, user 
tyler@example.com
# sendmail -bv sara@crab
sara@crab... deliverable: mailer esmtp, host crab.wrotethebook.com., user sara@crab.
wrotethebook.com
# sendmail -bv craig
craig... deliverable: mailer relay, host smtp.wrotethebook.com, user craig@smtp.
wrotethebook.com

The tests demonstrate that sendmail uses the esmtp mailer to directly deliver mail bound for systems in external domains and external systems in the local domain. Mail that is normally delivered by the local mailer, however, is sent to the mail hub (smtp.wrotethebook.com) via the relay mailer.

Notice the user address field of the mail delivery triple displayed by the third test. The address craig has been rewritten to craig@smtp.wrotethebook.com. The address is rewritten even if the delivery address contains the fully qualified name of the local host, for example:

# sendmail -bv craig@jamis.wrotethebook.com
craig@jamis.wrotethebook.com... deliverable: mailer relay, host smtp.wrotethebook.
com, user craig@smtp.wrotethebook.com

Thus, all mail that would be delivered by the local mailer, even mail arriving from an external source, is forwarded to the mail hub. When configuring the mail hub, be careful not to forward mail back to a system that relays mail to the hub as this will cause a mail loop.

In this example, the mail hub must be configured with either a craig account or an alias for craig. Often, the mail hub maintains a central mail spool directory and holds the mail for all of its clients. The clients themselves do not have local mail spool directories. They either mount the server’s spool directory via NFS, download the mail to a mail reader using POP or IMAP, or read the mail directly on the server.

Because the mail hub has an account or alias for every user on every client, it is important that the usernames on the clients are unique. If Craig Hunt has an account named craig on crab, and Craig Rider has an account named craig on jamis, and both systems use smtp.wrotethebook.com as a mail hub, then both systems will send mail to the hub using the same delivery address craig@smtp.wrotethebook.com, even though the mail is intended for two different users. Using a mail hub requires coordination among all of the clients.

One username that is obviously duplicated on all clients is root. Use the LOCAL_USER macro to ensure that sendmail does not relay mail to the mail hub when the mail is addressed to root. The LOCAL_USER macro adds users to sendmail.cf class $=L. Class $=L is a list of local users whose mail is not relayed to the mail hub.

The following test shows that without the LOCAL_USER macro, mail addressed to the root user is sent to the mail hub:

# sendmail -bv root
root... deliverable: mailer relay, host smtp.wrotethebook.com, user root@smtp.
wrotethebook.com

Clearly, sendmail is attempting to forward mail addressed to root to the mail hub. To prevent this, the following line was added to this recipe’s configuration:

LOCAL_USER(root)

With this command in the configuration, the sendmail -bv test shows the following results:

# sendmail -bv root
root... deliverable: mailer local, user root

The test shows the effect of the LOCAL_USER macro. It keeps root mail local, which is just what we intended. However, before you use the LOCAL_USER macro, make sure that keeping root mail local is correct for your configuration. Often, mail to the root account is routed via an alias to the administrator’s real login account. For example, you might route root to alana if Alana is the administrator of this system and alana is the account she uses to login. In that case, you would use an alias and you would not need the LOCAL_USER macro—although adding the macro would do no harm.

Chapter 2 provides extensive examples of using aliases; in particular, Recipe 2.2 provides an example of routing root’s mail to another username. Recipe 3.1 also sets a value for the mail hub in the $H macro. The sendmail book covers MAIL_HUB in 4.5.7, LOCAL_USER in 4.5.5, and LOCAL_RELAY in 4.5.4.

3.4. Passing Apparently Local Mail to a Relay

Discussion

A delivery address without a hostname is passed to the local mailer for delivery to a local user. If the user portion of the address does not specify a valid local username or alias, the “User unknown” error is returned. The following tests show how this works with a generic configuration:

# sendmail -bv fred -Cgeneric-linux.cf
fred... User unknown
# sendmail -bv craig -Cgeneric-linux.cf
craig... deliverable: mailer local, user craig

craig is a valid username on this host, so the mail is delivered by the local mailer. fred is not a valid local username, so an error is returned. After adding the LUSER_RELAY define to the configuration, mail to craig is delivered as before, but mail to fred is now sent to the mail relay host for delivery:

# sendmail -bv fred
fred... deliverable: mailer relay, host smtp.wrotethebook.com, user fred@smtp.
wrotethebook.com
# sendmail -bv craig
craig... deliverable: mailer local, user craig

The LUSER_RELAY define sets the value of the sendmail.cf $L macro and adds two rules to the localaddr ruleset (ruleset 5). Ruleset 5 is invoked after a local address is processed through the aliases database.[2] The two rewrite rules added by the LUSER_RELAY define are only executed if the recipient address has not already been resolved to a valid delivery address. The first LUSER_RELAY rewrite rule checks to see if the username from the email address is a key in the user database. If the username is found in the user database, the mail is delivered according to that database’s instructions. If the username is not found in the database, the mail is sent to the relay server identified by the $L macro. Thus, if a local delivery address does not resolve to a local username or is not covered by the aliases database or the user database, the mail is sent to the LUSER_RELAY server for delivery.

In the last test above, mail addressed to fred is forwarded to the LUSER_RELAY server. The LUSER_RELAY configuration assumes that the server knows how to deliver the mail to fred. Often, this means that the server has a large aliases file that defines how mail is routed to the users. Unlike the MAIL_HUB configuration described in Recipe 3.3, the LUSER_RELAY configuration does not assume that the server maintains a central mail spool directory. The LUSER_RELAY configuration allows the client to deliver some pieces of local mail and sends others to the server for delivery.

An alternative to LUSER_RELAY is to have a large aliases file on the client. This could be a local file or a centrally maintained file accessible via NIS or some other database service. An even more popular alternative is to require users to add the hostname to the email address when the hostname is required for delivery—in other words, let the users see the “User unknown” error until they understand that they need to address email properly. All of these are viable alternatives.

Chapter 2 provides examples of both the aliases database and the user database. The sendmail book covers LUSER_RELAY in 4.5.6.

3.5. Passing UUCP Mail to a Relay

Discussion

The UUCP_RELAY define sets the value of the sendmail.cf $Y macro, adds UUCP to class $=P, and adds a rule to the Parse1 ruleset. The new Parse1 rewrite rule sends UUCP mail that cannot be delivered by the local host to the mail relay identified by the $Y macro. If $Y is not defined, sendmail assumes that the local host can deliver all UUCP mail. Class $=P contains a list of pseudodomains (domain names used internally by sendmail), that sendmail does not attempt to look up in DNS. The .UUCP pseudodomain is used by sendmail to mark mail that should be handled as UUCP mail. When mail addressed to the .UUCP pseudodomain hits the new rule in the Parse1 ruleset, the address is rewritten as a mail delivery triple that sends the mail to the UUCP mail relay host defined in macro $Y. A sendmail -bt test shows all of this:

# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> /parse crab!craig
Cracked address = $g
Parsing envelope recipient address
canonify           input: crab ! craig
Canonify2          input: craig < @ crab . UUCP >
Canonify2        returns: craig < @ crab . UUCP . >
canonify         returns: craig < @ crab . UUCP . >
parse              input: craig < @ crab . UUCP . >
Parse0             input: craig < @ crab . UUCP . >
Parse0           returns: craig < @ crab . UUCP . >
Parse1             input: craig < @ crab . UUCP . >
MailerToTriple     input: < uucp . wrotethebook . com > craig < @ crab . UUCP . >
MailerToTriple   returns: $# relay $@ uucp . wrotethebook . com $: craig < @ crab . 
UUCP . >
Parse1           returns: $# relay $@ uucp . wrotethebook . com $: craig < @ crab . 
UUCP . >
parse            returns: $# relay $@ uucp . wrotethebook . com $: craig < @ crab . 
UUCP . >
2                  input: craig < @ crab . UUCP . >
2                returns: craig < @ crab . UUCP . >
MasqSMTP           input: craig < @ crab . UUCP . >
MasqSMTP         returns: craig < @ crab . UUCP . >
final              input: craig < @ crab . UUCP . >
final            returns: crab ! craig
mailer relay, host uucp.wrotethebook.com, user crab!craig
> /quit

This test asks sendmail to parse a delivery address written in the traditional UUCP bang format, host ! user. The canonify ruleset rewrites the address into the standard Internet address format and adds the .UUCP pseudodomain. The address continues unchanged until the Parse1 ruleset, which rewrites the address into a mail delivery triple. relay is the mailer used in the triple, and the value retrieved from the $Y macro is the host used in the triple. However, the user address remains in the .UUCP pseudodomain format until the final ruleset, which returns the user address to the UUCP bang format. The effect of all of this is to send bang formatted delivery addresses to the UUCP relay host and to count on that host to deliver the mail.

The UUCP_RELAY is not the only pseudodomain mail relay that can be configured in sendmail. The following defines are also available:

All of these relays are defined in the same way as the UUCP_RELAY in this recipe. Simply replace the keyword and the name of the relay host in the recipe with the values you require and rebuild the sendmail.cf file. However, none of these defines—UUCP_RELAY, BITNET_RELAY, DECNET_RELAY, or FAX_RELAY—are in widespread use. BITNET and DECnet are grossly out of date technologies and should not be used. UUCP carries only a small fraction of global email, and most of that passes transparently through Internet/UUCP gateways using the standard Internet address format. FAX_RELAY is not flexible enough to easily handle the variety of email to FAX servers. FAX server vendors cannot limit their market to those sites that have administrators knowledgeable enough to customize sendmail. For this reason, the vendors usually provide a technique that allows a user to send a FAX without any modifications to sendmail. Of the four defines, UUCP_RELAY is the only one that gets any real use, and thus it is the one covered in this book.

No m4 configuration commands are needed to create this mail relay host. sendmail checks for a file named /etc/mail/relay-domains and adds the names it finds there to class $=R by default. There are two ways to change the default pathname of the /etc/mail/relay-domains file:

Neither of these commands appears in this recipe because it is generally a bad idea to change the default pathname of any file used in the sendmail configuration. Other administrators may need to find information on your system. It is easier for them to do so if the information is stored in the standard location.

Before we created the relay-domains file, sendmail blocked all mail relaying—even relaying from other hosts within the local domain. By default, sendmail does not permit relaying. A test conducted before the relay-domains file is created shows this. Below, telnet is used on rodent.wrotethebook.com to connect directly to the SMTP port on the sendmail server smtp.wrotethebook.com:

$ telnet 192.168.0.8 smtp
Trying 192.168.0.8...
Connected to 192.168.0.8.
Escape character is '^]'.
220 smtp.wrotethebook.com ESMTP Sendmail 8.12.9/8.12.9; Fri, 15 Aug 2003 14:25:01 -
0400
HELO rodent.wrotethebook.com
250 smtp.wrotethebook.com Hello rodent.wrotethebook.com [192.168.0.3], pleased to 
meet you
MAIL From:<craig@rodent.wrotethebook.com>
250 2.1.0 craig@rodent.wrotethebook.com... Sender ok
RCPT To:<sara@crab.wrotethebook.com>
550 5.7.1 sara@crab.wrotethebook.com... Relaying denied
QUIT
221 2.0.0 smtp.wrotethebook.com closing connection
Connection closed by foreign host.

This test shows that the “Relaying denied” error is returned to the sender, indicating that the default sendmail configuration does not allow hosts in the local domain to relay mail to any third host.

After the domain name wrotethebook.com is written into the /etc/mail/relay-domains file and sendmail is restarted, rerunning the test produces a different result:

$ telnet 192.168.0.8 smtp
Trying 192.168.0.8...
Connected to 192.168.0.8.
Escape character is '^]'.
220 smtp.wrotethebook.com ESMTP Sendmail 8.12.9/8.12.9; Fri, 15 Aug 2003 15:12:21 -
0400
HELO rodent.wrotethebook.com
250 smtp.wrotethebook.com Hello rodent.wrotethebook.com [192.168.0.3], pleased to 
meet you
MAIL From:<craig@rodent.wrotethebook.com>
250 2.1.0 craig@rodent.wrotethebook.com... Sender ok
RCPT To:<sara@crab.wrotethebook.com>
250 2.1.5 sara@crab.wrotethebook.com... Recipient ok
DATA
354 Enter mail, end with "." on a line by itself
Subject: Test

               This is a test of wrotethebook.com entry in the relay-domains file.
               .
250 2.0.0 g8RJCLXf001550 Message accepted for delivery
QUIT
221 2.0.0 smtp.wrotethebook.com closing connection
Connection closed by foreign host.

Now, hosts within the local domain are allowed to relay through smtp.wrotethebook.com—all without any changes to the m4 configuration or any need to rebuild the sendmail.cf file. Mail from or to hosts in the wrotethebook.com domain is relayed. Mail that is neither from nor to a host in the wrotethebook.com domain is still blocked from relaying mail.

Alternatives

There are four alternative solutions: the RELAY_DOMAIN macro, the relay_entire_domain feature, the promiscuous_relay feature, and the relay_local_from feature.

The example in the Solution section adds only one domain name to class $=R. It is possible to add individual domains to class $=R from inside the m4 configuration by using the RELAY_DOMAIN macro. The following lines added to the sendmail configuration would have the same effect as the relay-domains file defined above:

dnl RELAY_DOMAIN adds a domain name to class R
RELAY_DOMAIN(`wrotethebook.com')

However, the RELAY_DOMAIN command requires modifying the m4 configuration and rebuilding and reinstalling the sendmail.cf file. Using the relay-domains file does not have these requirements, which makes the relay-domains file simpler to use.

There are a few other alternative solutions to this problem. We could have used the relay_entire_domain feature to enable relaying for hosts in the local domain. The following command added to a basic configuration would produce the same result as this recipe:

dnl A feature that relays mail for the local domain
FEATURE(`relay_entire_domain')dnl

The relay_entire_domain feature adds a few rewrite rules that relay mail from any host in a domain listed in class $=m. By default, class $=m contains the domain name of the server system. Thus, class $=m contains wrotethebook.com on a server named smtp.wrotethebook.com.

This alternative solution works well, but was rejected for a few reasons. First, this solution is slightly more complex that the one used in this recipe. Using the relay_entire_domain feature requires modifications to the m4 configuration, which means that the sendmail.cf file must be rebuilt and reinstalled before sendmail is restarted. Using the relay-domains file only requires sendmail to be restarted.

Second, the value in class $=m is determined internally by sendmail. It is usually correct, so this might be a minor point, but setting the value in relay-domains gives the administrator more control. Third, relay-domains is self-documenting. Looking in the file quickly tells you which domains the server is configured to relay mail.

Fourth, the relay-domains file is very flexible. The problem section describes creating a mail relay for other hosts in the local domain, which is the most common relaying example. However, any domain can be listed in the relay-domains file, and mail originating from any host in that domain will be relayed. For example, imagine we wanted to create a mail relay that would relay mail for the wrotethebook.com domain, the ora.com domain, the oreilly.com domain and the sybex.com domain. To do that we would just need the following relay-domains file:

# cat /etc/mail/relay-domains
wrotethebook.com
ora.com
oreilly.com
sybex.com

This recipe is used to permit relaying from any host in a specified domain. These reasons give the relay-domains file a slight advantage over the relay_entire_domain feature for this specific application, which is why relay-domains was chosen for this recipe.

Two other alternative solutions were rejected for security reasons:

Discussion

On our sample mail relay host, we create the following relay-domains file:

# cat > /etc/mail/relay-domains
               rodent.wrotethebook.com
               horseshoe.wrotethebook.com
               jamis.wrotethebook.com
               tcp.ora.com
               chill.sybex.com
               wrotethebook.com
               Ctrl-D

A sendmail -bt test shows the values stored in class $=R:

# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> $=R
tcp.ora.com
rodent.wrotethebook.com
chill.sybex.com
jamis.wrotethebook.com
horseshoe.wrotethebook.com
wrotethebook.com
> /quit

Normally, values in class $=R are interpreted as domain names, and any host within a domain listed in class $=R is allowed to relay mail through the server. Recipe 3.6 uses this fact to relay mail for entire domains. In this case, however, we want wrotethebook.com interpreted as a hostname, not a domain name, and we want mail routed for that host.

The relay_hosts_only feature changes how the entries in class $=R are used. In the default configuration, pattern matches against class $=R contain the string $*$=R, which matches zero or more tokens and any value stored in $=R. Thus wrotethebook.com, crab.wrotethebook.com, and fun.rodent.wrotethebook.com would all pattern match the wrotethebook.com value found in the class $=R array shown above. The relay_hosts_only feature changes the string in the pattern match to $=R, meaning that only exact matches of values found in class $=R are valid matches. When relay_hosts_only is used with our sample class $=R values, rodent.wrotethebook.com matches the pattern because rodent.wrotethebook.com is included in $=R; however, crab.wrotethebook.com does not match—even though the value wrotethebook.com appears in the array. Only a host named wrotethebook.com would match that specific value when relay_hosts_only is used.

A couple of tests demonstrate the impact of the relay_hosts_only feature. A telnet test from rodent shows that smtp is configured to allow relaying for rodent:

$ telnet smtp.wrotethebook.com smtp
Trying 192.168.0.8...
Connected to 192.168.0.8.
Escape character is '^]'.
220 smtp.wrotethebook.com ESMTP Sendmail 8.12.9/8.12.9; Fri, 15 Aug 2003 15:36:52 -
0400
HELO rodent.wrotethebook.com
250 smtp.wrotethebook.com Hello rodent.wrotethebook.com [192.168.0.3], pleased to 
meet you
MAIL From:<craig@rodent.wrotethebook.com>
250 2.1.0 craig@rodent.wrotethebook.com... Sender ok
RCPT To:<tyler@example.com>
250 2.1.5 tyler@example.com... Recipient ok
QUIT
221 2.0.0 smtp.wrotethebook.com closing connection
Connection closed by foreign host.

The same test run from crab shows that crab is not allowed to relay through smtp:

# telnet smtp.wrotethebook.com smtp
Trying 192.168.0.8...
Connected to 192.168.0.8.
Escape character is '^]'.
220 smtp.wrotethebook.com ESMTP Sendmail 8.12.9/8.12.9; Fri, 15 Aug 2003 20:43:11 -
0400
HELO crab.wrotethebook.com
250 smtp.wrotethebook.com Hello crab.wrotethebook.com [192.168.0.15], pleased to  meet 
you
MAIL From:<craig@crab.wrotethebook.com>
250 2.1.0 craig@crab.wrotethebook.com... Sender ok
RCPT To:<tyler@example.com>
550 5.7.1 tyler@example.com... Relaying denied
QUIT
221 2.0.0 smtp.wrotethebook.com closing connection
Connection closed by foreign host.

One final test from crab shows the full impact of class $=R on relaying:

$ telnet smtp.wrotethebook.com smtp
Trying 192.168.0.8...
Connected to smtp.
Escape character is '^]'.
220 smtp.wrotethebook.com ESMTP Sendmail 8.12.9/8.12.9; Fri, 15 Aug 2003 14:20:16 -
0400
HELO crab.wrotethebook.com
250 smtp.wrotethebook.com Hello crab.wrotethebook.com [192.168.0.3], pleased to meet 
you
MAIL From:<craig@crab.wrotethebook.com>
250 2.1.0 craig@crab.wrotethebook.com... Sender ok
RCPT To:<kathy@rodent.wrotethebook.com>
250 2.1.5 kathy@rodent.wrotethebook.com... Recipient ok
QUIT
221 2.0.0 smtp.wrotethebook.com closing connection
Connection closed by foreign host.

In this case, crab successfully relays mail through smtp. If either the sending host or the recipient host is listed in class $=R, the mail is accepted for relaying. Therefore, mail sent from crab to rodent is accepted for relaying, even though crab is not granted relaying privileges, because rodent is allowed to relay and rodent is the destination of this piece of mail.

Section 7.4.6 in the sendmail book covers the relay_hosts_only feature. Recipe 3.6 and Recipe 3.8 provide solutions to similar problems that may also need to be evaluated.

3.8. Configuring Relaying on a Mail Exchanger

If you have access to a system that can list the entire domain, you can use the following bit of Unix magic to create a listing of every host in the domain that uses your mail system as its MX server:[3]

# cd /etc/mail
# host -l wrotethebook.com | \
               > grep 'mail.*mail\.wrotethebook\.com' | \
               > awk '{ print $1 }' > temp-relay-domains

This example writes the list to a file named temp-relay-domains file. Verify the file contents before storing the data in either the relay-domains file or the local-host-names file. Additionally, this example requires the sendmail administrator to have direct access to a system that is entitled to list the entire domain file. Unless you pull double duty as both DNS administrator and sendmail administrator, you might have to rely on the domain administrator for the list of hostnames.

The local-host-names file configures the mail exchanger to accept mail for local delivery or forwarding, as discussed in Chapter 2. The relay-domains file configures the mail exchanger for relaying.

Mail to or from hosts in the relay-domains file will be relayed. Note that the names in the relay-domains file in this example are treated as hostnames, not domain names, because we are using the relay_hosts_only feature.

See Also

The discussion of the relay_based_on_MX feature in the cf/README file explains the developers’ recommendations. The sendmail book covers relay_based_on_MX in 7.4.4. Recipe 2.1 and Recipe 3.7 provide important material for this recipe.

3.9. Loading Class $=R via LDAP

On the LDAP server, add support for the sendmail schema as described in Recipe 1.3. Note that this only needs to be done once.

On the LDAP server, add to the LDAP database the names of the hosts and domains that are allowed to relay. Use the sendmailMTAClass object class format defined in the sendmail.schema file. The Discussion section shows an example doing this on a server that runs OpenLDAP.

On the mail relay host, use the command sendmail -bt -d0.1 to check if sendmail was compiled with LDAP support. The string LDAPMAP must appear in the “Compiled with:” list. If sendmail was not compiled with LDAP support, recompile and reinstall sendmail as shown in Recipe 1.3. Once sendmail supports LDAP, continue with the next steps.

On the mail relay host, add a RELAY_DOMAIN_FILE macro to the sendmail configuration specifing @LDAP as the relay domain file path, which tells sendmail to read class $=R values from the LDAP server using the standard sendmail schema. Use the confLDAP_CLUSTER define to set the ${sendmailMTACluster} macro to the same value used in the sendmailMTACluster attribute of the LDAP entry. Here are sample sendmail configuration commands:

dnl Set the LDAP cluster value
define(`confLDAP_CLUSTER', `wrotethebook.com')
dnl Tell sendmail to load $=R via LDAP
RELAY_DOMAIN_FILE(`@LDAP')

Build the configuration file, copy it to /etc/mail/sendmail.cf, and restart sendmail with the new configuration, as described in Recipe 1.8.

Discussion

This recipes assumes that LDAP is properly installed and running. This is not the place to start experimenting with LDAP. Both LDAP and sendmail are large, complex systems. You should have some experience with LDAP before attempting to use it with sendmail.

The sendmail.schema file that comes with the sendmail distribution must be included in the LDAP configuration in order for LDAP to understand and properly process queries from sendmail.[4] The LDAP administrator must ensure that LDAP is ready to work with sendmail’s standard schema before adding sendmail data to the LDAP database.

The LDAP administrator builds an LDIF file using the sendmail schema to define the list of hosts granted relay privileges and then runs ldapadd to add the contents of the LDIF file to the LDAP database. Here is an example that adds all of the hostnames from the relay-domains file used in Recipe 3.7:

# cat > ldap-relay-domains
               dn: sendmailMTAClassName=R, dc=wrotethebook, dc=com
               objectClass: sendmailMTA
               objectClass: sendmailMTAClass
               sendmailMTACluster: wrotethebook.com
               sendmailMTAClassName: R
               sendmailMTAClassValue: rodent.wrotethebook.com
               sendmailMTAClassValue: horseshoe.wrotethebook.com
               sendmailMTAClassValue: jamis.wrotethebook.com
               sendmailMTAClassValue: tcp.ora.com
               sendmailMTAClassValue: chill.sybex.com
               sendmailMTAClassValue: wrotethebook.com
               Ctrl-D
# ldapadd -x -D "cn=Manager,dc=wrotethebook,dc=com" \
               > -W -f ldap-relay-domains
Enter LDAP Password: SecretLDAPpassword
adding new entry "sendmailMTAClassName=R, dc=wrotethebook, dc=com"

An ldapsearch test shows the data in the LDAP database:[5]

# ldapsearch -LLL -x '(sendmailMTAClassName=R)' sendmailMTAClassValue
dn: sendmailMTAClassName=R, dc=wrotethebook, dc=com
sendmailMTAClassValue: rodent.wrotethebook.com
sendmailMTAClassValue: horseshoe.wrotethebook.com
sendmailMTAClassValue: jamis.wrotethebook.com
sendmailMTAClassValue: tcp.ora.com
sendmailMTAClassValue: chill.sybex.com
sendmailMTAClassValue: wrotethebook.com

Once the data is added to the LDAP server, the sendmail system can be configured to read it.

The list of hosts granted relaying privileges are added to the LDAP database as values in a single sendmailMTAClass object class record. As the sendmailMTAClassName attribute makes clear, these values will be stored in class $=R. Normally, class $=R is loaded from the file /etc/mail/relay-domains. Use the RELAY_DOMAIN_FILE macro to add another source of data for class $=R. The string @LDAP in the path field of the macro tells sendmail to load class $=R with values obtained from the LDAP server. The effect of the @LDAP string on the RELAY_DOMAINS_FILE macro can be easily seen using the sendmail -bt command:

# rm -f /etc/mail/relay-domains
# sendmail -bt -Cgeneric-linux.cf
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> $=R
> /quit
# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> $=R
rodent.wrotethebook.com
chill.sybex.com
jamis.wrotethebook.com
horseshoe.wrotethebook.com
wrotethebook.com
tcp.ora.com
> /quit

The rm command in this test is just to show the reader that no values are being loaded into class $=R from the relay-domains file. On a real system, you might want to use both the relay-domains file and LDAP.

The first sendmail -bt test loads the generic-linux.cf configuration provided with the sendmail distribution. The $=R command displays the contents of the class $=R, which, in this case, is empty. The sendmail -bt test is rerun using the sendmail.cf configuration file created in this recipe. This time the $=R command displays the values retrieved from the LDAP server.

Class $=R is not the only class that can be loaded via LDAP. Any class loaded from a file can be loaded via LDAP by replacing the file pathname on the F command with the string @LDAP, using this syntax:

F{name}@LDAP

where name is the class’ name. The same name is then used as the value for the sendmailMTAClassName attribute in the LDAP record that defines the values loaded into the class. Note that this is the class name without the curly braces.

This recipe uses the confLDAP_CLUSTER define because the LDAP record created for this recipe contains the sendmailMTACluster attribute. sendmail LDAP records apply to either a single host or a group of hosts, which sendmail calls a cluster. A record that applies to a single host uses the sendmailMTAHost attribute; a record that applies to a group of hosts uses the sendmailMTACluster attribute. If confLDAP_CLUSTER is not specified, a cluster name is not used, and only records with a sendmailMTAHost attribute that matches the hostname of the sendmail host are retrieved. The record added to the LDAP database in the example above used the sendmailMTACluster attribute, and sets that attribute to wrotethebook.com. Therefore, it is necessary to define a matching cluster value in the sendmail configuration. The confLDAP_CLUSTER define shown in the Solution section sets the sendmail.cf macro ${sendmailMTACluster}, which holds the cluster name, to wrotethebook.com. If that define was not used, queries from the local host would not successfully retrieve the sample class $=R values from the LDAP server.

Recipe 3.6 and Recipe 3.7 provide information about class $=R and how it is used for relaying. Information about LDAP is available in Understanding and Deploying LDAP Directory Services by Howes, Smith, and Good (Macmillan) and in LDAP System Administration by Gerald Carter (O’Reilly). The cf/README file covers this topic in the Using LDAP for Aliases, Maps, and Classes section. The sendmail book covers the RELAY_DOMAIN_FILE macro in Section 7.4.1.2 and the confLDAP_CLUSTER define in Section 21.9.82.

3.10. Relaying Only Outbound Mail

Discussion

Mail from, or addressed to, any domain defined in the relay-domains file is relayed. Thus, the relay-domains file creates a relay that handles both inbound and outbound mail, which is generally the configuration used. Occasionally, however, network designers decide to use separate systems for inbound and outbound mail. For example, a mail exchanger for all inbound mail and a mail relay host for all outbound mail might be placed on two physically separate systems. The access database provides the fine-grained control necessary to implement these design decisions. The access_db feature adds support for the access database to the sendmail configuration.

Entries in the access file, from which the access database is built, contain two basic fields:

  • The conditional test that determines whether an action is taken

  • The action taken when the condition is met

The conditional test can contain various types of data depending on what the access database entry is being used for. For relaying, the conditional test usually includes an address, which can be a full or partial domain name, hostname, or IP address. It can also be a full email address or just the user portion of an email address. Here is an example of creating an access database entry that relays mail received via a connection from any host in the local wrotethebook.com domain:

# cd /etc/mail
# cat > access
               Connect:wrotethebook.com        RELAY
               Ctrl-D
# makemap hash access < access

The example uses the domain name wrotethebook.com as the data in the conditional test because we want to relay mail for every host in that domain. An alternative would be to use the network address to relay mail for every host on the local network, for example:

Connect:192.168.0     RELAY

The keyword Connect: that is placed before the data in the conditional test is called a tag. A tag is used to limit the scope of the test. Tags are optional. Normally, the data in the conditional test field of an entry in the access database are tested against three different values—the sender and recipient addresses from the message envelope, and the IP address of the remote system that connected to the server to transfer the mail. If any one of these three addresses matches the conditional data in the access database, the condition is met, and the action defined in the database is taken. Use a tag to change this default behavior and limit the conditional test to one of the three possible addresses. The basic tag values are:

To:

Only the recipient address in the message envelope is matched against the conditional test data.

From:

Only the sender address in the message envelope is matched against the conditional test data.

Connect:

Only the IP address of the remote system that initiated the connection over which the mail was received is matched against the conditional test data.

The example above uses the Connect: tag to limit relaying to only those hosts that connect to the relay server from the wrotethebook.com domain. Mail from any other source is not relayed.

The action field in the sample access file entry contains the keyword RELAY, which simply means that the server should relay mail that matches the specified conditional test. RELAY is the only keyword that directly relates to relaying. Several other action keywords are covered in Chapter 7.

The access file must be converted to a hash database before sendmail can use it. The makemap program does the required conversion. It reads ASCII text from stdin and writes out a database of the specified type to the specified file. In our example, note that the input and output names appear the same. They’re not. The input file is named access and the output file is named access.db. However, you do not need to add the .db extension to the output filename. makemap automatically adds the correct extension based on the type of database specified. makemap is provided as part of the sendmail distribution.

After building the access database, building sendmail.cf, and restarting sendmail, the system relays mail from clients in the local domain but will not relay mail to those clients if the mail originates in some other domain.[6] Two tests show this. The first test is mail sent from a host in the local domain to a remote host:

$ telnet smtp.wrotethebook.com smtp
Trying 192.168.0.8...
Connected to smtp.
Escape character is '^]'.
220 smtp.wrotethebook.com ESMTP Sendmail 8.12.9/8.12.9; Fri, 15 Aug 2003 16:47:57 -
0400
HELO rodent.wrotethebook.com
250 smtp.wrotethebook.com Hello rodent.wrotethebook.com [192.168.0.3], pleased to 
meet you
MAIL From:<craig@rodent.wrotethebook.com>
250 2.1.0 craig@rodent.wrotethebook.com... Sender ok
RCPT To:<tyler@example.com>
250 2.1.5 tyler@example.com... Recipient ok
QUIT
221 2.0.0 smtp.wrotethebook.com closing connection
Connection closed by foreign host.

The second test is mail sent from a remote host to a host in the local domain. If the local domain was specified in the relay-domain file, this mail would be accepted. This test shows that with the access database from this recipe, the mail is rejected:

$ telnet smtp.wrotethebopok.com smtp
Trying 192.168.0.8...
Connected to smtp.
Escape character is '^]'.
220 smtp.wrotethebook.com ESMTP Sendmail 8.12.9/8.12.9; Fri, 15 Aug 2003 16:52:07 -
0400
HELO example.com
250 smtp.wrotethebook.com Hello example.com {10.20.3.3], pleased to meet you
MAIL From:<tyler@example.com> 
250 2.1.0 tyler@example.com... Sender ok
RCPT To:<craig@rodent.wrotethebook.com>
550 5.7.1 craig@rodent.wrotethebook.com... Relaying denied
QUIT
221 2.0.0 smtp.wrotethebook.com closing connection
Connection closed by foreign host.

Chapter 6 provides more information about the access database. The Finer control by using tags for the LHS of the access map section of the cf/README file provides additional information about tags. The sendmail book covers the access database in Section 7.5.



[1] Set /tryflags to ES and rerun this test to verify that the envelope/sender address is also rewritten.

[2] Ruleset 5 is invoked only when the F=5 flag is set for the selected mailer. By default, this flag is set for the local mailer, which is the mailer used in our examples.

[3] In the example, we list a domain named wrotethebook.com and grep for mail.wrotethebook.com. Replace these values with the correct values for your domain and mail system hostname.

[4] You can, of course, design your own schema. But that is not a topic for a sendmail book.

[5] If ldapsearch requires -h and -b arguments, matching values will also be required by sendmail. See Recipe 5.9 for an example of setting -h and -b for sendmail.

[6] sendmail does not need to be restarted when a database changes. The only reason a restart was needed for this recipe was because the sendmail.cf file changed.