Section 10.8. Testing sendmail.cf

sendmail provides powerful tools for configuration testing and debugging. These test tools are invoked on the sendmail command line using some of the many sendmail command-line arguments. Appendix E lists all of the command-line arguments; Table 10-5 summarizes those that relate to testing and debugging.

Table 10-5. sendmail arguments for testing and debugging

Argument

Function

-t

Send to everyone listed in To:, Cc:, and Bcc:.

-bt

Run in test mode.

-bv

Verify addresses; don't collect or deliver mail.

-bp

Print the mail queue.

-Cfile

Use file as the configuration file.

-dlevel

Set debugging level.

-Ooption=value

Set option to the specified value.

-e

Defines how errors are returned.

-v

Run in verbose mode.

Some command-line arguments are used to verify address processing and to gain confidence in the new configuration. Once you think your configuration will work, send mail to yourself at various sitestesting is a great reason to have several email accounts at various free services. Use the -C argument to read the test configuration file and the -v argument to display the details of the mail delivery. -v displays the complete SMTP exchange between the two hosts.

By observing whether your mailer properly connects to the remote mailer and formats the addresses correctly, you'll get a good idea of how the configuration is working. The following example is a test from rodent using the test.cf configuration file we just created:

rodent# sendmail -Ctest.cf -t -v 

To: craigh@ora.com 

From: craig 

Subject: Sendmail Test 

Ignore this test.       

^D 

craigh@ora.com... Connecting to ora.com. via esmtp... 

220-ruby.ora.com ESMTP Sendmail 8.9.3+Sun/8.9.3; Wed, 23 May 2001 

>>> EHLO rodent.wrotethebook.com 

250-ruby.ora.com Hello craig@rodent.wrotethebook.com [172.16.12.2],

pleased to meet you 

250-EXPN 

250-VERB

250-8BITMIME

250-SIZE

250-DSN

250-ONEX

250-ETRN

250-XUSR

250 HELP

>>> MAIL From:<craig@rodent.wrotethebook.com> SIZE=64 

250 <craig@rodent.wrotethebook.com>... Sender ok 

>>> RCPT To:<craigh@ora.com> 

250 <craigh@ora.com>... Recipient ok 

>>> DATA 

354 Enter mail, end with "." on a line by itself 

>>> . 

250 SAA27399 Message accepted for delivery 

craigh@ora.com... Sent (SAA27399 Message accepted for delivery) 

Closing connection to ora.com. 

>>> QUIT

221 ruby.ora.com closing connection

We entered everything before the Ctrl-D (^D). Everything after the ^D was displayed by sendmail. Figure 10-5 highlights some of the important information in this display and notes the sendmail macros that relate to the highlighted material.

Figure 10-5. Verbose mail output
figs/tcp3_1005.gif

This test successfully transfers mail to a remote Internet site. The sendmail output shows that rodent sent the mail to ora.com via the smtp mail delivery program. The sendmail greeting shows that the remote host handling this SMTP connection is ruby.ora.com. Therefore, ruby must be the mail server for the ora.com domain; i.e., the MX record for ora.com points to ruby.ora.com.

The EHLO messages indicate that both rodent and ruby use Extended Simple Mail Transfer Protocol (ESMTP).

Everything worked just fine! We could quit right now and use this configuration. But like most computer people, we cannot stop ourselves from tinkering in order to make things "better."

The From: address, craig@rodent.wrotethebook.com, is clearly a valid address but is not quite what we want. We want to have people address us as firstname.lastname@domain -- not as user@host.domain, which is exactly the configuration we created earlier in this chapter with a few lines of m4 code. We will create the same configuration here to provide an example of how to use the various troubleshooting tools that come with sendmail. However, if you really want to make major sendmail configuration changes, you should use m4 to build your configuration.

Most changes to sendmail.cf are small and are made near the beginning of the file in the Local Information section. Looking closely at that section provides the clues we need to solve part of our configuration problem.

Without knowing what "masquerading" means, the comments for class E, class M, and macro M lead us to guess that the value set for macro M will be used to rewrite the hostname.[16]

[16] In the m4 source file we configured masquerading with the MASQUERADE_AS(wrotethebook.com) command.

In particular, the comment "names that should be exposed as from this host, even if we masquerade" led me to believe that masquerading hides the hostname. Based on this guess, we set a value for macro M as follows:

# who I masquerade as (null for no masquerading) (see also $=M) DMwrotethebook.com

Are we sure that setting a value for the M macro will hide the hostname? No, but changing the value in test.cf and running another test will do no harm. Running the test program with the test configuration has no effect on the running sendmail daemon started by the sendmail -bd -q1h command in the boot script. Only an instantiation of sendmail with the -Ctest.cf argument will use the test.cf test configuration.

10.8.1 Testing Rewrite Rules

In the initial test, the From: address went into sendmail as craig, and it came out as craig@rodent.wrotethebook.com. Obviously it has been rewritten. This time we test whether the change we made to the macro M in the configuration files modifies the rewrite process by directly testing the rewrite rulesets. First, we need to find out what rules were used to rewrite this address. To get more information, we run sendmail with the -bt option.

When sendmail is invoked with the -bt option, it prompts for input using the greater-than symbol (>). At the prompt, enter one of the test commands shown in Table 10-6.

Table 10-6. sendmail testing commands

Command

Function

ruleset[,ruleset...] address

Process address through ruleset(s).

.Dmvalue

Assign value to macro m.

.Ccvalue

Add value to class c.

=Sruleset

Display the rules in ruleset.

=M

Display the mailer definitions.

-dvalue

Set the debug flag to value.

$m

Display the value of macro m.

$=c

Display the contents of class c.

/mxhost

Display the MX records for host.

/parseaddress

Return the mailer/host/user triple for address.

/try mailer address

Process address for mailer.

/tryflags flags

Set the address processed by /parse or /try to H (Header), E (Envelope), S (Sender), or R (Recipient).

/canonhostname

Canonify hostname.

/mapmapname key

Display the value for key found in mapname.

/quit

Exit address test mode.

The most basic test is a ruleset name followed by an email address. The address is the test data, and the name is the ruleset to be tested. The address is easy to select; it is the one that was improperly rewritten. But how do you know which ruleset to specify?

Use Figure 10-4 to determine which rulesets to enter. The canonify ruleset is applied to all addresses. It is followed by different rulesets depending on whether the address is a delivery address, a sender address, or a recipient address. Furthermore, the rulesets used for sender and recipient addresses vary depending on the mailer that is used to deliver the mail. All addresses are then processed by ruleset final.

There are two variables in determining the rulesets used to process an address: the type of address and the mailer through which it is processed. The three address types are delivery address, recipient address, and sender address. You know the address type because you select the address being tested. In our test mail we were concerned about the sender address. Which mailer is used is determined by the delivery address. One way to find out which mailer delivered the test mail is to run sendmail with the -bv argument and the delivery address:

# sendmail -bv craigh@ora.com 

craigh@ora.com... deliverable: mailer esmtp, host ora.com.,

      user craigh@ora.com

Knowing the mailer, we can use sendmail with the -bt option to process the sender From: address. There are two types of sender addresses: the sender address in the "envelope" and the sender address in the message header. The message header address is the one on the From: line sent with the message during the SMTP DATA transfer. You probably see it in the mail headers when you view the message with your mail reader. The "envelope" address is the address used during the SMTP protocol interactions. sendmail allows us to view the processing of both of these addresses:

# sendmail -bt -Ctest.cf 

ADDRESS TEST MODE (ruleset 3 NOT automatically invoked) 

Enter <ruleset> <address> 

> /tryflags HS 

> /try esmtp craig 

Trying header sender address craig for mailer esmtp 

canonify           input: craig

Canonify2          input: craig

Canonify2        returns: craig

canonify         returns: craig

1                  input: craig

1                returns: craig

HdrFromSMTP        input: craig

PseudoToReal       input: craig

PseudoToReal     returns: craig

MasqSMTP           input: craig

MasqSMTP         returns: craig < @ *LOCAL* >

MasqHdr            input: craig < @ *LOCAL* >

MasqHdr          returns: craig < @ wrotethebook . com . >

HdrFromSMTP      returns: craig < @ wrotethebook . com . >

final              input: craig < @ wrotethebook . com . >

final            returns: craig @ wrotethebook . com

Rcode = 0, addr = craig@wrotethebook.com 

> /tryflags ES 

> /try esmtp craig 

Trying envelope sender address craig for mailer esmtp 

canonify           input: craig

Canonify2          input: craig

Canonify2        returns: craig

canonify         returns: craig

1                  input: craig

1                returns: craig

EnvFromSMTP        input: craig

PseudoToReal       input: craig

PseudoToReal     returns: craig

MasqSMTP           input: craig

MasqSMTP         returns: craig < @ *LOCAL* >

MasqEnv            input: craig < @ *LOCAL* >

MasqEnv          returns: craig < @ rodent . wrotethebook . com . >

EnvFromSMTP      returns: craig < @ rodent . wrotethebook . com . >

final              input: craig < @ rodent . wrotethebook . com . >

final            returns: craig @ rodent . wrotethebook . com

Rcode = 0, addr = craig@rodent.wrotethebook.com

> /quit

The /tryflags command defines the type of address to be processed by a /try or a /parse command. Four flags are available for the /tryflags command: S for sender, R for recipient, H for header, and E for envelope. By combining two of these flags, the first /tryflags command says we will process a header sender (HS) address. The /try command tells sendmail to process the address through a specific mailer. In the example, we process the email address craig through the mailer esmtp. First, we process it as the header sender address, and then as the envelope sender address. From this test, we can tell that the value that we entered in the M macro is used to rewrite the sender address in the message header, but it is not used to rewrite the sender address in the envelope.

The results of these tests show that the value of the M macro rewrites the hostname in the message header sender address just as we wanted. The hostname in the envelope sender address is not rewritten. Usually this is acceptable. However, we want to create exactly the same configuration as in the m4 example. The FEATURE (masquerade_envelope) command used in the m4 example causes the envelope sender address to be rewritten. Therefore, we want this configuration to also rewrite it.

The only difference between how the message and envelope addresses are processed is that one goes through ruleset HdrFromSMTP and the other goes through ruleset EnvFromSMTP. The tests show that both rulesets call basically the same rulesets. They diverge where ruleset HdrFromSMTP calls ruleset MasqHdr and ruleset EnvFromSMTP calls ruleset MasqEnv. The tests also show that ruleset MasqHdr provides the address rewrite that we want for the message sender address, while the envelope sender address is not processed in the manner we desire by ruleset MasqEnv. The test.cf code for rulesets MasqEnv is shown here:

################################################################### 

###  Ruleset 94 -- convert envelope names to masquerade form    ### 

################################################################### 

SMasqEnv=94

R$+             $: $>93 $1      do masquerading 

R$* < @ *LOCAL* > $*    $: $1 < @ $j . > $2 

Clearly, ruleset MasqEnv does not do what we want, and ruleset MasqHdr does. A quick inspection of ruleset MasqEnv shows that it does not contain a single reference to macro M. Yet the comment on the line at the start of the ruleset indicates it should "do masquerading." Our solution is to add a line to ruleset MasqEnv so that it now calls ruleset MasqHdr, which is the ruleset that really does the masquerade processing. The modified ruleset is shown here:

################################################################### 

###  Ruleset 94 -- convert envelope names to masquerade form    ### 

################################################################### 

SMasqEnv=94

R$+                     $: $>93 $1           do masquerading

R$* < @ *LOCAL* > $*    $: $1 < @ $j . > $2 

Debugging a sendmail.cf file is more of an art than a science. Deciding to add the first line to ruleset MasqEnv to call ruleset MasqHdr is little more than a hunch. The only way to verify the hunch is through testing. We run sendmail -bt -Ctest.cf again to test the addresses craig, craig@rodent, and craig@localhost using the /try esmtp command. All tests run successfully, rewriting the various input addresses into craig@wrotethebook.com. We then retest by sending mail via sendmail -v -t -Ctest.cf. Only when all of these tests run successfully do we really believe in our hunch and move on to the next task, which is to rewrite the user part of the email address into the user's first and last names.

10.8.2 Using Key Files in sendmail

The last feature we added to the m4 source file was FEATURE(genericstable), which adds a database process to the configuration that we use to convert the user portion of the email address from the user's login name to the user's first and last names. To do the same thing here, create a text file of login names and first and last names and build a database with makemap.[17]

[17] See the m4 section for more information about makemap.

# cd /etc/mail

# cat realnames 

dan Dan.Scribner 

tyler Tyler.McCafferty 

pat Pat.Stover 

willy Bill.Wright 

craig Craig.Hunt 

# makemap hash realnames < realnames

Once the database is created, define it for sendmail. Use the K command to do this. To use the database that we have just built, insert the following lines into the Local Information section of the sendmail.cf file:

# define a database to map login names to firstname.lastname

Krealnames hash /etc/mail/realnames

The K command defines realnames as the internal sendmail name of this database. Further, it identifies that this is a database of type hash and that the path to the database is /etc/realnames. sendmail adds the correct filename extensions to the pathname depending on the type of the database, so you don't need to worry about it.

Finally, we add a new rule that uses the database to rewrite addresses. We add it to ruleset EnvFromSMTP and ruleset HdrFromSMTP immediately after the lines in those rulesets that call ruleset MasqHdr. This way, our new rule gets the address as soon as ruleset MasqHdr finishes processing it.

# when masquerading convert login name to firstname.lastname

R$-<@$M.>$*    $:$(realnames $1 $)<@$M.>$2    user=>first.last

This rule is designed to process the output of ruleset MasqHdr, which rewrites the hostname portion of the address. Addresses that meet the criteria to have the hostname part rewritten are also the addresses for which we want to rewrite the user part. Look at the output of ruleset MasqHdr from the earlier test. That address, craig<@wrotethebook.com.>, matches the pattern $-<@$M.>$*. The address has exactly one token (craig) before the literal <@, followed by the value of M (wrotethebook.com), the literal .>, and zero tokens.

The transformation part of this rule takes the first token ($1) from the input address and uses it as the key to the realnames database, as indicated by the $:$(realnames $1 $) syntax. For the sample address craig<@wrotethebook.com>, $1 is craig. When used as an index into the database realnames shown at the beginning of this section, it returns Craig.Hunt. This returned value is prepended to the literal <@, the value of macro M ($M), the literal .>, and the value of $2, as indicated by the <@$M.>$2 part of the transformation. The effect of this new rule is to convert the username to the user's real first and last names.

After adding the new rule to rulesets EnvFromSMTP and HdrFromSMTP, a test yields the following results:

# sendmail -bt -Ctest.cf 

ADDRESS TEST MODE (ruleset 3 NOT automatically invoked) 

Enter <ruleset> <address> 

> /tryflags HS

> /try esmtp craig

Trying header sender address craig for mailer esmtp

canonify           input: craig

Canonify2          input: craig

Canonify2        returns: craig

canonify         returns: craig

1                  input: craig

1                returns: craig

HdrFromSMTP        input: craig

PseudoToReal       input: craig

PseudoToReal     returns: craig

MasqSMTP           input: craig

MasqSMTP         returns: craig < @ *LOCAL* >

MasqHdr            input: craig < @ *LOCAL* >

MasqHdr          returns: craig < @ wrotethebook . com . >

HdrFromSMTP      returns: Craig . Hunt < @ wrotethebook . com . >

final              input: Craig . Hunt < @ wrotethebook . com . >

final            returns: Craig . Hunt @ wrotethebook . com

Rcode = 0, addr = Craig.Hunt@wrotethebook.com

> /tryflags ES

> /try esmtp craig

Trying envelope sender address craig for mailer esmtp

canonify           input: craig

Canonify2          input: craig

Canonify2        returns: craig

canonify         returns: craig

1                  input: craig

1                returns: craig

EnvFromSMTP        input: craig

PseudoToReal       input: craig

PseudoToReal     returns: craig

MasqSMTP           input: craig

MasqSMTP         returns: craig < @ *LOCAL* >

MasqEnv            input: craig < @ *LOCAL* >

MasqHdr            input: craig < @ *LOCAL* >

MasqHdr          returns: craig < @ wrotethebook . com . >

MasqEnv          returns: craig < @ wrotethebook . com . >

EnvFromSMTP      returns: Craig . Hunt < @ wrotethebook . com . >

final              input: Craig . Hunt < @ wrotethebook . com . >

final            returns: Craig . Hunt @ wrotethebook . com

Rcode = 0, addr = Craig.Hunt@wrotethebook.com

> /quit

If the tests do not give the results you want, make sure that you have correctly entered the new rewrite rules and that you have correctly built the database. The following error message could also be displayed:

 test.cf: line 116: readcf: map realnames: class hash not available

This indicates that your system does not support hash databases. You can try changing the database type on the K command line to hash and rerunning sendmail -bt until you find a type of database that your sendmail likes. When you do, rerun makemap using that database type. If your sendmail doesn't support any database type, see Appendix E for information on recompiling sendmail with database support.

Note that all of the changes made directly to the sendmail.cf file in the second half of this chapter (masquerading the sender address, masquerading the envelope address, and converting usernames) were handled by just three lines in the m4 source file. These examples demonstrated how to use the sendmail test tools. If you really need to make a new, custom configuration, use m4. It is easiest to maintain and enhance the sendmail configuration through the m4 source file.