In rule sets, it is often advantageous to compare individual
tokens to multiple strings in determining a match.
For example, consider the rules developed
in the last chapter, that is, the sender rewriting rules from
the hub delivery agent's
S=Hubset
rule set:
The second rule's LHS looks for any sender address
that is composed of a single username followed by an
@
character and ending with the short name of the local machine (
$w
).
Such an address is rewritten by the RHS to become that of
the central forwarding machine, defined by the
{HUB}
macro.
Now suppose that the local machine is known by several names in
addition to the short name in
$w
. All machines,
for example, have a short name (such as
here
)
and a fully qualified name (such as
here.us.edu
).
They also often refer to themselves as
localhost
.
In addition, some machines can play special roles at a site (such
as having a printer or containing a central directory of fonts) and
might have another name appropriate to that role.
To convert any sender address so that
it becomes the central forwarder's name, no matter what the local host's
name, you can use
sendmail
classes.
In this chapter we will cover the class configuration command
and its cousin, the file configuration command.
Proper use of the class and file commands allows
you to replace many rules with a single rule.
The class command declares a macro whose value is a list of strings.
Rule sets may then compare the workspace to that list of strings.
One such
list could be a list of names by which the local machine is known.
A class is referenced in the LHS with the
$=
prefix:
$=
X
single-character name
$=
{XXX}
multicharacter name (beginning with V8.7)
Here,
X
is a single-character class name.
Beginning with V8.7
sendmail
, multicharacter class name may be
used, as shown in the second line above.
Multicharacter class names must always be enclosed in
curly braces.
The workspace is tokenized as usual; then the appropriate
token is looked up to see whether it was defined as belonging
to the class referenced by
$=
. If the token was
found, the workspace at that point is considered to be matched.
We'll cover this in more detail shortly.
The words that form the list of words in a class
are declared with the
C
configuration command.
The form for the class configuration command is as follows:
C
Xlist
single-character name
C
{XXX}list
multicharacter name (beginning with V8.7)
The class configuration command starts with the letter
C
, which must begin a line. The
C
is immediately followed
(with no intervening whitespace) by the name of that class.
A class name can be a single ASCII character
or, beginning with V8.7
sendmail
,
multiple ASCII characters enclosed in curly braces.
A whitespace-separated
list
of word elements follows
on the same line. Space between the name and the
list
is optional.
For example, the following declaration places two possible
names for the local machine into the class named
w
:
Cw printer1 fontserver
Multiple declarations of the same class macro may exist.
Each appends its word elements to the preceding list. For example,
the following produces the same result as the single line above:
Cw printer1
Cw fontserver
Both examples define a class named
w
, and both assign to
that class the same list of two words.
Class names and macro names
are completely independent. To illustrate, consider
the following two configuration commands:
Dwprinter1
Cwprinter1
Both assign the value
printer1
to the name
w
.
The first assigns that value to a macro, the second to
a class. Internally,
sendmail
stores the value
printer1
twice, first as type "macro" and second
as type "class." Although they share the same
value (
printer1
), macros and classes are
completely independent of each other.
As an example of one common use of the class macro, we will consider
the case of machines that are known by multiple names.
Run
sendmail
again:
%
./sendmail -Cclient.cf -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
>
Now give it the special (new to V8.7
sendmail
)
rule-testing command
$=
(which
tells
sendmail
to print the values stored in a class) and follow
that with the letter (class name)
w
:
>
$=w
here.us.edu
here
[123.45.67.8]
>
This illustrates that
sendmail
is aware of
three of your machine's possible names (the last is an IP address).
But recall from the previous chapter that the rule that we created
looked for the local machine using only the
short name in the macro
w
, not the three names in class
w
:
R$-@$w $@ $1@${HUB} user@local -> user@hub
If we didn't have class macros, we would have to look for the local
machine name using a series of rules like those below:
R$-@$w $@ $1@${HUB} user@local -> user@hub
R$-@here.us.edu $@ $1@${HUB} user@local -> user@hub
R$-@[123.45.67.8] $@ $1@${HUB} user@local -> user@hub
Fortunately, the class configuration command provides an alternative.
Use of a class list allows one rule to be written that does exactly
the same thing as the three preceding rules:
R$-@$=w $@ $1@${HUB} user@local -> user@hub
Let's examine this new rule, then test it.
The list of words in a class are referenced in the LHS of
rules by prefixing the class name with the characters
$=
. For the class named
w
the expression
in the LHS looks like this:
$=w
To understand how this expression is evaluated, we need to
look at how the words in the class are stored.
Each word that
sendmail
adds to the list (whether
internally or from a
C
configuration command)
is stored by
sendmail
in its
symbol table
. The symbol table holds all the words
that appear in the configuration file. It holds the values
of macro definitions and the symbolic names of delivery agents.
It also holds the words that form a class list.
Next, we need to review how the workspace is tokenized and
compared to the LHS of a rule. Just before the LHS
is compared to the workspace, the workspace is tokenized.
The address of a local
user in the workspace might look like,
and be tokenized like, the following:
you@here
becomes
you @ here
When the
$=w
expression appears in the LHS of a rule,
it is compared to the workspace at the point that corresponds to
the class's position in the LHS:
R$-@$=w $@$1@${HUB} user@local -> user@hub
here
In performing its minimum match of the workspace to the LHS,
the matches are the following:
$-
match
you
@
match
@
$=w
does
here
match any token in class
w
?
When
sendmail
encounters a class-matching expression in
the LHS, it looks up the corresponding token from the workspace
in its symbol table. Here it looks up
here
.
Because
here
is listed in the symbol table
as belonging to the class
w
,
sendmail
finds a match.
If there were no
here
in the symbol table, the match
would fail. If there were one or
more
here
entries in the symbol table, but none marked as belonging to
class
w
, the match would also fail.
When a match in the LHS allows the RHS to rewrite the workspace, the
token matched by the
$=
prefix can be referenced
with a
$
digit
positional operator. This differs from the
$
macro prefix that you learned about earlier. Macros
are expanded when the configuration file is read (so
$
digit
doesn't work), but classes are
given values only when a rule is matched (so
$
digit
does work).
We don't use the positional operator in the RHS because we
don't care what word matched. Instead, we take the username from
the LHS with
$1
, add an
@
character and
the name of the forwarding hub machine (the
${HUB}
):
R$-@$=w $@$1@${HUB} user@local -> user@hub
1 2 $2 not needed
Modify the
client.cf
file by changing the
$w
in
the second rule (of
SHubset
) into a
$=w
so that we
can begin to test the class:
R$-@$
=
w $@ $1@${HUB} user@local -> user@hub
insert an
=
Now run
sendmail
in rule-testing mode once again:
%
./sendmail -Cclient.cf -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
>
First use the
$=
rule-testing command to list the contents
of the class
w
:
>
$=w
here.us.edu
here
[123.45.67.8]
Next, to test the new rule, specify rule set
Hubset
and
give
sendmail
an address with a user, an
@
, and
one of the local addresses shown for your machine:
>
Hubset you@here.us.edu
rewrite: ruleset 199 input: you @ here . us . edu
rewrite: ruleset 199 returns: you @ mail . us . edu
This result is just what we expected. After all, the value in
${HUB}
is
mail.us.edu
. Now give
sendmail
another address,
only this time give it one not in the list:
>
Hubset you@localhost
rewrite: ruleset 199 input: you @ localhost
rewrite: ruleset 199 returns: you @ localhost
This time the address was not rewritten because
localhost
is
not listed in the class
w
. But, in general,
localhost
is a legitimate name for the local machine, so
let's add it temporarily by employing a new rule-testing command
that you haven't seen before:
>
.Cw localhost
>
$=w
here.us.edu
localhost
here
[123.45.67.8]
The
.C
command (new with V8.7)
tells
sendmail
to temporarily add a word to a class. In this case
we added the word
localhost
to the class
w
. We then
ran the
$=
rule-testing command to confirm that
sendmail
had actually added the word to the class.
Test the address
localhost
again:
>
Hubset you@localhost
rewrite: ruleset 199 input: you @ localhost
rewrite: ruleset 199 returns: you @ mail . us . edu
This time,
sendmail
found
localhost
in the list
and so rewrote the sender address.
As we mentioned earlier, a host may be known by many names.
Some of those names are found automatically by
sendmail
; others must be added by you. The name
localhost
is one possibility, and there may be others (such as
printer1
).
You can add them to class
w
in rule-testing mode, but
that is not a permanent solution (because they are forgotten as soon
you leave rule-testing mode). Instead, you need to add
them to your configuration file using the
C
configuration
command.
Edit the
client.cf
file and add a new
C
configuration
line to it:
V7
# Defined macros
D{REMOTE}mailhost # The name of the mail hub
D{HUB}mail.us.edu # Hub as known to the outside world
Cw localhost printer1 # My other names.
new
Here we added
localhost
because it was missing and
printer1
to illustrate later points. Note that the name
printer1
is probably
not appropriate for your site, but include it for now.
Run
sendmail
again in rule-testing mode:
%
./sendmail -Cclient.cf -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
>
Use the
$=
rule-testing command again. Make sure the
C
line added the new names to the
class
w
:
>
$=w
here.us.edu
localhost
here
[123.45.67.8]
printer1
We can now test each name.
Remember that the rule we are testing is this one:
R$-@$=w $@ $1@${HUB} user@local -> user@hub
We want to match any sender address that is a username,
an
@
, and any of the names in the class
w
.
We also want to rewrite each to appear as though the user is from
the hub.
>
Hubset you@here.us.edu
rewrite: ruleset 199 input: you @ here . us . edu
rewrite: ruleset 199 returns: you @ mail . us . edu
>
Hubset you@localhost
rewrite: ruleset 199 input: you @ localhost
rewrite: ruleset 199 returns: you @ mail . us . edu
>
Hubset you@here
rewrite: ruleset 199 input: you @ here
rewrite: ruleset 199 returns: you @ mail . us . edu
>
Hubset you@[123.45.67.8]
rewrite: ruleset 199 input: you @ [ 123 . 45 . 67 . 8 ]
rewrite: ruleset 199 returns: you @ mail . us . edu
>
Hubset you@printer1
rewrite: ruleset 199 input: you @ printer1
rewrite: ruleset 199 returns: you @ mail . us . edu
>
Hubset you@printer1.us.edu
rewrite: ruleset 199 input: you @ printer1 . us . edu
rewrite: ruleset 199 returns: you @ printer1 . us . edu
All the names in class
w
match as they should. Notice
that
here
appears in the list as both a short and
a fully qualified name, but
printer1
does not.
When we tried
printer1
in a fully qualified form (in the last
three lines above), it failed.
To make
printer1
succeed,
we could add its fully qualified form
to the class
w
, but if we did that, we would have
to do the same for
localhost
and any other names
that we added. Instead, we will deal with the problem by adding
a rule to the
client.cf
file.
There are three approaches to writing a rule that matches any
of the local hostnames with a domain added:
-
Use a
$*
wildcard operator to match anything after the local hostname:
R$-@$=w$*
But this is unwise because it assumes that anything that
follows the host is a domain, which might not always be true.
-
Use your domain name directly in the rule:
R$-@$=w.us.edu
Although this will work,
it is better to use a macro in case you change your domain or use
this file on a machine in another domain.
-
Use a macro that has your domain name as its value.
We will use the third approach because it is the cleanest. Begin by
running
sendmail
with the
-d0.1
debugging switch
again:
Version 8.8.4
Compiled with: LOG MATCHGECOS MIME7TO8 MIME8TO7 NAMED_BIND NDBM NETINET
NETUNIX NIS SCANF XDEBUG
============ SYSTEM IDENTITY (after readcf) ============
(short domain name) $w = here
(canonical domain name) $j = here.us.edu
(subdomain name) $m = us.edu
(node name) $k = here
========================================================
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
>
Note that the line that defines the "subdomain name" (the one with the
$m
)
is the local domain that we need for our new rule.
Add another rule to the
Hubset
rule set. This new rule looks for
any local names (in
$=w
) that are followed by the
local domain name defined for you by
sendmail
:
SHubset # Rewrite the sender for the hub
R$- $@ $1@${HUB} user -> user@hub
R$-@$=w $@ $1@${HUB} user@local -> user@hub
R$-@$=w.$m $@ $1@${HUB} user@local.domain -> user@hub
new
Now run
sendmail
once again and feed it the address that failed in
the last test (but use your local domain):
%
./sendmail -Cclient.cf -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
>
Hubset you@printer1.us.edu
rewrite: ruleset 199 input: you @ printer1 . us . edu
rewrite: ruleset 199 returns: you @ mail . us . edu
As expected, the new rule in the
Hubset
rule set does its job.
It finds that
the
printer1.us.edu
part of the address matches the
$=w.$m
part
of the rule and rewrites the workspace to make the sender's address
appear as though it is from the hub.