Consulting djbware Publications

4. Recipients Verification

qmail-smtpd accepts messages if the SMTP domain part of Recipient address ("Rcpt to: <recip@domain>") matches an entry in control/rcpthosts or control/morercpthosts.cdb. The existence of a mailbox/maildir for the corresponding SMTP Recipient is checked later in the delivery chain. In case no Mailbox/Maildir exists, the message is bounced back to the SMTP Sender ("Mail From: <send@example.com>").

For normal SMTP mail traffic that's fine as long as the rate of undeliverable messages don't exceed 10% and the Sender is 'legitmate'; ie. exists. Today's situation is different: Spam and Virus attacks with forged/faked Sender addresses to a bunch of random Recipient addresses yield an undeliverable rate up to 90%. Worse, the generated bounces will never reach the Sender and a double-bounce is eventually send to the postmaster.

4.1 qmail-smtpd's Recipients mechanism

qmail-smtpd is aware of acceptable recipients, which are fetched from an external source (Recipients mechanism).
Which source to query depends on the domain-part of the recipient address.

The recipients check is done only in a none-RELAYCLIENT case and after control/rcpthosts, control/morercpthosts.cdb have been successfully consulted.

4.1.1 VERP Support

The Recipients mechanism supports natively Qmail's address extensions (VERP). This is in particular required fo EZMLM addreses.

Consider a recipient address like 'foo@mydomain.com. A VERP address would like 'foo-bar@mydomain.com. In order to accept emails for plain and VERP addresses you have to include:

as recipient addresses. The dash '-' tells the recipients extension to accept all VERP addreses. Of course, generic full qualified mail addreses with a dash a recognized as well.

The 'dash' character need to be substituted with the one defined in conf-break during setup.

4.1.2 SRS Support

In case SRS is used, bounced SRS messages need to be considered as well. For each domain (say: mydomain.com) - to be subject of SRS - include a generic entry for the recipients extension:

Result: The recipients extension will recognise this address to be acceptable. It should be noted, that the extension character needs to comply to your settings.

4.1.3 Customization

The Recipients mechanism needs no customization except for the following circumstances:

  1. Compile-time options:
    • Your Qmail address extension string is not '-'. This can be modified in conf-break.
    • If in case in a PAM lookup the PAM returns with a '111', the email will rejected for temporary reasons. If however, you disable #define PAM111421 in qmail-smtpd.c, the email will be accepted in case of a PAM failure.
  2. Run-time options:
    • Since some MTAs (namely Exchange) interprete a SMTP 450 reply to try to deliver the email even more frequently, the default behavior of qmail-smtpd is now to issue a 550 SMTP reply resulting in a "550, sorry no mailbox by that name (#5.7.1)" message. The advantage is, that a potential Sender receives a quick bounce with the propper reason, instead a deferred one. The disadvantage is, that this might be used for address harvesting
    • However, setting the environment variable RECIPIENTS450 in case the Recipients check is negative, qmail-smtpd issues the error: "450 sorry, mailbox currently unavailable (#4.2.1)".

4.1.4 Smart Rejections/Tarpitting Reloaded

Defining a TARPITCOUNT can be used to terminate the SMTP session if the number of invalid Recipients ("Rcpt to:") exceeds the TARPITCOUNT. Unlike the typical tarpitting mechanism, this is a hard limit (Smart Rejection).

However, defining a TARPITCOUNT together with a TARPITDELAY < 0 acts as 'tarpitting reloaded':

4.1.5 Operation and Results

With the Recipients mechanism qmail-smtpd will act for none-RELAYCLIENTs as follows

a) The domain part of the Recipient address is checked against rcpthosts/morercpthosts.cdb and accepted if either it matches or the domain is not provided.
b) the Recipient address is checked against all readable sources as listed in control/recipients and accepted if
  • either the entire address matches,
  • a VERP-part of the address matches,
  • the domain part of address is wildcarded or,
  • for the unqualified address an entry exist in the form 'user@localhost', or
  • perhaps, the fall-through statement '!*' is finally provided.

In any other case, a SMTP temporary failure protocol error is issued to the client saying:

4.2 Setting up control/recipients

The recipients lookup is triggered by the recipient domain, thus is domain-specific. You can specify which lookup is performed
per domain within control/recipients. Consider the following:

The Recipients mechanism can be used in a 'fail-closed' or 'fail-open' mode for the domains included in control/recipients.
Typically the recipient check is done 'fail-closed', thus if all queries are negative, the incoming email with this recipient address will be rejected.

A 'fail-open' behaviour can be achieved adding '!*' as last statement in control/recipients. Thus, emails for domains not listed in control/recipients will finally be accepted.

Method Operation
Legacy Put 'recipients.cdb' into control/recipients. This is a backward compatible mode
Per Domain cdbs Put 'example.com:example.cdb' in control/recipients and you advise the Recipients mechanism to do a per-domain lookup
Global cdbs Use '*:users/recipients.cdb' in control/recipients. This is equivalent to (1.)
Per Virtual Domain Put 'example.com|bin/qmail-vmailuser' into control/recipients and the Recipients mechanism will use qmail-vmailuser after the "|" to check the existence of the provided RCPT TO for this particular Domain
Global Virtual Domain Put '*|bin/qmail-vmailuser' into control/recipients and the Recipients mechanism will use use qmail-vmailuser after the "|" to check the existence of the provided RCPT TO
LDAP PAM Put 'example.com|ldap_pam.pl <ldaphost>' into control/recipients and the Recipients mechanism will use the program defined after the "|" to check the existence of the provided RCPT TO for this particular Domain
Gobal LDAP PAM Put '*|ldapam myldapserver' into control/recipients and you delegate the entire verification of the RCPT TO to the program in charge
Wildcarded domain Prepend the domain name with a '!' and emails for this domain will be entirely accepted: '!localhost'
Pass-Thru for unlisted domains Use '!*' as last statement in control/recipients

Lines in control/recipients starting with a '#' are not evaluated, thus are treated as comment lines.

4.3 Generating a cdb with Recipient addresses

Build a list of recipients (with full qualified addresses):

Verify that list to be found under users/recipients.
If you have a different Qmail home directory, modify the above scripts.
You may need to change "localhost" in the above scripts to the real hostname.

4.4 Checkpassword compatible PAMs for Recipients

Recipient's PAM mechanism allows to include any Checkpassword compliant modules. Currently, I ship which s/qmail the following native PAMs:

In the directory ./scripts you will find some undocumented PAM scripts:

Note: The qmail-vmailuser replaces the above Virtual Domain scripts; which are are deprecated. In the directory ./addons you will find the C program checkpassword-ldap.c which can be used as PAM as well.

4.4.1 Requirements for a Checkpassword compatible PAM

The checkpassword API is defined in:

http://cr.yp.to/checkpwd/interface.html

and typically consists of the string:

username\0password\0timestamp\0otherdata\0

written to file descriptor 3 (FD 3) to be read by the checkpassword compatible PAM.

For email address (recipient) verification, we replace

username\0

with

email-address\0

ie.

recipient@domain.tld\0

The attached PERL ldap_mail.pl serves as a sample.

Note: The PAM has to be either in the Unix $PATH or explicitely defined in control/recipients.

4.4.2 Calling a PAM from the recipients file

The general syntax invoking a PAM from control/recipients follows the principals invoking a cdb. However, the triggering token is a '|' and the PAM may be accompanied by at most five (5) arguments separated by white spaces:

@mydomain.com|/usr/local/bin/ldap_pam.pl -h:ldap.mydomain.com -d:admin -p:secret customer.com|bin/qmail-smtpam exchange.customer.com global.net|/usr/local/bin/ldapam -h:ldap.global.net -d:admin -p:topsecret !*

4.4.3 The ldap_pam.pl

The ldap_pam.pl is a PERL script using the PERL library 'Net::LDAP' from http://www.cpan.org while providing simple and TLS encrypted (strong) binds to an LDAP directory server.

Syntax of the ldap_pam.pl:

ldpap_pam.pl -h:host:port -d:DN -w:password -b:base -s:scope -c:certificate -l(ogging) -ll

The ldap_pam.pl shall be considered as prototype only. Due to the large variety of LDAP servers and possibilities to store the information into and to fetch it from the directory, there is the need for specific customization. Logging might be activated in two steps.

Fetching the RCPT TO: information from the LDAP directory requires some customization for the LDAP server:

4.4.4 qmail-smtpam

The additional module qmail-smtpam can be considered as stripped-down version of qmail-remote providing the first steps of the email dialoge using the standard Helo message, a Nullsender Mail From: address, and finally the provided the Rcpt To: address available on FD 3.

Once it receives the SMTP return code, it exits either with '0' in case of a '250' positive reply, a '1' in case of a failure, or a '111' realizing communication problems.

qmail-smtpam thus uses a standard SMTP dialoge without advanced features, though complying to RFC 821 while using the canonical name for host to connect to.

4.4.5 Virtualdomain PAMs

Beginning with s/qmail 3.2.16 the possibility is offered to verify for virtual users defined within

The existence of a respective user directory in the specified (virtual) domain is verified against the information taken from the (Rcpt to:) email address (on FD 3). Thus, neither quota nor any disabled checks are done; which is the responsibility of the final delivery program.

In case, the virtual domain owner's directory is not found by default beneath /home you can specify a particular home directory. Recipient addresses are recognized respecting the case, once the optional argument -C is given.

Now, with qmail-vmailuser a common and unified 'C' module is available with superseeds the previous scripts:

Don't use them anymore.