A.5. 添加 SMTP 事务延迟

A.5.1. 简单方法

添加 SMTP 事务延迟的最简单方法是附加一个delay控制到最终的accept语句,在我们声明的每个 ACL 中,如下所示

  accept
    delay = 20s

此外,您可能希望在deny语句中添加渐进延迟,该语句与 acl_rcpt_to 内的无效收件人("unknown user")有关。 这是为了减慢字典攻击的速度。 例如

  deny
    message     = unknown user
    !verify     = recipient/callout=20s,defer_ok,use_sender
    delay       = ${eval:$rcpt_fail_count*10 + 20}s

应该注意的是,在接收到消息数据后,在 acl_data 中施加延迟是没有意义的。 恶意软件通常在此处断开连接,甚至在收到来自您服务器的响应之前。 在任何情况下,客户端是否在此处断开连接与 Exim 是否将继续传递消息无关。

A.5.2. 选择性延迟

如果您像我一样,您希望对哪些主机施加 SMTP 事务延迟更具选择性。 例如,如本文档前面所述,您可能会认为来自 DNS 黑名单的匹配或不可验证的 EHLO/HELO 问候本身不足以保证拒绝 - 但它们很可能足以触发事务延迟。

为了执行选择性延迟,我们希望将我们之前在 acl_rcpt_to 中执行的一些检查移动到 SMTP 事务的更早的点。 这样做是为了我们可以在看到任何麻烦迹象时立即开始施加延迟,从而增加导致恶意软件同步错误和其他麻烦的机会。

具体来说,我们想要

但是,由于上述原因,我们不希望在 RCPT TO: 命令之后才实际拒绝邮件。 相反,在较早的 ACL 中,我们将转换各种deny语句为warn语句,并使用 Exim 的通用 ACL 变量来存储任何错误消息或警告,直到 RCPT TO: 命令之后。 我们这样做如下

下表总结了我们对这些变量的使用

表 A-1. ACL 连接/消息变量的使用

变量$acl_[cm]0 未设置$acl_[cm]0 设置
$acl_[cm]1 未设置(尚未决定)接受邮件
$acl_[cm]1 设置在标头中添加警告拒绝邮件

作为这种方法的一个例子,让我们考虑我们在响应 Hello 问候时执行的两个检查; 一个是如果对等方使用 IP 地址问候,则拒绝邮件,另一个是警告问候中不可验证的名称。 以前,我们在 acl_rcpt_to 中进行了这两项检查 - 现在我们将它们移动到 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

然后,在 acl_mail_from 中,我们将消息从$acl_c{0,1}转移到$acl_m{0,1}。 我们还将$acl_c1的内容添加到消息标头。

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

所有相关的更改都包含在接下来的最终 ACL 中。