Most corporate postmasters have been given the responsibility of
dealing with spam. A quick search on the Internet reveals various
technical solutions that have been created to help stop spam. One big
implementation problem with these anti-spam measures is that they are
usually applied on a site-wide basis. For most corporations, some email
addresses such as sales, technical support, and bug reporting
must not be blocked. Some of the spammers are our customers;
we want their purchase orders to get through but not their spam. We
never want to block bug reports from coming in, even if they are from a
known spammer.
This article discusses configuration changes that can be made to
sendmail rulesets in order to implement an anti-spam filtering policy
on a per-user basis. Users can decide if they want to activate
anti-spam features and what level of filtering they want.
1. The sending machine issues a HELO (or EHLO) in
which it identifies itself.
2. The sending machine issues a MAIL FROM in which it
identifies the sender of the message.
3. The sending machine issues a RCPT TO in which it identifies
the recipient of the message.
4. The sending machine issues a DATA to tell the receiving
machine it is about to transfer the message.
5. The message is transferred, and the sending machine ends the message
with a "." on a line by itself.
6. The receiving machine acknowledges that it got the message, usually
by issuing a unique number.
check_compat - this ruleset is called after step 5 in
the SMTP dialog. It can be used to stop delivery of a message after it
has been accepted.
Although these check_* hooks were provided, it was left to the
system administrator to actually develop rules using these hooks. Claus
Assmann[1] and Robert Harker[2] maintain a set of effective rules based
on these hooks.
When Sendmail 8.9 was released, Eric Allman included some basic
anti-spam features that could be configured into sendmail to take
advantage of these hooks. By default, Sendmail 8.9 had relaying turned
off (implemented in the check_rcpt ruleset). Furthermore, you
could enable rejection of email based on either a DNS lookup or the
results of a database lookup (implemented in the check_mail
ruleset).
The Problem
The main problem with the anti-spam features included with sendmail is
that the checks are made too early in the SMTP dialog. As configured by
sendmail, both the DNS and database check are made in
check_mail (SMTP step 2), after the sender has been
identified. If the sender fails the checks, the mail is rejected.
The rejection comes too early because we do not know whom the mail is
meant for yet. Also, this means that mail will be bounced regardless of
who the recipient was. This is a problem for corporations because there
may be some addresses that must receive all email. Also, some
users may actually want to get spam (true case)!
The Solution
I thought about ways we could block spam for our users while at the
same time allowing full access for other addresses. After a little
experimenting, I came up with a ruleset that I call
"Extended_check_rcpt." Basically, I hold off on the spam checks until
the recipient is identified. Then we can check to see if the recipient
wants filtering and apply the spam checks as appropriate.
At first I thought about implementing this delayed check by taking
advantage of check_compat. According to the sendmail book,
"Not all situations can be resolved by simply checking the recipient or
sender address. Sometimes you will need to make judgments based on
pairs of addresses. To handle this situation, V8.8 introduced the
check_compat rule set."[3] Unfortunately, the problem with
check_compat is that it is called after the message has been
accepted. That means that the sender has already transmitted the
message and has closed the connection. If you decide to bounce the
message because it fits the spam criteria, your server is then tasked
with delivering a bounce message back to the sender. If the sender's
address is fake, the bounce messages may back up and clog your mail
queue.
Ideally you want to be able to reject a message before it is accepted
and the sender has closed the connection. This will shift the burden of
delivering a bounce message back to the sending machine. Therefore the
best place to apply our spam checks is in check_rcpt, after
both the sender and recipient are identified but before the message is
sent. Fortunately, sendmail stores the sender's address in a macro, and
we can use sendmail's delayed macro expansion capabilities to access
this value during check_rcpt.
Since sendmail supports the use of a database to keep track of spamming
addresses, we can create another database to keep track of user
preferences. After our modifications are done, the SMTP dialog would
look something like this:
1. The sending machine issues a HELO (or EHLO) in
which it identifies itself.
2. The sending machine issues a MAIL FROM in which it
identifies the sender of the
message.
3. The sending machine issues a RCPT TO in which it identifies
the recipient of the
message.
a. Look into the user database to see if the recipient wants spam
filtering.
b. Apply DNS check if appropriate.
c. Apply spam database check if appropriate.
d. Reject the message if step b or c fails, otherwise continue
with step 4.
4. The sending machine issues a DATA to tell the receiving
machine it is about to transfer the message.
5. The message is transferred, and the sending machine ends the message
with a "." on a line by itself.
6. The receiving machine acknowledges that it got the message, usually
by issuing a unique number.
The new ruleset is called Extended_check_rcpt because it is
called after the sendmail's Basic_check_rcpt, which in turn is
called by check_rcpt.
sendmail.cf Changes to Implement
Extended_check_rcpt
I'll assume that you already know how to create a sendmail.cf
file from an mc file. You can have other features in your
mc file, but the two you should have in order to implement
Extended_check_rcpt are:
FEATURE(access_db, dbm -o /etc/mail_access)dnl
FEATURE(accept_unresolvable_domains)dnl
Even though the access database checks too early during the SMTP
process, it is still a useful feature to enable because the database
serves other purposes. First, it is used to enable selective relaying.
By listing domains in the access database, you can allow other domains
to relay through your site. Second, there may be instances in which you
really want to block email from a particular domain, regardless of
users' settings. You can use the access database to globally block a
particular sender or domain. However, we will not use the access
database for "spam stomping," as the config file puts it. We will put
our spammers into a different database.
The second feature, "accept unresolvable domains," is necessary because
by default sendmail will block email coming from domains that do not
exist. As with the access database, this check comes too early. We need
to disable this feature so that the DNS check doesn't get included in
the config file at check_mail. Instead, we will use our own
custom code in check_rcpt to do the DNS check.
Once the cf file has been generated, you will need to
hand-edit it to make a few changes.
1. Define the databases. A good place to add the database
definitions is after the access database line. My additions are in
bold:
# Access list database (for spam stomping)
Kaccess dbm -o /etc/mail_access
# Spam database (database of known spammers)
Kspammer dbm -o /etc/spammer
# User opt-in database
Kspamuser dbm -o /etc/spam_user
# Resolve map
Kresolve host -a<OK> -T<TEMP>
Explanation: The K configuration options tell sendmail
that we will be using two databases (referenced by the names
spammers and spamuser). The databases are set up as
dbm files, but you can substitute whatever database format you are
comfortable with here (db, hash, etc.). The Kresolve map is
needed for the DNS check.
2. Modify the current check_rcpt. The check_rcpt
ruleset as created by sendmail will look like this (note that the
numbers on the left are for reference only and will not appear in the
cf file itself):
1 SLocal_check_rcpt
2 Scheck_rcpt
3 R$* $: $1 $| $>"Local_check_rcpt" $1
4 R$* $| $#$* $#$2
5 R$* $| $* $@ $>"Basic_check_rcpt"
$1
Change the check_rcpt ruleset so that it reads:
1 Slocal_check_rcpt
2 Scheck_rcpt
3 R$* $: $1 $| $>"Local_check_rcpt" $1
4 R$* $| $#$* $#$2
5 R$* $| $* $: $1 $| $>"Basic_check_rcpt" $1
6 R$* $| OK $: $1 $| $>"Extended_check_rcpt" $1
7 R$* $| $* $: $2
Explanation: The first thing needed is to change line 5. You'll
see that I replaced the $@ with a $:. The reason is
that $@ tells sendmail to do the rewrite and exit the rule
set. Instead, we want sendmail to do the rewrite and continue to the
next line in which we call our customized checks. Also, note that I
prepended a $1 $| before the command to execute
Basic_check_rcpt. This is needed because
Basic_check_rcpt is called with the recipient name but returns
with a status condition (OK, err, etc.). We still need to keep track of
the recipient so that we can call our customized rulesets with it.
As an example, let's say that the recipient is [email protected].
In the original unmodified ruleset, line 5 would be called with the
argument [email protected]. However, the entire workspace is
replaced with the results from Basic_check_rcpt. Assuming that
Basic_check_rcpt finds the address acceptable, the workspace
would read just "OK." We have lost the original address and cannot use
it to call our customized ruleset.
However, with our modified ruleset, line 5 would rewrite our
workspace to be:
[email protected] $| (whatever Basic_check_rcpt returns).
Assuming Basic_check_rcpt finds the address to be acceptable, our
workspace would now read:
[email protected] $| OK
We can now check for the OK as the second token and call our
customized ruleset Extended_check_rcpt with the original
address that was saved as the first token. This is what happens with
line 6.
3. Insert into your cf file the code for
Extended_check_rcpt. This code should be inserted after the
last line in Basic_check_rcpt but before the first line of
mailer definitions. The source for Extended_check_mail is in
the Appendix. You can find an online version of
Extended_check_rcpt at
<http://www.employees.org/beetle/sendmail.html>.
R$*$: [ $1 ]put brackets around it...
R$=w $@ OK ... and see if it is local
# anything else is bogus
R$* $#error $@ 5.7.1 $: "550 Relaying denied"
[Insert code for Extended_check_rcpt here]
Mailer Definitions
The code for Extended_check_mail is basically the same DNS
check code and access database code as is shipped with sendmail. I have
broken out the checks into separate rulesets. I have also added some
logic to check the user choice database, which then calls the separate
rulesets as necessary. I'll comment on the interesting portions of
Extended_check_mail:
SExtended_check_rcpt
R$*$: $1 $| $>3 $&f
R$* $| $*$: $2 $| $>3 $1
R$* <@ $* > $| $* $: <$1 @ $2> $| $3
R$* $| $+ < @ $* > $* $: $1 $| $2 < @ $3 >
$4 $| $2
Again, we are making use of the $| token to separate fields in
our workspace. The code above basically rearranges our workspace so
that it contains:
sender $| recipient $| username
The username is just the recipient with the @domain chopped off. We
will use the username to look into our database to see if this user
wants spam filtering.
R$* $| $* $| $* $: $1 $| $2 $| $(spamuser $3 $:<?NOKEY>
$)
# No such user in database. Don't do any checks
R$* $| $* $| <?NOKEY> $@ $2
After we get the username as the third field, we pass that into the
database lookup. The $:<?NOKEY> tells sendmail to return
<?NOKEY> as a default value if no entry is found. If
<?NOKEY> is returned, the very next line tells sendmail
to return the second field (the recipient) and exit the ruleset.
If the database lookup did return a value, then we will check that
value and call My_check_domain (which does the DNS check), or
My_check_db (which checks the spammer database), or
both. Depending on the results of My_check_db or
My_check_domain, we will return either the original recipient
or an error and exit.
Creating the Databases
Once you are done making the sendmail configuration file changes,
you'll need to create the databases that contain the information. The
sendmail distribution comes with a handy tool to do this, called
makemap. My sample entries are shown in bold. We'll use these
sample entries for testing later.
1. The spam_user database is where your users'
preferences are kept. It will be used to tell sendmail if the recipient
wants DNS checking, spam database checking, or both. To create this
database, first create a text file called spam_user in this
format:
Username <choice>
where <choice> can be <?DOMAIN> for
DNS checking, <?DB> for database checking, or
<?BOTH> for both checks. After the text file is created,
use makemap to create the database:
tim <?BOTH>
brad <?DOMAIN>
john <?DB>
% makemap dbm /etc/spam_user < /etc/spam_user
Note that the location of the file (in my case
/etc/spam_user) should correspond with the location you
defined with the Kspamuser configuration command in step 1.
Also, be sure that the database type corresponds.
2. Create the spammer database. This is the database
where we will keep all of our known spammers. The procedure is
essentially the same as creating the spam_user database. Make
a text file called spammers that contains:
Address Message
where Address can be a fully qualified address
([email protected]), an address alone (free.stealth.mailer@), or
just a domain (somewhere.com). The message can be either
REJECT or a customized message (550 - We don't want
spammers here).
free.stealth.mailer@ 550 - We don't want spammers here
[email protected] REJECT
spamrus.com REJECT
% makemap dbm /etc/spammers < /etc/spammers
3. Create the access database. If you recall, we will not be using
the access database for spam stomping but have configured it to take
advantage of its other functions. This is the database where we will
keep addresses that are allowed to relay and also addresses that will
be globally blocked, regardless of how the users have their spam
preferences set. Read the sendmail documentation on how to use the
access database if you need to enable relaying. For the purposes of
this article, I will create an empty access database:
% touch /etc/access
% makemap dbm /etc/mail_access < /etc/mail_access
Testing the New Configuration
It's very important to test the new configuration before using it in
production. You can test the changes with sendmail's address-test mode
(sendmail -bt).
Before testing, you will need to define the f macro value that
holds the sender's address:
.Df<sender address>
Test Case #1 Tim has enabled both DNS and database
checking.
A. Testing mail from someone in the spammer database to Tim.
Since Tim has both checks turned on, the system should reject the
email.
% /usr/lib/sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> [email protected]
> check_rcpt [email protected]
[lots of output deleted]
rewrite: ruleset 3 returns: hot999 < @ aol . com . >
rewrite: ruleset 199 input: hot999 < @ aol . com . >
rewrite: ruleset 199 returns: hot999 < @ aol . com . >
rewrite: ruleset 183 returns: $# error $@ 5 . 7 . 1 $: "550 Access denied"
B. Testing mail from a bogus domain to Tim. Since Tim has both checks turned on, the system should reject the email.
% /usr/lib/sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> [email protected]
> check_rcpt [email protected]
[lots of output deleted]
rewrite: ruleset 3 returns: spam < @ garbagedomain . com >
rewrite: ruleset 199 input: spam < @ garbagedomain . com >
rewrite: ruleset 199 returns: spam < @ garbagedomain . com >
rewrite: ruleset 180 returns: < PERM>
rewrite: ruleset 181 returns: < PERM>
rewrite: ruleset 184 returns: $# error $@ 5 . 1 . 8 $: "501 Sender domain must exist"
C. Testing mail from a valid address not in spammer
database to Tim. The sender address comes from a valid domain and is
not in the spammer database, so the system should allow the email to
pass.
% /usr/lib/sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> [email protected]
> check_rcpt [email protected]
[lots of output deleted]
rewrite: ruleset 3 returns: tim < @ cisco . com . >
rewrite: ruleset 199 input: tim< @ cisco . com . >
rewrite: ruleset 199 returns: tim < @ cisco . com . >
rewrite: ruleset 179 input: < cisco . com > < ? > < >
rewrite: ruleset 196 input: < com > < ? > < >
rewrite: ruleset 196 returns: < ? > < >
rewrite: ruleset 179 returns: < ? > < >
rewrite: ruleset 183 returns: < OK >
Test Case #2 Brad has only enabled the DNS check.
A. Testing mail from someone in the spammer database to
Brad. Since Brad has only enabled the DNS check, the mail should be
accepted.
% /usr/lib/sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> [email protected]
> check_rcpt [email protected]
[lots of output deleted]
rewrite: ruleset 3 returns: hot999 < @ aol . com . >
rewrite: ruleset 199 input: hot999 < @ aol . com . >
rewrite: ruleset 199 returns: hot999 < @ aol . com . >
rewrite: ruleset 180 returns: < OK >
B. Testing mail from a bogus domain to Brad. Since Brad has
enabled DNS checking, the system should reject the email.
% /usr/lib/sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> [email protected]
> check_rcpt [email protected]
[lots of output deleted]
rewrite: ruleset 3 returns: spam < @ garbagedomain . com >
rewrite: ruleset 199 input: spam < @ garbagedomain . com >
rewrite: ruleset 199 returns: spam < @ garbagedomain . com >
rewrite: ruleset 180 returns: < PERM >
rewrite: ruleset 181 returns: < PERM >
rewrite: ruleset 184 returns: $# error $@ 5 . 1 . 8 $: "501 Sender domain must exist"
C. Testing mail from a valid address not in spammer
database to Brad. The sender address comes from a valid domain and is
not in the spammer database, so the system should allow the
email to pass.
% /usr/lib/sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> [email protected]
> check_rcpt [email protected]
[lots of output deleted]
rewrite: ruleset 3 returns: brad < @ cisco . com . >
rewrite: ruleset 199 input: brad < @ cisco . com . >
rewrite: ruleset 199 returns: brad < @ cisco . com . >
rewrite: ruleset 180 returns: < OK >
Test Case #3 the undefined user.
I'll leave it as an exercise for the reader to try John's settings. One
more test we should try is for the person who has not set up his or her
spam settings. In this case, the system should allow all email to pass
through.
A. Mail from an address in the spammer database to
Sally. Since Sally is not defined in the spam_user database,
the email should be allowed through.
% /usr/lib/sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> [email protected]
> check_rcpt [email protected]
rewrite: ruleset 3 returns: sally < @ cisco . com . >
rewrite: ruleset 184 returns: sally < @ cisco . com . >
rewrite: ruleset 186 returns: sally < @ cisco . com . >
B. Mail from a bogus domain should be ok too:
> [email protected]
> check_rcpt [email protected]
rewrite: ruleset 3 returns: sally < @ cisco . com . >
rewrite: ruleset 184 returns: sally < @ cisco . com . >
rewrite: ruleset 186 returns: sally < @ cisco . com . >
C. Of course, mail from a valid domain that is not in the
spammer database should be allowed through:
> [email protected]
> check_rcpt [email protected]
rewrite: ruleset 3 returns: sally < @ cisco . com . >
rewrite: ruleset 184 returns: sally < @ cisco . com . >
rewrite: ruleset 186 returns: sally < @ cisco . com . >
Implementation Details
Now that you have tested the sendmail configuration changes, you will
have to create some infrastructure to support the new features of
Extended_check_rcpt. First, the user must have a way to change
his or her settings in the spam_user database. We have created
a Web page so that the user can log in, read about how we fight spam,
and change the settings. The CGI then goes and updates the
spam_user database. If you are a small site, you could
probably get away with rdisting a text file and running
makemap once in a while.
I think it is important to explain to the user the ramifications of
turning on the spam checks. In the Web page that we use, we explain
that the DNS check may be dangerous and that you should not turn
it on if it is critical that you get all email. We even have a
JavaScript popup that makes the user acknowledge that they have chosen
a spam configuration that may cause them to miss email from
misconfigured domains.
If the DNS check is too aggressive, then the user can just enable the
spam database check. This option is pretty safe to use, since we will
only enter addresses from known spammers.
We have set up a mailing list called spam-fighters for our employees to
forward spam to. A volunteer team then looks at the email and adds the
address to the spammer database if appropriate. Instead of telling
users to "just delete the spam," we now tell them to forward it to the
spam-fighters alias so that the address can be blocked. Sometimes we
can get a report in fast enough that we can block the address before
the bulk of the spam comes through. Furthermore, we have created Web
pages that show the daily spam statistics, who is in our spammer
database, and the number of times we have blocked them. A user can
submit a spam report, see that the address gets added to the database,
and see the results of that report that the spammer was blocked
from further attempts at connecting to our servers. This is a big PR
win for the sysadmin team and a psychological win for the user.
Finally, we try to be proactive about adding spammers into our spam
database. We monitor <news.admin.net-abuse.sightings> for spam
reports from other sites. We also have set up some fake addresses used
by the spam-fighters team. We have publicized these fake addresses by
posting to USENET news and using other methods. Since these addresses
are not used by anyone inside of the company, any mail going to them is
unsolicited and therefore classified as spam.
Overall we have found the checks to be somewhat effective in blocking
spam and tremendously effective in fostering the notion that we are
actively doing something to curb the spam problem.
Notes
[1] Claus Assmann's rulesets can be found at <http://www.sendmail.org//%7Eca/ email/check.html>.
[2] Robert Harker's rulesets can be found at <http://www.harker.com>.
[3] Costales, Bryan and Allman, Eric. Sendmail. O'Reilly & Associates, 1997, p. 512.
Appendix: The Extended_check_rcpt Ruleset
#############################################################################
### Extended_check_rcpt -- called after check_rcpt. Apply DNS and spam db
### checks on a per-user basis.
#############################################################################
SExtended_check_rcpt
# First expand $&f to get the sender's address.
R$* $: $1 $| $>3 $&f
# Apply various rewrites to get workspace into the format:
# sender $| recipient $| username
R$* $| $* $: $2 $| $>3 $1
R$* <@ $* > $| $* $: <$1 @ $2> $| $3
R$* $| $+ < @ $* > $* $: $1 $| $2 < @ $3 > $4 $| $2
# Now look into our username to see what kind of checks the user wants
R$* $| $* $| $* $: $1 $| $2 $| $(spamuser $3 $:<?NOKEY> $)
# No such user in database. Don't do any check
R$* $| $* $| <?NOKEY> $@ $2
# See if user wants domain checking or both and apply check.
R$* $| $* $| <?BOTH> $: $1 $| $2 $| <?BOTH> $| $>My_check_domain $1
R$* $| $* $| <?DOMAIN> $: $1 $| $2 $| <?DOMAIN> $| $>My_check_domain $1
# Check results of domain check.
R$* $| $* $| <?BOTH> $| $#error $@ 5.1.8 $: "501 Sender domain must exist"
R$* $| $* $| <?DOMAIN> $$#error $@ 5.1.8 $: "501 Sender domain must exist"
R$* $| $* $| <?BOTH> $| $#error $@ 4.1.8 $: "451 Sender domain must resolve"
R$* $| $* $| <?DOMAIN> $$#error $@ 4.1.8 $: "451 Sender domain must resolve"
# Clear workspace of DNS check results.
R$* $| $* $| $* $| $* $: $1 $| $2 $| $3
# See if user wants database checking or both and apply check.
R$* $| $* $| <?BOTH> $: $1 $| $2 $| <?BOTH> $| $>My_check_db $1
R$* $| $* $| <?DB> $: $1 $| $2 $| <?DB> $| $>My_check_db $1
# Check results. If OK then return original recipient addr and exit.
R$* $| $* $| <?BOTH> $| <OK> $@$2
R$* $| $* $| <?DB> $| <OK> $@$2
# User didn't want database check, so return original recipient and exit.
R$* $| $* $| $* $@ $2
# Otherwise, recipient found in spam database, return error from database.
R$* $| $* $| $* $| $* $@ $4
#########################################################################
### Supporting Rulesets for Extended_check_rcpt. Mostly stolen from
### stock sendmail package and broken up into separate rulesets.
#########################################################################
SMy_check_db
# check for deferred delivery mode
R$* $: < ${deliveryMode} > $1
R< d > $* $@ deferred
R< $* > $* $: $2
R<>$@ <OK>
R$* $: <?> $>Parse0 $>3 $ make domain canonical
R<?> $* < @ $+ . > $* <?> $1 < @ $2 > $3 strip trailing dots
# lookup localpart (user@)
R<$+> $* < @ $+ > $* $: <USER $(spammer $2@ $: ? $) > <$1> $2 < @ $3 > $4
# no match, try full address (user@domain rest)
R<USER ?> <$+> $* < @ $* $: <USER $(spammer $2@$3$4 $: ? $) > <$1> $2 < @ $3 > $4
# no match, try address (user@domain)
R<USER ?> <$+> $+ < @ $+ $: <USER
$(spammer $2@$3 $: ? $) > <$1> $2 < @ $3 > $4
# no match, try (sub)domain (domain)
R<USER ?> <$+> $* < @ $+ $: $>SpammerLookUpDomain <$3> <$1> <>
# check unqualified user in access database
R<?> $* $: <USER $(spammer $1@ $: ? $) > <?> $1
# retransform for further use
R<USER $+> <$+> $* $: <$1> $3
# check results
R<?> $* $@ <OK>
R<OK> $* $@ <OK>
R<DISCARD> $* $#discard $: discard
R<REJECT> $* $#error $@ 5.7.1 $: "550 Access denied"
R<$+> $* $#error $@ 5.7.1 $: $1 error from access db
SMy_Local_check_domain
SMy_check_domain
R$* $: $1 $| $>"My_Local_check_domain" $1
R$* $| $#$* $#$2
R$* $| $* $@ $>"My_Basic_check_domain" $1
SMy_Basic_check_domain
# check for deferred delivery mode
R$* $: < ${deliveryMode} > $1
R< d > $* $@ deferred
R< $* > $* $: $2
R<> $@ <OK>
R$* $: <?> $>Parse0 $>3 $1 make domain canonical
R<?> $* < @ $+ . > $* <?> $1 < @ $2 > $3 strip trailing dots
# handle non-DNS hostnames (*.bitnet, *.decnet, *.uucp, etc)
R<?> $* < $* $=P > $* $: <OK> $1 < @ $2 $3 > $4
R<?> $* < @ $+ > $* $: <? $(resolve $2 $: $2 <PERM> $) > $1 < @ $2 > $3
R<? $* <$->> $* < @ $+ >$: <$2> $3 < @ $4 > $5
# handle case of @localhost on address
R<$+> $* < @localhost > $: < ? $&{client_name} > <$1> $2 < @localhost >
R<$+> $* < @localhost.$m $: < ? $&{client_name} > <$1> $2 < @localhost.$m >
R<$+> $* < @localhost.UU $: < ? $&{client_name} > <$1> $2 < @localhost.UUCP >
R<? $=w> <$+> $* <?> <$2> $3
R<? $+> <$+> $* $#error $@ 5.5.4 $: "553 Real domain name required"
R<?> <$+> $* $: <$1> $2
# retransform for further use
R<USER $+> <$+> $* $: <$1> $3
# handle case of no @domain on address
R<?> $* $: < ? $&{client_name} > $1
R<?> $* $@ <OK> ...local unqualed ok
>R<? $+> $* $#error $@ 5.5.4 $: "553 Domain ...remote is not
# check results
R<?> $* $@ <OK>
R<OK> $* $@ <OK>
R<TEMP> $* $@ <TEMP>
R<PERM> $* $@ <PERM>
R<RELAY> $* $@ <RELAY>
R<DISCARD> $* $#discard $: discard
SSpammerLookUpDomain
R<$+> <$+> <$*> $: < $(spammer $1 $: ? $) > <$1> <$2> <$3>
R<?> <$+.$+> <$+> <$*> $@ $>LookUpDomain <$2> <$3> <$4>
R<?> <$+> <$+> <$*> $@ <$2> <$3>
R<$*> <$+> <$+> <$*> $@ <$1> <$4>
###
### END of Extended_check_rcpt package
###