Chapter 4. Masquerading

Introduction

Masquerading is sendmail-speak for rewriting the hostname in the address of outbound mail. The reasons for masquerading fall into two general categories:

Mail routing

Many networks are designed to route all inbound mail through a central mail hub. When a host sends out mail using its own hostname in the sender address, replies to that mail may well come back to the host. Replacing the sending system’s hostname with the mail hub’s hostname in the sender address guarantees that replies come back to the hub. Masquerading is not the only way to do this. An MX record can also route mail to the hub. However, maintenance of the DNS zone file is under the control of the domain administrator. The sendmail administrator maintains masquerading, and most sendmail administrators prefer to be in charge of their own fate. Also, hostnames change over time. Masquerading can provide more consistent email addresses and can simplify maintenance.

Organizational requirements

Some organizations simply have a policy of hiding hostnames. Management may think that “busy” hostnames project an image of disorganization. Marketing may think that “frivolous” hostnames project the wrong image to customers. Naive security people may even believe that hiding hostnames increases security. For whatever reason, management requires masquerading, which, in turn, creates the need for systems configured to receive replies to the masqueraded mail.

The MASQUERADE_AS macro enables masquerading. This macro stores a value in the sendmail.cf $M macro and adds code to the MasqHdr ruleset to rewrite the header sender address using the value returned by $M.[1] By default, masquerading applies to all of the mail that originates on the local host. All of the valid hostnames for the local host are stored in class $=w. When MASQUERADE_AS is used, sendmail replaces the hostname of the sender with the value of $M—if the original address lacks a hostname or the hostname matches a value in class $=w.

sendmail checks both class $=w and class $=M when masquerading.[2] Class $=M, however, starts out empty. Use the MASQUERADE_DOMAIN macro to add individual hostnames to class $=M. To add multiple hosts, create a file containing a list of hosts and use the MASQUERADE_DOMAIN_FILE macro to load the file into class $=M.

Normally, masquerading interprets the values in class $=M as hostnames and only exact matches are masqueraded. Use the masquerade_entire_domain feature to interpret the values in class $=M as domain names. When masquerade_entire_domain is used, every host in a domain listed in class $=M is masqueraded. masquerade_entire_domain is used in Recipe 4.8.

By default, masquerading is applied to the header sender address. The masquerade_envelope feature causes sendmail to also apply masquerading to the envelope sender address. Recipe Recipe 4.10 shows an example of how the masquerade_envelope feature is used. Using the allmasquerade feature applies masquerading to both sender and recipient addresses. Recipe 4.5 discusses the allmasquerade feature.

Masquerading is widely used and highly configurable, yet it is not the only way, or even the most powerful way, to rewrite the sender address in the From: header. Masquerading rewrites the hostname in the sender address; the genericstable rewrites the entire address—both the username and the hostname. The genericstable is related to masquerading in that they both modify the sender address, however, there are distinct differences:

  • Masquerading rewrites the hostname based on the input hostname. The genericstable is applied to addresses based on the input hostname but can rewrite the entire address based on all or part of the input address.

  • Masquerading replaces each masqueraded hostname with the same value, whereas the genericstable can replace each input address with a different value.

The genericstable feature defines the generics database in the sendmail.cf file and adds code to the MasqHdr ruleset to use that database to rewrite the sender address in the From: header. The key to the genericstable database is a username or a full email address, and the value returned for the key is a complete email address. To use the genericstable, first construct a text file in which each line of the file is a key/value pair using the format: username, whitespace, email address. A sample input entry in the text file is:

pat     patstover@butler.wrotethebook.com

In this example, pat is the key against which the input username is matched, and patstover@butler.wrotethebook.com is the email address returned for that key. Before the genericstable can be used, the text file containing the key/value pairs must be converted to a hash type database using the sendmail makemap command.[3]

Both the username and the hostname from the input sender address are used by the genericstable process. The key to the genericstable database always contains a username, either by itself or as part of a full email address. However, sendmail only searches for that key if the hostname in the input sender address matches a value in class $=G or if the input sender address contains no hostname.

By default, class $=G is empty. Use the GENERICS_DOMAIN macro to add individual hostnames to class $=G, or the GENERICS_DOMAIN_FILE macro to load class $=G from a file. Normally, the values in class $=G are interpreted as hostnames. Use the generics_entire_domain feature to make sendmail interpret the values in class $=G as domain names. When the generics_entire_domain feature is used, the genericstable is applied to mail sent from every host in every domain listed in class $=G.

Both masquerading and the genericstable are used in the recipes in this chapter. But first we start with a recipe that uses the always_add_domain feature. always_add_domain has nothing to do with the genericstable, and, strictly speaking, it is not a masquerading command. However, it does rewrite the sender address, which is the common thread running through this chapter.

4.1. Adding Domains to All Sender Addresses

Problem

You have been asked to configure sendmail to add a hostname to every header sender address, even when the input address has no hostname part and the mail is being delivered by the local mailer.

Solution

Add the always_add_domain feature to the sendmail configuration. The command that you add is:

dnl Add the domain to all sender addresses
FEATURE(`always_add_domain')

Using Recipe 1.8 as a guide, build and install the sendmail configuration and then restart sendmail.

Discussion

The always_add_domain feature causes sendmail to make sure that every sender address includes a hostname part. By default, sendmail does not add a hostname part to the sender address when the address has no hostname part and the local mailer is delivering the mail. Thus, mail from the address alana goes through the local mailer’s header sender process unchanged when the default configuration is used, as this test shows:

# sendmail -bt -Cgeneric-linux.cf
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> /tryflags HS
> /try local alana
Trying header sender address alana for mailer local
canonify           input: alana
Canonify2          input: alana
Canonify2        returns: alana
canonify         returns: alana
1                  input: alana
1                returns: alana
HdrFromL           input: alana
MasqHdr            input: alana
MasqHdr          returns: alana
HdrFromL         returns: alana
final              input: alana
final            returns: alana
Rcode = 0, addr = alana
> /quit

Rerunning the test after adding the always_add_domain feature to the configuration shows a different result:

# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> /tryflags HS
> /try local alana
Trying header sender address alana for mailer local
canonify           input: alana
Canonify2          input: alana
Canonify2        returns: alana
canonify         returns: alana
1                  input: alana
1                returns: alana
HdrFromL           input: alana
AddDomain          input: alana
AddDomain        returns: alana < @ *LOCAL* >
MasqHdr            input: alana < @ *LOCAL* >
MasqHdr          returns: alana < @ chef . wrotethebook . com . >
HdrFromL         returns: alana < @ chef . wrotethebook . com . >
final              input: alana < @ chef . wrotethebook . com . >
final            returns: alana @ chef . wrotethebook . com
Rcode = 0, addr = alana@chef.wrotethebook.com
> /quit

Here, the address goes in as alana and comes out with the hostname added as alana@chef.wrotethebook.com.

See Also

Recipe 4.4 shows how the always_add_domain feature can be used with masquerading. The sendmail book covers always_add_domain in Section 4.8.4.

4.2. Masquerading the Sender Hostname

Problem

You have been asked to configure sendmail to replace the local hostname in the header sender address with a different hostname.

Solution

Add the MASQUERADE_AS macro to the sendmail configuration to rewrite the hostname in the From: address to the hostname specified by the MASQUERADE_AS macro. Add the EXPOSED_USER macro to the sendmail configuration to exclude non-unique user names from the address rewrite. Here are examples of these two macros:

dnl Masquerade the From address as wrotethebook.com
MASQUERADE_AS(`wrotethebook.com')
dnl Users whose mail is not masqueraded
EXPOSED_USER(root)

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

Discussion

Use the MASQUERADE_AS macro to configure sendmail to rewrite the host portion of the sender address on outbound mail. The value provided on the MASQUERADE_AS command line is stored in the sendmail.cf $M macro. sendmail uses the value from the $M macro to rewrite the hostname portion of the header sender address when the hostname matches any value found in sendmail.cf class $=w or class $=M. sendmail also uses the value from $M (instead of the value from the sendmail.cf $j macro) as the hostname portion of the header sender address, when the address lacks a hostname part. $j holds the fully qualified name of the local host. Normally, $j is added to the username to create a full email address. A test using the generic Linux configuration, which does not contain the MASQUERADE_AS macro, shows this:

# sendmail -bt -Cgeneric-linux.cf
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> $M
Undefined
> $j
chef.wrotethebook.com
> /tryflags HS
> /try esmtp alana
Trying header sender address alana for mailer esmtp
canonify           input: alana
Canonify2          input: alana
Canonify2        returns: alana
canonify         returns: alana
1                  input: alana
1                returns: alana
HdrFromSMTP        input: alana
PseudoToReal       input: alana
PseudoToReal     returns: alana
MasqSMTP           input: alana
MasqSMTP         returns: alana < @ *LOCAL* >
MasqHdr            input: alana < @ *LOCAL* >
MasqHdr          returns: alana < @ chef . wrotethebook . com . >
HdrFromSMTP      returns: alana < @ chef . wrotethebook . com . >
final              input: alana < @ chef . wrotethebook . com . >
final            returns: alana @ chef . wrotethebook . com
Rcode = 0, addr = alana@chef.wrotethebook.com
> /quit

The -C option on the sendmail command line loads the generic-linux.cf configuration, which does not contain the MASQUERADE_AS macro. The $M command shows that the $M macro is not defined. The $j command shows the fully qualified name of this host. In the example, the name is chef.wrotethebook.com. The /tryflags command tells sendmail to process the header sender (HS) address. The /try command tells sendmail to process alana as the header sender address for the esmtp mailer. Notice that alana is an email address that does not contain a host part. sendmail adds a hostname to the unqualified username, and, by default, it adds the hostname found in $j. The value returned by the MasqHdr ruleset shows this.

A second test, this time using the generic configuration with the addition of the sample lines shown in the Solution section, yields a different result. This time, a value is returned by the $M command, in addition to the value returned for $j. When alana is processed as the header sender address for the esmtp mailer, the MasqHdr ruleset rewrites the address using the value from $M instead of the value from $j:

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

The nullclient configuration covered in Recipe 3.1 also masquerades mail so that it appears to come from the mail hub instead of the local host. This configuration, however, differs substantially from the nullclient configuration. The nullclient did not deliver its own mail. All of its mail was relayed through the hub. In that sense, the nullclient’s mail really did originate from the mail hub. This recipe creates a configuration that delivers its own mail and changes the hostname in the header sender address even though the mail originates from the local host.

In this example, the host masquerades using the domain name. Because all hosts in this sample domain masquerade using the same value, the possibility exists for conflicts caused by non-unique usernames. The classic example of a non-unique username is root—every Unix system has a root account. If mail from root@crab.wrotethebook.com and mail from root@jamis.wrotethebook.com was sent out as mail from root@wrotethebook.com, it would be difficult to sort out where the mail really came from and who should receive replies to the mail. For that reason, the EXPOSED_USER macro is used to ensure that mail from the root user is not masqueraded. A test shows this:

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

The example in this recipe has only one username specified in an EXPOSED_USER macro. To specify multiple usernames, add additional EXPOSED_USER macros—one for each username. For more than a few usernames, use the EXPOSED_USER_FILE macro as in this example:

EXPOSED_USER_FILE(`/etc/mail/exposed.users')

The file, /etc/mail/exposed.users in our example, contains a list of usernames, with one username on each line. The sample file might look something like the following:

$ cat /etc/mail/exposed.users
root
postmaster
bin
daemon
adm
mail
news
operator
smmsp
nobody

This is just an example. Only non-unique usernames from which mail is actually sent would be placed in this file.

See Also

The nullclient configuration in Recipe 2.1 is a related configuration. Recipe 4.3 to Recipe 4.11 show masquerading with added features. The sendmail book covers MASQUERADE_AS in 4.4.2, and EXPOSED_USER and EXPOSED_USER_FILE are explained in 4.4.1. The “Address Masquerading” section of Linux Sendmail Administration, by Craig Hunt (Sybex), is a tutorial on masquerading. The cf/README file covers masquerading in the section Masquerading and Relaying.

4.3. Eliminating Masquerading for the Local Mailer

Problem

You have been asked to configure sendmail to masquerade the header sender address on all mail sent to external hosts, without adding the masquerade hostname to mail delivered by the local mailer.

Solution

Add the local_no_masquerade feature, the MASQUERADE_AS macro, and the EXPOSED_USER macro to the sendmail configuration. Here are examples of these configuration commands:

dnl Masquerade the From address as wrotethebook.com
MASQUERADE_AS(`wrotethebook.com')
dnl Users whose mail is not masqueraded
EXPOSED_USER(root)
dnl Don't masquerade addresses for the local mailer
FEATURE(`local_no_masquerade')

Build and install the new sendmail.cf file, and then restart sendmail. These steps are shown in Recipe 1.8.

Discussion

The hostname defined on the MASQUERADE_AS command line is stored in the sendmail.cf $M macro. sendmail rewrites the hostname in the From: address to the value found in the $M macro if the original hostname is listed in class $=w or class $=M. By default, class $=w contains all of the names and addresses of the local host. Thus, mail sent from the local host is masqueraded using the value from $M. This is exactly what you want when mail is sent to an external host, but it might not be exactly what you want when the local mailer delivers the mail locally. Some tests show how local mail is handled by the MASQUERADE_AS macro.

First, we run two tests using the configuration defined in Recipe Recipe 4.2 (i.e., masquerading without the l ocal_no_masquerade feature):

# sendmail -bt -Crecipe4.2.cf
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> /tryflags HS
> /try local alana
Trying header sender address alana for mailer local
canonify           input: alana
Canonify2          input: alana
Canonify2        returns: alana
canonify         returns: alana
1                  input: alana
1                returns: alana
HdrFromL           input: alana
MasqHdr            input: alana
MasqHdr          returns: alana
HdrFromL         returns: alana
final              input: alana
final            returns: alana
Rcode = 0, addr = alana 
> /try local alana@chef.wrotethebook.com
Trying header sender address alana@chef.wrotethebook.com for mailer local
canonify           input: alana @ chef . wrotethebook . com
Canonify2          input: alana < @ chef . wrotethebook . com >
Canonify2        returns: alana < @ chef . wrotethebook . com . >
canonify         returns: alana < @ chef . wrotethebook . com . >
1                  input: alana < @ chef . wrotethebook . com . >
1                returns: alana < @ chef . wrotethebook . com . >
HdrFromL           input: alana < @ chef . wrotethebook . com . >
MasqHdr            input: alana < @ chef . wrotethebook . com . >
MasqHdr          returns: alana < @ wrotethebook . com . >
HdrFromL         returns: alana < @ wrotethebook . com . >
final              input: alana < @ wrotethebook . com . >
final            returns: alana @ wrotethebook . com
Rcode = 0, addr = alana@wrotethebook.com
> /quit

Two valid local addresses are processed as header sender addresses for the local mailer. The first address is the local address for the username alana without any host part. In this case, the address goes in as alana, is processed, and comes out as alana. This is fine. Local addresses do not need a hostname part for delivery. Any local user receiving mail from alana can reply to that address and the mail will be successfully delivered by the local mailer. The second address, alana@chef.wrotethebook.com, is also a valid local address for alana because chef.wrotethebook.com is the name of the local host. This time, however, the address is changed to alana@wrotethebook.com by the header sender process. If a local user replies to alana@wrotethebook.com, the local mailer does not deliver the mail locally; instead, it is sent to the mail exchanger for wrotethebook.com by the esmtp mailer. Final delivery becomes the responsibility of the mail exchanger.

After adding the configuration lines shown in the Solution section, and building and installing the sendmail.cf file, the test results are different:

# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> =SHdrFromL
R< @ >          MAILER-DAEMON
R@ < @ $* >             MAILER-DAEMON
R$+             $: $> AddDomain $1
> /tryflags HS
> /try local alana
Trying header sender address alana for mailer local
canonify           input: alana
Canonify2          input: alana
Canonify2        returns: alana
canonify         returns: alana
1                  input: alana
1                returns: alana
HdrFromL           input: alana
HdrFromL         returns: alana
final              input: alana
final            returns: alana
Rcode = 0, addr = alana
> /try local alana@chef.wrotethebook.com
Trying header sender address alana@chef.wrotethebook.com for mailer local
canonify           input: alana @ chef . wrotethebook . com
Canonify2          input: alana < @ chef . wrotethebook . com >
Canonify2        returns: alana < @ chef . wrotethebook . com . >
canonify         returns: alana < @ chef . wrotethebook . com . >
1                  input: alana < @ chef . wrotethebook . com . >
1                returns: alana < @ chef . wrotethebook . com . >
HdrFromL           input: alana < @ chef . wrotethebook . com . >
HdrFromL         returns: alana < @ chef . wrotethebook . com . >
final              input: alana < @ chef . wrotethebook . com . >
final            returns: alana @ chef . wrotethebook . com
Rcode = 0, addr = alana@chef.wrotethebook.com
> /quit

Processing alana as a header sender address yields the same result as before. The address goes in as alana and comes out as alana. However, this time the process is different—the MasqHdr ruleset is not called by the HdrFromL ruleset. The difference is more clearly seen in the processing of the alana@chef.wrotethebook.com address, which also goes through the process unchanged. A reply to the header sender address for either alana or alana@chef.wrotethebook.com is handled as local mail and delivered by the local mailer.

Using local_no_masquerade reduces overhead by keeping local mail local, but the impact of having some local mail go through an external host is probably not too large. The local_no_masquerade feature also ensures that all mail from local users addressed to local users is handled in the same way. Consistency is an advantage of this feature. Recipe 4.4 shows the opposite approach to obtaining consistency, which is to force masquerading of all sender addresses.

See Also

Recipe 4.2 and Recipe 4.4 describe similar recipes. The sendmail book covers MASQUERADE_AS in 4.4.2, EXPOSED_USER in 4.4.1, and local_no_masquerade in 4.8.20. The “Address Masquerading” section of Linux Sendmail Administration, by Craig Hunt (Sybex), is a tutorial on masquerading. The cf/README file covers masquerading in the section Masquerading and Relaying.

4.4. Forcing Masquerading of Local Mail

Problem

You have been asked to configure sendmail to masquerade all header sender addresses, even those processed by the local mailer that lack a hostname part.

Solution

Create a sendmail configuration that combines the always_add_domain feature used in Recipe 4.1 with the MASQUERADE_AS and EXPOSED_USER macros used in Recipe 4.2. Examples of these commands are:

dnl Masquerade the From address as wrotethebook.com
MASQUERADE_AS(`wrotethebook.com')
dnl Users whose mail is not masqueraded
EXPOSED_USER(root)
dnl Add the domain name to all addresses
FEATURE(`always_add_domain')

Build and install the new sendmail.cf file, and restart sendmail as described in Recipe 1.8.

Discussion

The always_add_domain feature is not specific to masquerading. When always_add_domain is used without the MASQUERADE_AS macro, sendmail uses the fully qualified name of the local host found in the $j macro to create the hostname part of the sender address.[4] However, for this recipe we want to masquerade the hostname portion of the address; so the MASQUERADE_AS macro is used together with the always_add_domain feature in the recipe. After completing this recipe, running a sendmail -bt test produces the following result:

# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> /tryflags HS
> /try local alana
Trying header sender address alana for mailer local
canonify           input: alana
Canonify2          input: alana
Canonify2        returns: alana
canonify         returns: alana
1                  input: alana
1                returns: alana
HdrFromL           input: alana
AddDomain          input: alana
AddDomain        returns: alana < @ *LOCAL* >
MasqHdr            input: alana < @ *LOCAL* >
MasqHdr          returns: alana < @ wrotethebook . com . >
HdrFromL         returns: alana < @ wrotethebook . com . >
final              input: alana < @ wrotethebook . com . >
final            returns: alana @ wrotethebook . com
Rcode = 0, addr = alana@wrotethebook.com
> /try local alana@chef
Trying header sender address alana@chef for mailer local
canonify           input: alana @ chef
Canonify2          input: alana < @ chef >
Canonify2        returns: alana < @ chef . wrotethebook . com . >
canonify         returns: alana < @ chef . wrotethebook . com . >
1                  input: alana < @ chef . wrotethebook . com . >
1                returns: alana < @ chef . wrotethebook . com . >
HdrFromL           input: alana < @ chef . wrotethebook . com . >
AddDomain          input: alana < @ chef . wrotethebook . com . >
AddDomain        returns: alana < @ chef . wrotethebook . com . >
MasqHdr            input: alana < @ chef . wrotethebook . com . >
MasqHdr          returns: alana < @ wrotethebook . com . >
HdrFromL         returns: alana < @ wrotethebook . com . >
final              input: alana < @ wrotethebook . com . >
final            returns: alana @ wrotethebook . com
Rcode = 0, addr = alana@wrotethebook.com
> /try local alana@chef.wrotethebook.com
Trying header sender address alana@chef.wrotethebook.com for mailer local
canonify           input: alana @ chef . wrotethebook . com
Canonify2          input: alana < @ chef . wrotethebook . com >
Canonify2        returns: alana < @ chef . wrotethebook . com . >
canonify         returns: alana < @ chef . wrotethebook . com . >
1                  input: alana < @ chef . wrotethebook . com . >
1                returns: alana < @ chef . wrotethebook . com . >
HdrFromL           input: alana < @ chef . wrotethebook . com . >
AddDomain          input: alana < @ chef . wrotethebook . com . >
AddDomain        returns: alana < @ chef . wrotethebook . com . >
MasqHdr            input: alana < @ chef . wrotethebook . com . >
MasqHdr          returns: alana < @ wrotethebook . com . >
HdrFromL         returns: alana < @ wrotethebook . com . >
final              input: alana < @ wrotethebook . com . >
final            returns: alana @ wrotethebook . com
Rcode = 0, addr = alana@wrotethebook.com
> /quit

This test shows that local user addresses of all possible formats (user, user @ host, and user @ host.domain) are all rewritten into exactly the same format. The masquerade hostname stored in the $M macro is added to addresses that have no host part, and it is used to replace the hostname on addresses that do have a host part. Thus, the addresses alana, alana@chef, and alana@chef.wrotethebook.com all come out of the local mailer header sender address process rewritten to alana@wrotethebook.com. This consistency ensures that a reply to mail from Alana, regardless of how the original sender address was formatted, is handled in exactly the same way. In this case, all replies to mail from Alana will go to the mail exchanger first.

The goal of this recipe is to force sendmail to masquerade all sender addresses. At first glance, the allmasquerade feature might appear to be the correct choice for this recipe. However, the allmasquerade feature affects recipient addresses, and, in this recipe, we wish to rewrite only sender addresses. Recipe 4.5 covers masquerading the recipient address using the allmasquerade feature.

See Also

Recipe 4.1, Recipe 4.2, Recipe 4.3, and Recipe 4.5 provide information on related configurations. Recipe 3.1 covers the nullclient configuration. Recipe 3.3 covers the MAIL_HUB macro. The sendmail book covers the always_add_domain feature in 4.8.5 and the allmasquerade feature in 4.8.4.

4.5. Masquerading Recipient Addresses

Problem

By default, masquerading only affects sender addresses. In addition to masquerading sender addresses, you have been asked to configure sendmail to masquerade recipient addresses when those addresses include masqueraded hostnames.

Solution

Add the MASQUERADE_AS and EXPOSED_USER macros and the allmasquerade feature to the sendmail configuration. Here is an example of the lines that could be added:

dnl Masquerade as wrotethebook.com
MASQUERADE_AS(`wrotethebook.com')
dnl Users whose mail is not masqueraded
EXPOSED_USER(root)
dnl Masquerade recipient and sender addresses
FEATURE(`allmasquerade')

Build and install the new sendmail.cf file, and restart sendmail as described in Recipe 1.8.

Discussion

Using the basic masquerading configuration from Recipe 4.2, the header recipient address is not rewritten, as this test shows:

# sendmail -bt -Crecipe4.2.cf
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> /tryflags HR
> /try esmtp david@chef
Trying header recipient address david@chef for mailer esmtp
canonify           input: david @ chef
Canonify2          input: david < @ chef >
Canonify2        returns: david < @ chef . wrotethebook . com . >
canonify         returns: david < @ chef . wrotethebook . com . >
2                  input: david < @ chef . wrotethebook . com . >
2                returns: david < @ chef . wrotethebook . com . >
EnvToSMTP          input: david < @ chef . wrotethebook . com . >
PseudoToReal       input: david < @ chef . wrotethebook . com . >
PseudoToReal     returns: david < @ chef . wrotethebook . com . >
MasqSMTP           input: david < @ chef . wrotethebook . com . >
MasqSMTP         returns: david < @ chef . wrotethebook . com . >
EnvToSMTP        returns: david < @ chef . wrotethebook . com . >
final              input: david < @ chef . wrotethebook . com . >
final            returns: david @ chef . wrotethebook . com
Rcode = 0, addr = david@chef.wrotethebook.com
> /quit

The /tryflags command requests a test of the header recipient (HR) address processing. The /try command tells sendmail to process the address david@chef as a header recipient address for the esmtp mailer. The hostname chef is converted by DNS to the canonical name chef.wrotethebook.com. The hostname, however, is not masqueraded.

A configuration that contains the allmasquerade feature, in addition to the MASQUERADE_AS macro, rewrites recipient addresses for the emstp mailer, as this test shows:

# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> /tryflags HR
> /try esmtp david@chef
Trying header recipient address david@chef for mailer esmtp
canonify           input: david @ chef
Canonify2          input: david < @ chef >
Canonify2        returns: david < @ chef . wrotethebook . com . >
canonify         returns: david < @ chef . wrotethebook . com . >
2                  input: david < @ chef . wrotethebook . com . >
2                returns: david < @ chef . wrotethebook . com . >
HdrFromSMTP        input: david < @ chef . wrotethebook . com . >
PseudoToReal       input: david < @ chef . wrotethebook . com . >
PseudoToReal     returns: david < @ chef . wrotethebook . com . >
MasqSMTP           input: david < @ chef . wrotethebook . com . >
MasqSMTP         returns: david < @ chef . wrotethebook . com . >
MasqHdr            input: david < @ chef . wrotethebook . com . >
MasqHdr          returns: david < @ wrotethebook . com . >
HdrFromSMTP      returns: david < @ wrotethebook . com . >
final              input: david < @ wrotethebook . com . >
final            returns: david @ wrotethebook . com
Rcode = 0, addr = david@wrotethebook.com
> /quit

When the allmasquerade feature is used, masqueraded hostnames are hidden when they appear in the list of recipients. The advantage of this is that it provides a consistent view of the masqueraded addresses. A remote user might notice that people inside the wrotethebook.com domain send mail to the david account using the address david@chef.wrotethebook.com. That remote user might then try to do the same. If the organization really wants to encourage people to use the address david@wrotethebook.com to reach the david account, masquerading recipient addresses helps to do this by showing users only the preferred address.

See Also

Recipe 4.1 to Recipe 4.11 cover other masquerading features. The sendmail book covers MASQUERADE_AS in 4.4.2 and EXPOSED_USER in 4.4.1.

4.6. Masquerading at the Relay Host

Problem

You have been asked to configure the mail relay host to masquerade the header sender address of mail that originates on specific hosts as that mail passes through the mail relay.

Solution

Create a file that lists all of the hostnames that you want sendmail to masquerade. The file can be named anything you wish. This example names the file /etc/mail/masquerade-domains.

Add the MASQUERADE_AS , EXPOSED_USER , and MASQUERADE_DOMAIN_FILE macros to the sendmail configuration on the mail relay host. The MASQUERADE_DOMAIN_FILE macro must specify the masquerade-domains file created in the first step. Here are examples of the commands added to the mail relay’s configuration:

dnl Masquerade the From address as wrotethebook.com
MASQUERADE_AS(`wrotethebook.com')
dnl Users whose mail is not masqueraded
EXPOSED_USER(root)
dnl Load the list of hostnames that will be masqueraded
MASQUERADE_DOMAIN_FILE(`/etc/mail/masquerade-domains')

Build the new sendmail.cf file, copy it to /etc/mail, and restart sendmail. Recipe 1.8 provides examples of these steps.

Discussion

The MASQUERADE_DOMAIN_FILE macro specifies a file that is loaded into sendmail.cf class $=M. sendmail masquerades hosts listed in class $=M, as well as those listed in class $=w. However, hosts listed in class $=M are not equivalent to those listed in class $=w. Placing a hostname in class $=M enables masquerading. But, unlike mail addressed to hosts listed in class $=w, mail addressed to hosts in class $=M is not accepted for local delivery. Class $=M makes it possible to extend the set of hosts for which masquerading is performed without adding to the list of local hostname aliases. A simple test shows the effect of the MASQUERADE_DOMAIN_FILE macro:

# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> $=M
horseshoe.wrotethebook.com
rodent.wrotethebook.com
jamis.wrotethebook.com
> /tryflags HS
> /try esmtp david@jamis.wrotethebook.com
Trying header sender address david@jamis.wrotethebook.com for mailer esmtp
canonify           input: david @ jamis . wrotethebook . com
Canonify2          input: david < @ jamis . wrotethebook . com >
Canonify2        returns: david < @ jamis . wrotethebook . com . >
canonify         returns: david < @ jamis . wrotethebook . com . >
1                  input: david < @ jamis . wrotethebook . com . >
1                returns: david < @ jamis . wrotethebook . com . >
HdrFromSMTP        input: david < @ jamis . wrotethebook . com . >
PseudoToReal       input: david < @ jamis . wrotethebook . com . >
PseudoToReal     returns: david < @ jamis . wrotethebook . com . >
MasqSMTP           input: david < @ jamis . wrotethebook . com . >
MasqSMTP         returns: david < @ jamis . wrotethebook . com . >
MasqHdr            input: david < @ jamis . wrotethebook . com . >
MasqHdr          returns: david < @ wrotethebook . com . >
HdrFromSMTP      returns: david < @ wrotethebook . com . >
final              input: david < @ wrotethebook . com . >
final            returns: david @ wrotethebook . com
Rcode = 0, addr = david@wrotethebook.com
> $=w
chef
localhost.localdomain
localhost
[192.168.0.8]
[localhost.localdomain]
[127.0.0.1]
chef.wrotethebook.com
> /quit

The $=M command displays the contents of class $=M and shows that class $=M contains the data from the /etc/mail/masquerade-domains file we created. The email address david@jamis.wrotethebook.com is masqueraded as david@wrotethebook.com when it is processed as a header sender address for the esmtp mailer, even though jamis.wrotethebook.com is not included in class $=w because it is included in class $=M.

Alternatives

The sample masquerade-domains file contains only three entries. It is possible to replicate this configuration without creating the masquerade-domains file by placing three MASQUERADE_DOMAIN macros in the sendmail configuration file.

dnl Host names that will be masqueraded
MASQUERADE_DOMAIN(`rodent.wrotethebook.com')
MASQUERADE_DOMAIN(`horseshoe.wrotethebook.com')
MASQUERADE_DOMAIN(`jamis.wrotethebook.com')

This alternative was rejected because it is not as flexible as creating a separate masquerade-domains file. This recipe masquerades individual hostnames. Individual hosts come and go. Hostnames change. Each change would necessitate a change to the m4 configuration with the associated rebuild, reinstall, and restart if the MASQUERADE_DOMAIN solution were used. Changes in the masquerade-domains file only require a restart.

If you’re positive that you want to masquerade every host granted relay privileges, you might be tempted to use the relay-domains file as the MASQUERADE_DOMAIN_FILE:

MASQUERADE_DOMAIN_FILE(`/etc/mail/relay-domains')

This is generally a bad idea. A standard file should be used only for its standard purpose. The relay-domains file should be used only to grant relay privileges, and a separate file should be created to define masqueraded hostnames—even if those files are identical. The reason is that you cannot guarantee that they will remain identical into the future. In the long run, creating a separate file dedicated to a single purpose causes fewer problems than misusing a standard file.

See Also

Recipe 3.8 and Recipe 4.2 provide supporting information for this recipe. Recipe 4.4 and Recipe 4.7 cover similar configurations that should be evaluated before implementing this recipe. The sendmail book covers MASQUERADE_AS in 4.4.2, EXPOSED_USER in 4.4.1, MASQUERADE_DOMAIN in 4.4.3, and MASQUERADE_DOMAIN_FILE in 4.4.4. The “Address Masquerading” section of Linux Sendmail Administration, by Craig Hunt (Sybex), is a tutorial on masquerading. The cf/README file covers masquerading in the section Masquerading and Relaying.

4.7. Limiting Masquerading

Problem

By default, every hostname that is accepted for local delivery (i.e., every hostname that is accepted as an alias for the local host) is masqueraded when masquerading is enabled. You have been asked to create a sendmail configuration that does not masquerade every local hostname alias. Instead you are to masquerade only those hostnames that are specifically identified for masquerading.

Solution

Build a file that contains the names of just those hosts that you wish to masquerade. In this example, we name the file /etc/mail/masquerade-domains.

Create a sendmail configuration containing the MASQUERADE_AS , EXPOSED_USER , and MASQUERADE_DOMAIN_FILE macros and the limited_masquerade feature. Here are sample commands:

dnl Masquerade the From address as wrotethebook.com
MASQUERADE_AS(`wrotethebook.com')
dnl Users whose mail is not masqueraded
EXPOSED_USER(root)
dnl Load the list of hostnames that will be masqueraded
MASQUERADE_DOMAIN_FILE(`/etc/mail/masquerade-domains')
dnl Only masquerade names listed in class $=M
FEATURE(`limited_masquerade')

Rebuild and reinstall the sendmail.cf file, and then restart sendmail, as described in Recipe 1.8.

Discussion

By default, every host listed in class $=w is allowed to relay, and mail addressed to any host in class $=w is accepted for local delivery. In addition, when the MASQUERADE_AS macro is used, mail from any host listed in class $=w is masqueraded. This is usually just what you want. An exception, however, occurs when class $=w defines a larger set of hosts for relaying or local delivery than the set that should be masqueraded. For example, assume that you have a mail exchanger that handles mail for a few domains, and that your local-host-names file contains the following entries:

horseshoe.wrotethebook.com
wrotethebook.com
ora.com
example.com
stateu.edu

Two of these entries (horseshoe.wrotethebook.com and wrotethebook.com) are in the local domain. The others are not.

Normally, both the hostnames in class $=w and those in class $=M are masqueraded. While this system is the mail exchanger for ora.com, example.com, and stateu.edu, it should not masquerade those domains as wrotethebook.com. The limited_masquerade feature limits masquerading to just those hosts listed in class $=M. Relaying and local delivery continue to be influenced by class $=w, but class $=w is ignored for masquerading when the limited_masquerade feature is used. A few tests illustrate this.

The first test is a sendmail -bt test using the local-host-names file just shown and a masquerading configuration that does not use the limited_masquerade feature.

# sendmail -bt -Crecipe4.2.cf
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> $=w
example.com
chef
ora.com
localhost.localdomain
localhost
[192.168.0.8]
[localhost.localdomain]
stateu.edu
[127.0.0.1]
horseshoe.wrotethebook.com
wrotethebook.com
chef.wrotethebook.com
> /tryflags HS
> /try esmtp amanda@stateu.edu
Trying header sender address amanda@stateu.edu for mailer esmtp
canonify           input: amanda @ stateu . edu
Canonify2          input: amanda < @ stateu . edu >
Canonify2        returns: amanda < @ stateu . edu . >
canonify         returns: amanda < @ stateu . edu . >
1                  input: amanda < @ stateu . edu . >
1                returns: amanda < @ stateu . edu . >
HdrFromSMTP        input: amanda < @ stateu . edu . >
PseudoToReal       input: amanda < @ stateu . edu . >
PseudoToReal     returns: amanda < @ stateu . edu . >
MasqSMTP           input: amanda < @ stateu . edu . >
MasqSMTP         returns: amanda < @ stateu . edu . >
MasqHdr            input: amanda < @ stateu . edu . >
MasqHdr          returns: amanda < @ wrotethebook . com . >
HdrFromSMTP      returns: amanda < @ wrotethebook . com . >
final              input: amanda < @ wrotethebook . com . >
final            returns: amanda @ wrotethebook . com
Rcode = 0, addr = amanda@wrotethebook.com
> /quit

In this case, the header sender address amanda@stateu.edu is rewritten to amanda@wrotethebook.com. The people at stateu.edu do not want their addresses rewritten in this manner, even though they use the services of the mail exchanger. To fix this, add a MASQUERADE_DOMAIN_FILE macro to the configuration and create a masquerade-domains file containing the names of the hosts that should be masqueraded. The file might, for example, contain the following:

rodent.wrotethebook.com
crab.wrotethebook.com
jamis.wrotethebook.com
giant.wrotethebook.com
horseshoe.wrotethebook.com

The MASQUERADE_DOMAIN_FILE macro loads the file into class $=M. Adding the limited_masquerade feature to the configuration causes sendmail to ignore class $=w and use $=M for masquerading, as the following test shows:

# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> $=w
example.com
chef
ora.com
localhost.localdomain
localhost
[192.168.0.8]
[localhost.localdomain]
stateu.edu
[127.0.0.1]
horseshoe.wrotethebook.com
wrotethebook.com
chef.wrotethebook.com
> $=M
rodent.wrotethebook.com
crab.wrotethebook.com
jamis.wrotethebook.com
giant.wrotethebook.com
horseshoe.wrotethebook.com
> /tryflags HS
> /try esmtp amanda@stateu.edu
Trying header sender address amanda@stateu.edu for mailer esmtp
canonify           input: amanda @ stateu . edu
Canonify2          input: amanda < @ stateu . edu >
Canonify2        returns: amanda < @ stateu . edu . >
canonify         returns: amanda < @ stateu . edu . >
1                  input: amanda < @ stateu . edu . >
1                returns: amanda < @ stateu . edu . >
HdrFromSMTP        input: amanda < @ stateu . edu . >
PseudoToReal       input: amanda < @ stateu . edu . >
PseudoToReal     returns: amanda < @ stateu . edu . >
MasqSMTP           input: amanda < @ stateu . edu . >
MasqSMTP         returns: amanda < @ stateu . edu . >
MasqHdr            input: amanda < @ stateu . edu . >
MasqHdr          returns: amanda < @ stateu . edu . >
HdrFromSMTP      returns: amanda < @ stateu . edu . >
final              input: amanda < @ stateu . edu . >
final            returns: amanda @ stateu . edu
Rcode = 0, addr = amanda@stateu.edu
 > /quit

Now, mail from amanda@stateu.edu goes out with her full stateu.edu address despite the fact that stateu.edu still appears in class $=w. Only the hostnames in class $=M will be masqueraded.

The example used for these tests shows a single mail exchanger hosting multiple mail domains. This can also be done using virtual mail domains, which are covered in Chapter 5.

See Also

Recipe 2.1, Recipe 4.2, Recipe 4.4, and Recipe 4.6 provide supporting information for this recipe. Recipe 4.4 and Recipe 4.6 cover similar configurations that should be evaluated before implementing this recipe. The sendmail book covers MASQUERADE_AS in 4.4.2, EXPOSED_USER in 4.4.1, MASQUERADE_DOMAIN in 4.4.3, MASQUERADE_DOMAIN_FILE in 4.4.4, and the limited_masquerade feature in 4.8.18. The “Address Masquerading” section of Linux Sendmail Administration, by Craig Hunt (Sybex), is a tutorial on masquerading. The cf/README file covers masquerading in the section Masquerading and Relaying.

4.8. Masquerading All Hosts in a Domain

Problem

You want to masquerade every host within a domain without defining every individual hostname in class $=M or class $=w.

Solution

Create a sendmail configuration containing the MASQUERADE_AS and the EXPOSED_USER macros. Add the MASQUERADE_DOMAIN macro to define the domain to which the masqueraded hosts belong. Also add the masquerade_entire_domain feature to ensure that every host in the domain is masqueraded. Here is an example of these commands:

dnl Masquerade the From address as wrotethebook.com
MASQUERADE_AS(`wrotethebook.com')
dnl Users whose mail is not masqueraded
EXPOSED_USER(root)
dnl Store the domain name that will be masqueraded in class $=M
MASQUERADE_DOMAIN(`wrotethebook.com')
dnl Masquerade every host in the domain
FEATURE(`masquerade_entire_domain')

Build the new sendmail.cf file, install it, and restart sendmail. Recipe 1.8 provides an example of these steps.

Discussion

By default, every hostname listed in class $=w and class $=M is masqueraded when the MASQUERADE_AS macro is included in the configuration. This works perfectly on most systems because the system is only masquerading mail that originates on that system, and every valid hostname for the local host is defined in class $=w. Therefore, the system will masquerade all mail that is sent with one of its valid hostnames.

Mail exchangers, hubs, and relays are more complicated because they may handle mail for a variety of hosts. It is very common for a mail exchanger to handle mail for every host within a domain and to wish to masquerade mail from every host in that domain. Simply adding the domain name to class $=w or class $=M is not enough because the domain name is interpreted as a hostname, as this test shows:

# sendmail -bt -Crecipe4.6.cf
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> $=m
wrotethebook.com
horseshoe.wrotethebook.com
> /tryflags HS
> /try esmtp michael@crab.wrotethebook.com
Trying header sender address michael@crab.wrotethebook.com for mailer esmtp
canonify           input: michael @ crab . wrotethebook . com
Canonify2          input: michael < @ crab . wrotethebook . com >
Canonify2        returns: michael < @ crab . wrotethebook . com . >
canonify         returns: michael < @ crab . wrotethebook . com . >
1                  input: michael < @ crab . wrotethebook . com . >
1                returns: michael < @ crab . wrotethebook . com . >
HdrFromSMTP        input: michael < @ crab . wrotethebook . com . >
PseudoToReal       input: michael < @ crab . wrotethebook . com . >
PseudoToReal     returns: michael < @ crab . wrotethebook . com . >
MasqSMTP           input: michael < @ crab . wrotethebook . com . >
MasqSMTP         returns: michael < @ crab . wrotethebook . com . >
MasqHdr            input: michael < @ crab . wrotethebook . com . >
MasqHdr          returns: michael < @ crab . wrotethebook . com . >
HdrFromSMTP      returns: michael < @ crab . wrotethebook . com . >
final              input: michael < @ crab . wrotethebook . com . >
final            returns: michael @ crab . wrotethebook . com
Rcode = 0, addr = michael@crab.wrotethebook.com
> /quit

The test above shows that class $=M contains the value wrotethebook.com. However, mail from crab, which is a host in the wrotethebook.com domain, is not masqueraded because the values in $=M are viewed as hostnames, and only exact matches are masqueraded.

Adding the masquerade_entire_domain feature to the configuration changes this behavior. With this feature added, values in class $=w are still interpreted as hostnames, but values in class $=M are interpreted as domain names, and every host in a domain listed in class $=M is masqueraded. The masquerade_entire_domain feature is always associated with either a MASQUERADE_DOMAIN macro or a MASQUERADE_DOMAIN_FILE macro, both of which load values into class $=M, because the masquerade_entire_domain feature only affects values in class $=M. Testing the configuration created by this recipe shows the impact of this feature:

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

The $=M command shows the value stored in class $=M by the MASQUERADE_DOMAIN macro. In this case we have only one value to store in class $=M, and we do not anticipate changing it, so MASQUERADE_DOMAIN works well. If you have several values, you may want to use MASQUERADE_DOMAIN_FILE, which is used in Recipe Recipe 4.6. In this test, crab.wrotethebook.com is masqueraded because it is a host in a domain listed in class $=M—the impact of the masquerade_entire_domain feature. This feature does not impact values in class $=w. Those values are still interpreted as hosts and are still masqueraded. If you want to limit masquerading to just the domains defined in class $=M, add the limited_masquerade feature to the configuration, as described in Recipe 4.7.

See Also

Recipe 4.4 and Recipe 4.7 cover similar configurations that should be evaluated before implementing this recipe. The sendmail book covers the masquerade_entire_domain feature in 4.8.25, MASQUERADE_AS in 4.4.2, EXPOSED_USER in 4.4.1, MASQUERADE_DOMAIN in 4.4.3, and MASQUERADE_DOMAIN_FILE in 4.4.4. The “Address Masquerading” section of Linux Sendmail Administration, by Craig Hunt (Sybex), is a tutorial on masquerading. The cf/README file covers masquerading in the section Masquerading and Relaying.

4.9. Masquerading Most of the Hosts in a Domain

Problem

You have been asked to create a sendmail configuration that masquerades all of the hosts in a domain, with the exception of a few special purpose hosts that should be exposed to the outside world.

Solution

Create a file listing all of the hosts in the domain that should be exempted from masquerading. The name of the file is arbitrary. This recipe uses the name /etc/mail/masquerade-exceptions.

Add the masquerade_entire_domain feature and the MASQUERADE_AS , EXPOSED_USER , MASQUERADE_DOMAIN , and MASQUERADE_EXCEPTION_FILE macros to the sendmail configuration. Examples of the relevant commands are shown below:

dnl Masquerade the From address as wrotethebook.com
MASQUERADE_AS(`wrotethebook.com')
dnl Users whose mail is not masqueraded
EXPOSED_USER(root)
dnl Store the domain name that will be masqueraded in class $=M
MASQUERADE_DOMAIN(`wrotethebook.com')
dnl Masquerade all hosts in the domain
FEATURE(`masquerade_entire_domain')
dnl Load the list of hosts that should not be masqueraded
MASQUERADE_EXCEPTION_FILE(`/etc/mail/masquerade-exceptions')

Using Recipe 1.8 as a guide, rebuild and reinstall sendmail.cf, and then restart sendmail.

Discussion

The masquerade_entire_domain feature causes sendmail to treat every name in class $=M as a domain, and, when combined with the MASQUERADE_AS macro, to masquerade every host within those domains. Recipe 4.8 shows the effect of the masquerade_entire_domain feature. If you want to masquerade most of the hosts in a domain, it is often easier to masquerade the entire domain and then make exceptions than it is to list all of the individual hosts that you want to masquerade. The file identified by the MASQUERADE_EXCEPTION_FILE macro is loaded into sendmail.cf class $=N. Class $=N contains a lists of hosts that should not be masqueraded even if they belong to a domain that is being masqueraded. For example, assume the /etc/mail/masquerade-exceptions file created for this recipe contains the following entries:

# cat > /etc/mail/masquerade-exceptions
               www.wrotethebook.com
               info.wrotethebook.com
               sales.wrotethebook.com
               Ctrl-D

This recipe masquerades all hosts in wrotethebook.com except for these three hosts, as this test shows:

# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> $=M
wrotethebook.com
> /tryflags HS
> /try esmtp peyton@crab.wrotethebook.com
Trying header sender address peyton@crab.wrotethebook.com for mailer esmtp
canonify           input: peyton @ crab . wrotethebook . com
Canonify2          input: peyton < @ crab . wrotethebook . com >
Canonify2        returns: peyton < @ crab . wrotethebook . com . >
canonify         returns: peyton < @ crab . wrotethebook . com . >
1                  input: peyton < @ crab . wrotethebook . com . >
1                returns: peyton < @ crab . wrotethebook . com . >
HdrFromSMTP        input: peyton < @ crab . wrotethebook . com . >
PseudoToReal       input: peyton < @ crab . wrotethebook . com . >
PseudoToReal     returns: peyton < @ crab . wrotethebook . com . >
MasqSMTP           input: peyton < @ crab . wrotethebook . com . >
MasqSMTP         returns: peyton < @ crab . wrotethebook . com . >
MasqHdr            input: peyton < @ crab . wrotethebook . com . >
MasqHdr          returns: peyton < @ wrotethebook . com . >
HdrFromSMTP      returns: peyton < @ wrotethebook . com . >
final              input: peyton < @ wrotethebook . com . >
final            returns: peyton @ wrotethebook . com
Rcode = 0, addr = peyton@wrotethebook.com
> $=N
info.wrotethebook.com
www.wrotethebook.com
sales.wrotethebook.com
> /try esmtp jill@sales.wrotethebook.com
Trying header sender address jill@sales.wrotethebook.com for mailer esmtp
canonify           input: jill @ sales . wrotethebook . com
Canonify2          input: jill < @ sales . wrotethebook . com >
Canonify2        returns: jill < @ sales . wrotethebook . com . >
canonify         returns: jill < @ sales . wrotethebook . com . >
1                  input: jill < @ sales . wrotethebook . com . >
1                returns: jill < @ sales . wrotethebook . com . >
HdrFromSMTP        input: jill < @ sales . wrotethebook . com . >
PseudoToReal       input: jill < @ sales . wrotethebook . com . >
PseudoToReal     returns: jill < @ sales . wrotethebook . com . >
MasqSMTP           input: jill < @ sales . wrotethebook . com . >
MasqSMTP         returns: jill < @ sales . wrotethebook . com . >
MasqHdr            input: jill < @ sales . wrotethebook . com . >
MasqHdr          returns: jill < @ sales . wrotethebook . com . >
HdrFromSMTP      returns: jill < @ sales . wrotethebook . com . >
final              input: jill < @ sales . wrotethebook . com . >
final            returns: jill @ sales . wrotethebook . com
Rcode = 0, addr = jill@sales.wrotethebook.com
> /quit

The $=M command shows that class $=M contains the domain name wrotethebook.com. In this configuration, the masquerade_entire_domain feature is used, so processing peyton@crab.wrotethebook.com as a header sender address for the esmtp mailer yields the address peyton@wrotethebook.com because crab is a host in the wrotethebook.com domain. However, processing the address jill@sales.wrotethebook.com as a header sender address for the esmtp mailer returns the original address with no masquerading, even though sales.wrotethebook.com is a host in the wrotethebook.com domain. The reason that sales.wrotethebook.com is not masqueraded is because it is listed in class $=N as the $=N command shows.

Alternatives

Given the small number of hostnames in the masquerade-exceptions file, using the MASQUERADE_EXCEPTION macro would be a viable alternative to creating a file for the MASQUERADE_EXCEPTION_FILE macro. This recipe could be rewritten by replacing the MASQUERADE_EXCEPTION macro with the following lines:

dnl Define hosts that should not be masqueraded
MASQUERADE_EXCEPTION(`www.wrotethebook.com')
MASQUERADE_EXCEPTION(`info.wrotethebook.com')
MASQUERADE_EXCEPTION(`sales.wrotethebook.com')

The systems that are listed as exceptions to masquerading are generally special purpose systems. There are usually only a limited number of these systems, and there are few changes to this set of systems. For these reasons, the MASQUERADE_EXCEPTION macro is a viable alternative to creating a file to hold this list of hostnames. When you have a large number of hosts that should be excepted from masquerading, or the list of exempted hosts changes frequently, the MASQUERADE_EXCEPTION_FILE is the best choice. Use the macro that you prefer—they both work well.

See Also

Recipe 4.7 and Recipe 4.8 cover similar configurations that should be evaluated before implementing this recipe. The sendmail book covers the masquerade_entire_domain feature in 4.8.25, MASQUERADE_AS in 4.4.2, EXPOSED_USER in 4.4.1, MASQUERADE_DOMAIN in 4.4.3, MASQUERADE_DOMAIN_FILE in 4.4.4, and MASQUERADE_EXCEPTION and MASQUERADE_EXCEPTION_FILE in 4.4.5. The “Address Masquerading” section of Linux Sendmail Administration, by Craig Hunt (Sybex), is a tutorial on masquerading. The cf/README file covers masquerading in the section Masquerading and Relaying.

4.10. Masquerading the Envelope Address

Problem

In addition to masquerading the header sender address, you have been asked to create a configuration that masquerades the envelope sender address used by the SMTP protocol.

Solution

Add the masquerade_envelope feature, the MASQUERADE_AS macro, and the EXPOSED_USER macro to the sendmail configuration file. Here are examples:

dnl Masquerade the From address as wrotethebook.com
MASQUERADE_AS(`wrotethebook.com')
dnl Users whose mail is not masqueraded
EXPOSED_USER(root)
dnl Masquerade the envelope address as wrotethebook.com
FEATURE(`masquerade_envelope')

Build and install the new configuration, and then restart sendmail. Recipe 1.8 provides an example.

Discussion

By default, the MASQUERADE_AS macro replaces the hostname in the From: message header with the masquerade value. The From: header address is referred to as the header sender address. From the point of view of the SMTP protocol, the message headers are just part of the message—the data sent after the SMTP DATA command. The addresses exchanged by the SMTP protocol before the SMTP DATA command are called the envelope addresses, and the address of the source of the mail is called the envelope sender address. The envelope sender address appears in the SMTP protocol exchange as the value in the SMTP MAIL From: command. By default, the MASQUERADE_AS macro does not masquerade the hostname in the envelope sender address. A test of the basic masquerade configuration shows this:

# sendmail -bt -Crecipe4.2.cf
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> /tryflags HS
> /try esmtp clark@horseshoe.wrotethebook.com
Trying header sender address clark@horseshoe.wrotethebook.com for mailer esmtp
canonify           input: clark @ horseshoe . wrotethebook . com
Canonify2          input: clark < @ horseshoe . wrotethebook . com >
Canonify2        returns: clark < @ horseshoe . wrotethebook . com . >
canonify         returns: clark < @ horseshoe . wrotethebook . com . >
1                  input: clark < @ horseshoe . wrotethebook . com . >
1                returns: clark < @ horseshoe . wrotethebook . com . >
HdrFromSMTP        input: clark < @ horseshoe . wrotethebook . com . >
PseudoToReal       input: clark < @ horseshoe . wrotethebook . com . >
PseudoToReal     returns: clark < @ horseshoe . wrotethebook . com . >
MasqSMTP           input: clark < @ horseshoe . wrotethebook . com . >
MasqSMTP         returns: clark < @ horseshoe . wrotethebook . com . >
MasqHdr            input: clark < @ horseshoe . wrotethebook . com . >
MasqHdr          returns: clark < @ wrotethebook . com . >
HdrFromSMTP      returns: clark < @ wrotethebook . com . >
final              input: clark < @ wrotethebook . com . >
final            returns: clark @ wrotethebook . com
Rcode = 0, addr = clark@wrotethebook.com
> /tryflags ES
> /try esmtp clark@horseshoe.wrotethebook.com
Trying envelope sender address clark@horseshoe.wrotethebook.com for mailer esmtp
canonify           input: clark @ horseshoe . wrotethebook . com
Canonify2          input: clark < @ horseshoe . wrotethebook . com >
Canonify2        returns: clark < @ horseshoe . wrotethebook . com . >
canonify         returns: clark < @ horseshoe . wrotethebook . com . >
1                  input: clark < @ horseshoe . wrotethebook . com . >
1                returns: clark < @ horseshoe . wrotethebook . com . >
EnvFromSMTP        input: clark < @ horseshoe . wrotethebook . com . >
PseudoToReal       input: clark < @ horseshoe . wrotethebook . com . >
PseudoToReal     returns: clark < @ horseshoe . wrotethebook . com . >
MasqSMTP           input: clark < @ horseshoe . wrotethebook . com . >
MasqSMTP         returns: clark < @ horseshoe . wrotethebook . com . >
MasqEnv            input: clark < @ horseshoe . wrotethebook . com . >
MasqEnv          returns: clark < @ horseshoe . wrotethebook . com . >
EnvFromSMTP      returns: clark < @ horseshoe . wrotethebook . com . >
final              input: clark < @ horseshoe . wrotethebook . com . >
final            returns: clark @ horseshoe . wrotethebook . com
Rcode = 0, addr = clark@horseshoe.wrotethebook.com
> /quit

The first /tryflags command configures sendmail to test header sender (HS) address processing. The first /try command processes clark@horseshoe.wrotethebook.com as the header sender address for the esmtp mailer. The result shows that the address is masqueraded as clark@wrotethebook.com. The second /tryflags command configures the system for envelope sender (ES) address processing. This time, the address is not masqueraded. This is the basic masquerade configuration; it masquerades header addresses but not envelope addresses. The masquerade_envelope feature changes this, as the following test of this recipe’s configuration shows:

# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address> 
> /tryflags ES
> /try esmtp clark@horseshoe.wrotethebook.com
Trying envelope sender address clark@horseshoe.wrotethebook.com for mailer esmtp
canonify           input: clark @ horseshoe . wrotethebook . com
Canonify2          input: clark < @ horseshoe . wrotethebook . com >
Canonify2        returns: clark < @ horseshoe . wrotethebook . com . >
canonify         returns: clark < @ horseshoe . wrotethebook . com . >
1                  input: clark < @ horseshoe . wrotethebook . com . >
1                returns: clark < @ horseshoe . wrotethebook . com . >
EnvFromSMTP        input: clark < @ horseshoe . wrotethebook . com . >
PseudoToReal       input: clark < @ horseshoe . wrotethebook . com . >
PseudoToReal     returns: clark < @ horseshoe . wrotethebook . com . >
MasqSMTP           input: clark < @ horseshoe . wrotethebook . com . >
MasqSMTP         returns: clark < @ horseshoe . wrotethebook . com . >
MasqEnv            input: clark < @ horseshoe . wrotethebook . com . >
MasqHdr            input: clark < @ horseshoe . wrotethebook . com . >
MasqHdr          returns: clark < @ wrotethebook . com . >
MasqEnv          returns: clark < @ wrotethebook . com . >
EnvFromSMTP      returns: clark < @ wrotethebook . com . >
final              input: clark < @ wrotethebook . com . >
final            returns: clark @ wrotethebook . com
Rcode = 0, addr = clark@wrotethebook.com
> /quit

This test replicates the second part of the earlier test. With the basic masquerade configuration, the envelope sender address was not masqueraded—now it is.

Users and user mail tools deal with header sender addresses. A reply in a user mail tool will reply to an address found in a message header. Masquerading header sender addresses ensures that remote users receive the correct address for replying to local users. In this way, masquerading benefits users.

Users, however, do not usually deal with envelope addresses. Masquerading envelope addresses simplifies machine interactions, and there are several good reasons to do this:

  • To enable relaying. Evaluating the envelope address is one of the standard checks sendmail performs to authorize relaying. The header sender address is not normally used in relaying. Thus, if hosts need to masquerade in order to pass mail through a relay, it is the envelope sender address that is masqueraded.

  • To ensure proper delivery of error messages, which are sent to the envelope address.

  • To prevent sendmail from rejecting mail from hosts using private hostnames.sendmail checks the envelope sender address to see if it can be resolved via DNS. When private hostnames are used internally, mail from those hosts must be masqueraded to a hostname found in the public DNS and that masquerading must be applied to the envelope address. Otherwise, the mail might be rejected by the sendmail process running on the remote system.

For these and other reasons, many sites that use masquerading apply it to both the header and the envelope addresses.

See Also

Recipe 4.2 provides supporting information for this configuration. Chapter 3 covers configuring a mail relay and discusses the use of the envelope address in relaying. The sendmail book covers the MASQUERADE_AS macro in 4.4.2, the EXPOSED_USER macro in 4.4.1, and the feature in 4.8.26. The “Address Masquerading” section of Linux Sendmail Administration, by Craig Hunt (Sybex), is a tutorial on masquerading. The cf/README file covers masquerading in the section Masquerading and Relaying.

4.11. Rewriting the From Address with the genericstable

Problem

You have been asked to configure sendmail to replace the login name used as the sender address for mail originating from the local host with an address that meets the organization’s desired address format.

Solution

Build a genericstable database to map the input sender address to the format you desire in the header sender address. Each entry in the genericstable contains two fields: the key and the value returned for that key. The key field of a genericstable entry can be either a full email address or a username. The value returned is the value that will be used as the rewritten sender address, which is normally a full address containing both a username and a hostname. To create the genericstable , first create a text file that contains the database entries and then run that text file through the makemap script to build the genericstable database.

To use the newly created genericstable database, create a sendmail configuration containing the genericstable feature and the GENERICS_DOMAIN macro. The domain name argument of the GENERICS_DOMAIN macro tells sendmail when to use the genericstable. Here are sample lines that could be added to the sendmail configuration:

               dnl Process login names through the genericstable
               FEATURE(`genericstable')
               dnl Identify the host that the genericstable applies to
               GENERICS_DOMAIN(`chef.wrotethebook.com')

Rebuild, reinstall, and restart sendmail as described in Recipe 1.8.

Discussion

By default, mail originating on the local host uses the user’s login name as the username part, so, in this example, the key field of the genericstable is a login name and the value returned is a full email address. The mapping can be anything you wish, but, for this example, we map login names to the user’s real name and the local domain name formatted as firstname.lastname @ domain.[5] To do this, a text file is created and then run through makemap:

# cd /etc/mail
# cat > genericstable
               kathy      kathy.mccafferty@wrotethebook.com
               craig      craig.hunt@wrotethebook.com
               sara       sara.henson@wrotethebook.com
               dave       david.craig@wrotethebook.com
               becky      rebecca.fro@wrotethebook.com
               alana      alana.smiley@wrotethebook.com
               jay        jay.james@wrotethebook.com
               Ctrl-D
# makemap hash genericstable < genericstable

Of course, if mail arrives at the local host addressed to firstname.lastname @ domain, aliases are needed to deliver the mail to the users’ real address. Aliases based on the genericstable entries shown above could be added to the aliases database in the following manner:

# cd /etc/mail
# cat > aliases
               kathy.mccafferty: kathy
               craig.hunt:       craig
               sara.henson:      sara
               david.craig:      dave
               rebecca.fro:      becky
               alana.smiley:     alana
               jay.james:        jay
               Ctrl-D
# newaliases

After building the genericstable database and adding the genericstable feature to the configuration, the genericstable data is available to sendmail, as this sendmail -bt test shows:

# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> /map generics sara
map_lookup: generics (sara) returns sara.henson@wrotethebook.com (0)
> /quit

Here sendmail is run with the -bt option and the /map command is used to lookup the entry for the login name sara. The value returned is sara.henson@wrotethebook.com. Thus, one would expect that any mail originating on this host from sara would have the sender address rewritten to sara.henson@wrotethebook.com. That would be the case here, but only because this recipe includes the GENERICS_DOMAIN macro. Without the GENERICS_DOMAIN macro, the genericstable feature does not act as one might expect. Here is an example using the genericstable feature without the GENERICS_DOMAIN macro:

# sendmail -bt -Cspecial4-11-test.cf
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> $j
chef.wrotethebook.com
> /tryflags HS
> /try esmtp sara
Trying header sender address sara for mailer esmtp
canonify           input: sara
Canonify2          input: sara
Canonify2        returns: sara
canonify         returns: sara
1                  input: sara
1                returns: sara
HdrFromSMTP        input: sara
PseudoToReal       input: sara
PseudoToReal     returns: sara
MasqSMTP           input: sara
MasqSMTP         returns: sara < @ *LOCAL* >
MasqHdr            input: sara < @ *LOCAL* >
canonify           input: sara . henson @ wrotethebook . com
Canonify2          input: sara . henson < @ wrotethebook . com >
Canonify2        returns: sara . henson < @ wrotethebook . com . >
canonify         returns: sara . henson < @ wrotethebook . com . >
MasqHdr          returns: sara . henson < @ wrotethebook . com . >
HdrFromSMTP      returns: sara . henson < @ wrotethebook . com . >
final              input: sara . henson < @ wrotethebook . com . >
final            returns: sara . henson @ wrotethebook . com
Rcode = 0, addr = sara.henson@wrotethebook.com
> /try esmtp sara@chef
Trying header sender address sara@chef for mailer esmtp
canonify           input: sara @ chef
Canonify2          input: sara < @ chef >
Canonify2        returns: sara < @ chef . wrotethebook . com . >
canonify         returns: sara < @ chef . wrotethebook . com . >
1                  input: sara < @ chef . wrotethebook . com . >
1                returns: sara < @ chef . wrotethebook . com . >
HdrFromSMTP        input: sara < @ chef . wrotethebook . com . >
PseudoToReal       input: sara < @ chef . wrotethebook . com . >
PseudoToReal     returns: sara < @ chef . wrotethebook . com . >
MasqSMTP           input: sara < @ chef . wrotethebook . com . >
MasqSMTP         returns: sara < @ chef . wrotethebook . com . >
MasqHdr            input: sara < @ chef . wrotethebook . com . >
MasqHdr          returns: sara < @ chef . wrotethebook . com . >
HdrFromSMTP      returns: sara < @ chef . wrotethebook . com . >
final              input: sara < @ chef . wrotethebook . com . >
final            returns: sara @ chef . wrotethebook . com
Rcode = 0, addr = sara@chef.wrotethebook.com
> /quit

The test just shown is run on a host named chef.wrotethebook.com, as the $j command shows. This host is using the genericstable feature without the GENERICS_DOMAIN macro. Because Unix login names must be unique on a given host, we know that sara, sara@chef, and sara@chef.wrotethebook.com are all the same person. Yet the test shows that sendmail does not treat these input addresses the same with regard to the genericstable. When the input address has no host part, it is rewritten; otherwise, it is not rewritten. The reason is simple: the genericstable we created only maps login names; none of the database keys contains a hostname part. However, we want to create a consistent header sender address—not an address that varies depending on the address input by the user.

For this to be accomplished, this recipe uses the GENERICS_DOMAIN macro and sets that macro to the fully qualified name used in the $j macro. This causes sendmail to consistently apply the genericstable to rewriting the header sender address for all mail that originates on this host. Here is a test run with this recipe’s configuration:

# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> $=G
chef.wrotethebook.com
> /tryflags HS
> /try esmtp sara@[127.0.0.1]
Trying header sender address sara@[127.0.0.1] for mailer esmtp
canonify           input: sara @ [ 127 . 0 . 0 . 1 ]
Canonify2          input: sara < @ [ 127 . 0 . 0 . 1 ] >
Canonify2        returns: sara < @ chef . wrotethebook . com . >
canonify         returns: sara < @ chef . wrotethebook . com . >
1                  input: sara < @ chef . wrotethebook . com . >
1                returns: sara < @ chef . wrotethebook . com . >
HdrFromSMTP        input: sara < @ chef . wrotethebook . com . >
PseudoToReal       input: sara < @ chef . wrotethebook . com . >
PseudoToReal     returns: sara < @ chef . wrotethebook . com . >
MasqSMTP           input: sara < @ chef . wrotethebook . com . >
MasqSMTP         returns: sara < @ chef . wrotethebook . com . >
MasqHdr            input: sara < @ chef . wrotethebook . com . >
canonify           input: sara . henson @ wrotethebook . com
Canonify2          input: sara . henson < @ wrotethebook . com >
Canonify2        returns: sara . henson < @ wrotethebook . com . >
canonify         returns: sara . henson < @ wrotethebook . com . >
MasqHdr          returns: sara . henson < @ wrotethebook . com . >
HdrFromSMTP      returns: sara . henson < @ wrotethebook . com . >
final              input: sara . henson < @ wrotethebook . com . >
final            returns: sara . henson @ wrotethebook . com
Rcode = 0, addr = sara.henson@wrotethebook.com
> /quit

The GENERICS_DOMAIN macro loads the value specified on its command line into sendmail.cf class $=G. sendmail applies the genericstable to any sender address that has a name listed in class $=G as its hostname part. In this test, the $=G command shows that class $=G contains only chef.wrotethebook.com, which is the fully qualified name of this host—the same value found in $j. Therefore, any hostname that sendmail rewrites to $j will match the value in $=G and will also be processed through the genericstable. In this test, we show an extreme case using the loopback address [127.0.0.1] as the hostname, which sendmail rewrites to the value returned by $j and then rewrites through the genericstable. We could just as easily have used chef, localhost, [192.168.0.8], or chef.wrotethebook.com as the hostname—all valid variations of the local host’s name work.[6]

In this recipe, the email processed through the genericstable all originates on the local host. Thus, the input sender address either has no hostname part, or the hostname in the sender address is a hostname of the local host. Many systems (mail relay hosts are an example) handle mail from a variety of hosts. The sender address in that case can contain the hostname of any host granted relaying privileges. Because mail is only processed through the genericstable when the sender’s hostname is found in class $=G, the hostnames of all of the hosts allowed to send mail through the relay should be loaded into class $=G. If this is a small number of hostnames, it can be done by placing multiple GENERICS_DOMAIN macros in the configuration. If there are more than a few hostnames, it is simpler to create a file that lists all of the hosts and then to reference that file inside the configuration with a GENERICS_DOMAIN_FILE macro. For example, the following macro used in this recipe, in place of the GENERICS_DOMAIN macro, would load $=G with all of the hostnames listed in a file called /etc/mail/generics-domains:

               GENERICS_DOMAIN_FILE(`/etc/mail/generics-domains')

The GENERICS_DOMAIN_FILE macro makes it possible to coordinate genericstable processing with other sendmail functions simply by extracting hostnames from the files that control those other functions and storing those names in the file loaded by the GENERICS_DOMAIN_FILE macro. For example, copying the hostnames from /etc/mail/local-host-names to the file loaded by the GENERICS_DOMAIN_FILE macro enables genericstable processing for all systems that use a mail exchanger.

See Also

Recipe 4.12 shows another genericstable example. Recipe 4.14 shows how the genericstable can be read from an LDAP server. The sendmail book covers the genericstable in 4.8.16, the GENERICS_DOMAIN macro in 4.8.16.1, and the GENERICS_DOMAIN_FILE macro in 4.8.16.2. Chapter 9 of Linux Sendmail Administration, by Craig Hunt (Sybex), contains the tutorial section “Masquerading Usernames” that provides additional information.

4.12. Rewriting Sender Addresses for an Entire Domain

Problem

You have been asked to configure sendmail to rewrite the sender address into your organization’s standard header sender address format on all mail originating from the local domain.

Solution

Build a genericstable database to map the input address to the format desired for the header sender address. Each entry in the genericstable contains two fields. The first field matches the input address and the second field rewrites the address. To create the genericstable, first create a text file that contains the database entries, then run that text file through the makemap command to build the genericstable database.

Add the genericstable feature, the GENERICS_DOMAIN macro, and the generics_entire_domain feature to the sendmail configuration. The added commands would look something like the following:

dnl Process login names through the genericstable
FEATURE(`genericstable')
dnl Load wrotethebook.com into G
GENERICS_DOMAIN(`wrotethebook.com')
dnl Interpret the value in G as a domain name
FEATURE(`generics_entire_domain')

Build sendmail.cf, copy it to /etc/mail, and then restart sendmail. See Recipe Recipe 1.8 if you need an example of this step.

Discussion

The input sender address is rewritten as specified by the genericstable. All or part of the input address is used as a key to search the genericstable. When a value is returned by the search, that value is used to rewrite the address. For this example, we create the following genericstable:

# cd /etc/mail
# cat > genericstable
               kathy                            kathy.mccafferty@wrotethebook.com
               craig                            craig.hunt@wrotethebook.com
               sara                             sara.henson@wrotethebook.com
               dave                             david.craig@wrotethebook.com
               becky                            rebecca.fro@wrotethebook.com
               jay                              jay.james@wrotethebook.com
               alana@blur.wrotethebook.com      alana.darling@wrotethebook.com
               alana@giant.wrotethebook.com     alana.henson@wrotethebook.com
               alana                            alana.smiley@wrotethebook.com
               Ctrl-D
# makemap hash genericstable < genericstable

The genericstable feature adds the code sendmail needs to make use of the genericstable. The GENERICS_DOMAIN macro adds the value specified on the macro command line to sendmail class $=G.

Normally, the values listed in class $=G are interpreted as hostnames, and only exact matches enable genericstable processing. The generics_entire_domain feature causes sendmail to interpret the values in class $=G as domain names, and any host within one of those domains is processed through the genericstable. Here is a test of a system running this recipe:

# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> $=G
wrotethebook.com
> /tryflags HS
> /try esmtp dave@tassajara.wrotethebook.com
Trying header sender address dave@tassajara.wrotethebook.com for mailer esmtp
canonify           input: dave @ tassajara . wrotethebook . com
Canonify2          input: dave < @ tassajara . wrotethebook . com >
Canonify2        returns: dave < @ tassajara . wrotethebook . com . >
canonify         returns: dave < @ tassajara . wrotethebook . com . >
1                  input: dave < @ tassajara . wrotethebook . com . >
1                returns: dave < @ tassajara . wrotethebook . com . >
HdrFromSMTP        input: dave < @ tassajara . wrotethebook . com . >
PseudoToReal       input: dave < @ tassajara . wrotethebook . com . >
PseudoToReal     returns: dave < @ tassajara . wrotethebook . com . >
MasqSMTP           input: dave < @ tassajara . wrotethebook . com . >
MasqSMTP         returns: dave < @ tassajara . wrotethebook . com . >
MasqHdr            input: dave < @ tassajara . wrotethebook . com . >
canonify           input: david . craig @ wrotethebook . com
Canonify2          input: david . craig < @ wrotethebook . com >
Canonify2        returns: david . craig < @ wrotethebook . com . >
canonify         returns: david . craig < @ wrotethebook . com . >
MasqHdr          returns: david . craig < @ wrotethebook . com . >
HdrFromSMTP      returns: david . craig < @ wrotethebook . com . >
final              input: david . craig < @ wrotethebook . com . >
final            returns: david . craig @ wrotethebook . com
Rcode = 0, addr = david.craig@wrotethebook.com
> /map generics dave
map_lookup: generics (dave) returns david.craig@wrotethebook.com (0)
> /quit

The test shows that the hostname tassajara.wrotethebook.com is not in class $=G; in fact, class $=G only contains the domain name wrotethebook.com. Yet the header sender address dave@tassajara.wrotethebook.com is rewritten to david.craig@wrotethebook.com, which is the value returned by the genericstable for the key dave.

In this example, every dave account in the entire wrotethebook.com domain belongs to David Craig. No matter what host in that domain he sends mail from, when the mail passes through this system, it is rewritten to david.craig@wrotethebook.com. For replies to the rewritten address to work correctly, the rewritten hostname must resolve to a host that will accept the mail, and that host must have an alias for david.craig that delivers the mail to the real dave account.

A more interesting case is the mapping of the username alana. Three people in the wrotethebook.com domain have this username: Alana Darling, Alana Henson, and Alana Smiley. The following test shows how each of these names are mapped:

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

The complete addresses used in the genericstable keys for Alana Darling and Alana Henson make it possible for sendmail to do one-to-one mappings for those addresses. The key used for Alana Smiley’s entry, however, is just a username. That key matches any input address that contains the username alana, except for the input addresses alana@blur.wrotethebook.com and alana@giant.wrotethebook.com.

When a system handles mail that originates from several hosts, it is possible to have duplicate login names. The fact that the key in the genericstable can contain a full email address allows you to map these overlapping usernames.

See Also

Recipe 4.11 provides another genericstable example, and Recipe 4.14 shows how the genericstable can be read from an LDAP server. The sendmail book covers the genericstable in 4.8.16, the GENERICS_DOMAIN macro in 4.8.16.1, and the generics_entire_domain feature in 4.8.15. Chapter 9 of Linux Sendmail Administration, by Craig Hunt (Sybex), contains the tutorial section “Masquerading Usernames” that provides additional information.

4.13. Masquerading with LDAP

Problem

You have been asked to configure sendmail so that it reads data that controls masquerading from an LDAP server.

Solution

On the LDAP server, add the sendmail.schema file to the LDAP configuration as described in Recipe 1.3.

On the LDAP server, add the masquerade configuration data to the LDAP database. To do this, create an LDIF file containing the list of masqueraded domains and an LDIF file containing the list of exposed users. The object class of the data in both files must be the sendmailMTAClass defined in the sendmail.schema file. Use ldapadd to add this data to the LDAP database.

Check the sendmail compiler options on the sendmail host. If sendmail does not list LDAPMAP among the “Compiled with:” flags, recompile and reinstall sendmail as described in Recipe 1.3.

On the sendmail host, create a configuration that reads the MASQUERADE_DOMAIN_FILE and the EXPOSED_USER files from the LDAP server. Set confLDAP_CLUSTER to match the sendmailMTACluster attribute used in the records added to the LDAP database. Here is an example of the lines added to the sendmail configuration:

dnl Masquerade the From address as wrotethebook.com
MASQUERADE_AS(`wrotethebook.com')
dnl Define the LDAP cluster to which this host belongs
define(`confLDAP_CLUSTER', `wrotethebook.com')dnl 
dnl Use LDAP to read usernames that are not masqueraded
EXPOSED_USER_FILE(`@LDAP')
dnl Get the list of masqueraded hostnames from LDAP 
MASQUERADE_DOMAIN_FILE(`@LDAP')

Build the new sendmail.cf file and copy it to /etc/mail. Restart sendmail. See Recipe 1.8 for examples.

Discussion

This recipe assumes that you have a fully functional LDAP server to which the sendmail masquerading information can be added. If you need help on LDAP, see Understanding and Deploying LDAP Directory Services, by Howes, Smith, and Good (Macmillan), and LDAP System Administration, by Gerald Carter (O’Reilly). Get the LDAP server up, running, and debugged before you attempt this recipe.

After ensuring that the LDAP server is ready to support sendmail, add the masquerade data to the LDAP database. This example creates a record for the hostnames that will be loaded into the $=M class:

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

This example adds the list of exposed users to the LDAP database:

# cat > ldap-exposed-users
               dn: sendmailMTAClassName=E, dc=wrotethebook, dc=com
               objectClass: sendmailMTA
               objectClass: sendmailMTAClass
               sendmailMTACluster: wrotethebook.com
               sendmailMTAClassName: E
               sendmailMTAClassValue: root
               Ctrl-D
# ldapadd -x -D "cn=Manager,dc=wrotethebook,dc=com" \
               > -W -f ldap-exposed-users
Enter LDAP Password: SecretLDAPpassword
adding new entry "sendmailMTAClassName=E, dc=wrotethebook, dc=com"

The examples above show the LDAP format used for sendmail class data. The class to which the LDAP record applies is defined by the sendmailMTAClassName attribute. The values that should be loaded into the specified class are defined in the LDAP record using one or more sendmailMTAClassValue attributes. In the examples, the record for class $=M contains three values, and the record for class $=E contains one value. Convert the LDIF data to LDAP format and add it to the LDAP database using the ldapadd command.[7] Use the ldapsearch command to examine the new data:[8]

# ldapsearch -LLL -x '(sendmailMTAClassName=M)' sendmailMTAClassValue
dn: sendmailMTAClassName=M, dc=wrotethebook, dc=com
sendmailMTAClassValue: rodent.wrotethebook.com
sendmailMTAClassValue: horseshoe.wrotethebook.com
sendmailMTAClassValue: jamis.wrotethebook.com
# ldapsearch -LLL -x '(sendmailMTAClassName=E)' sendmailMTAClassValue
dn: sendmailMTAClassName=E, dc=wrotethebook, dc=com
sendmailMTAClassValue: root

LDAP is ready. Now configure sendmail to ask LDAP for the masquerade data by using the string @LDAP in place of the file path in the MASQUERADE_DOMAIN_FILE and the EXPOSED_USER_FILE macros. When @LDAP is specified, sendmail queries LDAP for the required data using the standard sendmail schema.

This recipe also defines a value for confLDAP_CLUSTER. sendmail LDAP records apply either to an individual host or to a group of hosts called a cluster. A cluster is analogous to a NIS domain—it is a group of hosts that use the same LDAP data. The cluster name is defined in the sendmail configuration with the confLDAP_CLUSTER variable and in the LDAP records using the sendmailMTACluster attribute. If the LDAP records apply to a single host, the host is identified in the LDAP records by the sendmailMTAHost attribute, but no special sendmail configuration is needed because the hostname value returned by $j is used for the LDAP query. Note that if the confLDAP_CLUSTER variable is configured for a host that also has host-specific LDAP data, both the host data and the cluster data will be returned when that host issues an LDAP query. This recipe defines a cluster name in the sendmail configuration and uses only cluster data in the LDAP database.

A simple test shows the impact of this recipe:

# sendmail -bt -Cgeneric-linux.cf
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> $=M
> $=E
> /quit
# sendmail -bt -Csendmail.cf
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> $=M
rodent.wrotethebook.com
jamis.wrotethebook.com
horseshoe.wrotethebook.com
> $=E
root
> /quit

A sendmail -bt test using the generic configuration shows that, by default, class $=M and class $=E are empty. When the test is rerun using the sendmail.cf file created in this recipe, class $=M and class $=E contain the data defined in the LDAP database.

The MASQUERADE_DOMAIN_FILE and the EXPOSE_USER_FILE are not the only files that can be read from LDAP. The MASQUERADE_EXCEPTION_FILE, used in Recipe 4.9, can also be read from an LDAP server by replacing the pathname in the macro command with the string @LDAP. In fact, any file that is loaded into a class can be read from LDAP. Additionally, sendmail databases can be read from LDAP, as the next recipe illustrates with the genericstable.

Because classes are loaded during startup, you must restart sendmail whenever you add, change, or delete any LDAP records that affect sendmail classes. The changes only take effect after sendmail is restarted.

See Also

Recipe 4.6 explains the role that class $=M plays in masquerading, and Recipe 4.2 explains why class $=E is needed. Recipe 3.9 is another recipe that uses LDAP to load a class, which provides additional insight on using LDAP. The sendmail book covers the MASQUERADE_DOMAIN_FILE macro in Section 4.4.4, the EXPOSE_USER_FILE macro in Section 4.4.1.1, and the confLDAP_CLUSTER define in Section 21.9.82. The cf/README file covers LDAP in the Using LDAP for Aliases, Maps, and Classes section.

4.14. Reading the genericstable via LDAP

Problem

You have been asked to configure sendmail to read the genericstable from an LDAP server.

Solution

On the LDAP server, add support for the sendmail.schema file to the LDAP configuration as described in Recipe 1.3.

On the LDAP server, add the genericstable data to the LDAP database by first creating an LDIF file and then running ldapadd. Use the sendmailMTAMap object class defined in the sendmail.schema file to format the genericstable data in the LDIF file.

On the LDAP server, you can also create an LDIF file containing the generics domain data for class $=G. The data should be formatted using the sendmailMTAClass object class defined in the sendmail.schema file. Use ldapadd to add the data from the LDIF file to the LDAP database.

On the sendmail system, check that sendmail includes LDAP support. If the command sendmail -bt -d0.1 does not display the string LDAPMAP in the “Compiled with:” list, recompile and reinstall sendmail as described in Recipe 1.3.

On the sendmail system, add a genericstable FEATURE macro that loads the genericstable from the LDAP server to the sendmail configuration. Use the GENERICS_DOMAIN_FILE macro to load class $=G from the LDAP server. Set the confLDAP_CLUSTER define to match the sendmailMTACluster attribute used in the genericstable entries added to the LDAP database. Here are sample lines you might add to a sendmail configuration to read the genericstable from an LDAP server:

dnl Define the sendmailMTACluster value
define(`confLDAP_CLUSTER', `wrotethebook.com')
dnl Load the genericstable from the LDAP server
FEATURE(`genericstable', `LDAP')
dnl Load class $=G from the LDAP server
GENERICS_DOMAIN_FILE(`@LDAP')

Follow the example in Recipe 1.8 to build and install the sendmail.cf file and then restart sendmail.

Discussion

This recipe uses an LDAP server to duplicate the configuration used in Recipe 4.11. Why and how the genericstable is used is covered in Recipe 4.11. This recipe focuses on how the genericstable data is stored in and retrieved from an LDAP server.

The data is first entered into an LDIF file. Each data entry is formatted in a manner compatible with the sendmail schema. The LDAP record format used for the genericstable can be used for any sendmail database by simply placing the database’s map name in the sendmailMTAMapName attribute, the correct key data in the sendmailMTAKey attribute, and the correct return value in the sendmailMTAMapValue attribute. Here is an example using the dave, becky, and alana entries from the genericstable described in Recipe 4.11.

# cat > ldap-generics
               dn: sendmailMTAMapName=generics, dc=wrotethebook, dc=com
               objectClass: sendmailMTA
               objectClass: sendmailMTAMap
               sendmailMTACluster: wrotethebook.com
               sendmailMTAMapName: generics

               dn: sendmailMTAKey=dave, sendmailMTAMapName=generics, dc=wrotethebook, dc=com
               objectClass: sendmailMTA
               objectClass: sendmailMTAMap
               objectClass: sendmailMTAMapObject
               sendmailMTAMapName: generics
               sendmailMTACluster: wrotethebook.com
               sendmailMTAKey: dave
               sendmailMTAMapValue: david.craig@wrotethebook.com

               dn: sendmailMTAKey=becky, sendmailMTAMapName=generics, dc=wrotethebook, dc=com
               objectClass: sendmailMTA
               objectClass: sendmailMTAMap
               objectClass: sendmailMTAMapObject
               sendmailMTAMapName: generics
               sendmailMTACluster: wrotethebook.com
               sendmailMTAKey: becky
               sendmailMTAMapValue: rebecca.fro@wrotethebook.com

               dn: sendmailMTAKey=alana, sendmailMTAMapName=generics, dc=wrotethebook, dc=com
               objectClass: sendmailMTA
               objectClass: sendmailMTAMap
               objectClass: sendmailMTAMapObject
               sendmailMTAMapName: generics
               sendmailMTACluster: wrotethebook.com
               sendmailMTAKey: alana
               sendmailMTAMapValue: alana.smiley@wrotethebook.com
               Ctrl-D
# ldapadd -x -D "cn=Manager,dc=wrotethebook,dc=com" \
               > -W -f ldap-generics
Enter LDAP Password: SecretLDAPpassword
adding new entry "sendmailMTAMapName=generics, dc=wrotethebook, dc=com"

adding new entry "sendmailMTAKey=dave, sendmailMTAMapName=generics, dc=wrotethebook, 
dc=com"

adding new entry "sendmailMTAKey=becky, sendmailMTAMapName=generics, dc=wrotethebook, 
dc=com"

adding new entry "sendmailMTAKey=alana, sendmailMTAMapName=generics, dc=wrotethebook, 
dc=com"

Notice that four entries are used to enter the first three genericstable values in the LDAP database. The first entry defines the genericstable map name, which is generics. Once the map name is known to LDAP, data can be associated with that map name. The next three entries contain the actual genericstable data.

The sendmail configuration in this recipe also uses LDAP to load class $=G. The following example adds a class $=G entry to the LDAP database containing a sendmailMTAClassValue attribute that matches the GENERICS_DOMAIN value used in Recipe 4.11:

# cat > ldap-generics-domain
               dn: sendmailMTAClassName=G, dc=wrotethebook, dc=com
               objectClass: sendmailMTA
               objectClass: sendmailMTAClass
               sendmailMTAHost: chef.wrotethebook.com
               sendmailMTAClassName: G
               sendmailMTAClassValue: chef.wrotethebook.com
               Ctrl-D
# ldapadd -x -D "cn=Manager,dc=wrotethebook,dc=com" \
               > -W -f ldap-generics-domain
Enter LDAP Password: SecretLDAPpassword
adding new entry "sendmailMTAClassName=G, dc=wrotethebook, dc=com"

Again, the data is first entered into an LDIF file and then stored in the LDAP database using the ldapadd command. The sendmailMTAClassName attribute identifies the class to which the data belongs. The individual values bound for class $=G are defined by the sendmailMTAClassValue attributes in the LDAP record. Only one sendmailMTAClassValue is used in the example, but a single sendmailMTAClassName record can hold multiple values.

The ldapsearch command can be used to examine the results:

# ldapsearch -LLL -x '(sendmailMTAMapName=generics)' sendmailMTAMapValue
dn: sendmailMTAMapName=generics, dc=wrotethebook, dc=com

dn: sendmailMTAKey=dave, sendmailMTAMapName=generics, dc=wrotethebook, dc=com
sendmailMTAMapValue: david.craig@wrotethebook.com

dn: sendmailMTAKey=becky, sendmailMTAMapName=generics, dc=wrotethebook, dc=com
sendmailMTAMapValue: rebecca.fro@wrotethebook.com

dn: sendmailMTAKey=alana, sendmailMTAMapName=generics, dc=wrotethebook, dc=com
sendmailMTAMapValue: alana.smiley@wrotethebook.com
# ldapsearch -LLL -x '(sendmailMTAClassName=G)' sendmailMTAClassValue
dn: sendmailMTAClassName=G, dc=wrotethebook, dc=com
sendmailMTAClassValue: chef.wrotethebook.com

Three modifications were made to the sendmail configuration from Recipe 4.11 to create a configuration that reads genericstable data from LDAP:

  • The confLDAP_CLUSTER define was added to the configuration to set the sendmail.cf ${sendmailMTACluster} macro to the same value as the sendmailMTACluster attribute used in the genericstable LDAP records. This ensures that sendmail retrieves the correct values from the LDAP server. If the confLDAP_CLUSTER define is not used, sendmail only retrieves LDAP records with a sendmailMTAHost attribute set to the fully qualified hostname of the sendmail host. In this example, the class $=G LDAP record uses the sendmailMTAHost attribute, so it would be retrieved by a host named chef.wrotethebook.com even if confLDAP_CLUSTER was not defined in that host’s sendmail configuration.

  • The GENERICS_DOMAIN macro used in Recipe 4.11 was changed to a GENERICS_DOMAIN_FILE macro in this recipe. The @LDAP string in the GENERICS_DOMAIN_FILE macro tells sendmail to load class $=G with data retrieved from the LDAP server. (Traditionally, the value passed to the GENERICS_DOMAIN_FILE macro is the pathname of a local file that contains the data for class $=G.)

  • The string LDAP was added to the genericstable FEATURE command. The LDAP string tells sendmail to read the genericstable data from the LDAP server using the standard sendmail schema.

Adding the LDAP string to the genericstable FEATURE command changes the format of the sendmail.cf K command that defines the generics database. A grep shows the effect of the LDAP string:

# grep 'Kgenerics' recipe4-10.cf
Kgenerics hash /etc/mail/genericstable
# grep '^Kgenerics' sendmail.cf
Kgenerics ldap -1 -v sendmailMTAMapValue -k 
(&(objectClass=sendmailMTAMapObject)(|(sendmailMTACluster=${sendmailMTACluster})
(sendmailMTAHost=$j))(sendmailMTAMapName=generics)(sendmailMTAKey=%0))

The first grep shows the default format of the K command used to declare the generics database when no arguments are used with the genericstable FEATURE command. By default, sendmail looks for a hash type database in the local file /etc/mail/genericstable. Adding the LDAP argument to the genericstable FEATURE command changes the database type to ldap and adds some LDAP-specific options to the K command. The -v option specifies the LDAP attribute used as the return value. The -k option defines the LDAP search criteria used as a database key.

All of the attribute names used in the K command shown above are attributes defined in the sendmail schema. If you define your own schema, you cannot simply use the LDAP string in the genericstable FEATURE command. You must instead manually define -v and -k options that use your custom schema. For example:

FEATURE(`genericstable', `ldap: -1 -k (&(objectClass=LocalUserObject) (LocalUserKey=%0)) -v LocalUserEmailAddress')

Of course, the attribute names used in this example are imaginary and would need to be replaced with the attributes you defined when you designed your custom schema. However, the format of the genericstable FEATURE command is similar to the command you would use in your sendmail configuration.

Notice that the K command created by adding the LDAP argument to the genericstable feature does not include the LDAP server name or the LDAP default base distinguished name. The server and the base can be added to the K command using the same -h and -b arguments that you would use with the ldapsearch command. In the sendmail configuration, the -h and -b arguments are defined using confLDAP_DEFAULT_SPEC; see Recipe 5.9 for an example. However, it is not necessary to use confLDAP_DEFAULT_SPEC if the HOST and BASE values in the ldap.conf file are correct for sendmail.

After this recipe is installed, genericstable data can be read from LDAP exactly as if it were being read from a local database, as the following test illustrates:

# rm -f /etc/mail/genericstable*
# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> /map generics alana
map_lookup: generics (alana) returns alana.smiley@wrotethebook.com (0)
> /quit

The rm command proves that the test cannot be reading genericstable data from a local file. The /map command passes the key alana to the generics database and gets the return value alana.smiley@wrotethebook.com. The /map command in this example is formatted exactly as it was in Recipe 4.11, when data was read from a local database. The only difference is that, here, data is being read from an LDAP server.

The test that was used in Recipe 4.11 to evaluate the effect of the $=G class can be rerun after this recipe is installed. Again, it works exactly as expected despite the fact that the data comes from an LDAP server:

# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> $=G
chef.wrotethebook.com
> /tryflags HS
> /try esmtp alana@[127.0.0.1]
Trying header sender address alana@[127.0.0.1] for mailer esmtp
canonify           input: alana @ [ 127 . 0 . 0 . 1 ]
Canonify2          input: alana < @ [ 127 . 0 . 0 . 1 ] >
Canonify2        returns: alana < @ rodent . wrotethebook . com . >
canonify         returns: alana < @ rodent . wrotethebook . com . >
1                  input: alana < @ rodent . wrotethebook . com . >
1                returns: alana < @ rodent . wrotethebook . com . >
HdrFromSMTP        input: alana < @ rodent . wrotethebook . com . >
PseudoToReal       input: alana < @ rodent . wrotethebook . com . >
PseudoToReal     returns: alana < @ rodent . wrotethebook . com . >
MasqSMTP           input: alana < @ rodent . wrotethebook . com . >
MasqSMTP         returns: alana < @ rodent . wrotethebook . com . >
MasqHdr            input: alana < @ rodent . wrotethebook . com . >
canonify           input: alana . smiley @ wrotethebook . com
Canonify2          input: alana . smiley < @ wrotethebook . com >
Canonify2        returns: alana . smiley < @ wrotethebook . com . >
canonify         returns: alana . smiley < @ wrotethebook . com . >
MasqHdr          returns: alana . smiley < @ wrotethebook . com . >
HdrFromSMTP      returns: alana . smiley < @ wrotethebook . com . >
final              input: alana . smiley < @ wrotethebook . com . >
final            returns: alana . smiley @ wrotethebook . com
Rcode = 0, addr = alana.smiley@wrotethebook.com
> /quit

The data for any sendmail database and for any sendmail class can be defined centrally through an LDAP server. sendmail treats the data the same, regardless of the source.

See Also

Recipe 4.11 explains how the genericstable is used and it explains the importance of class $=G. Chapter 5 provides additional examples of using LDAP for sendmail databases. The sendmail book covers the GENERICS_DOMAIN_FILE macro in Section 4.8.16.2, the genericstable in Section 4.8.16, and the confLDAP_CLUSTER define in Section 21.9.82. The cf/README file covers LDAP in the section Using LDAP for Aliases, Maps, and Classes.



[1] Even configurations that don’t use MASQUERADE_AS have a MasqHdr ruleset that adds the fully qualified name of the local host returned by $j to the sender address when the address lacks a hostname.

[2] To force sendmail to ignore class $=w and use only class $=M for masquerading, use the limited_masquerade feature.

[3] Recipe 4.11 provides an example of building a genericstable database.

[4] Recipe 4.1 shows the effect of always_add_domain when used without masquerading.

[5] The firstname.lastname @ domain format is not universally appreciated. See the FAQ for some reasons why you might not want to use this address format.

[6] This does not necessarily mean that all of the local hostname aliases found in class $=w will be processed through the genericstable. This recipe only processes hostnames that resolve to the value returned by $j.

[7] All LDAP examples in this book assume that OpenLDAP is being used. Adjust the commands to fit your system.

[8] If ldapsearch requires -h and -b values, use confLDAP_DEFAULT_SPEC to set the same values for sendmail, as described in Recipe 5.9.

Get sendmail Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.