The simplest way to add SMTP transaction delays is to append a
delay
control to the final
accept
statement in each of the ACLs we have
declared, as follows:
accept delay = 20s
In addition, you may want to add progressive delays in the
deny
statement pertaining to invalid
recipients (“unknown user”) within acl_rcpt_to. This is to slow down dictionary
attacks. For instance:
deny message = unknown user !verify = recipient/callout=20s,defer_ok,use_sender delay = ${eval:$rcpt_fail_count*10 + 20}s
It should be noted that there is no point in imposing a delay in acl_data, after the message data has been received. Ratware commonly disconnect at this point, before even receiving a response from your server. In any case, whether or not the client disconnects at this point has no bearing on whether Exim will proceed with the delivery of the message.
If you are like me, you want to be a little bit more selective about which hosts you subject to SMTP transaction delays. For instance, as described earlier in this document, you may decide that a match from a DNS blacklist or a non-verifiable EHLO/HELO greeting are not conditions that by themselves warrant a rejection - but they may well be sufficient triggers for transaction delays.
In order perform selective delays, we want move some of the checks that we previously did in acl_rcpt_to to earlier points in the SMTP transaction. This is so that we can start imposing the delays as soon as we see any sign of trouble, and thereby increase the chance of causing synchronization errors and other trouble for ratware.
Specifically, we want to:
Move the DNS checks to acl_connect.
Move the Hello checks to acl_helo. One exception: We cannot yet check for a missing Hello greeting at this point, because this ACL is processed in response to an EHLO or HELO command. We will do this check in the acl_mail_from ACL.
Move the Sender Address Checks checks to acl_mail_from.
However, for reasons described above, we do not want to
actually reject the mail until after the RCPT
TO: command. Instead, in the earlier ACLs, we
will convert the various deny
statements
into warn
statements, and use Exim's
general purpose ACL variables to store any error messages or
warnings until after the RCPT TO:
command. We do that as follows:
If we decide to reject the delivery, we store an error
message to be used in the forthcoming
550 response in
$acl_c0
or $acl_m0
:
If we identify the condition before a mail delivery
has started (i.e. in
acl_connect or
acl_helo), we use the
connection-persistent variable
$acl_c0
Once a mail transaction has started (i.e. after the
MAIL FROM: command), we copy any
contents from $acl_c0
into the
message-specific variable $acl_m0
,
and use the latter from this point forward. This
way, any conditions identified in this particular
message will not affect any subsequent messages
received in the same connection.
Also, we store a corresponding log
message in $acl_c1
or
$acl_m1
, in a similar manner.
If we come across a condition that does not warrant an
outright rejection, we only store a warning message in
$acl_c1
or $acl_m1
.
Once a mail transaction has started (i.e. in acl_mail_from), we add any content in
this variable to the message header as well.
If we decide to accept a message
without regard to the results of any subsequent checks
(such as a SpamAssassin scan), we set a flag in
$acl_c0
or $acl_m0
, but
$acl_c1
and $acl_m1
empty.
At the beginning of every ACL to and including acl_mail_from, we record the current
timestamp in $acl_m2
. At the end of the
ACL, we use the presence of $acl_c1
or
$acl_m1
to trigger a SMTP transaction
delay until a total of 20 seconds has elapsed.
The following table summarizes our use of these variables:
Table A.1. Use of ACL connection/message variables
Variables: | $acl_[cm]0 unset | $acl_[cm]0 set |
---|---|---|
$acl_[cm]1 unset | (No decision yet) | Accept the mail |
$acl_[cm]1 set | Add warning in header | Reject the mail |
As an example of this approach, let us consider two checks that we do in response to the Hello greeting; one that will reject mails if the peer greets with an IP address, and one that will warn about an unverifiable name in the greeting. Previously, we did both of these checks in acl_rcpt_to - now we move them to the acl_helo ACL.
acl_helo: # Record the current timestamp, in order to calculate elapsed time # for subsequent delays warn set acl_m2 = $tod_epoch # Accept mail received over local SMTP (i.e. not over TCP/IP). # We do this by testing for an empty sending host field. # Also accept mails received from hosts for which we relay mail. # accept hosts = : +relay_from_hosts # If the remote host greets with an IP address, then prepare a reject # message in $acl_c0, and a log message in $acl_c1. We will later use # these in a "deny" statement. In the mean time, their presence indicate # that we should keep stalling the sender. # warn condition = ${if isip {$sender_helo_name}{true}{false}} set acl_c0 = Message was delivered by ratware set acl_c1 = remote host used IP address in HELO/EHLO greeting # If HELO verification fails, we prepare a warning message in acl_c1. # We will later add this message to the mail header. In the mean time, # its presence indicates that we should keep stalling the sender. # warn condition = ${if !def:acl_c1 {true}{false}} !verify = helo set acl_c1 = X-HELO-Warning: Remote host $sender_host_address \ ${if def:sender_host_name {($sender_host_name) }}\ incorrectly presented itself as $sender_helo_name log_message = remote host presented unverifiable HELO/EHLO greeting. # # ... additional checks omitted for this example ... # # Accept the connection, but if we previously generated a message in # $acl_c1, stall the sender until 20 seconds has elapsed. accept set acl_m2 = ${if def:acl_c1 {${eval:20 + $acl_m2 - $tod_epoch}}{0}} delay = ${if >{$acl_m2}{0}{$acl_m2}{0}}s
Then, in acl_mail_from we transfer the
messages from $acl_c{0,1}
to
$acl_m{0,1}
. We also add the contents of
$acl_c1
to the message header.
acl_mail_from: # Record the current timestamp, in order to calculate elapsed time # for subsequent delays warn set acl_m2 = $tod_epoch # Accept mail received over local SMTP (i.e. not over TCP/IP). # We do this by testing for an empty sending host field. # Also accept mails received from hosts for which we relay mail. # accept hosts = : +relay_from_hosts # If present, the ACL variables $acl_c0 and $acl_c1 contain rejection # and/or warning messages to be applied to every delivery attempt in # in this SMTP transaction. Assign these to the corresponding # $acl_m{0,1} message-specific variables, and add any warning message # from $acl_m1 to the message header. (In the case of a rejection, # $acl_m1 actually contains a log message instead, but this does not # matter, as we will discard the header along with the message). # warn set acl_m0 = $acl_c0 set acl_m1 = $acl_c1 message = $acl_c1 # # ... additional checks omitted for this example ... # # Accept the sender, but if we previously generated a message in # $acl_c1, stall the sender until 20 seconds has elapsed. accept set acl_m2 = ${if def:acl_c1 {${eval:20 + $acl_m2 - $tod_epoch}}{0}} delay = ${if >{$acl_m2}{0}{$acl_m2}{0}}s
All the pertinent changes are incorporated in the Final ACLs, to follow.