在这里,我们在我们的外发邮件中实现 信封发件人签名,并在接受传入的 “退信” (即没有信封发件人的邮件)之前检查这些签名。
来自您的主机的外发邮件的信封发件人地址将按如下方式修改
sender=recipient=recipient.domain=hash@sender.domain |
然而,由于此方案可能会产生意想不到的后果(例如,在邮件列表服务器的情况下),我们使其对您的用户是可选的。只有当我们在发件人的主目录中找到名为 “.return-path-sign” 的文件,并且只有当我们发送到的域与该文件中的域匹配时,我们才会签署外发邮件的信封发件人地址。如果文件存在但为空,则所有域都匹配。
同样,只有当收件人的主目录中存在相同的文件时,我们才要求在传入的 “退信” 消息(即没有信封发件人的消息)中签名收件人地址。用户可以通过其用户特定的白名单将特定主机从此检查中豁免,如 豁免转发邮件 中所述。
此外,由于此方案除了 ACL 之外,还涉及到对路由器和传输的调整,因此我们没有将其包含在后续的 最终 ACL 中。如果您能够按照与这些部分相关的说明进行操作,您也应该能够添加此处描述的 ACL 部分。
首先,我们创建一个 Exim 传输,它将用于签署远程传递的信封发件人。
remote_smtp_signed: debug_print = "T: remote_smtp_signed for $local_part@$domain" driver = smtp max_rcpt = 1 return_path = $sender_address_local_part=$local_part=$domain=\ ${hash_8:${hmac{md5}{SECRET}{${lc:\ $sender_address_local_part=$local_part=$domain}}}}\ @$sender_address_domain |
发件人地址的 “本地部分” 现在由以下组件组成,以等号 (“=”) 分隔
发件人的用户名,即原始本地部分,
收件人地址的本地部分,
收件人地址的域部分,
通过以下方式生成的对此发件人/收件人组合唯一的字符串
使用 Exim 的${hmac{md5}...}函数以及在main部分中声明的SECRET进行加密,[1]
使用 Exim 的${hash...}函数将结果哈希为 8 个小写字母。
如果您需要对 “智能主机” 的传递进行身份验证,请在此处添加适当的hosts_try_auth行。(从您现有的智能主机传输中获取它)。
在当前处理您的外发邮件的现有路由器之前添加一个新的路由器。此路由器将对远程传递使用上述传输,但前提是发件人的主目录中存在文件 “.return-path-sign”,并且收件人的域与该文件中的域匹配。例如,如果您通过互联网直接将邮件发送到最终目的地
# Sign the envelope sender address (return path) for deliveries to # remote domains if the sender's home directory contains the file # ".return-path-sign", and if the remote domain is matched in that # file. If the file exists, but is empty, the envelope sender # address is always signed. # dnslookup_signed: debug_print = "R: dnslookup_signed for $local_part@$domain" driver = dnslookup transport = remote_smtp_signed senders = ! : * domains = ! +local_domains : !+relay_to_domains : \ ${if exists {/home/$sender_address_local_part/.return-path-sign}\ {/home/$sender_address_local_part/.return-path-sign}\ {!*}} no_more |
或者如果您使用智能主机
# Sign the envelope sender address (return path) for deliveries to # remote domains if the sender's home directory contains the file # ".return-path-sign", and if the remote domain is matched in that # file. If the file exists, but is empty, the envelope sender # address is always signed. # smarthost_signed: debug_print = "R: smarthost_signed for $local_part@$domain" driver = manualroute transport = remote_smtp_signed senders = ! : * route_list = * smarthost.address host_find_failed = defer domains = ! +local_domains : !+relay_to_domains : \ ${if exists {/home/$sender_address_local_part/.return-path-sign}\ {/home/$sender_address_local_part/.return-path-sign}\ {!*}} no_more |
根据您的需要添加其他选项(例如same_domain_copy_routing = yes),可能仿照您现有的路由器。
请注意,我们不将此路由器用于没有信封发件人地址的邮件 - 我们不想篡改这些邮件! [2]
接下来,您需要告诉 Exim,与上述格式匹配的传入收件人地址应传递到由第一个等号 (“=”) 之前的部分标识的邮箱。为此,您需要尽早插入一个redirect路由器,在您的配置文件的routers部分中 - 在任何其他与本地传递相关的路由器(例如 系统别名 路由器)之前
hashed_local: debug_print = "R: hashed_local for $local_part@$domain" driver = redirect domains = +local_domains local_part_suffix = =* data = $local_part@$domain |
包含等号的收件人地址将被重写,以便剥离本地部分中等号后面的部分。然后,所有路由器将再次被处理。
此方案的最后一部分是告诉 Exim,发送到具有此签名的有效收件人地址的邮件应始终被接受,并且如果收件人已选择加入此方案,则应拒绝其他没有 NULL 信封发件人的消息。在任何一种情况下都不应进行灰名单。
以下代码片段应放置在 acl_rcpt_to 中,在任何 SPF 检查、灰名单和/或最终的accept语句之前
# Accept the recipient addresss if it contains our own signature. # This means this is a response (DSN, sender callout verification...) # to a message that was previously sent from here. # accept domains = +local_domains condition = ${if and {{match{${lc:$local_part}}{^(.*)=(.*)}}\ {eq{${hash_8:${hmac{md5}{SECRET}{$1}}}}{$2}}}\ {true}{false}} # Otherwise, if this message claims to be a bounce (i.e. if there # is no envelope sender), but if the receiver has elected to use # and check against envelope sender signatures, reject it. # deny message = This address does not match a valid, signed \ return path from here.\n\ You are responding to a forged sender address. log_message = bogus bounce. senders = : postmaster@* domains = +local_domains set acl_m9 = /home/${extract{1}{=}{${lc:$local_part}}}/.return-path-sign condition = ${if exists {$acl_m9}{true}} |
当向对消息标头中的地址执行回调验证的主机发送邮件时,您将遇到问题,例如在您的外发邮件的From字段中提供的地址。此处的deny语句将有效地对此类验证尝试给出否定响应。
因此,您可能希望将最后一个deny语句转换为warn语句,将拒绝消息存储在$acl_m0中,并在 DATA 命令之后执行实际拒绝,方式与之前描述的类似
# Otherwise, if this message claims to be a bounce (i.e. if there # is no envelope sender), but if the receiver has elected to use # and check against envelope sender signatures, store a reject # message in $acl_m0, and a log message in $acl_m1. We will later # use these to reject the mail. In the mean time, their presence # indicate that we should keep stalling the sender. # warn senders = : postmaster@* domains = +local_domains set acl_m9 = /home/${extract{1}{=}{${lc:$local_part}}}/.return-path-sign condition = ${if exists {$acl_m9}{true}} set acl_m0 = The recipient address <$local_part@$domain> does not \ match a valid, signed return path from here.\n\ You are responding to a forged sender address. set acl_m1 = bogus bounce for <$local_part@$domain>. |
此外,即使收件人已选择在其外发邮件中使用信封发件人签名,他们也可能希望将特定主机从此签名要求中豁免,即使邮件没有信封发件人地址。这对于特定的邮件列表服务器可能是必需的,有关详细信息,请参阅关于 信封发件人签名 的讨论。
[1] | 如果您认为这是小题大做,我表面上也会倾向于同意。在本文档的早期版本中,我只是简单地使用${hash_8:SECRET=....}来生成签名的最后一个组件。然而,有了这个,从技术上来说,如果对 Exim 的${hash...}函数有一定的了解,并有一些发送给不同收件人的外发邮件样本,就有可能伪造签名。Matthew Byng-Maddic<mbm (at) colondot.net>注释: 您正在编写一份您期望很多人直接复制的文档。鉴于此,克尔克霍夫原则开始适用,并且您的所有秘密都应该在密钥中。如果密钥可以被逆向推导出来,就像几个返回路径似乎可能的那样,那么垃圾邮件发送者就可以再次开始从该域发出有效的返回路径,而您又回到了起点。[...] 最好,在我看来,从一开始就使其强大。 |
[2] | 在上面的示例中,senders条件实际上是冗余的,因为文件/home//.return-path-sign不太可能存在。但是,为了清晰起见,我们使条件明确化。 |