Even more access control for sendmail

Recent versions of sendmail have a great feature called access map (or access database) that allows to specify in detail who is allowed and/or forbidden to send mail and to whom. This feature is very helpful in filtering unwanted emails, however, there are situations when the amount of control provided by the access map is insufficient. Also, analyzing various sendmail features related to the access map, I found out that their behaviour is a bit illogical in some cases - at least for me... I wanted to change it, to make sendmail behave in a way that suits my needs, so I wrote a bunch of new FEATURE macros you can find here.

For those not very familiar with sendmail configuration files, I suggest reading the documentation first, because my features modify the behaviour of some standard sendmail features, and you surely should know what is modified and how!

There are two ways of using these features, the "original prototype" mode and the "hacked prototype" mode. In the "original prototype" mode, you don't need to modify any of the files that came with your sendmail distribution; only put the FEATURE files you want into the feature subdirectory of your sendmail config directory. The second mode requires, besides installing the FEATURE files, replacing the original m4/proto.m4 file from your sendmail distribution with the file provided here.

The mode is changed by means of the following definition in your .mc file:

     define(`RAJ_HACKED_PROTOM4',`')dnl

If this line is present, the macros work in "hacked prototype" mode; in it's absence, the "original prototype" mode is used. This line, if present, must be placed in the file before any of the new features.

If you include this line in your .mc file, but don't replace the original m4/proto.m4 file with my modified version, all the features described here will essentially do nothing. On the other hand, if you use the modified proto.m4 file and don't include the above definition, these features will not work properly and you might get unpredictable results. So you should use the above line in your .mc file if and only if you have replaced the m4/proto.m4 file with the modified version.

Why these two modes at all, when the features will work almost the same way in both modes? The difference is in the structure of the generated .cf file. The internal logic of standard sendmail rulesets is defined in the proto.m4 file, so without changing it (as in the first mode), one is unable to change that logic - we can only add new rulesets and "hook" them into existing ones using Local_check_* rulesets. We may then use some tricks to stop the default rulesets from running and use the added ones only. Because of this, in many cases achieving the desired effect requires creating a new ruleset, which is actually a duplicate of a standard ruleset with only a minor change. This makes the .cf file generated in "original prototype" mode very "dirty". It contains duplicated sections of almost the same code, some of which will never get called, and, because it uses the Local_check_* rulesets extensively, it may not work properly if you use these rulesets for your own purpose (it can happen that either your rulesets or rulesets from my features will get called, but not both). Also, in this mode you have to define the particular features in the .mc file in a strict order - if you do it out of order, they won't work properly.

In the second - "hacked prototype" - mode, the modified proto.m4 file actually changes the internal working of sendmail rulesets in much the same way as standard sendmail features do. The resulting .cf file is much "cleaner" and does not use Local_check_* rulesets, so they are free for you to use. I recommend using this mode - use the first one only if for some reasons you don't want to modify the original distribution files that came with your sendmail.

These rulesets have been created and tested using configuration files from sendmail version 8.13.8 only. I do not guarantee that they will work properly with other versions.

So let's go to the features. You can download any individual file clicking on the feature name on the left, or you can dowload a .tar.gz file containing all the features and the modified proto.m4 file here.

FEATURE(`local_sender_check')

The first (and by the way, easiest to implement) feature has actually nothing to do with the access map. It's also quite independent from the rest. It checks the validity of local sender addresses - I think it's the functionality that definitely lacks in standard sendmail config.

If the address specified in the MAIL FROM: command seems to be local (the sender's domain is in the $=w class, or in the $={VirtHost} class if you use FEATURE(`virtusertable'), or there is no domain at all (unqualified address)), sendmail will check if it exists - if not, the MAIL FROM: command will be rejected with the "User unknown" error message (if FEATURE(`delay_checks') is also used, then mail will be rejected in the RCPT TO: command with a message "Sender user xxx@yyy.com unknown", where xxx@yyy.com will be of course replaced by the actual sender address specified in the MAIL FROM: command). This can be useful eg. when your users often misconfigure their email clients and type in invalid return addresses, and in many other situations.

Caution: This feature depends on features virtusertable, virtuser_entire_domain and delay_checks. If you use any of these features, define them in your .mc file before this feature (this applies to both modes described above). The macro can't check this and won't display an error message if you define the features in wrong order, but it won't work correctly then, so take care!
 

FEATURE(`natural_check_order')

This feature is meant to be used together with FEATURE(`delay_checks') (it does nothing without it). It changes the order in which particular checks are made to something more sensible (in my opinion).

By default, when FEATURE(`delay_checks') is defined, sendmail calls first check_rcpt ruleset to check the recipient address, then check_mail to check the sender address and finally check_relay to check the connecting host. However, there are two problems with this:
1. If check_rcpt finds out that the client has successfully authenticated (either via STARTTLS or SMTP AUTH), skips the two other checks. Skipping check_relay is good, since it allows the authenticated users to send mail from any host - for example from a dialup that is blacklisted by a DNSBL - but skipping check_mail is in my opinion bad, since it allows these clients to send e-mail with invalid sender addresses (eg. from unresolvable domains, or from nonexistent local senders when FEATURE(`local_sender_check') is also used). This shouldn't happen.
2. For non-authenticated clients, if check_mail finds a sender address that is whitelisted in access map (marked as "OK" or "RELAY"), it accepts the mail and doesn't call check_relay. This makes no sense, because a sender address doesn't matter at all if you can't trust the host the email is coming from - it can be very well fake. So it's advisable to check the host first, to see if it isn't blacklisted, and then check the sender's address.

In short, this feature modifies the behaviour of FEATURE(`delay_checks') so that check_mail is called always, and check_relay is skipped only for authenticated clients. The "spam friend" or "spam hater" options of the delay_checks feature - if you use them - make an exception: if a recipient is marked in the access map as "spam friend" or "non-spam hater", all email to this user is accepted (both check_mail and check_relay are skipped).

Additionally, use of this feature changes the default error message output by sendmail when a mail is rejected by check_mail or check_relay to a more informative one. The default "Access denied" is replaced by "Access denied for sender xxx@yyy.com" if mail is rejected by check_mail, or "Access denied for host connecting.host.name" when mail is rejected by check_relay. The latter change is also introduced by default if you use the modified proto.m4 file, even without using this feature.

If you use this feature in "original prototype" mode, it requires FEATURE(`delay_checks') to be defined before it in the .mc file. If you also use FEATURE(`relay_maybe'), then the latter must be defined after FEATURE(`natural_check_order').
 

FEATURE(`skip_checkmail_if_auth')

This is a supplemental feature to the above one. It does nothing without it. It causes the behaviour described in point 1. above to be left unchanged (ie. for authenticated clients both check_mail and check_relay will be skipped), while still fixing the problem descibed in point 2. (ie. check_relay is still called for non-authenticated clients - with "spam friend" or "non-spam hater" being the exception of course). Use this if you really want to allow your authenticated users to send e-mail with any (even invalid) sender address. I don't recommend it.

If you use this in "original prototype" mode, it must be included in the .mc file before FEATURE(`natural_check_order').
 

FEATURE(`relay_maybe')

This feature makes standard features relay_local_from and relay_mail_from more secure and actually usable. With this feature, you can allow limited relaying for some hosts or networks, while other hosts are unaffected.

If your users are using old e-mail clients that don't support SMTP AUTH (I have some users who are still using MS-DOS based clients!) and the IP addresses these users use are changing (either because they are roaming, or because the address is assigned dynamically), allowing them to relay mail through the server can be problematic. FEATURE(`relay_local_from') and/or FEATURE(`relay_mail_from') may be of some help, but sendmail documentation strongly warns against using these features, because they can be easily abused by spammers, by means of faking the sender address. On the other hand, if the IP addresses in question are contained within some domain or some network (as it is usually with dynamic IP addresses), you can mark the whole domain or network as "RELAY" in the access map - but in case of, for example, a large ISP, this can also be dangerous and lead to abuse of your server. So, to achieve better security, both these options may be combined.

When FEATURE(`relay_maybe') is used, you can mark some hosts/domains/networks in the access map with the RHS of "MAYBE". If a connecting host will match the "MAYBE" entry, the features relay_local_from and/or relay_mail_from - if defined -will be applied, ie. relaying from this host will be allowed if the domain in MAIL FROM: address is local (domains from class $={VirtHost} - if FEATURE(`virtusertable') is used - are also considered local for this purpose) - for FEATURE(`relay_local_from'), or if this address is listed in the access map as "RELAY" - for FEATURE(`relay_mail_from'). Hosts that don't match any "MAYBE" entry will be unaffected by these features. In any other case, the "MAYBE" entry in the access map is considered equal to "OK".

FEATURE(`relay_maybe') by itself, without FEATURE(`relay_local_from') nor FEATURE(`relay_mail_from'), does not allow any relaying. On the other hand, features relay_local_from and/or relay_mail_from without FEATURE(`relay_maybe') behave in the standard way, ie. they affect all hosts, so they are dangerous and should be avoided, as stated in the sendmail documentation.

In "original prototype" mode, there are some restrictions and conditions for using this feature. First, this feature must be preceded in the .mc file with FEATURE(`relay_local_from') or/and FEATURE(`relay_mail_from') - the macro will complain if none of these features is defined. FEATURE(`access_db') is also required.
Second, if you use any of the features relay_entire_domain, relay_hosts_only, virtusertable, virtuser_entire_domain, delay_checks or natural_check_order, you also must define them before FEATURE(`relay_maybe'). It won't complain if these features are defined later, but it won't work properly.
Third, if you use FEATURE(`relay_maybe') with FEATURE(`delay_checks'), you must also use FEATURE(`natural_check_order'). The macro will display an error message if you use delay_checks, but don't use natural_check_order - it is not compatible with this configuration.

The idea of limiting relaying with "MAYBE" has been invented long time ago by Tomasz R. Surmacz, who wrote the appropriate rulesets for early versions of sendmail - I wish to express my thanks to him here.
 

FEATURE(`relay_maybe_disabled')

This is a "do-nothing" feature whose only purpose is to allow using "MAYBE" entries in access map (they are equal to "OK"). Use it if you want to turn FEATURE(`relay_maybe') off, but still have "MAYBE" entries in your access map. Instead of removing FEATURE(`relay_maybe') from your .mc file completely, replace it with FEATURE(`relay_maybe_disabled') to keep your access map from becoming invalid and sendmail from displaying bogus error messages for hosts marked as "MAYBE".
 

FEATURE(`checkrelay_ip_first')

By default, ruleset check_relay searches the access map first for domain name of connecting host, and then (if no match was found) for it's IP address. This feature reverses this order - IP address is looked up first, and then domain name if there's no match.

This has been introduced mainly for compatibility with check_rcpt, which, when checking if host is allowed to relay mail, searches the IP address first. Suppose that "somehost.domain.com" has the IP address of 1.2.3.4. If you have the following in your access map:

     somehost.domain.com   REJECT
     1.2.3.4               RELAY

then check_relay would reject connection from this host, while check_rcpt would accept it and allow to relay mail - everything now depends on the order in which these rulesets are called (features like delay_checks and natural_check_order can change it). With FEATURE(`checkrelay_ip_first'), both rulesets will behave coherently, ie. they will both accept (in this case) the connection.

Similar effects can occur if you whitelist (or blacklist) an entire network/domain in the access map and want to reject (or accept) connections from a single host in that network/domain. Suppose you have

     domain.com            OK
     1.2.3.4               REJECT
If 1.2.3.4 is "somehost.domain.com", this will not work with the standard configuration (ie. if you don't use FEATURE(`checkrelay_ip_first')). If you use this feature, the connection from the address 1.2.3.4 will be rejected. On the other hand, these entries:
     1.2.3                 OK
     somehost.domain.com   REJECT
will work (ie. reject connections from host somehost.domain.com but allow from all other in the 1.2.3.* network) in the standard configuration (without FEATURE(`checkrelay_ip_first')), and will not work if this feature is defined.

Probably you will not need this feature anyway, unless your access map is very complicated.

In "original prototype" mode, if you use any of the features conncontrol, ratecontrol, dnsbl or enhdnsbl, define them before this feature.


If you have written another useful feature for sendmail, or you have improved my features posted here, or you have found and corrected a bug in them, please send me the code - I will be glad to post it here.

Jarosław Rafa, 2.10.2006