21.5 Macro Expansion: $ and $&

The value of a macro can be used by putting a $ character in front of the macro's name. For example, consider the following definition:


Here, the macro named X is given text as its value.

If you later prefix a macro name with a $ character, you can use that value. This is called expanding a macro:


Here, the expression $X tells sendmail to use the value stored in X (the text) rather than its name (X).

For multicharacter names, the process is the same, but the name is surrounded with curly braces:

D{Xxx}text      declare {Xxx} 
${Xxx}          use {Xxx}

21.5.1 Macro Expansion Is Recursive

When text contains other macros, those other macros are also expanded. This process is recursive and continues until all macros have been expanded. For example, consider the following:


Here, the text for the macro D is $C.zzz. When the D macro is defined, it is recursively expanded like this:

$D    becomes   $C.zzz
$C.zzz      becomes   $A.$B.zzz
$A.$B.zzz      becomes   xxx.$B.zzz
xxx.$B.zzz     becomes   xxx.yyy.zzz

Notice that when sendmail recursively expands a macro, it does so one macro at a time, always expanding the leftmost macro first.

In rules, when sendmail expands a macro, it also tokenizes it. For example, placing the earlier $D in the following rule's LHS:

R$+ @ $D      $1

causes the LHS to contain seven tokens rather than three:

R$+ @ xxx . yyy . zzz          $1

Note that the largest a recursive expansion can grow is defined at compile time with the MACBUFSIZE compile-time macro (MAX...), which defaults to 4096 characters.

21.5.2 When Is a Macro Expanded?

A sendmail macro can be expanded either immediately or at runtime, depending on where the expansion takes place in the configuration file.

Macros are expanded in rule sets as the configuration file is read and parsed by sendmail, and (beginning with V8.7) so are macros in rule set names (Section 19.1.4) and in database maps declared with the K configuration command (Section 23.2). In other configuration lines, expansion is deferred until sendmail actually needs to use that value. In yet others, macros are neither recognized nor expanded.

To illustrate, macros used in header commands are not expanded until the headers of a mail message are processed:

H?x?Full-Name: $x

Here, $x ($x) can change as sendmail is running. It contains as its value the full name of the sender. Clearly, this macro should not be expanded until that full name is known.

On the other hand, macros in rules are always expanded when the configuration file is read. Therefore, macros such as $x should never be used in rules because the configuration file is read long before mail is processed:

R$x        ($x)

Rules such as this won't work because $x lacks a value when the configuration file is read. This rule will be expanded to become meaningless:

R      ( )

Note that the $digit positional operator (Section 18.7.1) in the RHS cannot be used to reference defined macros in the LHS. Consider this example, in which {HOST} has the value myhost:

R${HOST}     <$1>

The ${HOST} is expanded when the configuration file is read and is transformed into:

Rmyhost   <$1>    error

Here, the $1 has no wildcard operator in the LHS to reference and so will produce this error:

configfile: line num: replacement $1 out of bounds 

21.5.3 Use Value As Is with $&

For situations in which a macro should not be recursively expanded when the configuration file is read, but rather should be used in rules as is, V8 sendmail offers the $& prefix. For example, consider the following RHS of a rule:

R...     $w.$&m

When sendmail encounters this RHS in the configuration file, it recursively expands $w into its final text value (where that text value is your hostname, such as lady). But because the m macro is prefixed with $&, it is not expanded until the rule is later evaluated at runtime.[9]

[9] Prior to V8.9, expansions with $& remained a single token even if they were legitimately multitokened. Beginning with V8.9, $& correctly returns multitokens when a value is multitokened.

To illustrate one application of $&, consider a client/hub setup. In such a setup, all mail sent from a client machine is forwarded to the hub for eventual delivery. If the client were to run a sendmail daemon to receive mail for local delivery, a mail loop could (in the absence of an MX record) develop where a message would bounce back and fourth between the client and the hub, eventually failing.

To break such a loop, a rule must be devised that recognizes that a received message is from the hub:

R $+              $: $&r @ $&s <$1>       Get protocol and host
R smtp @ $H <$+>  $#local $: $1           Local delivery breaks a loop
R $* <$+>         $#smtp  $@ $H $: $2     Punt to hub

These rules appear in the parse rule set 0. By the time they are reached, other rules have forwarded any nonlocal mail to the hub. What is left in the workspace is a lone username. The first rule in the preceding example matches the workspace and rewrites it to be the sending protocol ($&r; see $r), an @, the sending host ($&s; see $s), and the username in angle brackets:

user      becomes     smtp@hub<user >

The second rule checks to make sure the message was received with the SMTP protocol from the hub. If it was, the local delivery agent is used to deliver the message on the local machine. If it was received from any other host or by any other protocol, the second rule fails and the third forwards the lone user address to the hub.

    Part I: Build and Install
    Part II: Administration
    Part III: The Configuration File
    Chapter 21. The D (Define a Macro) Configuration Command
    Chapter 24. The O (Options) Configuration Command