Chapter 8. Test Rule Sets with -bt

The sendmail program offers a mode of operation called rule-testing mode that allows you to observe the flow of addresses through rule sets. The -bt command-line switch causes sendmail to run in rule-testing mode. This mode is interactive. You enter rule set numbers or names, addresses, and other commands, and sendmail processes them and prints the results. The -bt switch’s chief use is in testing changes in the configuration file. It is also useful for learning how rules and rule sets work.

Overview

The following command runs sendmail in rule-testing mode:[141]

% /usr/sbin/sendmail -bt

At first, the output produced by this command line prompts you like this:

ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
>

The “ruleset 3” statement says that, beginning with V8 (and IDA) sendmail, the canonify rule set 3 is no longer automatically called when rule sets are listed at the prompt. We cover this property in detail in The Address on page 315.

Prior to V8 sendmail, rule-testing mode could be used only to test addresses. But beginning with V8.7 sendmail, new functions were added. To see a summary of those functions, enter a ? character followed by a RETURN at the > prompt. The output, which we reproduce next, lists and gives a brief description of each function. Note that the numbers to the right refer to sections in this chapter and are not a part of sendmail’s output:

> ?
Help for test mode:
?                :this help message.
.Dmvalue         :define macro `m' to `value'.                     ← §8.2.1 on page
301
.Ccvalue         :add `value' to class `c'.                        ← §8.2.2 on page
302
=Sruleset        :dump the contents of the indicated ruleset.      ← §8.4.1 on page
306
=M               :display the known mailers.                       ← §8.4.2 on page
307
-ddebug-spec     :equivalent to the command-line -d debug flag.
$m               :print the value of macro $m.                     ← §8.3.1 on page
304
$=c              :print the contents of class $=c.                 ← §8.3.2 on page
305
/mx host         :returns the MX records for `host'.               ← §8.5.2 on page
309
/parse address   :parse address, returning the value of crackaddr, and
                  the parsed address (same as -bv).                ← §8.5.5 on page
311
/try mailer addr :rewrite address into the form it will have when  ← §8.5.6 on page
313
                  presented to the indicated mailer.
/tryflags flags  :set flags used by parsing.  The flags can be `H' for
                  Header or `E' for Envelope, and `S' for Sender or `R'
                  for Recipient.  These can be combined. `HR' sets
                  flags for header recipients.                     ← §8.5.4 on page
311
/canon hostname  :try to canonify hostname.                        ← §8.5.1 on page
308
/map mapname key :look up `key' in the indicated `mapname'.        ← §8.5.3 on page
310
/quit            :quit address test mode.                          ←V8.10 and later
rules addr       :run the indicated address through the named rules. ← §8.6 on page
314
                  Rules can be a comma-separated list of rules.
End of HELP info
>

This help output is contained in the helpfile[142] file, the location of which is defined by the HelpFile option (HelpFile on page 1035). If that option is not defined, or if the file specified does not exist, you will get the following error message instead of help:

Sendmail 8.12 -- HELP not implemented

Help for rule-testing mode requires that the helpfile both exist and contain lines that begin with:

-bt

If you installed a new sendmail but did not install the new help file (thus causing the old file to be used), you might see this error:

HELP topic "-bt" unknown

The solution here is to upgrade your helpfile file to the newest version.

Note that each function listed in the help output will also produce a usage message if it is executed with no arguments. Consider the /try function, for example:

> /try
Usage: /try mailer address
>

This parallels the syntax shown in the earlier help output. These mini-usage messages can effectively replace the helpfile file in case it is missing.

Finally, note that although address testing was not listed in the help output for V8.7 sendmail, it still existed.

Configuration Lines

Selected configuration file lines can be entered in rule-testing mode. They will behave just as they do when being read from the configuration file. For V8.8 sendmail and later, three configuration commands are honored:

#

Commands that begin with a # are treated as comments and ignored. Blank lines are also ignored.

D

The D configuration command (Configuration-File Definitions on page 787) is used to define a sendmail macro. Both single-character and multicharacter sendmail macro names can be used.

C

The C configuration command (Class Configuration Commands on page 854) is used to add a value to a class. Both single-character and multicharacter class names can be used.

The # can begin a line. The other two configuration commands in rule-testing mode must begin with a dot:

.D{ntries} 23
.Cw localhost

Failure to use a dot will produce this error message:

Undefined ruleset Cw

The use of any character other than the two listed will produce this error:

Unknown "." command .bad command here

To get a usage message, just type a dot:

> .
Usage: .[DC]macro value(s)

Define a Macro with .D

The .D rule-testing command is used to define a sendmail macro. One use for this command might be to modify a rule that depends on the $& prefix (Use Value As Is with $& on page 793). For example, consider this small configuration file that contains a rule in parse rule set 0 that is intended to deliver a local user’s address via the local delivery agent:

V10
Sparse=0
R$+      $#local $@ $&X $: $1

If $X has a value, this rule returns that value as the host (the $@) part of a parse rule set 0 triple (The parse Rule Set 0 on page 696). If $X lacks a value, the host part is empty. This technique is useful because the $@ part with the local delivery agent is used to implement plussed users (Plussed Detail Addressing on page 476).

This scheme can be tested in rule-testing mode by first specifying a local user with $X undefined:

% /usr/sbin/sendmail -bt -Ctest.cf
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> parse bob
parse              input: bob
parse            returns: $# local $@ $: bob

This form of rule testing and the output produced are described in detail in Process-Specified Addresses on page 314. Here, it is important only to note that the host part of the triple (the $@ part) is empty.

Now, use the .D command to give $X the value home:

> .DXhome

Now, test those rules again:

> parse bob
parse              input: bob
parse            returns: $# local $@ home $: bob

This time the host part of the triple (the $@ part) has the value home as intended.

The .D command can also be used to redefine the value of existing sendmail macros. It cannot, however, be used to redefine sendmail macros used in rules (except for $&), because those macros are expanded as rules are read from the configuration file (When Is a Macro Expanded? on page 792). Also see Dump a Defined Macro with $ on page 304, which describes how to view sendmail macro values in rule-testing mode.

Add to a Class with .C

The .C rule-testing command is used to add a member to a class. If the class does not exist, it is created. One possible use for this command would be to test whether adding a member to $=w will have the effect you desire. For example, suppose that a new alias called mailhub has been created for the local host. In the following, we test sendmail to see whether it will detect that new name as local:

% /usr/sbin/sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> canonify,parse bob@mailhub
canonify           input: bob @ mailhub
Canonify2          input: bob < @ mailhub >
Canonify2        returns: bob < @ mailhub >
canonify         returns: bob < @ mailhub >
parse              input: bob < @ mailhub >
Parse0             input: bob < @ mailhub >
Parse0           returns: bob < @ mailhub >
ParseLocal         input: bob < @ mailhub >
ParseLocal       returns: bob < @ mailhub >
Parse1             input: bob < @ mailhub >
MailerToTriple     input: < > bob < @ mailhub >
MailerToTriple   returns: bob < @ mailhub >
Parse1           returns: $# esmtp $@ mailhub $: bob < @ mailhub >
parse            returns: $# esmtp $@ mailhub $: bob < @ mailhub >

This form of rule testing and the output that is produced are described in detail in Process-Specified Addresses on page 314. Here, merely note that the esmtp delivery agent was selected, suggesting that mailhub was not automatically recognized as local.

One way to fix this is to add mailhub to the class $=w ($=w on page 876). In rule-testing mode this can be done by using the .C command:

> .Cw mailhub

Now, feed sendmail the same rules and address as before to see whether this fixed the problem:

> canonify,parse bob@mailhub
canonify           input: bob @ mailhub
Canonify2          input: bob < @ mailhub >
Canonify2        returns: bob < @ mailhub . >
canonify         returns: bob < @ mailhub . >
parse              input: bob < @ mailhub . >
Parse0             input: bob < @ mailhub . >
Parse0           returns: bob < @ mailhub . >
ParseLocal         input: bob < @ mailhub . >
ParseLocal       returns: bob < @ mailhub . >
Parse1             input: bob < @ mailhub . >
Parse1           returns: $# local $: bob
parse            returns: $# local $: bob

Success! Adding mailhub to the class $=w fixed the problem. You could now make that change permanent by editing your mc file and using that to create a new configuration file, or by adding the name to the /etc/mail/local-host-names file[143] (FEATURE(use_cw_file) on page 643).

Another use for .C would include trying out masquerading for a subdomain (FEATURE(limited_masquerade) on page 625). See also Dump a Class Macro with $= on page 305 for a way to print the members of a class while in rule-testing mode.

Dump a sendmail Macro or Class

Beginning with V8.7, rule-testing commands allow you to print the value of a defined sendmail macro and the members of a class. With either command, you can use single-character or multicharacter macro names. Both commands begin with a $ character. An error is caused if nothing follows that $:

Name required for macro/class

If an = character follows, sendmail will display the requested class. Otherwise, the value of the sendmail macro is displayed:

$X              ← display the value of the X macro
$=X             ← list the members of the class X

Dump a Defined Macro with $

The $ rule-testing command causes sendmail to print the value of a defined sendmail macro. The form for this command looks like this:

$X               ← show value of the single-character macro name X
${YYY}           ← show value of the multicharacter macro name YYY

Only one sendmail macro can be listed per line. If more than one is listed, all but the first are ignored:

$X $Y
    ↑
   ignored

One use for this command might be in solving the problem of duplicate domains. For example, suppose you just installed a new configuration file and discovered that your host was no longer known as here.our.domain, but instead wrongly had an extra domain attached, like this: here.our.domain.our.domain. To check the value of $j ($j on page 830) which should contain the canonical name of your host, you could run sendmail in rule-testing mode:

ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> $j
$w.our.domain
>

This looks right because $w ($w on page 850) is supposed to contain our short hostname. But just to check, you could also print the value of $w:

> $w
here.our.domain

Aha! Somehow, $w got the full canonical name. A quick scan of your .mc file (Configure with m4 on page 587) turns up this error:

LOCAL_CONFIG
Dwhere.our.domain           # $w is supposed to be full -- joachim

Apparently, your assistant, Joachim, mistakenly thought the new sendmail was wrong. You can take care of the configuration problem by deleting the offending line and creating a new configuration file. To solve the problem with Joachim, consider buying him a copy of this book.

Dump a Class Macro with $=

The $= rule-testing command tells sendmail to print all the members for a class. The class name must immediately follow the = with no intervening space, or the name is ignored. Both single-character and multicharacter names can be used:

$= X            ← the X is ignored
$=X             ← list the members of the class X
$={YYY}         ← list the members of the multicharacter class YYY

The list of members (if any) is printed one per line:

> $=w
here.our.domain
here
[123.45.67.89]
fax
fax.our.domain
>

To illustrate one use for this command, imagine that you just made the local host the fax server for your site. Of course, you were careful to modify the configuration file and add fax and fax.our.domain to the $=w class in it. But incoming mail to fax.our.domain is still failing. You run sendmail in rule-testing mode, as earlier, to verify that the correct entries are in $=w:

here.our.domain
here
[123.45.67.89]
fax                    ← correct
fax.our.domain         ← correct

Because they are correct, it could be that you made the mistake of changing the configuration file and failing to restart the daemon (Daemon mode (-bd) on page 20). The following command line fixes the problem (SIGHUP on page 509):

# kill -HUP `head −1 /etc/mail/sendmail.pid`

Show an Item

Beginning with V8.7 sendmail, two rule-testing commands became available: the =S command displays all the rules in a given rule set, and the =M command displays all the delivery agents. Both display their items after the configuration has been read. Thus, in the case of rules, all the macros will have already been expanded.

Both commands are triggered by the leading = character. If nothing follows the =, this usage message is printed:

Usage: =Sruleset or =M

If any character other than S or M follows the = character, the following error is printed:

Unknown "=" command =bad character here

Show Rules in a Rule Set with =S

The =S rule-testing command causes sendmail to show all the rules of a rule set. The form of this command looks like this:

=Sruleset

Optional whitespace can separate the ruleset from the S. The ruleset can be a number or a symbolic name (Rule Set Names on page 684):

=S0                    ← a number
=SMyrule               ← a name

Note that, although sendmail macros can be used in defining rule sets (Macros in Rule Set Names on page 686), they cannot be used with the =S command:

> =S$X
invalid ruleset name: "$X"
Undefined ruleset $X
>

One use for the =S command is to determine why a rule set is not behaving as expected. Consider a rule set named LocalizeSender that is intended to rewrite all sending addresses so that the local host’s name makes the message appear as though it came from the mail hub machine. Suppose that, when testing, you send an address through that rule but it comes out unchanged:

> LocalizeSender bob@localhost
LocalizeSender     input: bob @ localhost
LocalizeSender   returns: bob @ localhost
>

Puzzled, you look at the actual rule with the =S rule-testing command:

> =SLocalizeSender
R$* < @ $=w > $*                $@ $1 < @ mailhub . our . domain > $3
>

Aha! The rule set named LocalizeSender[144] expects the host part of the address to be surrounded by angle brackets! Knowing this, you run the address through the rule again, this time using angle brackets, and it succeeds:

>  LocalizeSender bob<@localhost >
LocalizeSender     input: bob < @ localhost >
LocalizeSender   returns: bob < @ mailhub . our . domain >
>

Show Delivery Agents with =M

The =M rule-testing command causes sendmail to print its list of delivery agents. This command takes no argument. Note that in the following example, the lines are wrapped to fit on the page:

> =M
mailer 0 (prog): P=/bin/sh S=EnvFromL/HdrFromL R=EnvToL/HdrToL M=0 U=0:0 F=9DFMeloq
su L=0 E=\n T=X-Unix/X-Unix/X-Unix r=100 A=sh -c $u
mailer 1 (*file*): P=[FILE] S=parse/parse R=parse/parse M=0 U=0:0 F=9DEFMPloqsu L=0
E=\n T=X-Unix/X-Unix/X-Unix r=100 A=FILE $u
mailer 2 (*include*): P=/dev/null S=parse/parse R=parse/parse M=0 U=0:0 F=su L=0 E=
\n T=<undefined>/<undefined>/<undefined> r=100 A=INCLUDE $u
mailer 3 (local): P=/usr/lib/mail.local S=EnvFromSMTP/HdrFromL R=EnvToL/HdrToL M=0
U=0:0 F=/59:@ADFMPSXflmnqswz| L=0 E=\r\n T=DNS/RFC822/SMTP r=100 A=mail.local -l
mailer 4 (smtp): P=[IPC] S=EnvFromSMTP/HdrFromSMTP R=EnvToSMTP/HdrFromSMTP M=0 U=0:
0 F=DFMXmu L=990 E=\r\n T=DNS/RFC822/SMTP r=100 A=TCP $h
mailer 5 (esmtp): P=[IPC] S=EnvFromSMTP/HdrFromSMTP R=EnvToSMTP/HdrFromSMTP M=0 U=0
:0 F=DFMXamu L=990 E=\r\n T=DNS/RFC822/SMTP r=100 A=TCP $h
mailer 6 (smtp8): P=[IPC] S=EnvFromSMTP/HdrFromSMTP R=EnvToSMTP/HdrFromSMTP M=0 U=0
:0 F=8DFMXmu L=990 E=\r\n T=DNS/RFC822/SMTP r=100 A=TCP $h
mailer 7 (dsmtp): P=[IPC] S=EnvFromSMTP/HdrFromSMTP R=EnvToSMTP/HdrFromSMTP M=0 U=0
:0 F=%DFMXamu L=990 E=\r\n T=DNS/RFC822/SMTP r=100 A=TCP $h
mailer 8 (relay): P=[IPC] S=EnvFromSMTP/HdrFromSMTP R=MasqSMTP/MasqRelay M=0 U=0:0
F=8DFMXamu L=2040 E=\r\n T=DNS/RFC822/SMTP r=100 A=TCP $h

This output is the same as that produced with the -d0.15 debugging switch (-d0.15 on page 544). The individual items in each line are explained in Chapter 20 on page 711.

Complex Actions Made Simple

Beginning with V8.7 sendmail, rule-testing mode offers six simple commands that accomplish complex tasks. They are listed in Table 8-1.

Table 8-1. Available -bt / commands

Command

Version

§

Description

/canon

V8.7 and later

Canonify a Host with /canon on page 308

Canonify a host.

/mx

V8.7 and later

Look Up MX Records with /mx on page 309

Look up MX records.

/map

V8.7 and later

Look Up a Database Item with /map on page 310

Look up a database item.

/tryflags

V8.7 and later

Select Whom to /parse or /try with /tryflags on page 311

Select whom to /parse or /try.

/parse

V8.7 and later

Parse an Address with /parse on page 311

Parse an address.

/try

V8.7 and later

Try a Delivery Agent with /try on page 313

Try a delivery agent.

A lone / character will cause the following usage message to print:

Usage: /[canon|map|mx|parse|try|tryflags]

Anything other than the commands shown in Table 8-1 (such as /foo) will produce an error:

Unknown "/" command /foo

Canonify a Host with /canon

The /canon rule-testing command causes sendmail to look up the canonical (official, fully qualified) name of a host and print the result. The form for this command looks like this:

/canon host

If host is missing, the following usage message is printed:

Usage: /canon address

When you correctly supply the hostname as the argument, sendmail looks up the canonical name and returns the result:

> /canon icsic
getcanonname(icsic) returns icsic.icsi.berkeley.edu
>

Here, the hostname icsic was looked up. Because its canonical name was found, that name is printed following the returns. If the hostname had not been found, sendmail would have printed that same name after the returns:

> /canon foo
getcanonname(foo) returns foo

If you wish to watch the actual process of a host being canonified, you can turn on the -d38.20 debugging switch (-d38.20 on page 568) with the rule-testing -d command (Add Debugging for Detail on page 318):

> -d38.20
>

With that setting, the previous lookup of icsic produces a trace of all the steps that sendmail takes:

> /canon icsic
getcanonname(icsic), trying dns
getcanonname(icsic), trying files
text_getcanonname(icsic)
getcanonname(icsic.icsi.berkeley.edu), found
getcanonname(icsic) returns icsic.icsi.berkeley.edu

Here, sendmail first looked up icsic using DNS. That lookup failed, so sendmail fell back to looking it up in the /etc/hosts file, where it was found. The order in which these techniques are tried is defined by your service switch (ServiceSwitchFile on page 1088). If a service-switch mechanism is lacking, the order is internally defined by sendmail and varies depending on the operating system used.

Internally, the /canon rule-testing command can be watched in greater detail with the -d38.20 debugging switch (-d38.20 on page 568) and with the -d8.2 debugging switch (-d8.2 on page 548).

Look Up MX Records with /mx

The /mx rule-testing command causes sendmail to look up a specified hostname and return a list of MX records for that host. The form for this command looks like this:

/mx host

Here, host is the short or fully qualified name of a host. If host is missing, sendmail prints the following usage message:

Usage: /mx address

When host exists and has MX records associated with it, sendmail will look up and print those records. The MX records are listed in the order in which they will be tried (lowest to highest preference values). For example:

> /mx ourhost
getmxrr(ourhost) returns 2 value(s):
        mx.our.domain
        offsite.mx.domain
>

If no MX records are found (as for a.com), sendmail prints the following message:

getmxrr(a.com) returns 0 value(s):

When multiple MX records have the same preference values, sendmail randomizes the list. During a single run of sendmail, the randomization will be the same each time. You can see this by looking up aol.com:

> /mx aol.com
getmxrr(aol.com) returns 4 value(s):
        mailin-02.mx.aol.com.
        mailin-01.mx.aol.com.
        mailin-04.mx.aol.com.
        mailin-03.mx.aol.com.

If you have defined the FallbackMXhost option (FallbackMXhost on page 1030) the host that is specified in that option will always appear last in the list of MX hosts. As a side benefit, the fallback host will also be listed for hosts that do not exist:

% /usr/sbin/sendmail -OFallBackMXhost=mx.our.domain -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> /mx a.com
getmxrr(a.com) returns 1 value(s):
        mx.our.domain
>

This /mx command is available for your use only if sendmail was compiled with NAMED_BIND defined (NAMED_BIND on page 124). If NAMED_BIND was not defined, sendmail will print the following error instead of listing MX records:

No MX code compiled in

Look Up a Database Item with /map

The /map rule-testing command causes sendmail to look up a key in a database and print the value found (if there is one). The /map command is used like this:

/map name key

Here, name is the name of a database. It is either a name you assigned using a K configuration command (The K Configuration Command on page 882) or a name that is internally defined by sendmail, such as aliases.files (switch on page 938). The key is the item you wish to look up in the database. If both name and key are missing, sendmail prints this usage message:

Usage: /map mapname key

If just the key is missing, sendmail prints this error:

No key specified

If the name is that of a database that does not exist, sendmail prints this error:

Map named "bad name here" not found

Otherwise, the database does exist, so sendmail looks up the key in it. If the key is not found in the database, sendmail prints this:

map_lookup: name (key) no match (error number here
)

The error number corresponds to error numbers listed in the sysexits.h file.

The /map rule-testing command is very useful for testing databases of your own design. If a rule that uses the database fails to work as predicted, use /map to test that database by hand. To illustrate, consider the sampling of maps in the following sections.

The aliases database map

The aliases map is used to convert a local address into one or more new addresses. Using the rule-testing /map command, you can see how sendmail looks up an alias:

> /map aliases root
map_lookup: aliases (root) returns you, hans@other.site (0)

The host map

The host database behaves the same as the /canon command shown earlier. It looks up a hostname by using sendmail’s internal host map ($[ and $]: A Special Case on page 895) which returns the canonical name of the looked-up host:

> /map host localhost
map_lookup: host (localhost) returns localhost.our.domain. (0)
> /map host bogus.no.domain
map_lookup: host (bogus.no.domain) no match (68)

The dequote map

The dequote map (dequote on page 904) is not really a database at all, but a hook into a routine that removes quotation marks from addresses:

> /map dequote "a"@"@b"
map_lookup: dequote ("a"@"@b") returns a@@b (0)
> /map dequote "a
map_lookup: dequote ("a) no match (0)
> /map dequote "<a"
map_lookup: dequote ("<a") no match (0)
> /map dequote "(a"
map_lookup: dequote ("(a") no match (0)

Note in the second example that it removes only balanced quotation marks. Note in the last two examples that it will remove quotation marks only if the enclosed expression is a valid address expression. In neither of the last two examples were the enclosing angle braces or parentheses balanced.

Select Whom to /parse or /try with /tryflags

Before we cover the /parse and /try commands, we need to mention the /tryflags rule-testing command because it is used to select the sender, recipient, headers, and envelope for the /parse and /try commands. The /tryflags command is used like this:

/tryflags h             ← set headers
/tryflags e             ← set envelope
/tryflags s             ← set sender
/tryflags r             ← set recipient
/tryflags er            ← set envelope recipient

The arguments are single letters that can appear in uppercase or lowercase and in any order. Any letter other than those shown is silently ignored.

The default setting when sendmail first starts to run in rule-testing mode is er for envelope recipient. Omitting the argument causes sendmail to print the following usage statement:

Usage: /tryflags [Hh|Ee][Ss|Rr]

Parse an Address with /parse

The /parse rule-testing command instructs sendmail to pass an address through a predetermined sequence of rules to select a delivery agent and to put the $u macro ($u on page 848) into its final form. The /parse command is used like this:

/parse address

If the address is missing, sendmail prints the following usage message:

Usage: /parse address

The following example shows a local address being fed into /parse. Note that the numbers on the left are for later reference and are not part of sendmail’s output:

   > /parse you@localhost (Your Name)
❶   Cracked address = $g (Your Name)
❷   Parsing envelope-recipient address
   canonify           input: you @ localhost
❸   Canonify2          input: you < @ localhost  >
❹   Canonify2        returns: you < @ here . our. domain .  >
   canonify         returns: you < @ here . our. domain .  >
❺   parse              input: you < @ here . our. domain .  >
   Parse0             input: you < @ here . our. domain .  >
   Parse0           returns: you < @ here . our. domain .  >
   ParseLocal         input: you < @ here . our. domain .  >
   ParseLocal       returns: you < @ here . our. domain .  >
   Parse1             input: you < @ here . our. domain .  >
   Parse1           returns: $# local $: you
❻   parse            returns: $# local $: you
❼   2                  input: you
   2                returns: you
❽   EnvToL             input: you
❾   EnvToL           returns: you
   final              input: you
   final            returns: you
❿   mailer local, user you

The address you@localhost is first fed into crackaddr (line ❶) to separate it from any surrounding RFC822 comments such as "(Your Name).” If mail were actually to be sent, the address would be stored in the $g macro before being passed to rules. This is illustrated by line ❶, which uses $g as a placeholder to show where the address was found.

The next line (line ❷) shows that the address will be treated as that of an envelope recipient. The /tryflags command (Select Whom to /parse or /try with /tryflags on page 311) sets whether it is treated as a header or envelope or as a sender or recipient address.

The address is passed to the canonify rule set 3 (The canonify Rule Set 3 on page 690) because all addresses are rewritten by the canonify rule set 3 first. The job of the canonify rule set 3 is to focus on (surround in angle brackets) the host part of the address, which it does (line ❸). The canonify rule set 3, in this example, then passes the address to the Canonify2 rule set to see whether localhost is a synonym for the local machine’s name. It is, so the Canonify2 rule set makes that translation (line ❹).

The output of the canonify rule set 3 is passed to the parse rule set 0, whose job is to select a delivery agent (line ❺). Because here.our.domain is the local machine, the parse rule set 0 (by way of other rule sets) selects the local delivery agent (line ❻).

Line ❻ shows that the $: part of the delivery agent “triple” (The parse Rule Set 0 on page 696) will eventually be tucked into $u ($u on page 848) for use by the delivery agent’s A= equate (A= on page 738). But before that happens, that address needs to be passed through its own set of specific rules. It is given to rule set 2 because all recipient addresses are given to rule set 2 (line ❼). It is then given to rule set EnvToL because the R= equate (R= on page 751) for the local delivery agent specifies rule set EnvToL for the envelope recipient (line ❽). Finally, it is given to the final rule set 4 (The final Rule Set 4 on page 694) because all addresses are lastly rewritten by the final rule set 4 (line ❾).

The last line of output shows that the local delivery agent was selected and that the value that would be put into $u (were mail really being sent) would be you.

When you /parse an address that is not local, the parse rule set 3 will also select a host ($@) part for delivery:

parse            returns: $# esmtp $@ uofa . edu . $: friend < @ uofa . edu .  >

In this instance, the last line of /parse output will also include the host information that will be placed into $h:

mailer esmtp, host uofa.edu., user friend@uofa.edu

When you /parse an address that is illegal (from the point of view of rules), sendmail selects the #error delivery agent:

> /parse @host
Cracked address = $g
Parsing envelope-recipient address
canonify           input: @ host
Canonify2          input: < @ host >
Canonify2        returns: < @ host >
canonify         returns: < @ host >
parse              input: < @ host >
Parse0             input: < @ host >
Parse0           returns: $# error $@ 5 . 1 . 3 $: "553 User address required"
parse            returns: $# error $@ 5 . 1 . 3 $: "553 User address required"
@host... User address required
mailer *error*, host 5.1.3, user "553 User address required"

The error here was that the address lacked a user part. The meanings of all the parts of the #error delivery agent are described in error on page 720. The second to last line in this example shows the message that would be printed or returned if such an address appeared in actual mail. The delivery agent *error* is internal to sendmail and cannot be directly used.

Try a Delivery Agent with /try

In the SMTP RCPT To: command, sendmail is required to express the recipient’s address relative to the local host. For domain addresses, this simply means that the address should be RFC2822-compliant (such as you@here.our.domain). For UUCP addresses, this can mean reversing the path (such as you@there reversing to there!you). The /try rule-testing command causes an address to be rewritten so that it appears to be correct relative to the local host.

The /try command is used like this:

/try agent address

Here, agent is the delivery agent, and address is the address to rewrite. The following usage message is produced if both agent and address are missing or if just the address is missing:

Usage: /try mailer address

The delivery agent (mailer) is used to select only the R= or S= rule set for the address. The /tryflags command (Select Whom to /parse or /try with /tryflags on page 311) determines which is selected (by selecting recipient or sender).

In the following example, the numbers to the left are for reference only and are not part of sendmail’s output:

   > /try smtp you
   Trying envelope-recipient address you for mailer esmtp
❶   canonify           input: you
   Canonify2          input: you
   Canonify2        returns: you
   canonify         returns: you
❷   2                  input: you
   2                returns: you
❸   EnvToSMTP          input: you
   PseudoToReal       input: you
   PseudoToReal     returns: you
   MasqSMTP           input: you
❹   MasqSMTP         returns: you < @ *LOCAL*  >
   EnvToSMTP        returns: you < @ here . our . domain .  >
❺   final              input: you < @ here . our . domain .  >
   final            returns: you @ here . our . domain
   Rcode = 0, addr = you@here.our.domain

Here, the envelope-recipient address you is rewritten on the basis of the smtp delivery agent. Rule set canonify is called first (line ❶) because all addresses are rewritten by it first. Rule set 2 (line ❷) is called because all recipient addresses get rewritten by it. Rule set EnvToSMTP (line ❹) sees the special tag *LOCAL* and converts that tag to the canonical name of your local machine. Rule set final (line ❺) removes focusing from the address, thus forming the final address in its canonical form.

Process-Specified Addresses

The sendmail rule-testing mode has always had the ability to test individual rule sets, but prior to V8.7 sendmail, rule sets could be specified only by number. Beginning with V8.7, rule sets can also be specified by name. Prior to V8 sendmail, rule set 3 was always called first, even if you did not specify it.[145]

Syntax

The > prompt expects rule sets and addresses to be specified like this:

> ident,ident,ident ...   address

Each ident is a rule set name or number. When there is more than one rule set, they must be separated from each other by commas (with no spaces between them).

For numbered rule sets, the number must be in the range of 0 through the highest number allowed. A number that is too large causes sendmail to print the following two errors:

bad rule set number (max max)
Undefined rule set number

A rule set whose number is below the maximum but was never defined will act as though it was defined but lacks rules.

Named rule sets must exist in the symbol table. If the name specified was never defined, the following error is printed:

Undefined rule set ident

If any rule set number in the comma-separated list of rule sets is omitted (e.g., ident,,ident), sendmail interprets the second comma as part of the second identifier, thus producing this error:

Undefined rule set ,identifier

The address is everything following the first whitespace (space and tab characters) to the end of the line. If whitespace characters appear anywhere in the list of rule sets, the rule sets to the right of the whitespace are interpreted as part of the address.

We show named rule sets in our examples, even though numbered rule sets will work just as well. But by using named rule sets, the examples will still work even if the corresponding numbers change in the future.

The Address

Each address that is specified is handed almost as is to the rule set or sets being tested. Each is tokenized and placed into the workspace for rule set processing. To illustrate, observe the following rule-testing session:

ADDRESS TEST MODE (rule set 3 NOT automatically invoked)
Enter <rule set> <address>
> parse bill (Bill Bix)
parse              input: bill ( Bill Bix )
Parse0             input: bill ( Bill Bix )
Parse0           returns: bill ( Bill Bix )
ParseLocal         input: bill ( Bill Bix )
ParseLocal       returns: bill ( Bill Bix )
Parse1             input: bill ( Bill Bix )
Parse1           returns: $# local $: bill ( Bill Bix )
parse            returns: $# local $: bill ( Bill Bix )
> parse Bill Bix <bill
 >
parse              input: Bill Bix < bill >
Parse0             input: Bill Bix < bill >
Parse0           returns: Bill Bix < bill >
ParseLocal         input: Bill Bix < bill >
ParseLocal       returns: Bill Bix < bill >
Parse1             input: Bill Bix < bill >
Parse1           returns: $# local $: Bill Bix < bill >
parse            returns: $# local $: Bill Bix < bill >
> canonify,parse Bill Bix <bill
 >
canonify           input: Bill Bix < bill >
Canonify2          input: bill
Canonify2        returns: bill
canonify         returns: bill
parse              input: bill
Parse0             input: bill
Parse0           returns: bill
ParseLocal         input: bill
ParseLocal       returns: bill
Parse1             input: bill
Parse1           returns: $# local $: bill
parse            returns: $# local $: bill
>

The first test illustrates that sendmail does not strip RFC822-style comments from addresses before tokenizing them.

The second test illustrates that sendmail does not internally recognize addresses in angle brackets. Instead, the canonify rule set throws away everything but the address in angle brackets, as shown in the third test.

Note that in many actual configuration files, the canonify rule set 3 also focuses on the host part of the address. For this reason, you should always begin with the canonify rule set 3 unless you are tuning a particular rule for which you know the precise input required.

Rule Set 3 Always Called First with -bt

When sendmail starts to run in rule-testing mode, its appearance and initial behavior vary from vendor to vendor and from version to version. When rule-testing mode begins, sendmail always prints an introductory banner. Pre-V8 sendmail printed the following banner:

ADDRESS TEST MODE
Enter <rule set> <address>
>

It is important to note that (unless a banner says otherwise) sendmail always calls rule set 3 first.[146] That is, even if you try to test rule set 0, you always first see the effects of rule set 3.

Beginning with V8 sendmail, rule set 3 is no longer automatically called. To ensure that there is no confusion, V8 sendmail prints this banner:

ADDRESS TEST MODE (rule set 3 NOT automatically invoked)
Enter <rule set> <address>
>

Note that in all versions, the last line (the >) is a prompt. At this prompt, you can specify a rule set and an address or, beginning with V8.7, any of the commands shown in Overview on page 299.

The Output

Each line of output produced during rule testing begins with an indication of the rule set number or name being processed:

canonify           input: Bill Bix < bill >

The word input precedes each address that is about to be processed by a rule set:

canonify           input: Bill Bix < bill  >

The word returns precedes each address that is the result of rewriting by a rule set:

canonify         returns: bill

When rule sets call other rule sets as subroutines, those calls are shown in the output with input and returns pairs. In the following, the Canonify2 rule set is called as a subroutine rule set from inside the canonify rule set 3:

canonify           input: Bill Bix < bill >
Canonify2          input: bill
Canonify2        returns: bill
canonify         returns: bill

The output can also contain rule set operators:

parse            returns: $# local  $:  bill

In this output, the operators are printed as they would appear in the configuration file. The $# selects a delivery agent, and the $: specifies the user. Under old versions of sendmail, those operators are printed in the output as control characters:

rewrite: rule set  0 returns: ^V local  ^X  bill

The correspondence between control characters in the old-style output and sendmail configuration file operators is given in Table 8-2.

Table 8-2. Control characters versus operators

Control

Operator

Meaning

^V

$#

Select delivery agent.

^W

$@

Specify host for delivery agent.

^X

$:

Specify user for delivery agent.

Add Debugging for Detail

In rule-testing mode, the -d command (The Syntax of -d on page 530) can be used to turn debugging output on and off. Prior to V8.7 sendmail, the -d could be specified only on the command line. Beginning with V8.7 sendmail, the -d can also be specified in rule-testing mode. We illustrate the latter technique here.

Debugging output can reveal in great detail how individual rules are being handled. A debugging category and level of 21.12 (-d21.1 on page 554), for example, causes sendmail to print the LHS of each rule as it is tried. To illustrate, consider the following (highly simplified) configuration-file rule set:

V10
STest
R @                  $#local $:$n                 handle <> form
R $* < @ $+ > $*     $#$M $@$R $:$1<@$2>$3        user@some.where
R $+                 $#local $:$1                 local names

Normal output that is produced when a rule set name and an address are entered at the > prompt looks like this:

> Test george
Test               input: george
Test             returns: $# local $: george

But if we turn on debugging using the -d rule-testing command:

> -d21.12

the output that is produced when the same rule set number and address are entered is more verbose than it was before:

>  Test george
Test               input: george
-----trying rule: @
----- rule fails
-----trying rule: $* < @ $+ > $*
----- rule fails
-----trying rule: $+
-----rule matches: $# local $: $1
rewritten as: $# local $: george
Test             returns: $# local $: george

Observe that the first rule in the Test rule set (the lone @) does not match george in the workspace. Therefore, that rule fails and is skipped. Then the more complicated rule ($*<@$+>$*) is tried, and it too fails. Finally, the $+ operator in the last rule matches george, and the workspace is rewritten.

Note that the extra output that is produced by -d can potentially run to many lines. To capture the output for later examination, consider running sendmail in rule-testing mode from within a script(1), emacs(1), or similar session.

To turn off the extra debugging output, just reuse the -d rule-testing command and specify a level of zero:

> -d21.0

A -d with no category or level behaves the same as the -d command-line switch (The Syntax of -d on page 530). It sets a default of 0–99.1.

A Trick

In debugging large configuration files, the output that is produced by the -d21.15 switch can become too huge to examine conveniently. A good alternative (when modifying or adding rules) is to temporarily insert a fake subroutine call before and after individual rules to see what they do:

R$*      $:$>TEST $1       ← fake subroutine call
Rlhs     rhs               ← new rule
R$*      $:$>TEST $1       ← fake subroutine call

With the fake wrapper around the new rule (the name TEST is arbitrary), ordinary rule testing with -bt now shows how the address is rewritten by that rule:

3                  input: ...
TEST               input: ...
TEST             returns: ...
                                  ← new rule acted here
TEST               input: ...
TEST             returns: ...
3                returns: ...
>

If you use this technique, remember, of course, to remove the fake subroutine calls before putting that configuration file into use.

Batch Rule-Set Testing

The output that is produced by sendmail can become huge, especially when many addresses need testing. To simplify the process (and to help bulletproof your configuration file), consider using a shell script such as the following:

#!/bin/sh
/usr/sbin/sendmail -bt < $1 |\
         egrep "canonify.*input:|canonify.*returns|^>"

Here, the output is piped through egrep(1), which selects only the lines of interest. If this script were to be called testcf.sh, it could be invoked with the following command line:

% testcf.sh address.list

Here, the address.list is a file consisting of pairs of rule set names and addresses such as the following:

canonify,parse nobody@ourhost
canonify,parse nobody@ourhost.domain
canonify,parse nobody@distant.domain
 ... and so on

The output that is produced shows the input to the canonify rule set 3 and the result of each pass through that rule set:

> canonify           input: nobody @ ourhost
canonify         returns: nobody < @ ourhost . domain . >
> canonify           input: nobody @ ourhost . domain
canonify         returns: nobody < @ ourhost . domain . >
> canonify           input: nobody @ distant . domain
canonify         returns: nobody < @ distant . domain . >

Note that the address.list file should contain every conceivable kind of address. The output from the shell script should be saved. At a later time, after the configuration file is changed, diff(1) can be used to see whether the saved output differs from the new output (to see whether anything unexpected changed as a result of your modifications).

Also note that directly calling the canonify and parse rule sets 0 produces less useful information than does the /parse rule-testing command (Parse an Address with /parse on page 311). If you use that command, a diff(1) against prior output can provide more interesting and complete information.

Pitfalls

  • Old programs and scripts that are designed to use -bt mode to test addresses and the like tend to break with each release of sendmail. Fortunately, they are easy to fix if you know the language involved.

  • There is no way to currently define rules on the fly. Consequently, you need to modify a configuration file and run sendmail in rule-testing mode repeatedly until the new rules work.



[141] * If you get an error such as “sendmail: Address test mode not supported”, you are probably not running the real sendmail. Some programs, such as Netscape’s Internet Mail Server, masquerade as sendmail without letting you know that they are doing so. If this offends you, complain to the vendor of the imposter.

[142] * Prior to V8.10, this file was called sendmail.hf

[143] * Prior to V8.10 sendmail, this file was called /etc/mail/sendmail.cw.

[144] * For the sake of the example, we limited this rule set to a single rule. Most rule sets will have many rules.

[145] * This was adopted from IDA sendmail.

[146] * We use a rule set number here because the versions of sendmail that always started with rule set 3 are too old to use named rule sets.