Chapter 7. How to Handle Spam

In general use, SPAM® is a brand name of luncheon meat and is a registered trademark of Hormel Foods Corporation. After SPAM was lampooned by Monty Python in a famous sketch,[114] it was jokingly adopted by the Internet community to describe unsolicited mass postings across many USENET groups. Soon, a new word was coined, the lowercase “spam,” a word that now describes unsolicited, commercial email.

As you read this chapter, remember that spam is a moving target. On the one hand, sendmail offers constantly improving tools to filter and reject it. On the other hand, the spammer’s tools are also constantly being updated to bypass existing protections. Mix in the fact that laws are being written which might modify or limit spam, and you can think of this as an arms race. That is, you won’t set up sendmail just once and be done, but will find yourself continually modifying database files and rule sets in an effort to stay even with the spammer’s cleverness.

Over the years, spam email has evolved into a greater and greater threat. No longer is spam a mere nuisance, because it now seriously threatens all who receive email. Spam can contain viruses, spyware, and realistic-looking phishing attempts to steal identities and money. As spam has increased in volume, so too has the need to effectively fight it.

In fact, beginning with V8.14, sendmail now recognizes that open HTTP proxies can be used to send spam. So now, if the first command a sendmail server receives from a client is GET, POST, CONNECT, or USER, V8.14 sendmail immediately terminates the connection. This is the exact aggressive antispam behavior you should always expect from sendmail.

The Local_check_ Rule Sets

The rapid spread of the Internet has led to an increase of mail abuses. Prior to V8.8 sendmail, detecting and rejecting abusive email required that you write C-language code for use in the checkcompat/( ) routine. Beginning with V8.8 sendmail, important and useful checking and rejecting can be done from within pairs of complementary rule sets. They are presented here in the order that sendmail calls them:[115]

Local_check_relay and check_relay

Validate the host initiating the SMTP connection.

Local_check_mail and check_mail

Validate the envelope-sender address given to the SMTP MAIL From: command.

Local_check_rcpt and check_rcpt

Validate the envelope-recipient address given to the SMTP RCPT To: command.

check_eom

Validate size of the message before calling any Milter.

check_compat

Compare or contrast each pair of envelope-sender and envelope-recipient addresses before delivery, and validate them based on the result.

These routines are all handled in the same manner. If the rule set returns anything other than a #error or a #discard delivery agent, the message is accepted. Otherwise, the #error delivery agent causes the message to be rejected or deferred (error on page 720) whereas the #discard delivery agent causes the message to be accepted, then discarded (discard on page 719).

Local_check_relay and check_relay

V8.8 sendmail supports two mechanisms for screening incoming SMTP connections. One is the libwrap.a mechanism, and the other is the check_relay rule set. V8.9 sendmail added a third mechanism, the access database (The access Database on page 277).

The Local_check_relay rule set provides a hook into the check_relay rule set, which is used to screen incoming network connections and accept or reject them based on the hostname, domain, or IP address. It is called just before the libwrap.a code and can be used even if that code was omitted from your release of sendmail. Note that the check_relay rule set is not called if sendmail was run with the -bs command-line switch (-bs on page 236).

The check_relay rule set is called with a workspace that looks like this:

host $| IPaddress

The hostname and IP address are separated by the $| operator. The host is the fully qualified canonical name of the connecting host. The IPaddress is the IP address of that host in dotted-quad form without surrounding square brackets, or the IPv6 address prefixed with a literal IPv6:. Note that if you also declare the FEATURE(use_client_ptr) (FEATURE(use_client_ptr)—V8.13 and Later on page 297), the value from the ${client_ptr} macro (${client_ptr} on page 813) will be used in place of the IPaddress.

By default, the check_relay rule set allows all connections. This behavior can be overridden or enforced in the access database by prefixing leftmost keys with a literal Connect: (Finer Control with V8.10 on page 282):

Connect:bad.host       REJECT

Here, for example, any connection from the host bad.host is rejected.

The default behavior of the check_relay rule set can also be overridden by the various DNS blacklist features (see How DNSBL Works on page 260).

In the event you need to add checks to this check_relay rule set, you can do so by adding a Local_check_relay rule set. Declaring this latter rule set gives you a hook into the start of check_relay, which means your rules are applied before the default rules.

One way to use Local_check_relay might be to list offensive sites in a database and reject any connections from those sites.[116] Consider a database that contains hostnames or addresses as its keys and descriptions of each host’s offense as its values:

hostA.edu      Spamming site
hostB.com      Mail Bombing site
123.45.6       Offensive domain
IPv6:2002:c0a8:51d2::23f4      Offending host

Notice that the keys can be hostnames, or IPv4 or IPv6 addresses. Such a database might be declared in the configuration file like this:

LOCAL_CONFIG
Kbadhosts dbm -a<> /etc/mail/badhosts

Now, each time a site connects to your running daemon, the following rule set will be called:

SLocal_check_relay
R $* $| $*            $: $(badhosts $1 $) $| $2             look up hostname
R $*<> $| $*          $#error $@ 5.1.3 $: 550 Sorry, $1 denied
R $* $|  $*           $: $2                                 select the IP address
R $-.$-.$-.$-         $: $(badhosts $1.$2.$3.$4 $)          look up host address
R IPv6 : $+           $: $(badhosts IPv6:$1 $)              look up host or network
address
R $-.$-.$-.$-        $: $(badhosts $1.$2.$3 $)              look up network address
R $*<>                $#error $@ 5.1.3 $: 550 Sorry, $1 denied
R $*                  $@ ok                                 otherwise OK

The first rule looks up the host part in the database. If it is found, the value (reason for rejection) is returned and the two characters < > are appended. The second rule looks for anything to the left of the $| that ends in < > and, if anything is found, issues the error:[117]

550 5.1.3 Sorry, reason for rejectdenied

Rejected connections are handled in the same way as connections rejected by the access database (The access Database on page 277).

The rest of the rules do the same thing, but also check for the IP address.

If the Local_check_relay rule set returns a #error or #discard delivery agent, the connection is rejected. If it returns a $#OK,[118] the connection is accepted and subsequent check_relay rule set rules are skipped:

SLocal_check_relay
R $*              $# OK      skip check_relay rule set rules

But if it returns a $@OK, further check_relay rule set rules are allowed which might themselves reject the connection:

SLocal_check_relay
R $*              $@ OK      allow check_relay rule set rules

Note that the rules presented here are not nearly as complex or sophisticated as your site will likely need. They do not, for example, reject on the basis of the domain part of the hostname, nor do they reject on the basis of the individual host IP addresses.

Beginning with V8.14 sendmail, any macro that is given a value as part of this check_relay rule set will have that value maintained by sendmail for the duration of the current SMTP session. To illustrate, consider a policy that allows multiple recipients for local delivery, but only one recipient per envelope when mail is relayed. A rule inside this check_relay rule set could, for example, define a flag:

Kstorage macro
R$*     $: $(storage {WeAreRelaying} $@ TRUE $)

This rule stores the constant value TRUE in the ${WeAreRelaying} macro. Later, when the check_compat rule set (The check_compat Rule Set on page 259) is called, the flag will cause sendmail to limit the number of allowed recipients.

Note that the rules in the Local_check_relay and check_relay rule sets cannot be tested in rule-testing mode because that mode wrongly interprets the expression $| (when you enter it at the > prompt) as two separate text characters instead of as a single operator. To test an address that contains an embedded $| operator, we suggest that you create a translation rule set something like this:

LOCAL_RULESETS
STranslate
R $* $$| $*              $: $1 $| $2                            fake for -bt mode

This rule set changes a literal $ and | into a $| operator so that you can test rule sets such as Local_check_relay from rule-testing mode:

ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> Translate,Local_check_relay bogus.host.domain $| 123.45.67.89

Here, the comma-separated list of rule sets begins with Translate, which changes the two-character text expression $| into the single operator $|. The result, an address expression that is suitable for the Local_check_relay rule set, can then be successfully tested.[119]

Local_check_mail and check_mail

The Local_check_mail rule set provides a hook into the check_mail rule set, which is used to validate the envelope-sender address given in the MAIL From: command of the SMTP dialog:

MAIL From:<sender@host.domain>

The check_mail rule set is called immediately after the MAIL From: command is read. The workspace passed to check_mail is the address following the colon in the MAIL From: command. That envelope-sender address might or might not be surrounded by angle braces.

If sendmail’s delivery mode is anything other than deferred (-bd on page 234), the check_mail rule set performs the following default actions:

  • Calls the tls_client rule set (The access database with tls_server and tls_client on page 214) to perform TLS verification, if needed

  • Accepts all envelope-sender addresses of the form < >

  • Makes certain that the host and domain part of the envelope-sender address exists

  • If the access database (The access Database on page 277) is used, looks up the envelope-sender in that database and rejects, accepts, or defers the message based on the returned lookup value

The Local_check_mail rule set provides a hook into check_mail before the preceding checks are made, and provides a place for you to insert your own rules.

To illustrate one use for the Local_check_mail rule set, consider the need to accept all mail from an internal domain, even when many of the hosts in that domain cannot be looked up with DNS.[120] One method might look like this:

LOCAL_RULESETS
SLocal_check_mail
R $*                   $: $>canonify $1       focus on the host
R $* <@ $+. > $*       $1 <@ $2> $3           strip trailing dots
R $* <@ $+ > $*        $: $2                  isolate the host
R $* . $+ . $+         $2 . $3                strip subdomains
R internal.org         $# OK

Here, we force the rule set named canonify to preprocess the address so that any RFC2822 comments will be thrown away and the host part of the address will be focused.[121] We then strip any trailing dots from the hostname to prevent a trailing dot from wrongly affecting our validation. In the third line, we throw away everything but the hostname. In the fourth line, we throw away all but the rightmost two components of the hostname to eliminate the host part and any subdomain prefixes. What remains is the domain name. We then compare that domain name to the hostname internal.org. If they match, we accept the sender. If they don’t match, the default rules in the check_mail rule set continue to process the address.

Note that if this Local_check_mail rule set returns $#OK,[122] all subsequent check_mail rule set checks of the envelope-sender will be suppressed:

SLocal_check_mail
R $*              $# OK      skip check_mail rule set checks

But if it returns $@OK, further envelope-sender check_mail rule set checks are processed (such as looking up the user and host parts in the access database, or trying to resolve the host part):

SLocal_check_mail
R $*              $@ OK      allow check_mail rule set checks

After this rule set is installed (and the sendmail daemon had been restarted), all mail from internal.org will be accepted during the SMTP dialog even if the hostname does not exist.

Other uses for the Local_check_mail rule set might include limiting certain senders to only a few outbound messages per day, by using an external database to record attempts; rejecting the user part of sender addresses for special reasons, such as being all numeric; and rejecting mail from a specific list of users at a given site.

If you need to base a decision to reject mail on both the sender and the recipient, you might be able to use the check_compat rule set described next, or design your own rules for this rule set using $&f ($f on page 824).

Local_check_rcpt and check_rcpt

The Local_check_rcpt rule set provides a hook into the check_rcpt rule set, which is used to validate the recipient-sender address given in the RCPT To: command in the SMTP dialog:

RCPT To:<recipient@host.domain>

The check_rcpt rule set is called immediately after the RCPT To: command is read. The workspace that is passed to check_rcpt is the address following the colon. The envelope-recipient address might or might not be surrounded by angle brackets and might or might not have other RFC2822 comments associated with it.

The check_rcpt rule set has default rules that do the following:

  • Reject empty envelope-recipient addresses, such as < >, and those which have nothing following the RCPT To:.

  • Ensure that the envelope-recipient address is either local, or one that is allowed to be relayed.

  • If the access database (The access Database on page 277) is used, look up the envelope-recipient’s host in that database and reject, accept, or defer the message based on the returned lookup value. If the FEATURE(blacklist_recipients) (Reject per Recipient on page 284) is declared, they also look up the envelope recipient in that database.

The Local_check_rcpt gives you a hook into the check_rcpt rule set before any of the default rules are called. To illustrate one use for the Local_check_rcpt rule set, consider the need to reject all incoming mail destined for the recipient named fax. One method might look like this:

LOCAL_RULESETS
SLocal_check_rcpt
R $*                   $: $>canonify $1        focus on host
R fax <@ $=w . > $*    $#error $@ 5.1.3 $: "cannot send mail to fax"

Here, the first rule calls the rule set named canonify to focus on the host part of the address and normalize it. The second rule rejects anything to fax in any of our local domains (the $=w). A recipient address of fax at any other domain will pass through these rules and be accepted:

RCPT To: <fax@ourhost>
553 5.1.3 <fax@ourhost>... cannot send mail to fax

Other possible uses for this Local_check_rcpt rule set include:

  • Creating a special bounce-handling machine that accepts all bounced mail, then logs and discards it

  • Creating a special performance-testing blackhole machine that accepts all outside mail and silently discards it

Note that if this Local_check_rcpt rule set returns $#OK,[123] all subsequent checks with the check_rcpt rule set will be suppressed:

SLocal_check_rcpt
R $*              $# OK      skip check_rcpt rule set checks

But if it returns $@OK, further checks with the check_rcpt rule set are processed (such as looking up the user and host parts in the access database, and such as validating that the host part is local):

SLocal_check_rcpt
R $*              $@ OK      allow check_rcpt rule set checks

If you need to base a decision to reject mail on both the sender and the recipient, you can either use the check_compat rule set described next, or design your own rules for this rule set using $&f ($f on page 824).

Note that check_rcpt rule set rules apply only to mail that arrives via SMTP. If your site submits mail using SMTP, you might find locally originating mail being wrongly rejected. If yours is such a site, you can add the following rules to Local_check_rcpt, which should fix the problem:

SLocal_check_rcpt
R $*                 $: $&{client_addr}
R 127.0.0.1          $# OK

The check_eom Rule Set

The check_eom rule set (V8.14 and later) is called after the terminating dot is received from the sending client, but before the xxfi_eom entry (Milter xxfi_eom() on page 1215) into any Milters is called. The check_eom rule set is called only if it exists in the configuration file; otherwise, it is skipped. When it is called, its workspace is passed an ASCII representation of an unsigned integer which represents the size of the message (header lines and body) in bytes (characters). This size is the same as the value stored in the ${msg_size} macro (${msg_size} on page 835).

The check_eom rule set can be used to validate the size of the message, but it does not have to be used in that way. Instead, you might, for example, have a policy that requires only one recipient per message. One way to use the check_eom rule set to enable this policy might look like the following:

Karith math
Scheck_eom
R$*            $: $(math = $@ $&{nrcpts} $@ 1 $)
R TRUE            $#OK
R FALSE             $#error $@ 5.1.3 $: "Policy limits one recipient per envelope."

Here, we first declare a database map of type arith called math (arith on page 898). In the second line, we declare the check_eom rule set, which, in this case, contains three rules (the R lines).

The first rule compares the number of recipients in the current value of the ${nrcpts} macro (${nrcpts} on page 837) to the constant value 1 to see whether the two are equal. The second rule matches if the two are equal (if the number of recipients is one) and returns OK so that the message will be accepted for final review by Milters (if any). The last rule rejects the message with a statement that the policy “limits one recipient per message.” But note that Milters can add recipients, so a better place to enforce this policy is in the check_compat rule set (The check_compat Rule Set on page 259).

The check_compat Rule Set

Not all situations can be resolved by simply checking the RCPT To: or MAIL From: address. Sometimes you will need to make judgments based on pairs of addresses, or non-SMTP addresses or other information. To handle this situation, V8.8 introduced the check_compat rule set. Unlike check_mail and check_rcpt, check_compat is called for all deliveries, not just SMTP transactions. It is called after an address has undergone aliases translation, just after the check for too large a size (as defined by M=; see M= on page 746) and just before the checkcompat( ) routine (Appendix C on page 1248).

Note that although with V8.12 and later you can still write your own check_compat rule set, doing so has been made unnecessary by the FEATURE(compat_check) (FEATURE(compat_check)—V8.12 and Later on page 288). But also note that, as of V8.12, you cannot both declare the FEATURE(compat_check) and use this check_compat rule set.

The check_compat rule set is called with a workspace that looks like this:

sender $| recipient

The sender and recipient addresses are separated by the $| operator. Each has undergone aliasing and ~/.forward file processing.

One use for the check_compat rule set is to prevent a certain user (here, operator/) from sending mail offsite:

LOCAL_RULESETS
SGet_domain
R $*                     $: $>canonify $1        focus on host
R $* <@ $+. > $*         $1 <@ $2> $3            strip trailing dots
R $* <@ $+ > $*          $: $2                   isolate the host
R $* . $+ . $+           $2 . $3                 strip host and subdomains

SGet_user
R $*                     $: $>3 $1               focus on host
R $* <@ $+ > $*          $@ $1                   discard host

Scheck_compat
R $* $| $*               $: $1 $|  $>Get_domain $2       fetch recipient domain
R $* $|  $=w             $@ ok                           local is OK
R $* $|  $=m             $@ ok                           local is OK
R $* $|  $*              $: $>Get_user $1                fetch sender user
R operator               $#error $@ 5.1.3 $: "operator might not mail off site"

First, we set up two subroutines patterned after the code in the previous two sections. The first reduces its workspace to just the domain part of an address. The second reduces an address to just the user part. These two subroutines are called by check_compat.

The first rule in check_compat uses the Get_domain subroutine to convert the address on the right of the $| (the recipient) into just a domain name. That right side is compared to the local hostnames ($=w and $=m). If the domain is a local one, delivery is allowed (we return anything but a #error or a $#discard).

But if the domain is an offsite one, we call Get_user to fetch the user part of the address to the left of the $| (the sender). If that user is operator, delivery is denied and the message bounces.

Other uses for the check_compat rule set might include the following:

  • Logging a record of when a DSN NOTIFY request of success is requested (${dsn_notify} on page 821)

  • Creating a class of users who, possibly for security reasons, might send mail inside the organization but not outside it

  • Screening a particular recipient to prevent that user from receiving objectionable mail from a specific source

Note that such rule sets cannot be tested in rule-testing mode because that mode wrongly interprets the expression $| (when you enter it at the > prompt) as two separate text characters instead of a single one. See Local_check_relay and check_relay on page 252 for one suggested solution to this problem.

How DNSBL Works

The acronym DNSBL stands for “Domain Name Services BlackList,” where the term BlackList refers to the desire to prohibit all spam.

When sendmail accepts a connection from another site, one of the first things it does is to get the IP address of that site. Once armed with that address, it can do a lookup of that address at a DNSBL site. To illustrate, we will use the mail-abuse.org site.[124] To see whether the connecting site is an open relay site, sendmail first reverses the IP address. For example, the address 123.45.67.89 becomes 89.67.45.123. Then sendmail prefixes the hostname relays.mail-abuse.org with that reversed IP address and looks up the result as though it is a hostname:

89.67.45.123.relays.mail-abuse.org

If that hostname is found, that means the site is listed with mail-abuse.org as an open relay site. If that hostname is not found, the site is a good one.

Prior to V8.12, the FEATURE(rbl) allowed you to use this DNSBL process. Beginning with V8.10, a new FEATURE(dnsbl) was added. As of V8.12, the FEATURE(rbl) was removed. The FEATURE(enhdnsbl) which is an extended version of FEATURE(dnsbl) became available. These features are summarized in Table 7-1 and explained in the following sections.

Table 7-1. DNSBL features

Feature

Description

rbl

Deprecated; see dnsbl

dnsbl

Reject mail from hosts in a DNS-based rejection list

enhdnsbl

An enhanced version of dnsbl

FEATURE(dnsbl)

The FEATURE(dnsbl) is used to enable the blocking of email from open relay sites, dial-up sites, or known spamming sites. It does so by invoking the rbl technique discussed in the previous section. The feature is included in your mc configuration file like this:

FEATURE(dnsbl)                           ← simple form
FEATURE(dnsbl, `optional arguments')declared with arguments

In its simplest form, when mail arrives from a site, that site’s IP address is reversed and prefixed to the default host blackholes.mail-abuse.org.[125] If the lookup succeeds, the host is considered bad and the following error is sent in reply to the initial connection:

550 5.7.1 Rejected: IP listed at blackholes.mail-abuse.org

If the address is not found, the connection is allowed and the mail is accepted depending on subsequent SMTP and header checks. By default, temporary failures are ignored and the connection is treated as good. If you wish temporary failures to cause the sending site to defer the message, you can supply a third argument such as this:

FEATURE(dnsbl, , ,`t')

If the third argument is a literal t character, instead of ignoring temporary errors, the following will be returned:

451 Temporary lookup failure of IP at blackholes.mail-abuse.org

An argument can be supplied to this feature if you wish to use a lookup host other than, or in addition to, blackholes.mail-abuse.org. The canonical name of the lookup host is simply inserted following a comma after the literal dnsbl:

FEATURE(dnsbl,`dialups.mail-abuse.org')
FEATURE(dnsbl,`dialups.mail-abuse.org', ,`t')

Here, the same check and error returns are done as described earlier, but with the host you specify, dialups.mail-abuse.org, replacing the default host, blackholes.mail-abuse.org. The first of the two alternatives ignores temporary errors, and the second honors temporary errors.

Multiple dnsbl features can be included in a single mc file. Each will cause the same host’s IP address to be looked up at a different server. For example, the following will cause the IP address to be looked up first with blackholes.mail-abuse.org, and then with dialups.mail-abuse.org:

FEATURE(dnsbl)
FEATURE(dnsbl,`dialups.mail-abuse.org')

In addition to the name of a lookup host, you can also specify your own error message as a second argument. For example, the following looks up the IP address on the host dialups.mail-abuse.org and issues a custom error message in the second argument to the feature (note that this is one line that is wrapped):

FEATURE(dnsbl,`dialups.mail-abuse.org', `"550 Mail from dial-up site " $&
{client_addr}
" refused"',`t')

Here, the value of the {client_addr} macro will contain the IP address of the offending host at the time the error is reported.

Note that beginning with V8.14, the second argument may be a literal discard or quarantine:

FEATURE(dnsbl,`dialups.mail-abuse.org', `discard')          ← V8.14 and later
FEATURE(dnsbl,`dialups.mail-abuse.org', `quarantine')       ← V8.14 and later

Here, discard causes the rejected message to be silently accepted and discarded, whereas the quarantine causes the rejected message to be accepted and quarantined.

Note that, beginning with V8.13, this FEATURE(dnsbl) no longer uses the host database-map type to look up addresses. Instead, it now uses the dns database-map type (dns on page 905). The DNSBL_MAP_OPT mc macro has been added to help tune the use of the dns database-map type with the FEATURE(dnsbl).

The default declaration for the dns databasemap for this feature looks like this:

Kdnsbl dns -R A -T<TMP>

If you wish to change the type of the lookup, you may redefine the dns -R A part of the expression:

define(`DNSBL_MAP', `dns -R TXT')
FEATURE(dnsbl, ...)

Here, the DNSBL_MAP redefines the lookup so that it performs TXT record lookups instead of A record lookups. Note that DNSBL_MAP must be defined before this feature is declared for the feature to have any effect.

You may also list additional arguments for the dns databasemap used with this feature. Those additional arguments will follow the -T<TMP> part in the declaration and are specified like this:

define(`DNSBL_MAP_OPT', `-d1s')
FEATURE(dnsbl, ...)

Here, the -d1s tells sendmail to reduce the res_search( ) _res.retry interval to one second from the default of five seconds. Note that DNSBL_MAP_OPT must be defined before this feature is declared for the feature to have any effect.

FEATURE(enhdnsbl)

The FEATURE(enhdnsbl) (for enhanced dnsbl) is a superset of the FEATURE(dnsbl) described earlier. It is used like this:

FEATURE(enhdnsbl, optional args)

The enhancement consists of additional arguments—that is, one or more literal addresses you expect returned when an address should be rejected. For example, the following rejects bad dial-up hosts and defers temporary lookup errors:

FEATURE(enhdnsbl,`dialups.mail-abuse.org',`"550 dial-up site refused"',`t',
`127.0.0.3.')
                    ↑
                additional

The first three arguments are the same as those you saw for the FEATURE(dnsbl) (FEATURE(dnsbl) on page 261): the lookup host, an error message, and a t character. But unlike the FEATURE(dnsbl), an error specified in the second argument prevents temporary lookup errors from being deferred. The third argument to FEATURE(enhdnsbl) (the t) allows temporary lookup errors to be recognized, which causes delivery to be deferred:

451 Temporary lookup failure of address at dialups.mail-abuse.org

Here, the address is the IP address of the sending host. The dialups.mail-abuse.org matches the lookup host specified in the second argument to the FEATURE(enhdnsbl). If the t were omitted, as for example:

FEATURE(enhdnsbl,`dialups.mail-abuse.org', `"550 dial-up site refused"',
,`127.0.0.3.')

temporary lookups will be ignored and the message will be accepted.

The fourth argument is the expected result of the lookup. For the lookup host dialups.mail-abuse.org, a successful lookup (one that means the message should be rejected) will return the address 127.0.0.3. Different lookup hosts will return different addresses on success, so you will need to visit the appropriate web site to determine the address to match. If the address is omitted from the FEATURE(enhdnsbl), any successfully returned address will cause the message to be rejected.

If more than one address can be returned, you can list up to five more following the first one. In the following, we list three possible returned addresses (the line is wrapped to fit the page):

FEATURE(enhdnsbl,`dialups.mail-abuse.org', `"550 dial-up site refused"',
,`127.0.0.1.',
`127.0.0.2.', `127.0.0.3.')

Here, if any of the three addresses is returned, the message will be rejected. Note that if you don’t know specifically what will be returned, you can use rule LHS-operators (The R Configuration Command) in place of specific numbers. For example, instead of the three addresses shown earlier, you can specify one like this:

FEATURE(enhdnsbl,`dialups.mail-abuse.org', `"550 dial-up site refused"', ,`127.0.0.$-
.')

Here, the $- will match any number in that position. If you need to restrict the range of acceptable values you can use a class, perhaps like this:

LOCAL_CONFIG
C{OneTwoThree}1 2 3
FEATURE(enhdnsbl,`dialups.mail-abuse.org', `"550 dial-up site refused"',
,`127.0.0.$={OneTwoThree}.')

Here, the $={OneTwoThree} class restricts a match to any 127.0.0. address that ends in a 1, 2, or 3. Other operators you might find useful are $+ (match one or more), and $@ (match zero tokens).

Note that beginning with V8.14, the second argument may be a literal discard or quarantine:

FEATURE(enhdnsbl,`dialups.mail-abuse.org', `discard')          ← V8.14 and later
FEATURE(enhdnsbl,`dialups.mail-abuse.org', `quarantine')        ← V8.14 and later

Here, discard causes the rejected message to be silently accepted and discarded, whereas the quarantine causes the rejected message to be accepted and quarantined.

Check Headers with Rule Sets

Beginning with V8.10, sendmail provides the ability to screen selected headers with rule sets. This is described in detail in Rules Check Header Contents on page 1130. In this section, we show two more techniques for using header checks to reject spam messages:

  • Reject messages that have subjects which indicate that the message contains a virus.

  • Reject messages that have an illegally formed Received: header.

Virus Screening by Subject

Many messages that contain viruses, worms, or Trojan horses have distinctive subject lines, the text of which is usually reported in the news. When a new virus is discovered, it is often quicker to reject messages based on its reported subject line than it is to await the latest update of your favorite virus filter software. But this is only a temporary fix. Because legitimate email will often share the same subjects, it is best to only screen on the Subject: header between the time the virus is detected and announced, and the time your virus screening software is updated.

One way to screen by subject is to create a database of subject lines to reject, and then use that database in a subject-checking rule set. Consider the following text file which contains one subject per line. The subject is to the left, the word REJECT is to the right, and the two are separated by one or more tab characters:

I Love You       REJECT
Visit Home Now!  REJECT

If you were to call this file /etc/mail/spamsubjects, you could turn it into a database map with commands like this:

# cd /etc/mail
# makemap -t\tab hash spamsubjects < spamsubjects

The -t command-line switch tells makemap that the key and value pairs are separated by a tab instead of spaces or tabs. The backslash protects the tab from interpretation by your shell. We use that command-line switch because our keys can contain internal spaces.[126]

Once this database is in place, it will be easy to update its contents whenever a new virus is announced. Because it is a database, you will be able to update it without having to restart sendmail. In fact, because the righthand side says REJECT, you simply have to change that word to OK to allow a header. This allows you to maintain a history of spam subjects for later review or reuse.

The rules for the use of this database can be added to your mc configuration file like this:

LOCAL_CONFIG
Kspamsubjdb hash /etc/mail/spamsubjects
HSubject: $>ScreenSubject

LOCAL_RULESETS
SScreenSubject
R $*            $: $(spamsubjdb $&{currHeader} $: OK $) $1
R REJECT $*     $#error $: "553 Subject:" $1 ": Indicates virus, rejected"

Here, the LOCAL_CONFIG part defines a database map called spamsubjdb of type hash that will use the database file you created earlier. The second line under LOCAL_CONFIG defines the Subject: header, and says that the value of that header should be passed (the $ > operator) through the ScreenSubject rule set.

In the LOCAL_RULESETS part of your mc file, the S configuration line defines the ScreenSubject rule set, which has just two rules.

The first rule looks up the entire workspace (the $* operator) in the LHS (lefthand side, The R Configuration Command, in the database map called spamsubjdb. If the literal text of the Subject: header’s value is found in the database, the token from the right side of the database, the REJECT in our example, is returned. If it is not found in the database, the default (as indicated by the $: operator) is returned (the OK is returned). Whichever token is returned, the original subject value is also returned (the trailing $1 operator).

The second rule looks for the literal text REJECT in the workspace, followed by zero or more tokens (the $* operator). If the workspace begins with REJECT, the message is rejected; otherwise, it is accepted.

The RHS (right-hand side, The R Configuration Command) of the second rule performs the rejection. The $#error instructs sendmail to reject the message. The $: part defines the text of the error message that will be issued. For a subject value of I Love You, the following error will be produced during the SMTP exchange.

553 5.3.5 Subject: I Love You : Indicates virus, rejected

Note that when sendmail sees an SMTP code of 553 that is not followed by a DSN code, it will insert the appropriate DSN code, here the 5.3.5.

Finally, we say again that you should reject email based on the subject only as a temporary measure. The likelihood that legitimate email will have an identical subject is very high. When erring, it is better to allow the occasional spam than it is to reject any legitimate email.

Check Validity of Received:

The Received: header traces the succession of hosts that an email message passes through. One technique used by spam messages is to create false Received: headers both to mask the real identity of the original sending host, and to divert blame to some innocent site. One form of bad Received: header that appears in spam messages looks like this:

Received: from ...............................................................
........................................................................
........................................................................
........................................................................
........................................................................!

This form of Received: header was popular with spam software for a few months, then fell out of favor. The following rule shows one way of dealing with such headers:

LOCAL_RULESETS
H*: $>ScreenForDots

SScreenForDots
R $+ .......... $*     $#error $: "553 Ten or more dots begin " $&{hdr_name} "header"

Here, the LOCAL_RULESETS part of your mc file begins with an unusual-looking H configuration command. The H* is special (H* a Default for All Headers on page 1134) because it matches all headers. When sendmail screens headers, it first calls each rule set specified for a specific named header (as with Subject: in the previous section). If no rule set exists for a particular header name, sendmail next looks for the special definition H* and, if found, passes the header to that rule set. You can think of H* as specifying a default rule set.

The rule set named ScreenForDots has only a single rule. That rule matches any value part of any header that does not have its own rule set. The LHS checks for a value that begins with 10 dots followed by zero or more arbitrary tokens.

Any header that has such a bad value will be rejected and the message bounced. The bounce will have the following text as its error, where the offending header was the Received: header shown earlier:

553 5.3.5 Ten or more dots begin Received header

Remember that the techniques used by spam email senders change over time—the bad guys learn and adapt too. We solved the dots in the Received: header with a general rule set because it was transient (a spam technique used for a brief period and then abandoned). The problem will doubtless appear again, perhaps in a different header, or when some poor sap downloads an old version of spamming software. But by defining with a general-purpose rule set (the H* one), we anticipate the return of a technique in the future, possibly with a differently named header.

Relaying

Promiscuous relaying is the process of accepting email from outside your site and then transmitting it to another host also outside your site. Hosts that relay are quickly discovered by spam programs and are used to mask the identity of the originating spam site. Prior to V8.9, promiscuous relaying was allowed by default. Beginning with V8.9, promiscuous relaying is turned off by default.

In place of default relaying, V8.9 and later sendmail provide a variety of features, macros, and databases that allow you to relay in a variety of manners. The access database (The access Database on page 277) provides a way to relay on a host-by-host, or network basis. Adding domains to the class $=R ($=R on page 874) is another method. In this section, we describe features that allow you to tune relaying to your taste. Table 7-2 lists the features available as of V8.12.

Table 7-2. Relay features

Feature

§

Description

access_db

The access Database on page 277

Screen addresses and set policy.

loose_relay_check

FEATURE(loose_relay_check) on page 270

Allow %-hack relaying.

promiscuous_relay

FEATURE(promiscuous_relay) on page 271

Allow all relaying.

relay_based_on_MX

FEATURE(relay_based_on_MX) on page 271

Relay for any site for which you are an MX server.

relay_entire_domain

FEATURE(relay_entire_domain) on page 272

Relay based on $=m.

relay_hosts_only

FEATURE(relay_hosts_only) on page 273

Interpret domains in relay domains, and access database, as hosts.

relay_local_from

FEATURE(relay_local_from) on page 273

Relay if SMTP MAIL From: domain is in $=w.

relay_mail_from

FEATURE(relay_mail_from) on page 274

Relay if SMTP MAIL From: address is RELAY in access database, and provided the entry is properly tagged.

In addition to the features we discuss here, you should also see Chapter 4 on page 154 for a discussion of how relaying can be controlled with AUTH= and STARTTLS.

Before you turn on relaying of any sort, be sure you understand the potential risks of your decision. A mistake that loosens relaying restrictions too much can open your site to abuse as a spam relay from anywhere in the world.

The following features are presented in alphabetical order, not in order of recommendation or safety. In fact, the first is more fraught with risk than the others. Take care to read about all these relaying features so that you fully understand them before choosing any.

Macros to Allow Relaying

Hosts and domains to which mail can be relayed are listed either in a special sendmail class, or in the access database. You add hosts and domains to the special class with either the RELAY_DOMAIN mc macro, or the RELAY_DOMAIN_FILE mc macro.

The RELAY_DOMAIN mc macro

A special class (currently $=R)[127] holds as its list of values host and domain names to which sendmail should allow mail to be relayed. Relaying is discussed in general in the sections that follow.

You add domain names to this class like this:

RELAY_DOMAIN(`list of hosts and domains')

Here, the list is one or more hosts or domains separated from each other by spaces:

RELAY_DOMAIN(`our.internal.domain our.company.domain')

If you find it more convenient to list them on separate lines, you can do so like this:

RELAY_DOMAIN(`our.internal.domain')
RELAY_DOMAIN(`our.company.domain')

The list can be host or domain names, or IP addresses, or network numbers. IPv6 addresses can be specified by prefixing each with the literal text IPv6:, as for example:

host.another.domain          ← a hostname
your.domain                  ← a domain name
123.45                       ← a network (leftmost numbers)
123.45.67.89                 ← a host IP address
IPv6:2002:c0a8:02c7          ← an IPv6 network
IPv6:2002:c0a8:51d2::23f4    ← an IPv6 host address

The RELAY_DOMAIN_FILE mc macro

You can also maintain a list of hosts, domains, and addresses that can be relayed to in an external file. That file is declared with the following macro:

define(confCR_FILE, `path')     ← deprecated
RELAY_DOMAIN_FILE(`path')

The recommended value for path is /etc/mail/relay-domains:

RELAY_DOMAIN_FILE(`/etc/mail/relay-domains')

This declaration causes a list of relay hosts, domains, or addresses to be read from the file /etc/mail/relay-domains. Because RELAY_DOMAIN_FILE is implemented with an F configuration command (The F Class Command on page 857), you can add whatever F command arguments you desire. For example:

RELAY_DOMAIN_FILE(`-o /etc/mail/relay-domains')

Here, the -o switch makes the presence of the /etc/mail/relay-domains file optional.[128]

If you are currently reading relaying information from a file declared directly with the F configuration command, you are encouraged to convert to this new macro. Use of it will insulate you from change in the future if a different class name is ever used.

FEATURE(loose_relay_check)

The percent-hack is a form of address that uses the % character to route mail through one host to another. For example, the following address uses the percent-hack to relay mail through hostA for delivery to hostB:

user%hostB@hostA

The intention here is to cause sendmail to connect to the hostA host and send the message by specifying user@hostB as the envelope recipient, meaning that hostA will relay the message to hostB.

V8 sendmail no longer allows the percent-hack form of relaying without first performing two checks. First, the connected-to host, the one following the @ (hostA), is looked up in the class macro defined by the RELAY_DOMAIN mc macro, and in the access database. If the connected-to host is neither in that class nor OK’d by the access database, the message is rejected with:[129]

550 5.7.1 Relaying denied.

If the connected-to host is OK, sendmail looks up the destination host hostB, also in the class macro defined by the RELAY_DOMAIN mc macro (The RELAY_DOMAIN mc macro on page 269) and in the access database. If the destination host is neither in that class nor OK’d by the access database, the message is rejected; otherwise, it is accepted for delivery.

In brief, for the percent-hack to work, both hosts must be listed in the class macro defined by the RELAY_DOMAIN mc macro, or OK’d by the access database, or both.

One way to list them might look like this in your mc configuration file:

RELAY_DOMAIN(`hostA.domain hostB.domain')         ← V8.9 and later

If it is not possible for you to know ahead of time which hosts should be listed in that class, you might want to loosen this check. But be forewarned: if you think you need to loosen this check, you probably do not need to.

To loosen these checks, you can use the FEATURE(loose_relay_check) which is declared in your mc configuration file like this:

FEATURE(loose_relay_check)           ← CAUTION!

Use this feature with caution! It risks allowing spammers to relay through your server because it skips the check for the destination host—that is, the host following the % (hostA in our example). Consider, for example, a spam site sending a spam message with the following envelope recipient:

user%site.to.spam@your.mail.server

If your mail server is listed with RELAY_DOMAIN (as it would be if it relays mail inward to your internal network), and if your mail server uses this FEATURE(loose_relay_check), there is nothing to prevent a spam message from being relayed to any arbitrary site on the Internet, or for your site to be abused as a spam relay.

Note that this feature can be of benefit in an internal network protected by a well-configured mail gateway and a firewall because it allows testing of internal mail hubs as potential MX servers for internal-only email.

FEATURE(promiscuous_relay)

Sometimes it is beneficial to set up a mail server that will relay mail from any host that connects to it. Consider a main mail-sending machine that exists behind a firewall. In this example, the mail-sending machine is separate from the mail-receiving machine. The mail-sending machine has inbound port 25 blocked at the firewall so that it cannot receive mail from anywhere but the internal network. In such an arrangement, it is simpler to allow any internal host to relay mail than to specify individual hosts or domains in the access database, or with the class $=R, or with authentication.

If this simpler approach is applicable to your site, and if your network is totally secure around port 25, you can enable unfettered or “promiscuous” relaying with this FEATURE(promiscuous_relay). You declare it like this:

FEATURE(`promiscuous_relay')

To underscore the risk associated with this feature, the following warning will be printed each time you build with your mc configuration file:

*** WARNING: FEATURE(`promiscuous_relay') configures your system as open
        relay.  Do NOT use it on a server that is connected to the Internet!

By declaring this feature, you tell sendmail to allow mail received by the local machine from anywhere in the world to be relayed outward to any machine in the world. This opens up the local machine to be used by spam engines worldwide, and almost guarantees that the local machine will eventually become listed by one or more DNSBL sites.

You should use this feature only if the affected machine is secured by other means. If you don’t have an effective firewall, or don’t have knowledgeable network administrators, you should avoid using this FEATURE(promiscuous_relay).

FEATURE(relay_based_on_MX)

When sendmail receives a message bound for another host, it might be doing so because the local machine is listed as an MX record for that other host (How sendmail Uses DNS on page 325) and the other host is temporarily down. When that other host comes back up, sendmail will deliver all such queued messages to it. You allow relaying for hosts for which your site serves as an MX record, by listing the names of those sites in your relay-domains file ($=R on page 874).

There might be times, however, when your site must be an MX server for an unknown number of sites, or an unknown variety of domains. One such example might occur when your machine is behind a firewall on a private network. You might be the central MX site for all internal domains that are created or renamed often. For such sites, sendmail offers the FEATURE(relay_based_on_MX) that looks like this:

FEATURE(`relay_based_on_MX')

When you declare this feature, you allow sendmail to relay mail to any host for which your site is listed as an MX record. Fortunately, you don’t have to keep track of which hosts do list your site because this feature makes the process automatic.

This feature should be used only in an environment where you administer or trust the DNS records. You should not use it if your DNS lookups come from the Internet at large because, in that instance, anyone in the world would be able to use your machine as an MX server without your knowledge or permission.

Note that you should not use this FEATURE(relay_based_on_MX) if you also use the -z switch with the bestmx database map (bestmx on page 902).

Also note that relaying for MX purposes is different from relaying for the % hack (see FEATURE(loose_relay_check) on page 270).

FEATURE(relay_entire_domain)

By default, only hosts listed in the access database (The access Database on page 277) with the righthand side keyword RELAY, or hosts that are listed with the RELAY_DOMAIN macro (The RELAY_DOMAIN mc macro on page 269), are allowed to relay mail through the local host. You can allow all the hosts in your domain to relay mail through the mail server by listing them there, but it is much easier to use the shorthand method provided by the FEATURE(relay_entire_domain):

FEATURE(`relay_entire_domain')

When you define this feature, you enable any host listed in the class $=m (which contains your domain; $=m on page 872) to relay mail through the local host. Note, however, that if your host is named something such as bob.gov, your host and domain will be the same. Whatever you do, never put a top-level domain such as gov, or com, or de into $=m, or you will find your site relaying mail for any host in that top-level domain.

Note that $=m should not be used to have mail accepted as local under a variety of domains. Instead, use the FEATURE(domaintable) (FEATURE(domaintable) on page 621).

FEATURE(relay_hosts_only)

Normally, relaying is based on the domains listed with the RELAY_DOMAIN mc macro (The RELAY_DOMAIN mc macro on page 269) or in the file specified by the RELAY_DOMAIN_FILE mc macro (The RELAY_DOMAIN_FILE mc macro on page 269) or on the domains allowed to relay in the access database. When sendmail checks to see whether a domain should be allowed to relay, it interprets each domain as a top-level domain. For example, if RELAY_DOMAIN listed the following entry, or if the RELAY_DOMAIN_FILE file contained the following entry:

your.domain

all the following domains would also match that single domain entry:

sub.your.domain
a.very.deep.sub.your.domain

As an alternative, you can have sendmail interpret each name as the literal name of a host. If you prefer this second method, you can enable it by declaring the relay_hosts_only feature like this:

FEATURE(relay_hosts_only)

With this feature declared, sendmail will compare the sending host to the list of hosts, and to hosts looked up in the access database, on a host-by-host basis. For example, if the RELAY_DOMAIN defined the following:

sub.domain

only a host named sub.domain would be allowed to relay. Another host—say, hostB.sub.domain—would not be allowed to relay unless it too was listed, or OK’d by the access database.

Clearly this feature gives you more control over who can and cannot relay. It can be of value at a site that is populated by some network printers and some Unix machines. The file specified by RELAY_DOMAIN_FILE could be set up to allow the Unix machines to relay, but not the printers.

FEATURE(relay_local_from)

During an SMTP conversation, the sending host specifies the address of the envelope sender by issuing a MAIL From: SMTP command. RFC2822 commentary and DSN extensions are then discarded from that specified address, and the result is stored in the $f macro ($f on page 824).

If you wish, you can use the value in the $f macro to determine whether a message should be relayed to any outside or inside host. Although such a method is fraught with risk, it is still made available with the FEATURE(relay_local_from) which is declared like this:

FEATURE(`relay_local_from')

Because this feature poses risk, the following warning will be printed each time you build with your mc configuration file:

*** WARNING: FEATURE(`relay_local_from') may cause your system to act as open
        relay.  Use SMTP AUTH or STARTTLS instead. If you cannot use those,
        try FEATURE(`relay_mail_from').

When you declare this feature you cause the domain part of the address in $f (the portion of the address to the right of the @ character) to be compared to the list of hosts in the $=w class macro ($=w on page 876). Recall that the class $=w contains all the names by which the local host can be known. If the domain in $f is found in that class, relaying is allowed.

The risk should be obvious. Because $f is given its value as a part of the SMTP MAIL From: command, that address can be forged to appear local by anyone on the Internet. That is, by declaring this feature, you are opening up your host to abuse by the entire world.

So, why does sendmail offer this FEATURE(relay_local_from)? If you administer a site that is behind a firewall and an Internet mail hub, and if your internal machines cannot be contacted on any port from the outside world, you might find this a simple way to allow global relaying within that network.

We suggest, however, that SMTP AUTH (Support SMTP AUTH on page 183) or STARTTLS (STARTTLS on page 202) will provide a safer way to authenticate local origination addresses upon which to base the permission to relay. A safer way to relay based on connection domains is the Connect: keyword in the access database. If you prefer a simpler solution, the FEATURE(relay_mail_from), described next, might be just what you are looking for, although it, too, is risky.

FEATURE(relay_mail_from)

During an SMTP conversation, the sending host specifies the address of the envelope sender by issuing a MAIL From: SMTP command. RFC2822 commentary and DSN extensions are then discarded from that address, and the result is stored in the $f sendmail macro ($f on page 824).

If you wish, you can use the value in the $f sendmail macro to determine whether a message should be relayed to any outside or inside host. Although such a method is fraught with risk, it is still made available with the FEATURE(relay_mail_from) which is declared like this:

FEATURE(`relay_mail_from')

Because this feature poses risk, the following warning will be printed each time you build your cf file from your mc file:

*** WARNING: FEATURE(`relay_mail_from') may cause your system to act as open
        relay.  Use SMTP AUTH or STARTTLS instead.

By declaring this feature, you cause the address in the $f sendmail macro to be prefixed with a literal From: and looked up in the access database (The access Database on page 277). If it is found in that database, and the value returned is a literal RELAY, that address is allowed to be relayed:

From:bob@your.domain    RELAY       ← this sender can relay

If you want to base the decision to relay on a domain instead of on an individual’s address, you can declare this feature with an additional argument that is a literal domain:

FEATURE(`relay_mail_from', `domain')

With this extra argument in place, the domain part of the address in $f (the portion of the address to the right of the @ character) will be prefixed with a literal From: and looked up in the access database. If it is found, and if the value returned is a literal RELAY, that domain will be allowed to be relayed:

From:your.domain    RELAY       ← this domain can relay

This feature is fraught with risk. By defining it, you allow anyone on the Internet to spoof allowed addresses as part of any SMTP MAIL From: command. If you want to allow local hosts to relay mail from the local network to the world, you can either authenticate with SMTP AUTH (Support SMTP AUTH on page 183) or STARTTLS (STARTTLS on page 202), or you can relay based on connections using the Connect: keyword in the access database.

Risk with FEATURE(nouucp)

UUCP addresses are those that use a ! character to separate address components. For example, the following address says to send the message first to the host hostA, and then hostA will relay that message to the user at hostB:

hostB!user@hostA

If you have tuned your site to prevent unintended relaying, misuse of the nouucp feature can open your site to an unexpected form of relaying.

Consider a workstation on your network that forwards all its mail to the central mail hub using LOCAL_RELAY (LOCAL_RELAY mc Macro on page 604) or LUSER_RELAY ($L on page 832). If that workstation also defines:

FEATURE(`nouucp', `nospecial')

addresses containing the ! character will not be recognized as special and will be forwarded to the mail hub as is.

If, on the mail hub, you forget to declare the FEATURE(nouucp), the as-is address forwarded to it will be recognized as special. Because the address was received from an internal workstation, relaying is allowed. The ! address will have the hostA part stripped and the result will be relayed to user@hostB.

Thus, it is a good idea to define nouucp on the mail hub if you define it on any of your workstations.

FEATURE(accept_unresolvable_domains)

Beginning with V8.9, sendmail will refuse a mail message if the address specified as part of the SMTP MAIL From: has a domain part that cannot be looked up. For example, if the domain foo.bar does not exist, the following error will be logged with syslog and the message will be rejected with the same error message:

553 5.1.8 <user@your.domain>... Domain of sender address other@foo.bar does
not exist

If the domain cannot be looked up, the result is a temporary error:

451 4.1.8 <user@your.domain>... Domain of sender address other@foo.bar does
not resolve

We recommend rejecting such addresses, but there might be circumstances in which you cannot. If, for example, you are behind a firewall and lack access to full DNS lookups, you might want to accept everything. But if that is the case, you will need a sending mail hub with good DNS access so that you can reply to such messages.

You can accept such addresses by defining the FEATURE(accept_unresolvable_domains):

FEATURE(`accept_unresolvable_domains')

This tells sendmail to accept all envelope-sender addresses, even if the domain part following the @ cannot be looked up with DNS.

FEATURE(accept_unqualified_senders)

The sendmail program refuses to accept a message if the address specified as part of an SMTP MAIL From: command lacks a domain. That is, if the address has a user part but lacks the @ followed by a domain, the message will be rejected:

MAIL From:<bob@foo.com>          ← good, has a domain part
MAIL From:<bob>                  ← bad, lacks a domain part

Some mail submission programs will submit mail without including a domain part. Improperly configured PCs are one example, as are poorly configured Unix hosts. Generally, such problems will appear on your local network. If you lack the authority to fix such a problem, you can tweak sendmail to accept such addresses by including the FEATURE(accept_unqualified_senders) like this:

FEATURE(`accept_unqualified_senders')

Note that this feature accepts unqualified addresses regardless of the port on which they are received. Such a broad solution might be acceptable on an internal network, but it is discouraged on machines that service the Internet. For those hosts, we recommend you tune acceptance or rejection of unqualified addresses on a port-by-port basis.

The DaemonPortOptions option u modifier (DaemonPortOptions=Modify= on page 996), when set, has the same effect as declaring this feature for the given single port. That is, unqualified addresses are accepted on a port-by-port basis, without the need to declare this feature.

The DaemonPortOptions option f modifier (DaemonPortOptions=Modify= on page 996), when set, tells sendmail to reject unqualified addresses received on this port, even if this feature is declared. That is, you accept unqualified addresses on all ports by declaring this feature, and then reject them on a port-by-port basis with this f keyword.

The access Database

The access database was introduced in V8.9 sendmail, and improved upon in V8.10. It provides a single, central database with rules to accept, reject, and discard messages based on the sender name, address, or IP address. It is enabled with the FEATURE(access_db).[130]

For example, consider an access database with the following contents:

From:postmaster@spam.com   OK
From:spam.com              REJECT

Here, mail from postmaster at the site spam.com is accepted, whereas mail from any other sender at that site is rejected.

Note that this example uses V8.10 syntax. Next, we will describe the access database using the old V8.9 syntax, and then describe the V8.10 and V8.12 updates.

Enabling the access Database Generally

To enable use of this access database, declare it in your mc configuration file like this:

FEATURE(`access_db')

This enables use of the access database, and enables the default database type and path as:

hash /etc/mail/access             ← V8.11 and earlier
hash -T<TMPF> /etc/mail/access    ← V8.12 and later

Note that with V8.12 and later a -T<TMPF> has been added to specify that temporary errors should return a 4xy SMTP code.

If you wish to use a different database type or pathname, you can do so by providing an appropriate argument to the FEATURE(access_db):

FEATURE(`access_db', `hash -o /etc/mail/access')           ← V8.11 and earlier
FEATURE(`access_db', `hash -o -T<TMPF> /etc/mail/access')  ← V8.12 and later

Here, we add the -o switch (-o on page 889) to the definition to make the existence of the /etc/mail/access database file optional.

Beginning with V8.12, this feature takes two more arguments:

FEATURE(`access_db', `db specification'`skip', `lookupdotdomain')

The skip (the third argument), if present, enables SKIP as a possible return value for the access database (SKIP on page 280).

The lookupdotdomain (the fourth argument), if present, enables the same behavior as though you independently declared the FEATURE(lookupdotdomain) (FEATURE(lookupdotdomain) on page 628).

Beginning with V8.14, the new keyword relaytofulladdress can appear as either the second, third, or forth argument. Here, for example, it is supplied as the second argument:

FEATURE(`access_db', `db specification'`relaytofulladdress')V8.14 and
later

If given as an argument to this feature, relaytofulladdress allows entries like the following to appear in the access database:

To:user@host.domain     RELAY   ← V8.14 and later

This allows relaying based on the recipient’s full address rather than just to the host, as was the case under previous versions.

Create the access Database

To create the access database, you first create a text file that contains lines of hosts, addresses, and IP addresses paired with keywords and values. After that, you run makemap to create the actual database from the text file. If the text file is named /etc/mail/access, you would build the database like this:

# cd /etc/mail
# makemap hash access < access

The text file itself looks like this:

key          value
        ↑
  whitespace: one or more tabs or spaces

The text file is composed of two columns of information. The lefthand column is the key which is composed of a prefix and an address expression. The prefix depends on the rule set doing the lookup. For some it is Connect: or From:, and for others it is TLS_Srv: or TLS_Clt:. These are described in the sections of this book dealing with the appropriate rule set.

The address expression can be any of the following depending on what the rule set is trying to do:

host.your.domain             ← a hostname
your.domain                  ← a domain name
user@                        ← a username
user@host.another.domain     ← a user address
123.45.67.89                 ← an IPv4 host address
123.45                       ← an IPv4 network (leftmost numbers)
IPv6:2002:c0a8:51d2::23f4    ← an IPv6 host address
IPv6:2002:c0a8:02c7          ← an IPv6 network (leftmost numbers)

Note that for usernames the @ is mandatory. More address expressions can be used than we show here. These are the most common. Others are described under the rule sets that use them.

The righthand column contains the value, which can be keywords or values that determine what should be done with the item described on the left. They are shown in Table 7-3, and are described in the sections indicated.

Table 7-3. access database righthand-side values

Righthand value

§

Description

OK

OK on page 279

Accept the lefthand-side entry.

RELAY

RELAY on page 279

Allow the lefthand side to relay mail through this machine.

REJECT

REJECT on page 280

Reject the lefthand side (bounce the message).

DISCARD

DISCARD on page 280

Reject the lefthand side (bounce the message).

SKIP

SKIP on page 280

Stop looking for the key, and don’t look for any future parts of the key.

XYZ text

XYZ text on page 280

Reject with custom SMTP code and message.

ERROR:XYZ text

ERROR:XYZ text on page 281

Reject with optional custom SMTP code and message.

ERROR:D.S.N:XYZ text

ERROR:D.S.N:XYZ text on page 281

Reject with a more precise DSN code.

SUBJECT

The access database and Local_Relay_Auth on page 213

Also look up the CERT subject.

VERIFY

The access database with tls_server and tls_client on page 214

Verify the certificate.

VERIFY:bits

The access database with tls_server and tls_client on page 214

Verify the certificate and require minimum number of encryption bits.

ENCR:bits

The access database with tls_server and tls_client on page 214

The minimum number of encryption bits.

OK

The OK righthand value for the access database tells sendmail to accept the user, host, domain, or address on the lefthand side. That is, even if other rules in the rule set that did the lookup reject it (because the domain cannot be looked up with DNS, for example), this return value will still cause it to be accepted by that rule set. Note, however, that other rule sets can subsequently reject it.

RELAY

The RELAY access database righthand value tells sendmail to allow the user (if the FEATURE(relay_mail_from) is defined), host, domain, or address listed on the lefthand side to relay mail through this machine. It also allows mail to be relayed by anyone when routing is to the host, domain, or IP addresses listed. Note that RELAY also includes the behavior of OK.

REJECT

The REJECT access database righthand value tells sendmail to reject the user, host, domain, or address listed on the lefthand side. The rejection will use the default message defined by the confREJECT_MSG mc macro (Rejection Message for REJECT on page 283).

DISCARD

The DISCARD access database righthand value tells sendmail to accept any sender that is listed on the lefthand side (either as a user, host, or IP address), but to silently discard the message using the discard delivery agent (discard on page 719). The DISCARD keyword can also be used for recipients if the FEATURE(blacklist_recipients) (Reject per Recipient on page 284) is declared. When a recipient is discarded and when there are other recipients in the envelope, all recipients are discarded. The one exception is if DISCARD is returned to the check_compat rule set, established by the FEATURE(compat_check) (FEATURE(compat_check)—V8.12 and Later on page 288), only the one recipient is discarded.

SKIP

This keyword provides a way to list hosts, domains, or addresses that can give a default behavior. Such defaults are defined by your selection of features to enable. Sometimes, for example, you might desire to have the lookup of a host, domain, or address return (if found), but have no further checks performed on it. One way to do that is by using this special SKIP keyword as the return value for the lookup:

From:bob.domain     SKIP

If the lookup was done to see whether relaying is OK for the domain bob.domain, this SKIP instructs sendmail to act as though the lookup did not find bob.domain. Thus, if the default is to deny relaying, relaying for bob.domain will be denied. If the default is to allow relaying, relaying for bob.domain will be allowed.

The main use for SKIP is with the FEATURE(lookupdotdomain) (FEATURE(lookupdotdomain) on page 628). With that feature defined, you could set up the access database like this:

From:server.bob.domain     SKIP
From:.bob.domain           RELAY

Here, mail from the machine server.bob.domain will be handled by the default rules. All other hosts in the bob.domain domain will be allowed to relay.

XYZ text

The SMTP protocol, as documented in RFC2821, defines a set of three-digit codes that have special meaning to the sending site. When sendmail rejects the envelope sender, it does so by printing a 550 code in reply to the MAIL From: command. This special form of keyword in your access database allows you to cause sendmail to print a code, followed by words of your choice. Consider this entry in the access database:

From:sales@spam.com    ERROR:554 Spam delivery is unavailable.

Here we chose 554, which stands for “service unavailable.” When mail is received at your site, this rule will cause the following interaction in the SMTP conversation:

MAIL From:<sales@spam.com>
554 5.0.0 <sales@spam.com>... Spam delivery is unavailable.

The text you give following the SMTP might or might not appear in the bounced mail that sales receives.

The XYZ code you specify does not have to be a 500 code (meaning failure). You are also free to use 400 codes (to defer the mail) too. Deferral might be appropriate as a means to handle a temporary resource limitation:

newsupdates@your.domain      421 Our database is down for two days for repair

ERROR:XYZ text

This righthand value is the same as the "XYX text" expression discussed earlier, but the ERROR: signals that the envelope sender should be rejected. The XYZ is optional, and sendmail will supply a value if it is missing. This provides a handy way to reject mail without having to remember the correct SMTP numbers:

sales@cybermarketing.com     ERROR: Stop spamming us

When this address arrives from the outside, the SMTP will look like this:

MAIL From:<sales@cybermarketing.com>
553 5.3.0 <sales@cybermarketing.com>... Stop spamming us

ERROR:D.S.N:XYZ text

This righthand value is the same as ERROR:XYZ text, but it allows you finer control of the SMTP rejection message.[131] The D.S.N can be any of the DSN codes defined in RFC1893. You should use this form if you change the SMTP code from the default used by sendmail:

newsupdates@your.domain      450 Cache mailbox disk is full

Here, for example, you reject mail to newsupdates at your site because the database is down, so you cannot drain the cache file. By changing the SMTP error from its default, you will cause sendmail to wrongly report the DNS error:

RCPT To:<newsupdates@your.domain>
450 4.0.0 Cache mailbox disk is full

The 4.0.0 is not the correct DSN code for a full mailbox. Instead, you should specify 4.2.2, like this:

newsupdates@your.domain ERROR:4.2.2:450 Cache mailbox disk is full

This form uses three colon-delimited fields on the righthand side of the access database. The first field is the literal ERROR string. That is followed by your specification for the correct DSN code. The third field is the SMTP error text as we described earlier.

Finer Control with V8.10

Prior to V8.10, the lefthand side of the access database could contain only a user, host, domain, or address, and would only look them up based on the client name or address, the MAIL From: address, or the RCPT To: address.

Beginning with V8.10, sendmail offers much finer control of addresses and rejections in the access database. The lefthand side of the access database can begin with one of three possible prefixes:[132]

Connect:

The address is either the IP address or the hostname of a connecting host.

From:

The address is that of an envelope sender.

To:

The address is that of an envelope recipient.

When an address is looked up in the access database, it is first looked up with the prefix. If it is not found, it is looked up again without a prefix, meaning that the old access databases will still work with newer versions of sendmail. To illustrate, consider this update to the access database shown in the previous section:

From:spamuser@hotmail.com    REJECT
From:cybermarkets.com        REJECT
Connect:example.org          REJECT
Connect:192.168.212          REJECT

This access database will cause mail from to be rejected. Mail from any user at cybermarkets.com will be rejected, connections from the host example.org will be rejected, and any mail from any host with an IP address ranging from 192.168.212.0 through 192.168.212.255 will have the initial connection rejected. In that last example, any missing righthand part of an IP address is assumed to be a wildcard for matching purposes.

This behavior is exactly the same as the example without prefixes, with one exception. The example.org line, without a prefix, will reject mail from all users at example.org as well as connections from example.org.

A more complex example will better illustrate the properties of the three prefixes:

To:bob@host.domain            RELAY  ← V8.14 and later with relaytofulladdress (§
7.5 on page 277) defined
To:friend.domain        RELAY
From:friend.domain      RELAY
Connect:friend.domain   OK
Connect:bad.domain      REJECT

Here, we reject or allow based only on the envelope-recipient or the connecting host.

The first two lines say that mail arriving at your site, regardless of its origin, will be allowed to be relayed to bob at the site friend.domain (the first line) or to any user at the site friend.domain (the second line). This provides a way to allow relaying on a host-by-host basis, even if you have all relaying turned off with the various antirelay features (Relaying on page 267). This is useful if you are a secondary MX site for friend.domain.

The second line says that we will also relay mail from the site friend.domain. A line such as this requires that you have declared the FEATURE(relay_mail_from) (FEATURE(relay_mail_from) on page 274) with a literal domain second argument. Here, if the envelope sender is any user at friend.domain, the envelope recipient can be a local or remote address.

The third line says that we will specifically accept connections from the host friend.domain. We do this because that site might be rejected if it is listed on some DNSBL site. An OK via a Connect prefix overrides any rejection based on DNSBL lists.

The fourth line rejects connections from the host bad.domain no matter what. This is one way to reject connections on a site-by-site basis, if, for example, you want to block messages from a site that is pushy but not eligible for listing with a DNSBL server.

Even if you lack an immediate use for these prefixes you should consider using them, just to experience their power.

Rejection Message for REJECT

When an address is rejected because of the presence of REJECT in the access database, it is rejected with the default message:

550 5.7.1 Access denied

Beginning with V8.9 sendmail, you can change that message (to augment it or to clarify the reason for the rejection) using the confREJECT_MSG mc file macro. For example, to show why the message was rejected, you could place the following in your mc file:

define(`confREJECT_MSG', `550 Access denied. See http://www.your.domain/
access_denied')

Because the message you specify will be quoted in the configuration file, you cannot place any m4 macros or positional m4 macros in the message. They will be silently stripped from the message.

But note that, beginning with V8.13, quotation marks are no longer automatically inserted. Instead, the value in confREJECT_MSG is inserted into your cf file as is (with no added quotation marks). Note that if you previously depended on this auto-quoting in your mc file, you will now have to add quotation marks of your own.

Reject per Recipient

The FEATURE(access_db) (The access Database on page 277) provides a way to selectively reject envelope-sender addresses. By declaring this FEATURE(blacklist_recipients), you enable the access database to also selectively reject envelope-recipient addresses:

FEATURE(`blacklist_recipients')

Consider the need to prevent outsiders from posting to strictly inside mailing lists. In this example, the mailing lists are handled on a machine different from that on which outside mail is received. On the receiving machine, you would put lines such as these in your access database:

board@our.domain               550 Outside access to private mailing list banned
accounting@our.domain          550 Outside access to private mailing list banned
401k-help@our.domain           550 Outside access to private mailing list banned

By declaring this FEATURE(blacklist_recipients), these addresses will be prevented from receiving outside mail.

All forms of addresses in the access database can be used for this recipient rejection. Consider:

To:badguy@           ERROR:550 Mailbox disabled for this user
To:host.our.domain   ERROR:550 This machine bans email
To:123.45.67.89      ERROR:550 Printers cannot receive email

Be careful when rejecting recipients based on the username alone, as in the first line in this example, because the username is rejected for both the envelope sender and the envelope recipient. Thus, this line will reject mail to both badguy locally, and from badguy at all other sites in the world.

Accept and Reject per Recipient

When a connection is made to your site by another, the access database is checked to reject unwanted connections.[133] It is checked again when the SMTP MAIL From: command is given to accept or reject the envelope sender. It is checked a third time when the SMTP RCPT To: command is given to accept or reject the envelope recipient, and prevent unwanted relaying.

This order is good for most sites, but might not be the best for your particular needs. In case it isn’t, the FEATURE(delay_checks) offers a way to check the SMTP RCPT To: address first, before the other two checks, and then proceed with those other two checks, if appropriate. Delayed checks are enabled with the FEATURE(delay_checks) which you declare in your mc file like this:

FEATURE(access_db)
FEATURE(delay_checks)

Note that the FEATURE(access_db) needs to be enabled before you enable the FEATURE(delay_checks).

Once enabled, the order of checks is changed. If the righthand side in the access database is either REJECT or an SMTP error for the envelope recipient, the envelope recipient is rejected as usual. But if the envelope recipient is allowed, the envelope sender is then checked, and if it is rejected, the envelope recipient is rejected with the envelope sender’s error message. If the envelope sender is allowed, the connecting host is checked, and if it is rejected, the envelope recipient is rejected with the connecting host’s error message.

For example, consider the following abstract from an access database:

To:postmaster@           OK
From:larry@              REJECT
Connect:spammer.domain   REJECT

With the FEATURE(delay_checks) enabled, the first check will come as part of the SMTP RCPT To: command, and that address will be looked up in the access database. In this example, if the user part of the recipient address is postmaster, the message will be accepted[134] by the current calling rule set. Subsequent rule sets can still reject it.

If the user part of the envelope-sender address is not postmaster, the address given to the earlier SMTP MAIL From: command will be looked up. If that envelope-sender address has a user part that is larry (in our example) the message will be rejected, but because it is too late to reject the SMTP MAIL From:, the rejection will be given to the SMTP RCPT To: command.

If the envelope sender is OK, the name of the connecting host will be looked up. If the host is found in the access database, and if the righthand side is REJECT, the message is rejected and the error will be reported in reply to the SMTP RCPT To: command. If the host is found in the access database, and if the righthand side is RELAY, the message is allowed to be relayed. If the host is not found, and if no other relaying is allowed, the message will not be allowed to be relayed and the denial-of-relay error, if any, will be reported in reply to the SMTP RCPT To: command.

One reason to check the SMTP RCPT To: address first might be to allow mail from a spam site to be delivered to a specific local user, but still block mail from that site for all other users. Another reason might be to block mail from a spam site for a specific user, but allow it to be delivered to all others. You can tune the access database to do one (but only one) of these two things by defining the FEATURE(delay_checks) with an extra argument that must be one, and only one, of two possible lowercase words:

FEATURE(access_db)
FEATURE(`delay_checks', `friend')         ← this one or
FEATURE(`delay_checks', `hater')          ← this one, but not both

When the extra argument is friend, you can allow mail from a spam site to a specific local user, while still blocking mail from that site for all other users. When the extra argument is hater, you can block mail from a spam site for a specific user, while allowing it to be delivered to all other users. If the extra argument is neither (or was uppercase), the following error will be printed when you build your configuration file, and that file will be incomplete:

*** ERROR: illegal argument bad word here for FEATURE(delay_checks)

The check_rcpt (Local_check_rcpt and check_rcpt on page 257) rule set performs the lookup, and the relationship found (friend or hater) determines whether the check_mail (Local_check_mail and check_mail on page 255) and check_relay (Local_check_relay and check_relay on page 252) rule sets should be called to perform further checks.

If the extra argument is friend and if the database lookup returns FRIEND, those further rule set checks will be skipped and the message will be accepted. But if the database lookup fails to find the address, or returns something other than FRIEND, further screening by the check_mail and check_relay rule sets is performed.

If the extra argument is hater and the database lookup returns HATER, further screening by the check_mail and check_relay rule sets is performed. But if the database lookup fails to find the address, or returns something other than HATER, those additional rule sets will be skipped.

As a first step, decide which of the two forms you prefer (remember, you can do only one or the other), and then add the new definition to your mc file, generate a new configuration file, and install it. Once the new configuration is ready, you can use one of two new righthand-side keywords in your access database:

SPAMFRIEND (V8.10 through V8.11) or FRIEND (V8.12)

This address is allowed to receive messages that would otherwise be rejected by the check_mail and check_relay rule sets. You can use this keyword only if you defined friend when you declared the FEATURE(delay_checks).

SPAMHATER (V8.10 through V8.11) or HATER (V8.12)

This address will only receive messages that are also accepted by the check_mail and check_relay rule sets. An address not listed as SPAMHATER (V8.10 and V8.11) or HATER (V8.12) will have processing by those additional rule sets skipped. You can use this keyword only if you defined hater when you declared the FEATURE(delay_checks).

To illustrate, consider the following abstract from such an access database where we declared the FEATURE(delay_checks) as friend:

Connect:spam-mail.com     REJECT
To:abuse@your.domain      SPAMFRIEND        ← V8.10 through V8.11
Spam:abuse@your.domain    FRIEND            ← V8.12

The site spam-mail.com is one of possibly many sites that will be rejected by our site because they are known spammers. Mail addressed to will be accepted even if it is from any of the rejected spam sites.

In the following example, we declared the FEATURE(delay_checks) as hater:

Connect:spam-mail.com     REJECT
To:payroll@your.domain    SPAMHATER              ← V8.10 through V8.11
Spam:payroll@your.domain  HATER                  ← V8.12
To:abuse@your.domain      OK

Here, the site spam-mail.com is one of possibly many sites that will be rejected by our site because they are known spammers. Mail to payroll@your.domain will have spam sites rejected. Mail to abuse@your.domain will still be allowed to receive mail from otherwise rejected sites.

Whether you choose the spam haters or spam friends approach is entirely dependent on your site’s unique needs. Think through the logic of your choice before setting up your access database. If you make a mistake, you might inadvertently allow spam to a user who doesn’t want it.

Note that you cannot mix friends and haters. If you do, sendmail will ignore the mismatch. For example, if you declare the FEATURE(delay_checks) as friend and place the following entry into your access database, that entry will be ignored:

Spam:payroll@your.domain  HATER    ← ignored because delay_checks specified friend

Remember, you must choose one, and only one: either friend or hater. You cannot choose both, nor can you mix the two.

Note that the syntax of this FEATURE(delay_checks) has changed. It differs between V8.10 through V8.11 and V8.12. If you have already used this feature with V8.10 or V8.11 sendmail, you will need to change it for V8.12. You will need to add the Spam: prefix and new righthand-side values to your access database.

As an aid to conversion, the old syntax will be ignored. Once you have finished converting from the earlier syntax to the new, you can redeclare this V8.12 FEATURE(delay_checks) by adding a literal n as a third argument:

FEATURE(`delay_checks', `friend', `n')         ← V8.12 and later
FEATURE(`delay_checks', `hater', `n')          ← V8.12 and later

This n turns off backward compatibility (the ability to ignore the old syntax) and causes the old syntax to produce an error. This is a good way to check to be sure your conversion was good.

Also note that, with V8.12, if an envelope recipient is found to be trusted, using one of the mechanisms listed with the AuthMechanisms option (AuthMechanisms on page 975), that envelope-recipient is accepted and further access database checks are skipped.

FEATURE(compat_check)—V8.12 and Later

Beginning with V8.12 sendmail, you can create a rule set that makes decisions about envelope-sender and envelope-recipient pairs with entries in the access database. To enable these checks, just add the FEATURE(compat_check) to your mc configuration file:

FEATURE(access_db)           ← must be first
FEATURE(compat_check)

Once this is enabled, you can then add entries such as the following to your access database (note that the <@ > is literal):

Compat:sender<@>recipient     keyword

Here, the Compat: prefix is literal and must be present. It is immediately followed (with no intervening spaces) by the envelope-sender address, a literal <@>, and the envelope-recipient address (where the envelope-recipient address has already undergone aliasing and processing by a user’s ~/.forward file). Neither address should be surrounded with angle braces. The address pair is followed by whitespace (spaces and tabs) and then a keyword. There are three possible keywords:

DISCARD

Mail from this sender to this recipient is accepted, and then discarded and logged. DISCARD can be followed by a colon. It can also be followed by optional text that will be logged as the reason for the discard.

TEMP:

Mail from this sender to this recipient is rejected with a temporary error (causing the message to be deferred for a later delivery attempt). This keyword must be followed by a valid 4xy SMTP code and text that describes the reason for the temporary failure.

ERROR:

Mail from this sender to this recipient is rejected with a permanent error (causing the message to bounce). This keyword must be followed by a valid 5xy SMTP code and text that describes the reason for the rejection.

To illustrate, consider the following example of such entries in an access database:

Compat:bin@your.site<@>admin@your.site    DISCARD
Compat:ads@spam.site<@>taka@your.site     DISCARD
Compat:db@your.site<@>lp@your.site        TEMP:421 printer is down for repair
Compat:bob@your.site<@>betty@your.site    ERROR:553 Interoffice banter banned
Compat:betty@your.site<@>bob@your.site    ERROR:553 Interoffice banter banned

The first line might be used at a site where the pseudouser bin generates a great deal of automated email and that email is sent to admin, among others. This line causes that automated mail to be accepted and discarded.

The second line might be used to prevent mail from a known spam list from being sent to the user taka at your site. This line causes that spam mail to be accepted and discarded.

The third line might be used to defer mail to a printer from the database. The TEMP will cause the message to be deferred for a later try. You could then remove this line after the printer is repaired and back in service.

The last two lines show a way to prevent two users at your site from sending email to each other. The idea is that it is OK for them to send email to each other at other sites, but not this one. Each such prohibited message is rejected and bounced. For this scheme to work, however, you will need to place an empty root-owned ~/.forward file in each of the two users’ home directories to prevent them from bypassing this restriction by setting up their own ~/.forward files.

This last example underscores a weakness in this FEATURE(compat_check). Because each envelope recipient undergoes aliases translation, and ~/.forward translation before the lookup, the entry in the access database must correctly represent the translated address. For example, consider a pseudouser named nill who has an aliases file entry such as this:

# deep six mail to nill
nill:        /dev/null

Here, the intention is to have all mail to nill delivered to the /dev/null file. If you wanted selected mail to nill to be rejected, do not do this:

Compat:user@other.domain<@>nill@your.site    ERROR:553 Don't mail to nill
                              ↑
                            note

The nill@your.site will never be found because by the time this lookup happens, nill has been transformed into /dev/null. The correct way to set up your access database to handle this situation would look like this:

Compat:user@other.domain<@>/dev/null         ERROR:553 Don't mail to nill
                              ↑
                            note

Also note that when the recipient is an actual user (as, for example, bob):

Compat:user@other.domain<@>bob@your.site         ERROR:553 Don't mail to bob

bob can alter his ~/.forward file at any time, thus rendering his recipient entry useless.

Screen by domain and .domain

Normally, lookups of hosts in the access database are literal. That is, host.domain is looked up first as host.domain and then as domain. If you declare the FEATURE(lookupdotdomain)[135] (FEATURE(lookupdotdomain) on page 628) or add a literal lookupdotdomain fourth argument (Enabling the access Database Generally on page 277) to the FEATURE(access_db)’s declaration, you cause the sequence to become host.domain, then .domain, and lastly domain. This feature allows you to structure an access database to handle the domain differently than it handles hosts in the domain:

From:.domain      REJECT
From:domain       OK

Here, envelope senders with a host part of @anything.domain will be rejected, but those with a host part of @domain will be accepted. To illustrate, consider the following attempt to accept mail only from cs.Berkeley.EDU and to reject mail from hosts in that subdomain:

From:.cs.Berkeley.EDU      REJECT
From:cs.Berkeley.EDU       OK

Choose Queue Groups Via the access Database

Beginning with V8.12, it is possible to select queue groups using the access database by declaring the FEATURE(queuegroup). Queue groups and the FEATURE(queuegroup) are discussed in detail in Queue Groups (V8.12 and Later) on page 408.

Screen Based on STARTTLS and AUTH=

Beginning with V8.12, it is possible to accept, reject, and allow relaying based on the STARTTLS and AUTH= SMTP extensions. These abilities, and the features that support them, are detailed in Chapter 5 on page 183.

Spam Suppression Features

As spam and phishing problems become more and more pervasive, sendmail has added more features specifically targeting those problems:

FEATURE(badmx)—V8.14 and Later

Most Windows PCs that exist on the Internet lack a fixed IP address. Instead, each uses the DHCP protocol to fetch a fresh IP address each time the machine boots. Such a machine is unable to publish an MX record (Set Up MX Records on page 332) because it has no fixed IP address. Unfortunately, many Windows PCs are hijacked without knowledge of the owner and are made to send out spam email. From such a hijacked machine, it is unlikely that a valid MX record will exist.

To avoid getting spam from such machines, you may use the FEATURE(badmx). It is declared like this:

FEATURE(`badmx')

With this feature declared, each time a client machine connects to your server, the hostname found (by reverse lookup of the connecting client) is stripped back to the domain part. For example, if the host www.example.com were to connect to your server, the connecting host’s IP address would be 192.0.34.166. That address is reverse looked up to find the hostname www.example.com. This FEATURE(badmx) strips the host part from the hostname (the www) and performs an MX lookup on the result (the example.com part):

  • If the lookup returns a temporary error (a DNS retry), the following SMTP error is returned to the client and the connection is deferred:

    450 4.1.2 MX lookup failure for domain part looked up is shown here
  • If the lookup returns no MX record, the following SMTP error is returned to the client and the connection is refused:

    550 5.1.2 Illegal MX record for recipient host  domain part looked up is shown here
  • If any of the IP addresses returned in the MX list begin with 127.0 (the loopback interface) or 10. (a nonrouting address) or 0. (a broadcast address), that address is considered bad and the following SMTP error is returned to the client and the connection is refused:

    550 5.1.2 Invalid MX record for recipient host  domain part looked up is shown here

The FEATURE(badmx) uses the bestmx database map (bestmx on page 902); the regex database map (regex on page 932); and the dns database map (dns on page 905). You may use this FEATURE(badmx) only if sendmail was built with support for all three database-map types.

FEATURE(block_bad_helo)—V8.14 and Later

The HELO and EHLO client SMTP commands are each formed by a command followed by a hostname:

HELO client.host.domain
EHLO client.host.domain

According to RFC2821, the hostname provided by the client (following the HELO or EHLO SMTP) must be:

  • The canonical hostname of the sending client machine. That is, a host with a domain part. For example, the name “foo” is bad because it lacks a domain. The name “.com” is bad because it lacks a host part. But the name “foo.com” is canonical and valid.

  • The name of the sending client. That is, it must not be any of the names by which the server knows itself, such as “localhost” or “127.0.0.1.”

Although RFC2821 requires that these characteristics be met by the client, it also prohibits rejection based on bad values. If your site is besieged by spam, however, the niceties of RFC2821 may not seem worth following. If you desire to reject badly formed HELO/EHLO hostnames, you may do so by using this FEATURE(block_bad_helo):

FEATURE(`block_bad_helo')

Note that, with this feature defined, certain clients are not checked at all:

  • If the client has authenticated with AUTH, the HELO/EHLO host is not checked.

  • If the client is listed with the RELAY_DOMAIN mc macro (The RELAY_DOMAIN mc macro on page 269) or in the file specified by the RELAY_DOMAIN_FILE mc macro (The RELAY_DOMAIN_FILE mc macro on page 269), that client’s HELO/EHLO hostname is not checked.

Otherwise, all other clients have the host part of the HELO/EHLO greeting checked:

  • If the hostname exists as part of the server’s $=w class ($=w on page 876), the HELO/EHLO command is rejected.

  • If the hostname exists as an IP address in the server’s $=w class ($=w on page 876), the HELO/EHLO command is rejected.

  • If the hostname has only an initial dot, a final dot, or no dot at all, the HELO/EHLO command is rejected.

If the HELO/EHLO greeting is rejected, the client will receive a permanent rejection like the following:

550 5.7.1 bogus HELO name used: client's bogus hostname here

This FEATURE(block_bad_helo) is implemented as the last check in the check_rcpt rule set (Local_check_rcpt and check_rcpt on page 257).

FEATURE(greet_pause)—V8.13 and Later

Slamming is a technique used by some senders of spam email. It allows spamming machines and hijacked proxies to send a great deal of spam email very rapidly, without the need to monitor for rejections.[136] This is a boon to spam-email companies, but a bane to those who resent that behavior.

To slam, a spammer first opens a connection to the SMTP server (in our case, a listening sendmail daemon). Normally, the sending client will not send anything to the server until the server issues its initial greeting:

220 mail.example.com ESMTP Sendmail 8.14.1/8.14.1; Thu, 13 Aug 2007 07:45:41 −0800
(PST)

With slamming, however, the client does not wait for the initial greeting. Instead, the offending client sends its entire SMTP message all at once, then disconnects, before the server (sendmail) has a chance to review the message’s contents.

The FEATURE(greet_pause) was added to V8.13 sendmail to combat slamming. You use the FEATURE(greet_pause) like this:

FEATURE(`greet_pause', `ms_pause')

The FEATURE(greet_pause) takes a single argument, an integer representation of the number of milliseconds to wait before sendmail may send its initial greeting. The ms_pause sets the default wait (we cover this shortly). If ms_pause is missing, no default is set. If ms_pause is greater than five minutes, the wait is silently truncated to five minutes.[137]

If sendmail detects input from the client during this wait, that input is interpreted as an indication of slamming. If slamming is detected, the following rejection (instead of the initial greeting shown earlier) will be issued to the client:

554 server_host_name not accepting messages

Whenever a slamming site is rejected like this, the following is logged with syslog(3):

rejecting commands from host [ip_addr] due to pre-greeting traffic
                                                        ← V8.13 and earlier
rejecting commands from host [ip_addr] after secs seconds due to pre-greeting
traffic                                                 ← V8.14

Beginning with V8.14, this FEATURE(greet_pause) will not log anything if the connecting client disconnects on its own because of the wait.

If the offending site continues to send SMTP commands, each command will be rejected with the following:

554 5.5.0 Command rejected

But note that beginning with V8.14, greet_pause will be called only if the connection has not already been rejected. Prior to V8.14, greet_pause was called whether the connection was already rejected or not.

The FEATURE(greet_pause) may also take advantage of the access database. To do so, the FEATURE(greet_pause) must be declared after the FEATURE(access_db) (The access Database on page 277) is declared. If greet_pause is declared before access_db (or if access_db is not declared), the access database cannot be used with this feature.

When the access database is enabled, sendmail looks up the connecting host in the access database just before it begins to wait. First, the hostname (as taken from the ${client_name} macro; ${client_name} on page 812) is looked up to see whether the canonical hostname is in the database. Then, the host part (to the left of the dot) is recursively stripped to see whether the domain part is listed in the database (host.sub.domain, then sub.domain, then domain). If nothing matches, the same lookups are performed for the client’s IP address (as taken from the ${client_addr} macro; ${client_addr} on page 810). First the full address is looked up, and then the network portions on dot boundaries are looked up (192.168.2.5, then 192.168.2, then 192.168, then 192).

To put entries into the access database’s source file, you prefix each line with a literal GreetPause and a colon. You then specify the host, domain, or IP address followed by a tab,[138] then an ASCII representation of the number of milliseconds to wait. For example:

GreetPause:host.domain        5000
GreetPause:domain                0
GreetPause:127.0.0.1             0
GreetPause:192.186.2          5000

Here, the first entry tells sendmail to wait 5,000 milliseconds (five seconds) before issuing its initial greeting to host.domain (a hostname). The second entry tells sendmail to not wait at all (zero milliseconds) for the domain listed. The third entry tells sendmail to not wait when the connection is from the loopback interface (a memory interface on the local machine). And the last line tells sendmail to wait 5,000 milliseconds before sending its initial greeting to any host on the 192.168.2 network.

If a connecting client is not found in the access database, the wait used is taken from the second argument to the FEATURE(greet_pause):

FEATURE(`greet_pause', `ms_pause')

Here, ms_pause sets the default number of milliseconds to pause for any host, domain, or IP address that is not found in the access database.

Note that any detection of slamming will result in no Milter being called, and will prevent checkcompat( ) from being called.

FEATURE(mtamark)—V8.13 and Later, Experimental

One way to reduce spam email is to set up a mechanism for marking each MTA as an MTA. To illustrate, consider a spam email received from a host with the IP address 192.168.123.45, that claims to be a legitimate MTA. Currently, sendmail can only look up that address using various open relay sites to see whether the IP address corresponds to an open relay, and to reject the message if it does. Under the MTA mark proposal,[139] sendmail can look up a special TXT record associated with that address to see whether that IP address is marked as that of an MTA. You may emulate this lookup using dig(1) like this:

% dig txt _perm._smtp._srv.45.123.168.192.in-addr.arpa

Here, the _perm._smtp._srv is a literal defined by the MTA mark proposal. The 45.123.168.192 is the original IP address reversed, and the in-addr.arpa is the special domain used to treat IP addresses like domain names.

This lookup can return one of two possible TXT records. A “1” means that this IP address is that of an MTA. A “0” (or any other character) means that this IP address is not that of an MTA. Mail from an unmarked MTA may, under this proposal, be rejected.

Once this proposal is in place, spam sites will no longer be able to send spam email via hijacked PCs, via hired PCs, or via worms implanted in PCs. When spam email does arrive, you will be certain that it is from a marked MTA and only from a marked MTA. Then, by blocking email from that IP address, you will be able to turn off that site’s spam at the source.

This experimental FEATURE(mtamark) enables use of this proposal, but it should not be used unless you are willing to experiment. It is declared like this:

FEATURE(`mtamark', `reject', `tempfail')

Here reject is either a rejection message of your own or, if it is omitted, a default that looks like this:

550 Rejected: $&{client_addr} not listed as MTA

Here, the ${client_addr} macro (${client_addr} on page 810) contains the IP address of the connecting host that was looked up.

The second argument, the tempfail, is either a literal t or a temporary failure message of your own. The t causes the following default to be used:

451 Temporary lookup failure of  _perm._smtp._srv.$&{client_addr}

Thus, if the lookup fails to get a “1”, the reject text is used and the message is rejected. If the lookup fails for a temporary (recoverable) reason the tempfail text is used and the message’s acceptance is deferred.

Note that, if the MTA mark proposal is revised at a later date, the literal _perm._smtp._srv may need to be changed. If so, you may replace it by adding a third argument to the feature declaration, such as _permit._srv:

FEATURE(`mtamark', `reject', `tempfail', `_permit._srv')

The default timeout for the lookup is five seconds. If that turns out to be too short for your needs, you may increase it by defining the MTAMARK_TO mc macro:

define(`MTAMARK_TO', `20')
FEATURE(`mtamark', `reject
', `tempfail')

Note that the timeout must be defined before you declare the feature.

FEATURE(require_rdns)—V8.14 and Later

When a client machine connects to your sendmail server, sendmail records the IP address of the connecting client in the ${client_addr} macro (${client_addr} on page 810). Next, sendmail looks up that IP address (performs a reverse DNS lookup of that address) to find the client’s hostname. The status of that lookup is stored in the ${client_resolve} macro (${client_resolve} on page 814). This FEATURE(require_rdns) rejects connections from clients for whom the reverse lookup fails.

You declare this FEATURE(require_rdns) like this:

FEATURE(`require_rdns')

If you declare this feature, the following logic will be performed as the last step under basic relay checks:

  • If the value in ${client_addr} is also in the RELAY_DOMAIN (The RELAY_DOMAIN mc macro on page 269) or the RELAY_DOMAIN_FILE (The RELAY_DOMAIN_FILE mc macro on page 269) list of domains and hosts for which to relay, the connection is allowed to relay, and no further checking is done by this feature.

  • If the result of the lookup (the value in the ${client_resolve} macro) is the literal OK, the address is accepted and any additional relay checks are performed.

  • If the result of the lookup (the value in the ${client_resolve} macro) is the literal FAIL, the following error is returned in the SMTP transaction and the connection is disallowed:

    550 5.7.1 Fix reverse DNS for failed IP address here
  • If the result of the lookup (the value in the ${client_resolve} macro) is the literal TEMP, the following error is returned in the SMTP transaction and the connection is tempfailed:

    451 4.1.8 Client IP address failed IP address here does not resolve
  • If the result of the lookup (the value in the ${client_resolve} macro) is the literal FORGED, the following error is returned in the SMTP transaction and the connection is tempfailed:

    451 4.1.8 Possibly forged hostname for failed IP address here

This feature should probably not be set if you relay based on IP addresses in the access database, because the feature does not look in that database.

FEATURE(use_client_ptr)—V8.13 and Later

The check_relay rule set (Local_check_relay and check_relay on page 252) is used to screen incoming network connections and accept or reject them based on the hostname, domain, or IP address. The check_relay rule set is called with a workspace that looks like this:

host $| IPaddress

The host name and IP address are separated by the $| operator. As of V8.13, this FEATURE(use_client_ptr) causes a new rule to be inserted as the first rule under the check_relay rule set, which substitutes the value of the ${client_ptr} macro (${client_ptr} on page 813) for the prior host value passed.

Essentially, this causes V8.13 sendmail to behave like earlier versions of sendmail that did not use the delay_checks.

Pitfalls

  • If your site supports dial-up clients or machines that are assigned an IP address on startup, you should prevent such machines from sending mail directly to the outside world. If you fail to take this precaution, you might find such machines sending spam email that you can neither detect nor control. The easiest way to limit mail access to the world is with a firewall or router. Make it your published policy to always configure your firewall or router to prevent access to port 25 for all but your main mail hub machines.[140] This prevents dial-up clients from sending mail directly to the world. Instead, they will be required to send all email by way of your mail hub machines—which PC mail-reading software can easily be configured to do.

  • On your mail hub machines, you will need to use any of the appropriate methods discussed in the relaying section (Relaying on page 267) to enable the hub to relay messages outward for your dial-up clients. By requiring that all outbound email from dial-up clients be relayed through your mail hub, you enable your hub to impose limits on sending rates, to limit the number of recipients per envelope, and to log all email transactions. In brief, this puts you in position to detect spam attempts by your customers.

  • A common technique used by spammers is to lie about the true host that was used to send the offensive email by manufacturing headers that mislead the end recipient. Such headers can range from falsely made-up Message-Id: headers, to misleading Received: headers. As an ISP, it is your responsibility to ensure that all mail passing through your hubs is truthfully labeled. One way to do this is to ensure that all hostnames in headers are fully canonical.

  • One sure way to know whether your site is spamming is to receive and read email from people who complain about receiving such spam. You should always read mail addressed to Postmaster. As an added precaution, you should also create an alias for the address abuse and read that mail too. Complaints will also be sent to webmaster about HTTP problems, and to hostmaster about DNS problems. You should accept and read all mail that might indicate a problem needing attention.

  • If you are running an old version of sendmail and have not yet upgraded, beware that you might be running a site that will relay email to anywhere in the world. Called “promiscuous relaying,” this could get your site listed with DNSBL sites. Try to upgrade soon.



[114] * See http://www.spam.com/ for the official story.

[115] * See the FEATURE(delay_checks) (Accept and Reject per Recipient on page 284) to see how that feature changes this order.

[116] * We illustrate this scheme, despite the fact that it is available in the access database, because other meaningful uses for this rule set are rare.

[117] * Actually, the message is not printed; instead, the SMTP daemon goes into a “reject everything” mode. This prevents some SMTP implementations from retrying the connection.

[118] Actually, $#anything will have the same effect, but you should use $#OK only to remain compatible with future releases of sendmail.

[119] * Don’t be tempted to put this rule directly into the Local_check_relay rule set. You might someday encounter an address that has the two adjacent characters $ and | as a legal part of it. Also be aware that such addresses might be intentionally sent to circumvent your checks.

[120] * Normally, sendmail rejects mail from a site whose name cannot be found with DNS with the error “domain of sender must exist.”

[121] The name canonify corresponds to rule set 3.

[122] Actually, $#anything will have the same effect, but you should use $#OK only to remain compatible with future releases of sendmail. Other rules might still reject it, possibly for other reasons, so always test new rules carefully.

[123] * Actually, $#anything will have the same effect, but you should use $#OK only to remain compatible with future releases of sendmail.

[124] * This is a commercial site to which your name server must subscribe to use. Visit http://www.mail-abuse.com/ to find your cost, which can range from free for hobbyists to several thousand dollars for ISPs. Other such sites have existed prior to this writing, but they are now defunct. Sites worth investigating are http://www.ordb.org/, http://spamcop.net/, and http://www.iki.fi/.

[125] See http://www.mail-abuse.org/ for information about how to subscribe to this service.

[126] * Depending on your shell, you might have to prefix the tab with a control-V character to embed it into your command line.

[127] * Do not use this class directly. Instead, use the macros we present here. If you use it directly, you risk breaking your configuration file if sendmail changes in the future.

[128] This is not recommended, but serves as an example of one way to modify the underlying F command.

[129] * Beginning with V8.12, you can customize this rejection message using the confRELAY_MSG mc configuration macro. To use it, include the leading 550, but exclude the 5.7.1, and replace the old text with the new (see cf/README).

[130] * Another feature, FEATURE(blacklist_recipients), allows recipients to also be rejected. Yet another, FEATURE(delay_checks), allows even finer tuning based on the desire of individual recipients.

[131] * In support of the examples in this section, we are assuming the FEATURE(blacklist_recipients) (Reject per Recipient on page 284) has also been declared so that the access database can reject mail to specific local recipients.

[132] * Note that V8.11 and later have added prefixes by embodying them in new features. See the sections that follow for a description of these new prefixes.

[133] * If the host is listed with the RELAY_DOMAIN mc macro (The RELAY_DOMAIN mc macro on page 269) or in the file specified by the RELAY_DOMAIN_FILE mc macro (The RELAY_DOMAIN_FILE mc macro on page 269), it is relayed without checking the access database.

[134] * Accepting and rejecting based on the user part of an address require that you also declare the FEATURE(blacklist_recipients) (Reject per Recipient on page 284).

[135] * If you have not already done so, you will also first need to declare the FEATURE(access_db).

[136] * Hijacking worms, loaded into unsuspecting PCs, are often used as proxies to perform just this sort of rapid spam email attack.

[137] RFC2821 defines five minutes as the maximum timeout for the 220 greeting.

[138] * Unless you set a different column delimiter with the -t command-line switch for makemap.

[140] * There are many legal issues surrounding ad hoc filtering of customer access. You are strongly advised to consult with an attorney before applying such filters.