Using check_* in sendmail 8.8

Last Update 2009-06-22
Content: check_mail | check_rcpt | check_relay | check_compat | Some more hints

Introduction

If you plan to upgrade your sendmail version, go directly to the current release of sendmail (or at least 8.9.3) which has standard FEATUREs included. However, you may read on, because this page and related pages give background informations about the check_* rulesets.

sendmail 8.8 introduced several new rulesets to control who can use your machine to send/relay e-mail and to avoid UBE from well known sites. These are:

check_relay
checking the host name and host address separated by $|. This ruleset is called whenever a client connects via (E)SMTP to the server.
check_mail
for the MAIL command.
check_rcpt
for the RCPT command (used to prevent unauthorized relaying). This ruleset disables all kinds of known relaying tricks (the trick which is tested by ORBS currently (1999-07-14) is fixed since the end of 1997 according to my logs).
check_compat
checking both MAIL and RCPT also separated by $| before delivery.

Moreover, it also defines new macros: ${client_name}, ${client_addr}, and ${client_port} that have the name, IP address, and port number of the SMTP client, respectively.

After you have carefully read this text, you can download (last update: 1999-09-20) the HACKs for use in your .mc file. They have many options to provide different features.
Be sure you understand what they are trying to accomplish and check them yourself before you use them on a production system!

Most of these HACKs can be translated into standard FEATUREs in sendmail 8.9 which has been released (1998-05-20).

New features are available via the beta version (last update: 1998-12-28). Please send me some feedback if you test this.

General Information about the new Rulesets

These new rulesets can do whatever you want, the only interesting case is when the resolve to the the $#error mailer, i.e., $#error $@ error-code $: error-text . In this case the indicated message is printed and the command is rejected with an error code. sendmail can return different reply codes, which are defined in RFC 821 . However, if you don't repeat the error number in the error text, sendmail uses the default value 553.

Just as a reminder, the parts of an (E)SMTP dialogue which are important here are:
MAIL From:<sender@address>
to specify the sender's address, and
RCPT To:<rcpt@address>
to specify the recipient's address, of which multiple can be given.

The check_compat ruleset is called before delivery with from_addr $| to_addr as argument. The $| is a new meta-symbol used to separate the two addresses.

check_compat is called for all deliveries, while check_mail and check_rcpt are only called for SMTP connections. So the latter won't work when you run sendmail in queue delivery mode behind smap or something similar. In this case, you should try the patch for checkcompat() written by Kyle Jones.

check_mail

The address supplied by the MAIL command can be checked against the check_mail ruleset.

A new version uses a map with error messages as right side. It has several advantages over the version presented here.

This can be used to prevent UBE from known senders. First, you may have a list of domains in an external file which you want to ban completely:
F{SpamDomains} /etc/mail/SpamDomains
e.g.,

cyberpromo.com
quantcom.com
Next, you may have a list of users which you want to ban too:
F{Spammer} /etc/mail/Spammer
e.g.,
lamer@aol.com
Now you can use these as follows:
Scheck_mail
R<$={Spammer}>		$#error $@ 5.7.1 $: "550 We don't accept junk mail"
R<$={Spammer}.>		$#error $@ 5.7.1 $: "550 We don't accept junk mail"
R$*			$: $>3 $1
R$*<@$={SpamDomains}.>$*	$#error $@ 5.7.1 $: "550 We don't accept junk mail from your domain"
R$*<@$={SpamDomains}>$*		$#error $@ 5.7.1 $: "550 We don't accept junk mail from your domain"
In addition, you may want to act on broken mailers which don't use <> around addresses:
R$={Spammer}		$#error $@ 5.7.1 $: "550 We don't accept junk mail"
R$={Spammer}.		$#error $@ 5.7.1 $: "550 We don't accept junk mail"
If you want to stop receiving mails from subdomains of well known spammers, you can modify the last two rules a bit:
R$*<@$*$={SpamDomains}.>$*	$#error $@ 5.7.1 $: "550 We don't accept junk mail from your domain"
R$*<@$*$={SpamDomains}>$*		$#error $@ 5.7.1 $: "550 We don't accept junk mail from your domain"
Next step could be the following: you want also to reject mail from those domains, which are not registered in the DNS. However, this may also be a temporary failure, so you should give back a temporary failure. sendmail 8.9 does this by default.
# if you enable the last rule, you can disable this one.
# host without a . in the FQHN ?
R$*<@$->$*	$#error $@ 4.1.8 $: "451 invalid host name"		no real name
# lookup IP address (reverse mapping available?)
# R$*<@[$-.$-.$-.$-]>$*	$: $1 < @ $[ [ $2.$3.$4.$5 ] $] > $6 
# no DNS entry? this is dangerous!
# R$*<@$*$~P>$*	$#error $@ 4.1.8 $: "451 unresolvable host name, check your configuration."		no real name
The hint to perform a reverse-mapping of the IP address comes from Jan Krüger.

Paul Vixie maintains a MAPS Realtime Blackhole List which can be used as follows:

R$*			$: $(dequote "" $&{client_addr} $)
R$-.$-.$-.$-		$: $[ $4.$3.$2.$1.rbl.maps.vix.com $] 
R$-.$-.$-.$-.rbl.maps.vix.com.	$#error $@ 5.7.1 $: 550 no access from [$4.$3.$2.$1], see http://mail-abuse.org/rbl/
This code can be appended to check_mail or some other rule. Thanks to Thomas 'Mike' Michlmayr for this suggestion.

Note: Don't block the empty reverse path address <>. RFC 1123 requires to accept this address. Try to find another pattern to block spammer who misuse this.

Based on popular demand, there is a new check_rcpt version in the check.tar file which allows you to accept mail for some specified local accounts regardless of any checks in check_mail if you set the appropriate options. See the README for further informations.

There is an (1997-12-07) experimental version which can check all parts of an e-mail address. However, it is not yet intended for general use.

check_rcpt

The address supplied through the RCPT command can be checked against the check_rcpt ruleset. On first look, this ruleset doesn't make much sense. Why check the recipient? sendmail does this anyway when trying to deliver, esp. for local recipients. However, this ruleset can be used to check whether your system is (mis)used as a relay. The check_compat ruleset, which seems to be better suited for this purpose, since it gets both addresses (sender and recipient) as parameters, is called too late. To reject a misuse at the earliest moment (and save your bandwidth etc), you can refer to the address of the sending system, which is available in the macro ${client_addr}. However, to use it in a rule, you have to refer to it as: $(dequote "" $&{client_addr} $) so sendmail defers evaluation and tokenizes it. The old solution is based on a proposal from Chin Huang: But since there is a problem with these rules, here is a new solution. First, we check whether it is a local client: it can do whatever it want. Next, we remove the local part, maybe repeatedly. If it still has routing information in it, it seems to be a relay attempt. (There are also a more detailed explanation of this ruleset and some additional thoughts.)
So list in the class
F{LocalIP} /etc/mail/LocalIP
the IP addresses of the local clients you will allow to relay through your mail server, for example
134.245
127.0.0.1
A client which connects from one of these IP numbers can send mail through your relay anywhere.
Scheck_rcpt
# first: get client addr
R$+			$: $(dequote "" $&{client_addr} $) $| $1
R0 $| $*		$@ ok			no client addr: directly invoked
R$={LocalIP}$* $| $*	$@ ok			from here
# not local, check rcpt
R$* $| $*		$: $>3 $2
# remove local part, maybe repeatedly
R$+			$:$>removelocal $1
# still something left?
R$*<@$*>$*		$#error $@ 5.7.1 $: 550 we do not relay

Sremovelocal
# remove RelayTo part (maybe repeatedly)
# R$*<@$*$={RelayTo}.>$*	$>3 $1 $4
R$*<@$=w.>$*		$: $>removelocal $>3 $1 $3
R$*<@$*>$*		$@ $1<@$2>$3
# dequote local part
R$+			$: $>3 $(dequote $1 $)
R$*<@$*>$*		$: $>removelocal $1<@$2>$3
The trailing $* after $={LocalIP} matches incompletely specified IP addresses on octet boundaries, as can be seen by 134.245 which matches a whole class B subnet.

If you relay mail for other systems (e.g., the secondary MX of a system points to your mailhost or your server is the primary MX, but you forward the mail to another system), use also:

F{RelayTo} /etc/mail/RelayTo
to list all hosts you relay mail to or accept mail for. For example, we put
uni-kiel.de
in RelayTo. Then enable the rule
R$*<@$*$={RelayTo}.>$*	$>3 $1 $4
The leading $* will match subdomains of those domains in RelayTo too. You can also use a map instead of a class, if you slightly change the rules.

If you encounter a problem with this ruleset, try to find out which options you're missing or debug it directly.

Relay control for roaming users

Several people asked for a possibility to allow relaying based on the FROM address too. This behavior can be enabled with the option _ALLOW_SOME_ in check_rcpt4.m4.
A better proposal requires a modification to a POP daemon and some utilities. It uses the authentification provided by POP to temporary enable the relaying of mail through your system. The option_POPAUTH_ supports this.

Support for UUCP

Some support for UUCP is available in this ruleset, too.

check_relay

Despite its name, this ruleset can't be used to deny unauthorized relaying. check_rcpt is the appropriate ruleset for this purpose.

check_relay gets the host name and host address of the client separated by $| as parameters. This can be used as a substitute for TCPWRAPPERS. You can enable the code for TCPWRAPPERS by compiling sendmail with -DTCPWRAPPERS=1. This ruleset is called whenever a client connects via (E)SMTP to the server.

A small example is:

F{DeniedIP} /etc/mail/DeniedIP
F{DeniedNames} /etc/mail/DeniedNames
These (plain text) files contain a list of IP addresses and hostnames which are not allowed to access your mailserver.
Scheck_relay
R$+ $| $={DeniedIP}$*		$#error $@ 5.7.1 $: "no access from your IP address"
R$*$={DeniedNames} $| $*	$#error $@ 5.7.1 $: "no access from your host"
(note the trailing/leading $* to match incompletely specified IP addresses/names).

Any access (i.e., any (E)SMTP command except QUIT, HELO, EHLO, and NOOP) will be refused with the error message:

550 Access denied
and the error string will be logged. Since this is the first ruleset to be called, it can't be overridden by some exceptions based on envelope sender or recipient. However, the host name and address are also available in other rulesets, so they can be tested there.

If you try to use maps, you need a patch for sendmail 8.8.5 (it's fixed in later versions). The latest version shares a map with check_mail.

check_compat

Although check_compat gets both addresses (sender and recipient) as parameters to check whether your machine is used as a relay, it's too late. check_compat is called after the whole message has been received. You could do something like this:
Scheck_compat
R$+ $| $+		$: $2 $| $>3 $1	canonicalize sender
R$+ $| $+		$: $2 $| $>3 $1	canonicalize recipient
R$- $| $+		$@ok		from here
R$+ $| $-		$@ok		to here
R$+<@$=w.> $| $+	$@ok		from here
R$+ $| $*<@$=w.>	$@ok		to here
R$*			$#error $@ 5.7.1 $: "550 we do not support relaying"
to prevent (mis)use of your machine as a mail relay by other people. Maybe you have to use some other class than w. If you have a better example for this purpose, please let me know. However, this ruleset has a problem with forwarding. That's one of the reasons why you should use the check_rcpt solution.

Some more hints

Problems with Reply Codes 551, 571, 418

Thanks to Sean Vickery for pointing out a mistake with the reply codes used earlier. The correct SMTP reply codes are 45x or 55x according to RFC 821 . The codes 5.7.1, 4.1.8 etc. are Enhanced Mail System Status Codes as defined in RFC 1893 .

Kari E. Hurtta pointed out that the reply code 551 isn't appropriate either. Hence it has been changed to 550. If someone has a better proposal, please let me know. In several rulesets the reply code can be specified in a map anyway.

Tom Smith wrote me he has no problems with other MTAs if he uses:

as reply codes for check_mail. So probably these codes are used in the next versions, unless someone tells me about other solutions, problems, etc.

Nasty?

If you have a good connection to the internet, and you want to be a bit nasty, you could change all error codes from permanent (5) to temporary (4), e.g., instead of "550 ..." you can use "450 ...". This has the consequence that the spammer will try again later on, so his resources are tied up. The more people implement such a scheme, the longer it will take the spammer to distribute his junk. But be careful: this ties up your resources too!

The same trick can also be used for a test period: you can examine your logfiles for (temporarily) reject mails and determine whether it was a desired action.

Testing the rules

Before installing these rules on a productions system, you need to test them. Read the guide to debug these new rules. It also contains a link to a script to test mail relaying capabilities. If there still is a problem with the rules, you may have a look at the list of known bugs before contacting me.

Other proposals and more information

More examples can be found at the Sendmail site and in a list that also contains some links to WWW pages with more explanations. There is also a more detailed explanation of some features of these rulesets.

Yet another possibility is to use the checkcompat() routine. Kyle Jones proposed a patch. It is intended to disallow all non-local e-mail traffic through your host.

These rules can act only based on the information given during the (E)SMTP dialogue (the envelope), and on the address of the sender (or recipient). They can't filter based on the header or the content of the e-mail. If you want this, either install procmail as local delivery agent, patch sendmail 8.8, or install sendmail 8.9.

Where to put this in sendmail.cf?

If you can't use the supplied HACKs to build a sendmail.cf from a .mc file then you have to place the new stuff in your .mc or sendmail.cf by hand (this is only a very last resort! Upgrade instead). Or read the instructions written by Glenn Fleishman.

Mailing List for Announcements

M. Grape suggested to install a mailing list for announcement of new versions of the check_* rulesets.

It is strongly encouraged to use sendmail 8.11 (or later) instead of these HACKs. There is no further development for them. So please consider to unsubscribe from that list (it's pretty much dead).

Unsubscribe requests must be sent to Majordomo@informatik.uni-kiel.de with the body:

unsubscribe sendmail-check Your@E-Mail.Address
(of course you have to replace your own e-mail address for Your@E-Mail.Address.)
[(links)] [Hints] [cf/README] [New]
Copyright © Claus Aßmann Please send comments to: <ca at sendmail.org>
Disclaimer: the information provided may be inaccurate or outdated or incomplete. Please contact me if you find an error.