邮件交换器的垃圾邮件过滤

如何在传入的 SMTP 事务中拒绝垃圾邮件。

Tor Slettnes

编辑人

Joost De Cock

Devdas Bhagat

Tom Wright

版本 1.0 -- 发布版


目录
简介
1. 本文档的目的
2. 读者
3. 本文档的新版本
4. 修订历史
5. 致谢
6. 反馈
7. 翻译
8. 版权信息
9. 您需要什么?
10. 本文档中使用的约定
11. 本文档的组织结构
1. 背景
1.1. 为什么在 SMTP 事务期间过滤邮件?
1.2. 好、坏、丑
1.3. SMTP 事务
2. 技术
2.1. SMTP 事务延迟
2.2. DNS 检查
2.3. SMTP 检查
2.4. 灰名单
2.5. 发件人授权方案
2.6. 消息数据检查
2.7. 阻止附带垃圾邮件
3. 注意事项
3.1. 多个传入邮件交换器
3.2. 阻止访问其他 SMTP 服务器
3.3. 转发邮件
3.4. 用户设置和数据
4. 问答
A. Exim 实现
A.1. 先决条件
A.2. Exim 配置文件
A.3. 选项和设置
A.4. 构建 ACL - 第一步
A.5. 添加 SMTP 事务延迟
A.6. 添加灰名单支持
A.7. 添加 SPF 检查
A.8. 添加 MIME 和文件类型检查
A.9. 添加防病毒软件
A.10. 添加 SpamAssassin
A.11. 添加信封发件人签名
A.12. 仅接受真实用户的退信
A.13. 豁免转发邮件
A.14. 最终 ACL
术语表
B. GNU 通用公共许可证
B.1. 序言
B.2. 复制、分发和修改的条款和条件
B.3. 如何将这些条款应用于您的新程序
表格列表
1. 排版和用法约定
1-1. 简单 SMTP 对话
A-1. ACL 连接/消息变量的使用

简介

1. 本文档的目的

本文档讨论了各种高效且影响较小的方法,用于在邮件交换器 (MX 主机) 中传入的 SMTP 事务期间清除垃圾邮件和恶意软件,并特别强调消除所谓的附带垃圾邮件

讨论本质上是概念性的,但提供了使用 Exim MTA 和其他特定软件工具的示例实现。整个过程中表达了各种其他偏见。


2. 读者

目标读者是邮件系统管理员,他们已经熟悉 SMTP、MTA/MDA/MUA、DNS/rDNS 和 MX 记录等首字母缩略词。如果您是正在为您的邮件阅读器(例如 Evolution、Thunderbird、Mail.app 或 Outlook Express)寻找垃圾邮件过滤解决方案的最终用户,则本文档适合您;但您可能希望将您的域名(公司、学校、ISP...)的邮件系统管理员指向它的存在。


3. 本文档的新版本

可以在 http://slett.net/spam-filtering-for-mx/ 找到本文档的最新版本。请定期回来查看更正和补充。


4. 修订历史

修订历史
修订 1.02004-09-08修订人:TS
首次公开发布。
修订 0.182004-09-07修订人:TS
纳入了来自 Tom Wright 的第二次语言审核。
修订 0.172004-09-06修订人:TS
纳入了来自 Tom Wright 的语言审核。
修订 0.162004-08-13修订人:TS
纳入了来自 Devdas Bhagat 的第三轮更改。
修订 0.152004-08-04修订人:TS
纳入了来自 Devdas Bhagat 技术审查的第二轮更改。
修订 0.142004-08-01修订人:TS
纳入了来自 Devdas Bhagat 的技术审查意见/更正。
修订 0.132004-08-01修订人:TS
纳入了来自 Joost De Cock 的技术审查。
修订 0.122004-07-27修订人:TS
将“关于争议的说明”替换为更主观的“好、坏、丑”部分。 还重写了关于 DNS 黑名单的文本。 Seymour J. Metz 的一些更正。
修订 0.112004-07-19修订人:TS
纳入了 Rick Stewart 关于 RMX++ 的评论。 交换了“技术”和“注意事项”的顺序。 Exim 实现中的一些排版修复。
修订 0.102004-07-16修订人:TS
添加了 <?dbhtml..?> 标签来控制生成的 HTML 文件名 - 应防止来自 google 等的断开链接。 交换了“转发邮件”和“用户设置”的顺序。 Tony Finch 对贝叶斯过滤器的更正; 根据 Johannes Berg 的建议,注释掉了对 Subject:、Date: 和 Message-ID: 标头的检查; 根据 Alan Flavell 的建议,从 SMTP 延迟中减去了处理时间。
修订 0.092004-07-13修订人:TS
详细说明了信封发件人签名和邮件列表服务器的问题,以及一种针对每个用户的每个主机/域使此类签名成为可选的方案。 将“注意事项”部分移出作为一个单独的章节; 添加了子部分“阻止访问其他 SMTP 服务器”、“用户设置”和“转发邮件”。 纳入了 Matthew Byng-Maddick 关于用于生成这些签名的机制的评论、Chris Edwards 关于发件人回拨验证的评论以及 Hadmut Danisch 关于 RMX++ 和其他主题的评论。 更改了许可条款(GPL 而不是 GFDL)。
修订 0.082004-07-09修订人:TS
Exim 实现的其他工作:根据 Tollef Fog Heen 的建议,添加了关于每个用户 SpamAssassin 设置和数据的部分。 通过 Exiscan-ACL 添加了 SPF 检查。 Sam Michaels 的更正。
修订 0.072004-07-08修订人:TS
更正了 Exim 信封发件人签名示例,并添加了用户“选择加入”此功能的支持,根据 Christian Balzer 的建议。
修订 0.062004-07-08修订人:TS
纳入了 Johannes Berg 的 Exim/MySQL 灰名单实现和各种更正。 将“发件人授权方案”向上移动两层,成为技术章节中的顶级部分。 在 DATA 之后添加了针对 NULL 空信封发件人的灰名单。 添加了与 Exim 示例匹配的 SpamAssassin 配置。 纳入了来自 Dominik Ruff、Mark Valites、Supernews 的“Andrew”的更正。
修订 0.052004-07-07修订人:TS
暂时消除了(空的)Sendmail 实现,以便继续进行最终审查过程。
修订 0.042004-07-06修订人:TS
稍微重新组织了布局:将“SMTP 时间过滤”、“SMTP 简介”和“注意事项”合并为一个“背景”章节。 将 Exim 实现中之前的“构建 ACL”部分拆分为顶级部分。 向 SPF 添加了备用发件人授权方案:Microsoft 电子邮件发件人 ID 和 RMX++。 纳入了 Ken Raeburn 的评论。
修订 0.032004-07-02修订人:TS
添加了关于多个传入邮件交换器的讨论; 与发件人回拨验证相关的次要更正。
修订 0.022004-06-30修订人:TS
添加了 Exim 实现作为附录
修订 0.012004-06-16修订人:TS
初始草案。


5. 致谢

许多人提供了反馈、更正和贡献,如修订历史中所示。 谢谢!

以下是一些向本文档提供工具和想法的人员和团体,排名不分先后


6. 反馈

我很乐意听到您在使用本文档中概述的技术方面的经验,以及您可能有的任何其他意见、问题、建议和/或贡献。 请给我发送电子邮件至.

如果您能够为其他邮件传输代理(例如 Sendmail 或 Postfix)提供实现,请告诉我。


7. 翻译

目前还没有翻译。如果您想创建一个翻译,请告诉我。


8. 版权信息

版权所有 © 2004 Tor Slettnes。

本文档是自由软件;您可以根据自由软件基金会发布的 GNU 通用公共许可证的条款重新发布和/或修改它;无论是许可证的第 2 版,还是(由您选择)任何更高版本。

本文档的发布是为了实用性,但不作任何担保;甚至没有对适销性或特定用途适用性的暗示保证。有关更多详细信息,请参见 GNU 通用公共许可证。许可证的副本包含在附录 B中。

如果您想知道为什么为本书选择此许可证,请阅读GNU宣言

本书中使用的徽标、商标和符号均为其各自所有者的财产。


9. 您需要什么?

本文档中描述的技术需要系统访问您接收电子邮件的互联网域的入站邮件交换器。本质上,您需要能够安装软件和/或修改该系统上的邮件传输代理的配置文件。

虽然本文档中的讨论本质上是概念性的,可以合并到许多不同的 MTA 中,但提供了一个示例 Exim 4 实现。 反过来,此实现整合了其他软件工具,例如SpamAssassin。 有关详细信息,请参见附录 A


10. 本文档中使用的约定

以下排版和使用约定出现在本文中

表 1. 排版和使用约定

文本类型含义
"引用的文本"来自人的引用,引用的计算机输出。
terminal view
从终端捕获的字面计算机输入和输出,通常以浅灰色背景呈现。
command可以在命令行上输入的命令的名称。
VARIABLE变量的名称或指向变量内容的指针,例如$VARNAME.
option命令的选项,例如"命令ls-a选项"
argument命令的参数,例如"阅读man ls "

commandoptions arguments

命令概要或一般用法,在单独的行上。
filename文件或目录的名称,例如"更改为/usr/bin目录。"
Key要在键盘上按下的键,例如"键入Q退出"
Button要单击的图形按钮,例如确定按钮。
菜单->选择要从图形菜单中选择的选项,例如:"在浏览器中选择帮助->关于 Mozilla。"
Terminology重要术语或概念:"Linux内核是系统的核心。"
参见术语表链接到本指南中相关的题目。
作者 指向外部 Web 资源的可点击链接。

11. 本文档的组织

本文档分为以下章节

背景

SMTP 时间过滤和 SMTP 的一般介绍。

技术

在 SMTP 事务中阻止垃圾邮件的各种方法。

注意事项

与事务时间过滤有关的问题。

问题与解答

我尝试预测您的问题,然后回答它们。

附录 A中提供了一个示例 Exim 实现。


第 1 章。背景

在这里,我们介绍了在传入的 SMTP 事务期间过滤邮件的优势,而不是采用更传统的方法,将此任务卸载到邮件路由和传递阶段。我们还简要介绍了 SMTP 事务。


1.1. 为什么在 SMTP 事务期间过滤邮件?

1.1.1. 现状

如果您收到垃圾邮件,请举手。 保持举着。

如果您收到计算机病毒或其他恶意软件,也请举手。

如果您收到伪造的传递状态通知 (DSN),例如"消息无法传递""发现病毒""请确认传递"等,与您从未发送的消息相关,也请举手。 这被称为附带垃圾邮件

最后一种形式尤其麻烦,因为它比"标准"垃圾邮件或恶意软件更难清除,并且因为此类消息对于不具备解析消息标头的神力技能的收件人来说可能非常令人困惑。 在病毒警告的情况下,这通常会在收件人方面引起不必要的担忧; 更一般而言,一种常见的趋势是忽略所有此类消息,从而错过合法的 DSN。

最后,我希望那些由于垃圾邮件或病毒扫描器错误分类而丢失了合法邮件到大黑洞中的人抬起脚。

如果您之前站着并且仍然站着,我建议您可能没有完全意识到您的邮件发生了什么。 如果您一直在进行任何类型的垃圾邮件过滤,即使是通过手动将邮件移动到邮件阅读器中的垃圾箱,更不用说尝试使用原始过滤技术,例如 DNS 黑名单(SpamHaus、SPEWS、SORBS...),您很可能已经丢失了一些有效的邮件。


1.1.2. 原因

垃圾邮件,就像贪婪的许多其他产物一样,是一种社会疾病。 称之为富流感,或者你喜欢的任何东西; 低级生命形式试图摧毁一个更大的生态系统,如果成功,最终会毁掉他们自己的栖息地。

抛开更大的社会问题和哲学:你 - 邮件系统管理员 - 面临着非常具体和现实的生活困境,即找到一种方法来处理所有这些垃圾。

事实证明,邮件传输和传递软件的各个组件处理和委派邮件的传统方式存在一些限制。 在传统设置中,一个或多个邮件交换器接受大部分或所有传入邮件,这些邮件传递到域中的地址。 通常,它们随后将邮件转发到一个或多个内部机器以进行进一步处理,和/或传递到用户的邮箱。 如果这些服务器中的任何一个发现它无法执行请求的传递或功能,它会生成并将 DSN 返回到原始邮件中的发件人地址。

随着组织开始部署垃圾邮件和病毒扫描器,他们通常发现阻力最小的路径是将这些扫描器纳入消息传递路径,因为邮件从传入的邮件交换器传输到内部传递主机和/或软件。 例如,过滤垃圾邮件的常用方法是通过路由邮件通过 SpamAssassin 或其他软件,然后将其传递到用户的邮箱,和/或依赖于用户邮件用户代理中的垃圾邮件过滤功能。

此时处理被分类为垃圾邮件或病毒的邮件的选项有限

  • 您可以将传递状态通知返回给发件人。 问题是,几乎所有垃圾邮件和电子邮件传播的病毒都使用伪造的发件人地址传递。 如果您退回此邮件,它将总是发送给无辜的第三方 - 也许会警告瑞典的一位使用 Mac OS X 并且不太了解计算机的祖母,说她感染了 Blaster 蠕虫。 换句话说,您将生成附带垃圾邮件

  • 您可以将邮件丢弃到黑洞中,而不向发件人发送任何通知。 在误报的情况下,这是一个更大的问题,因为发件人和收件人都永远不会知道邮件发生了什么(或者在收件人的情况下,它是否曾经存在)。

  • 根据您的用户访问其邮件的方式(例如,如果他们通过 IMAP 协议访问它或使用基于 Web 的邮件阅读器,但如果他们通过 POP-3 检索它则不然),您也许可以将它归档到单独的垃圾邮件文件夹中 - 也许作为他们帐户设置中的一个选项。

    这可能是这三个选项中最好的一个。 即便如此,消息可能仍然有一段时间没有被看到,或者只是被忽略,因为收件人或多或少定期扫描并通过删除其“垃圾邮件”文件夹中的邮件。


1.1.3. 解决方案

正如您现在所猜测的那样,这个问题的唯一正确的解决方案是在 SMTP 对话期间从远程主机进行垃圾邮件和病毒过滤,因为邮件正在被您域的入站邮件交换器接收。 这样,如果邮件结果是不受欢迎的,您可以发出 SMTP 拒绝响应,而不是面对上述困境。 因此

  • 您将能够在 SMTP 事务的早期停止大多数垃圾邮件的传递,在收到实际消息数据之前,从而为您节省网络带宽和 CPU 处理。

  • 您将能够部署一些以后不可能实现的垃圾邮件过滤技术,例如SMTP 事务延迟灰名单

  • 您将能够在传递失败的情况下通知发件人(例如,由于收件人地址无效),而不会直接生成附带垃圾邮件

    我们将讨论如何避免由于拒绝从受信任的来源(例如邮件列表服务器或其他站点上的邮件帐户)转发的邮件而间接导致附带垃圾邮件[1]

  • 您将能够保护自己免受来自其他人的附带垃圾邮件的侵害(例如来自防病毒软件的伪造的"您有病毒"消息)。

好的,现在您可以放下双手了。 如果您之前站着,并且您的脚从您身下消失了,您现在也可以再次站起来了。


1.2. 好、坏、丑

一些过滤技术比其他技术更适合在 SMTP 事务期间使用。 有些只是比其他更好。 几乎所有人都拥有自己的支持者和反对者。

不用说,这些争议也扩展到此处描述的方法。 例如

  • 有些人认为DNS 检查仅根据他们的互联网服务提供商 (ISP) 来惩罚个人邮件发件人,而不是根据他们特定消息的优点。

  • 有些人指出,像SMTP 事务延迟灰名单这样的耗费资源的陷阱很容易被克服,并且随着时间的推移效果会降低,同时会继续降低合法邮件的服务质量。

  • 有些人认为,像 发件人授权方案 这样的机制,比如 发件人策略框架,给了 ISP 锁定其客户的方法,并且没有充分解决用户在不同网络之间漫游或将电子邮件从一个主机转发到另一个主机的问题。

我将避开大多数这些争议。相反,我将尝试提供可用技术的实用描述,包括它们可能产生的副作用,然后简单谈谈我使用其中一些技术的经验。

也就是说,目前使用的一些过滤方法,我特意不在本文档中提及。

  • 质询/应答系统(例如 TMDA)。这些系统不适合 SMTP 时间过滤,因为它们依赖于首先接受邮件,然后向 信封发件人 返回确认请求。因此,该技术不在本文档的范围内。 [2]

  • 贝叶斯过滤器。这些需要针对特定用户和/或特定语言进行专门训练。因此,这些通常也不适合在 SMTP 事务期间使用(但请参阅 用户设置和数据)。

  • 微支付计划 在世界上所有合法的邮件都使用虚拟邮票发送之前,并不真正适合用于消除垃圾邮件。(尽管在此期间,它们可以用于相反的目的 - 即,接受带有邮票的邮件,否则这些邮件将被拒绝)。

总的来说,我试图提供尽可能精确的技术,并尽最大努力避免 误报。人们的电子邮件对他们来说很重要,他们花费时间和精力来编写邮件。在我看来,故意使用拒绝大量合法邮件的技术或工具是对直接受影响的人和整个互联网的不尊重。 [3] 对于 SMTP 时间的系统级过滤尤其如此,因为最终接收者通常对用于过滤邮件的标准几乎没有或根本没有控制权。


1.3. SMTP 事务

SMTP 是用于在互联网上进行邮件传递的协议。有关该协议的详细描述,请参阅 RFC 2821,以及 Dave Crocker 的 互联网邮件架构简介。

邮件传递涉及连接主机(客户端)和接收主机(服务器)之间的 SMTP 事务。 在本次讨论中,连接主机是对等方,接收主机是您的服务器。

在典型的 SMTP 事务中,客户端发出 SMTP 命令,例如 EHLOMAIL FROM:RCPT TO:DATA。 您的服务器使用 3 位数字代码响应每个命令,指示该命令是否被接受 (2xx),是否受到临时故障或限制 (4xx) ,或彻底/永久失败 (5xx),后跟一些人类可读的解释。 这些代码的完整描述包含在 RFC 2821 中。

最佳情况下的 SMTP 事务通常包含以下相关步骤:

表 1-1. 简单的 SMTP 对话

客户端服务器

发起与服务器的 TCP 连接。

呈现 SMTP 标语 - 也就是以代码 220 开头的问候语,表示它已准备好使用 SMTP(或通常是 ESMTP,SMTP 的超集)进行通信。

220 your.f.q.d.n ESTMP...

通过 Hello 命令(HELO(现在已过时)或 EHLO)介绍自己,后跟它自己的 完全限定域名

EHLO peers.f.q.d.n

使用 250 响应接受此问候语。如果客户端使用了 Hello 命令的扩展版本 (EHLO),您的服务器知道它能够处理多行响应,因此通常会发回几行指示您的服务器提供的功能。

250-your.f.q.d.n Hello ...
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250-STARTTLS
250-AUTH
250 HELP

如果此响应中包含 PIPELINING 功能,则客户端可以从此刻起一次发出多个命令,而无需等待每个命令的响应。

通过指定 信封发件人 来启动新的邮件事务。

MAIL FROM:<sender@address>
		

发出 250 响应,指示发件人已被接受。

使用以下命令一次列出消息的 信封收件人

RCPT TO:<receiver@address>

向每个命令发出响应 (2xx4xx5xx,具体取决于是否接受、受到临时故障或拒绝传递给此收件人)。

发出 DATA 命令,指示它已准备好发送消息。

响应 354,指示该命令已被暂时接受。

传输消息,从符合 RFC 2822 的标头行开始(例如发件人, 收件人, 主题, 日期, 消息 ID)。 标头和正文用空行分隔。 为了指示消息的结尾,客户端在单独的一行上发送单个句点 (“.”)。

回复 250,指示该消息已被接受。

如果要传递更多消息,则发出下一个 MAIL FROM: 命令。 否则,它会说 QUIT,或者在极少数情况下,直接断开连接。

断开连接。


第 2 章。技术

在本章中,我们将研究在 SMTP 事务期间从远程主机中删除垃圾邮件的各种方法。 我们还将尝试预测部署这些技术的一些副作用。


2.1. SMTP 事务延迟

事实证明,阻止垃圾邮件更有效的方法之一是在入站 SMTP 对话期间施加事务延迟。 这是一种原始形式的延迟连接,请参阅:http://www.iks-jena.de/mitarb/lutz/usenet/teergrube.en.html

大多数垃圾邮件和几乎所有通过电子邮件传播的病毒都是通过专门的 SMTP 客户端软件直接传递到您的服务器的,这些软件经过优化,可以在很短的时间内发送大量邮件。 这种客户端通常被称为 鼠器

为了完成这项任务,鼠器作者通常会采取一些捷径,这些捷径,嗯,与 RFC 2821 规范有点 "分歧"。 鼠器的固有特性之一是它出了名的不耐烦,尤其是对于响应缓慢的邮件服务器。 他们可能会在服务器呈现初始 SMTP 标语之前发出 HELOEHLO 命令,和/或尝试在服务器宣布 PIPELINING 功能之前通过管道传输多个 SMTP 命令。

某些 邮件传输代理(例如 Exim)会自动将此类 SMTP 协议违规视为同步错误,并立即断开传入连接。 如果您碰巧正在使用这样的 MTA,您可能已经在您的日志文件中看到了很多这样的条目。 事实上,如果您在呈现初始 SMTP 标语之前执行任何耗时的检查(例如 DNS 检查),此类错误将频繁发生,因为鼠器客户端根本不花时间等待您的服务器启动 (要做的事情,要发送垃圾邮件的人)。

我们可以通过施加额外的延迟来提供帮助。 例如,您可以决定等待

  • 20 秒后呈现初始 SMTP 标语,

  • Hello(EHLOHELO)问候语后 20 秒,

  • MAIL FROM: 命令后 20 秒,以及

  • 每个 RCPT TO: 命令后 20 秒。

您会问 20 秒是从哪里来的。 为什么不是一分钟? 或者几分钟? 毕竟,RFC 2821 规定发送主机(客户端)应等待长达几分钟才能获得每个 SMTP 响应。 问题是,一些接收主机,特别是那些使用 Exim 的主机,可能会执行 发件人回拨验证 以响应传入的邮件传递尝试。 如果您或您的用户之一向这样的主机发送邮件,它将联系您域的 邮件交换器(MX 主机)并启动 SMTP 对话,以验证发件人地址。 此类 发件人回拨验证 的默认超时时间为 30 秒 - 如果您施加这么长的延迟,对等方的发件人回拨验证将失败,反过来,来自您/您的用户的原始邮件传递可能会被拒绝(通常是临时故障,这意味着邮件传递将在最终返回给发件人之前重试大约 5 天)。

换句话说,20 秒是您在开始干扰合法邮件传递之前可以拖延的时间。

如果您不喜欢对每个 SMTP 事务施加此类延迟(例如,您的站点非常繁忙并且机器资源不足),您可以选择使用“选择性”事务延迟。 在这种情况下,您可以施加延迟

  • 如果对等方的 DNS 信息存在问题(请参阅 DNS 检查)。

  • 在 SMTP 事务期间检测到一些麻烦迹象后(请参阅 SMTP 检查)。

  • 仅在您的 DNS 区域中编号最高的 MX 主机中,即优先级最低的邮件交换器。 通常,鼠器 专门针对这些主机,而合法的 MTA 将首先尝试编号较低的 MX 主机。

实际上,选择性地延迟事务处理可能是整合一些不太确定的检查的好方法,我们将在后续章节中讨论这些检查。你可能不希望仅仅基于SPEWS 黑名单的结果就直接拒绝邮件,但另一方面,它可能提供了足够强烈的麻烦迹象,至少你可以强制执行事务处理延迟。毕竟,合法的邮件传递不会受到影响,除了会受到轻微的延迟之外。

相反,如果你发现了垃圾邮件的确凿证据(例如通过某些SMTP检查),并且你的服务器可以承受,你可以选择施加较长时间的延迟,例如15分钟左右,然后最终拒绝传递[4]。除了在DNS黑名单和其他协作网络检查赶上来之前,尽可能地减缓垃圾邮件发送者接触更多人的速度之外,这样做几乎没有任何好处。换句话说,完全是出于你的利他主义。 :-)

就我个人而言,选择性事务处理延迟和由此产生的SMTP同步错误占我拒绝的传入传递尝试的近50%。这大致相当于说,仅通过SMTP事务处理延迟就阻止了近50%的传入垃圾邮件。

另请参见当垃圾邮件发送者适应...会发生什么


2.2. DNS 检查

可以直接从域名系统 (DNS) 中获取有关特定对等方完整性的一些指示,甚至在发出SMTP命令之前。 特别是,可以查询各种DNS黑名单,以查明特定IP地址是否已知违反或满足某些标准,并且可以使用简单的一对正向/反向(DNS/rDNS)查找作为主机总体完整性的模糊指标。

此外,SMTP对话期间呈现的各种数据项(例如Hello问候语中呈现的名称)一旦可用,就可以进行DNS验证。 有关这些项目的讨论,请参见下面的有关SMTP检查的部分。

不过,请注意。 DNS检查并不总是确定的(例如,所需的DNS服务器可能没有响应),并且并不总是指示垃圾邮件。 此外,如果你的站点非常繁忙,则它们在每个消息的处理时间方面可能会很昂贵。 也就是说,它们可以为日志记录目的和/或作为更全面的完整性检查的一部分提供有用的信息。


2.2.1. DNS 黑名单

DNS黑名单(DNSbl's,以前称为“实时黑洞列表”,以原始黑名单“mail-abuse.org”命名)构成了执行事务处理时间垃圾邮件阻止的最常用工具。 接收服务器在各种DNSbl区域(例如“dnsbl.sorbs.net”,“opm.blitzed.org”,“lists.dsbl.org”等)中执行对等方的IP地址的一个或多个rDNS查找。 如果找到匹配的DNS记录,则典型操作是拒绝邮件传递。 [5]

如果除了DNS地址(“A”记录)之外,你还查找条目的“TXT”记录,通常会收到对列表的单行描述,适合包含在SMTP拒绝响应中。 要尝试此操作,可以使用大多数Linux和UNIX系统上提供的“host”命令

host -t txt 2.0.0.127.dnsbl.sorbs.net

目前有数百个这样的列表可用,每个列表具有不同的列出标准以及不同的列出/取消列出策略。 有些列表甚至将多个列出标准组合到同一DNSbl中,并根据哪个标准影响所提供的地址,发出不同的数据以响应rDNS查找。 例如,针对的rDNS查找sbl-xbl.spamhaus.org对于SpamHaus工作人员认为直接属于垃圾邮件发送者及其提供商的IP地址,返回127.0.0.2,对于僵尸主机返回127.0.0.4响应,或者对于开放代理服务器返回127.0.0.6响应。

不幸的是,许多这些列表包含大量的IP地址块,这些地址块并不直接对所谓的违规行为负责,没有明确的列出/取消列出策略,和/或发布有关列出哪些地址的误导性信息[6]。 盲目信任此类列表通常会导致大量被称为附带损害的事件(不要与附带垃圾邮件混淆)。

因此,许多管理员宁愿以更细微的方式使用这些列表,而不是仅仅基于来自DNS黑名单的单个肯定响应来直接拒绝邮件传递。 他们可能会咨询多个列表,并为每个肯定响应分配一个“分数”。 如果给定IP地址的总分达到给定的阈值,则拒绝来自该地址的传递。 这就是诸如SpamAssassin(垃圾邮件扫描器)之类的过滤软件使用DNS黑名单的方式。

也可以将此类列表用作传入连接上SMTP事务处理延迟(又称“teergrubing”)的几个触发器之一。 如果主机在DNSbl中列出,则你的服务器将延迟其对等方发出的每个SMTP命令的响应,例如20秒。 其他几个标准可以用作此类延迟的触发器; 请参见有关SMTP事务处理延迟的部分。


2.2.2. DNS 完整性检查

使用DNS的另一种方法是对等方的IP地址执行反向查找,然后对生成的名称执行正向查找。 如果原始IP地址包含在结果中,则其DNS完整性已得到验证。 否则,连接主机的DNS信息无效。

如果如果你是DNS警察的激进成员,为你的个人域设置传入MX,并且不介意拒绝合法邮件,以此来给发件人留下深刻的印象,即他们需要要求自己的系统管理员清理其DNS记录,那么基于此标准拒绝邮件可能是一种选择。 对于其他人,DNS完整性检查的结果可能仅应用作更大的一组启发法中的一个数据点。 或者,如上所述,对配置错误的主机使用SMTP事务处理延迟可能不是一个坏主意。


2.3. SMTP 检查

一旦SMTP对话开始,就可以对远程主机呈现的命令和参数执行各种检查。 例如,你将需要确保Hello问候语中呈现的名称有效。

但是,即使你决定在SMTP事务处理的早期拒绝传递尝试,你可能也不希望立即执行实际拒绝。 相反,你可以使用SMTP事务处理延迟来阻止发件人,直到RCPT TO:之后,然后在此时拒绝邮件。

原因是某些老鼠软件不了解SMTP事务处理中的早期拒绝; 他们会一直尝试。 另一方面,如果RCPT TO:失败,则大多数会放弃。

此外,这提供了一个很好的机会来进行一些teergrubing


2.3.1. Hello (HELO/EHLO) 检查

根据RFC 2821,客户端发出的第一个SMTP命令应为EHLO(如果不支持,则为HELO),后跟其主要完全限定域名。 这称为Hello问候语。 如果没有有意义的FQDN可用,则客户端可以提供括在方括号中的其IP地址:“[1.2.3.4]”。 最后一种形式称为IPv4地址“字面量”表示法。

完全可以理解的是,老鼠软件很少在Hello问候语中显示自己的FQDN。 相反,来自老鼠软件的问候语通常试图隐藏发送主机的身份,和/或在消息头中生成令人困惑和/或误导性的“Received:”轨迹。 一些此类问候语的示例为

  • 不合格的名称(即没有句点的名称),例如收件人地址的“本地部分”(用户名)。

  • 纯IP地址(即不是IP字面量); 通常是你的地址,但也可以是随机地址。

  • 你的域名或服务器的FQDN。

  • 第三方域名,例如yahoo.comhotmail.com.

  • 不存在的域名或具有不存在的名称服务器的域名。

  • 完全没有问候语。


2.3.1.1. 简单的HELO/EHLO语法检查

其中一些违反RFC 2821的行为既容易检查,又能清楚地表明发送主机正在运行某种形式的老鼠软件。 你可以拒绝此类问候语 - 立即拒绝,或者例如在RCPT TO:命令之后拒绝。

首先,可以随意拒绝Hello问候语中的纯IP地址。 即使你希望慷慨地允许RFC 2821强制、推荐和建议的所有内容,你也会注意到,当IP地址代替名称出现时,应始终将其括在方括号中。 [7]

特别是,你可能希望向使用你的IP地址(或就此而言,你的主机名)介绍自己的主机发出措辞严厉的拒绝消息。 他们显然在撒谎。 也许你希望通过对这种问候语做出反应,使用极其长的SMTP事务处理延迟来阻止发件人; 比方说,几个小时。

就此而言,我自己的经验表明,互联网上没有合法站点使用IP地址字面量([x.y.z.w]表示法)向其他互联网站点介绍自己。 他们也不应该这样做; 所有直接在Internet上发送邮件的主机都应使用其有效的完全限定域名。 我遇到的唯一使用IP字面量的情况来自我的本地网络上的邮件用户代理,例如配置为使用我的服务器作为传出SMTP服务器(智能主机)的Ximian Evolution。 实际上,我只接受来自我自己的LAN的字面量。

你也可以拒绝不合格的主机名(没有句点的主机名)。 我发现这些很少(但并非从不 - 这算双重否定)是合法的。

同样,你可以拒绝包含无效字符的主机名。 对于Internet域,只有字母数字字符和连字符是有效字符; 不允许将连字符作为第一个字符。 (你可能还想将下划线视为有效字符,因为从配置错误但最终善意的Windows客户端中看到这种情况很常见)。

最后,如果你收到MAIL FROM:命令而没有先收到Hello问候语,那么,有礼貌的人会先问候。

在我的服务器上,我拒绝未通过这些语法检查的问候语。 但是,拒绝实际上直到RCPT TO:命令之后才会发生。 同时,我在每个SMTP命令(HELO/EHLOMAIL FROM:RCPT TO:)之后施加20秒的事务处理延迟。


2.3.1.2. 通过 DNS 验证 Hello 问候语

如果主机已经通过了初步的问候语验证,那么现在可以通过 DNS 验证提供的名称。你可以:

  • 执行提供的名称的正向查找,并将结果与对等方的 IP 地址进行匹配。

  • 执行对等方 IP 地址的反向查找,并将结果与问候语中提供的名称进行匹配。

如果这两个检查中的任何一个成功,则名称已通过验证。

你的 MTA 可能有内置的选项来执行此检查。例如,在 Exim 中 (参见附录 A),你可能想要设置 "helo_try_verify_hosts = *",并创建基于 "verify = helo" 条件采取行动的 ACL。

与简单的语法检查相比,此检查在处理时间和网络资源方面成本略高。此外,与语法检查不同,不匹配并不总是表明是恶意软件;一些大型互联网站点,例如 hotmail.com、yahoo.com 和 amazon.com,经常提供无法验证的 Hello 问候语。

在我的服务器上,如果我没有根据之前的检查对发送方进行事务延迟,我会对 Hello 问候语进行 DNS 验证。然后,如果此检查失败,我将从现在开始对每个 SMTP 命令施加 20 秒的延迟。我还会准备一个 "X-HELO-Warning:" 标头,稍后将其添加到消息中,并使用它来增加 SpamAssassin 分数,以便在收到消息数据后可能拒绝该消息。


2.3.2. 发件人地址检查

在客户端提供 MAIL FROM: <地址> 命令后,你可以按如下方式验证提供的 信封发件人 地址。[8]


2.3.2.1. 发件人地址语法检查

提供的地址是否符合 <本地部分@域名> 格式?这个域名部分是否是语法上有效的 完全限定域名

通常,你的 MTA 默认执行这些检查。


2.3.2.2. 冒名顶替者检查

如果你和你的用户仅通过少数几个服务器发送所有传出邮件,则可以拒绝来自其他主机的邮件,其中发件人地址的 "domain" 是你自己的。

此检查的更通用的替代方法是 发件人策略框架 (SPF)


2.3.2.3. 简单发件人地址验证

如果地址是本地地址,则 "local part" ( @ 符号之前的部分) 是否是你系统上的有效邮箱?

如果地址是远程地址,则 "domain" ( @ 符号之后的部分) 是否存在?


2.3.2.4. 发件人回拨验证

这是一种由某些 MTA (如 Exim 和 Postfix) 提供的机制,用于验证远程发件人地址的 "local part"。在 Postfix 术语中,它被称为 "发件人地址验证"

你的服务器联系发件人地址中提供的域名的 MX 记录,尝试启动辅助 SMTP 事务,就像将邮件传递到此地址一样。它实际上不发送任何邮件;而是,一旦 RCPT TO: 命令被远程主机接受或拒绝,你的服务器将发送 QUIT 命令。

默认情况下,Exim 使用空信封发件人地址进行此类回拨验证。目的是确定如果将 传递状态通知 (DSN) 返回给发件人,是否会被接受。

另一方面,Postfix 默认使用发件人地址 <postmaster@域名> 用于地址验证目的 (域名取自$myorigin变量)。因此,你可能希望以对待 NULL 信封发件人的方式对待此发件人地址 (例如,避免 SMTP 事务延迟灰名单,但需要收件人地址中的 信封发件人签名)。有关更多信息,请参见实现附录。

你可能会发现,仅靠此检查可能不适合作为拒绝传入邮件的触发器。偶尔,合法邮件 (例如定期账单) 是从具有无效回复地址的自动化服务发送的。此外,垃圾邮件的一个不幸的副作用是,某些用户倾向于篡改其传出邮件中的回复地址 (尽管这可能更经常影响消息本身的 "From:" 标头,而不是 信封发件人)。

此外,此检查仅验证地址是否有效,而不是验证地址是否是此特定消息的真实发件人 (但另请参见 信封发件人签名)。

最后,有报告称,某些站点 (例如 "aol.com") 将无条件地将发现发件人回拨请求的任何系统列入黑名单。这些站点可能是 Joe Job 的频繁受害者,因此,会收到大量的发件人回拨请求。通过参与这些 DDoS (分布式拒绝服务) 攻击,你实际上正在将自己变成垃圾邮件发送者的爪牙。


2.3.3. 收件人地址检查

你说,这应该很简单。收件人地址要么有效,在这种情况下,邮件将被传递;要么无效,在这种情况下,你的 MTA 默认会处理拒绝。

让我们来看看,好吗?


2.3.3.1. 防止开放中继

不要将来自远程主机的邮件中继到远程地址! (除非发件人经过身份验证)。

对于我们大多数人来说,这似乎是显而易见的,但显然这是一个经常被忽视的考虑因素。此外,并非每个人都完全掌握与电子邮件地址和传递路径相关的各种互联网标准 (考虑 "百分比黑客域名", "感叹号 (!) 路径" 等等)。

如果你不确定你的 MTA 是否充当 开放中继,你可以通过 "relay-test.mail-abuse.org" 进行测试。在服务器上的 shell 提示符下,键入

telnet relay-test.mail-abuse.org

这项服务将使用各种测试来查看你的 SMTP 服务器是否似乎正在将邮件转发到远程电子邮件地址,和/或任何数量的地址 "hacks",例如上面提到的那些。

防止你的服务器充当开放中继非常重要。如果你的服务器是开放中继,并且垃圾邮件发送者找到了你,你将立即被列入众多 DNS 黑名单中。如果某些其他 DNS 黑名单的维护者找到了你 (通过探测,和/或通过响应投诉),你将在那些黑名单中列出很长时间。


2.3.3.2. 收件人地址查找

对于我们大多数人来说,这似乎也很平庸。但并非总是如此。

如果用户的邮件帐户和邮箱直接存储在你的传入邮件交换机上,你可以简单地检查收件人地址的 "local part" 是否对应于有效的邮箱。这里没有问题。

在两种情况下,收件人地址的验证更加麻烦:

  • 如果你的机器是收件人域的备份 MX。

  • 如果你的机器将你域的所有邮件转发到另一个 (可能是内部) 服务器。

收件人地址验证的替代方法是接受这些相应域内的所有收件人地址,这反过来意味着你或目标服务器可能需要为后来被证明无效的收件人地址生成 传递状态通知 (DSN)。最终,这意味着你将生成附带垃圾邮件。

考虑到这一点,让我们看看如何验证上面列出的场景中的收件人。


2.3.3.2.1. 收件人回拨验证

这是一种由某些 MTA (如 Exim 和 Postfix) 提供的机制,用于验证远程收件人地址的 "local part" (有关其工作原理的描述,请参见发件人回拨验证)。在 Postfix 术语中,这被称为 "收件人地址验证"

在这种情况下,服务器尝试联系最终目标主机以验证每个收件人地址,然后你才能接受来自对等方的 RCPT TO: 命令。

此解决方案简单而优雅。它可以与在最终目标主机上运行的任何 MTA 一起使用,而无需访问任何特定的目录服务。此外,如果该 MTA 恰好对收件人地址执行模糊匹配 (Lotus Domino 服务器就是这种情况),则此检查将准确反映收件人地址最终是否会被接受 - 这对于下面描述的机制可能不是真的。

确保保持原始 信封发件人 完整以进行收件人回拨,否则来自目标主机的响应可能不准确。例如,如 仅为真实用户接受退回邮件 中所述,它可能会拒绝系统用户和别名的退回邮件 (即,没有信封发件人的邮件)。

在主要的 MTA 中,Exim 和 Postfix 都支持此机制。


2.3.3.2.2. 目录服务

另一个好的解决方案是目录服务 (例如,一个或多个 LDAP 服务器),你的 MTA 可以查询这些服务。最常见的 MTA 都支持 LDAP、NIS 和/或通常用于提供用户帐户信息的各种其他后端。

主要的症结在于,除非电子邮件的最终目标主机已经使用这样的目录服务将用户名映射到邮箱,否则可能需要一些工作来设置此服务。


2.3.3.2.3. 复制的邮箱列表

如果以上选项都不可行,您可以退而求其次,使用一个 "简易目录服务",定期从邮箱所在的机器上复制一份最新的邮箱列表到您的 MX 主机上。然后您的 MTA 可以查询这个列表来验证传入邮件中的 RCPT TO: 命令。

如果您的邮箱所在的机器运行的是某种 UNIX 或 Linux 版本,您可以编写一个脚本来生成这样的列表,可能从本地的 "/etc/passwd" 文件中获取,然后使用 OpenSSH 套件中的 "scp" 命令将其复制到您的 MX 主机上。 之后,您可以设置一个 "cron" 任务 (使用 man cron 命令查看详细信息) 来定期运行此脚本。


2.3.3.3. 防止字典攻击

字典攻击 是一个术语,用于描述 SMTP 事务中,发送主机不断发送 RCPT TO: 命令来探测可能的收件人地址,这些地址基于常见的名称(通常从字母顺序的 "aaron" 开始,但有时从字母表的后面开始,或者随机开始)。如果您的服务器接受了某个特定的地址,那么该地址就会被添加到垃圾邮件发送者的武器库中。

一些网站,特别是大型网站,发现它们经常成为此类攻击的目标。 从垃圾邮件发送者的角度来看,在大型网站上找到给定用户名的机会比在只有几个用户的网站上要好。

一种有效的对抗字典攻击的方法是,对每个失败的地址发出逐渐增加的事务延迟。 例如,第一个不存在的收件人地址可以被拒绝并延迟 20 秒,第二个地址延迟 30 秒,依此类推。


2.3.3.4. DSN 只接受一个收件人

合法的 传递状态通知 应该只发送给一个收件人地址 - 触发通知的原始消息的发送者。 如果 信封发件人 地址为空,但收件人多于一个,则可以断开连接。


2.4. 灰名单

Evan Harris 在一篇白皮书中介绍了 灰名单 的概念:http://projects.puremagic.com/greylisting/.


2.4.1. 工作原理

SMTP 事务延迟 一样,灰名单是一种简单但高效的机制,可以过滤掉通过 恶意软件 传递的消息。 这个想法是确定在消息的发送者和接收者之间是否存在先前的关系。 对于大多数合法的邮件,这种关系确实存在,并且传递正常进行。

另一方面,如果不存在先前的关系,则传递将被暂时拒绝(使用 451 SMTP 响应)。 合法的 MTA 将相应地处理此响应,并在稍后重试传递[9]。 相比之下,恶意软件要么立即重复尝试传递,要么干脆放弃并转到其地址列表中的下一个目标。

来自传递尝试的三个信息片段(称为 三元组)用于唯一标识发送者和接收者之间的关系

如果传递尝试被暂时拒绝,则会缓存此三元组。 它将在给定的时间内(通常为 1 小时)保持在灰名单中,之后它将被列入白名单,并且新的传递尝试将会成功。 如果在给定的超时时间(通常为 4 小时)之前没有发生新的传递尝试,则该三元组将从缓存中过期。

如果一个白名单的三元组长时间未被看到(至少一个月,以考虑到每月账单等情况),它就会过期。 这可以防止列表的无限增长。

这些超时时间取自 Evan Harris 的原始灰名单白皮书(或者我们应该说,呵呵,"灰皮书"?)。 一些人发现,在灰名单三元组过期之前,可能需要更大的超时时间,因为某些 ISP(例如 earthlink.net)仅每 6 小时左右重试传递。 [10]


2.4.2. 多个邮件交换器中的灰名单

如果您运行多个传入邮件交换器,并且每个交换器都维护自己的灰名单缓存,那么

  • 从给定的发送者到您的某个用户的首次传递理论上可能会延迟最多N倍的初始 1 小时延迟,其中NN 是邮件交换器的数量。 这是因为消息可能会在与最初发出 451 响应的服务器不同的服务器上重试。 在最坏的情况下,发送主机可能无法在 4 小时内或在灰名单三元组过期之前重试到第一个交换器的传递,从而导致传递尝试一次又一次地被拒绝,直到发送者放弃(通常在 4 天左右后)。

    实际上,这种情况不太可能发生。 如果传递尝试暂时失败,则发送主机通常会立即使用不同的 MX 重试传递。 因此,在一个小时后,任何这些 MX 主机都将接受该消息。

  • 即使某个三元组已在您的某个 MX 中列入白名单,如果将具有相同三元组的下一条消息传递到不同的 MX,它也会被列入灰名单。

由于这些原因,您可能需要实施一种解决方案,其中灰名单三元组的数据库在您的传入邮件交换器之间共享。 但是,由于托管此数据库的机器将成为单点故障,因此您必须在该机器出现故障时采取明智的措施(例如,接受所有传递)。 或者,您可以使用数据库复制技术,并让 SMTP 服务器回退到其中一个复制服务器进行查找。


2.4.3. 结果

根据我自己的经验,灰名单 可以消除大约 90% 的独特垃圾邮件传递, 应用了先前描述的大多数 SMTP 检查 之后! 如果您将灰名单用作第一道防线,它可能会捕获更高百分比的传入垃圾邮件。

相反,使用此技术几乎不会导致 误报。 所有主要的 邮件传输代理 都会在暂时失败后以最终导致传递成功的方式执行传递重试。

灰名单的缺点是,过去没有给特定收件人发送过电子邮件的人发来的合法邮件会受到一个小时的延迟(如果您运行多个 MX 主机,则可能需要几个小时)。

另请参见当垃圾邮件发送者适应...会发生什么


2.5. 发件人授权方案

已经开发了各种发件人验证方案,其中不仅检查了发件人地址的有效性,还检查了真实性。 Internet 域的所有者指定了必须满足的某些条件,以便验证来自该域内发件人的传递。

此类的两个早期提议方案是

在这两种方案下,来自的所有邮件都必须来自的 DNS 区域中指定的主机。

这些方案已经演变。 可惜的是,它们也分叉了。


2.5.1. 发件人策略框架 (SPF)

"发件人策略框架"(以前称为 "Sender Permitted From")可能是最著名的发件人授权方案。 它大致基于上述原始方案,但在域持有者可以发布的标准方面允许更大的灵活性。

SPF 信息以TXT记录的形式发布在域的顶级 DNS 区域中。 此记录可以指定

  • 哪些主机可以从该域发送邮件

  • 从该域发出的邮件中必须存在的 GPG(GNU Privacy Guard)签名

  • 其他标准; 有关详细信息,请参阅 http://spf.pobox.com/

TXT 记录的结构仍在开发中,但是用于完成上述任务的基本功能已到位。 它以字符串v=spf1开头,后跟诸如

  • a- 域本身的 IP 地址是有效的发件人主机

  • mx- 该域的传入邮件交换器也是有效的发件人

  • ptr- 如果发送主机的 IP 地址的 rDNS 查找在发件人地址的域部分中产生一个名称,则它是有效的发件人。

每个修饰符都可以加上加号 (+)、减号 (-)、问号 (?) 或波浪号 (~) 前缀,以指示它是否指定权威源、非权威源、中立立场或可能的非权威源。

每个修饰符也可以用冒号扩展,后跟一个备用域名。 例如,如果您是 Comcast 的订户,则您自己的 DNS 区域可能包含字符串 "-ptr:client.comcast.net ptr:comcast.net",以指示您的外发电子邮件永远不会来自解析为任何内容.client.comcast.net 的主机,但可能来自解析为任何内容.comcast.net 的其他主机.

目前已为许多备受瞩目的 Internet 域(例如 aol.com、altavista.com、dyndns.org、earthlink.net 和 google.com)发布了 SPF 信息。

发件人授权方案通常和 SPF 特别地,尚未被普遍接受。 特别是,一个反对意见是,域持有者可能会有效地垄断从其用户/客户转发外发邮件的权利。

另一个反对意见是,SPF 会破坏传统的电子邮件转发 - 转发主机可能没有权限根据信封发件人域中的 SPF 信息进行转发。 这部分通过 SRS发件人重写方案 来解决,其中邮件的转发者会将 信封发件人 地址修改为格式

user=source.domain@forwarder.domain


2.5.2. Microsoft 电子邮件发件人 ID

与 SPF 类似,接受标准通过发送域的 DNS 区域中的 TXT 记录发布。 但是,MS CIDE 信息不是依赖于简单的关键字,而是由以 XML 编码的相当大的结构组成。 XML 架构由 Microsoft 授权发布。

虽然 SPF 通常用于检查电子邮件的 信封发件人 地址,但 MS CIDE 主要是一个验证消息本身 RFC 2822 标头的工具。因此,可以应用此类检查的最早时间点是在消息数据已传递之后,在发出最终的 250 响应之前。

坦率地说,胎死腹中。受到专利问题和极度复杂性的阻碍。

也就是说,http://spf.pobox.com/ 上发布的最新 SPF 工具除了 SPF 之外,还能够检查 MS Caller-ID 信息。


2.5.3. RMX++

简单呼叫者授权框架 - SCAF 的一部分)。该方案由 Hadmut Danisch 开发,他也是最初 RMX 的构想者。

RMX++ 允许通过 HTTP 服务器进行动态授权。域所有者通过 DNS 发布服务器位置,接收主机联系该服务器以获取授权记录,从而验证呼叫者的真实性。

该方案允许域所有者更精细地控制用于验证发件人地址的标准,而无需公开其网络的结构(如静态 TXT 记录中的 SPF 信息)。例如,Hadmut 的一个示例是一个授权服务器,该服务器允许每个地址在工作时间外每天最多发送五个消息,并在达到限制后发出警报。

此外,SCAF 不仅限于电子邮件,还可以用于为其他服务(如网络电话 (VoIP))提供呼叫者身份验证。

RMX++ 可能的一个缺点,正如 Rick Stewart 指出的,是它对机器和网络资源的影响:来自 HTTP 服务器的回复不如直接通过 DNS 获取的信息那样被广泛缓存,并且发出 HTTP 请求比 DNS 请求的成本更高。

此外,Rick 指出 RMX++ 的动态特性使得故障更难追踪。如果存在每天五个消息的限制,如上面的示例所示,并且一条消息被检查了五次,那么只需一条消息就会达到限制。这使得重新检查消息变得不可能。

有关 RMX、RMX++ 和 SCAF 的更多信息,请参阅:http://www.danisch.de/work/security/antispam.html


2.6. 消息数据检查

现在是时候查看消息本身的内容了。这就是传统的垃圾邮件和病毒扫描程序所做的事情,因为它们通常在消息被接受后对其进行操作。然而,在我们的例子中,我们发出最终的 250 响应之前执行这些检查,以便我们有机会当场拒绝邮件,而不是稍后生成 附带垃圾邮件

如果您的传入邮件交换器非常繁忙(即大型站点,机器很少),您可能会发现直接在邮件交换器中执行某些或所有这些检查成本太高。特别是,运行 病毒扫描程序垃圾邮件扫描程序 确实会占用大量的 CPU 带宽和时间。

如果是这样,您需要为这些扫描操作设置专用机器。大多数服务器端反垃圾邮件和反病毒软件都可以通过网络调用,即从您的邮件交换器调用。更多信息将在以下章节中介绍,我们在其中讨论各种 MTA 的实现。


2.6.1. 标头检查

2.6.1.1. 缺少标头行

RFC 2822 强制要求消息包含至少以下标头行

From: ...
To: ...
Subject: ...
Message-ID: ...
Date: ...

缺少其中任何一行意味着该消息不是由主流的 邮件用户代理 生成的,并且很可能是垃圾邮件 [11]


2.6.1.2. 标头地址语法检查

消息标头中呈现的地址(即 To:Cc:From: ... 字段)在语法上应该是有效的。说够了。


2.6.1.3. 简单标头地址验证

对于消息标头中的每个地址

  • 如果地址是本地的,那么本地部分(@ 符号之前)是否是有效的邮箱?

  • 如果地址是远程的,那么域部分(@ 符号之后)是否存在?


2.6.1.4. 标头地址回调验证

这类似于 发件人回调验证收件人回调验证。每个远程标头地址都通过调用相应域的主 MX 进行验证,以确定是否会接受 传递状态通知


2.6.2. 垃圾邮件签名存储库

垃圾邮件的一个特征是它被发送到大量的地址。如果其他 50 位收件人已经将特定消息标记为垃圾邮件,那么为什么您不能利用这一事实来决定是否接受发送给您的消息?更好的是,为什么不设置 垃圾邮件陷阱,以提供已知垃圾邮件的公共池?

很高兴你问。事实证明,这样的池确实存在

这些工具已经超越了简单的签名检查,只有当您收到已知为垃圾邮件的完全相同的消息副本时才会触发。相反,它们评估常见的模式,以解释消息标头和正文中的细微变化。


2.6.3. 二进制垃圾检查

包含非打印字符的消息很少见。当它们出现时,该消息几乎总是一个病毒,或者在某些情况下是用非西方语言编写的垃圾邮件,而没有适当的 MIME 编码。

一个特殊的情况是消息包含 NUL 字符(序数零)。即使您认为弄清楚非打印字符的含义比有益的更复杂,您也可以考虑检查此字符。这是因为一些 邮件传递代理,例如 Cyrus Mail Suite,最终会拒绝包含它的邮件。[12]。如果您使用此类软件,您绝对应该考虑摆脱 NUL 字符。

另一方面,(现在已过时的)RFC 822 规范并未明确禁止消息中的 NUL 字符。因此,作为拒绝包含它的邮件的替代方法,您可以选择在将这些字符传递到 Cyrus 之前将其从消息中删除。


2.6.4. MIME 检查

同样,验证传入消息的 MIME 结构可能也是值得的。MIME 解码错误或不一致的情况并不经常发生;但是当它们发生时,该消息肯定是垃圾邮件。此外,此类错误可能表明后续检查中存在潜在问题,例如 文件附件检查病毒扫描程序垃圾邮件扫描程序

换句话说,如果 MIME 编码非法,则拒绝该消息。


2.6.5. 文件附件检查

上次有人向您发送您真正想要的 Windows 屏幕保护程序(".scr" 文件)或 Windows 程序信息文件(".pif")是什么时候?

考虑阻止带有 "Windows 可执行文件" 附件的消息 - 即文件名以句点开头,后跟任意数量的三个字母组合,如上所述。此检查消耗的服务器资源远少于 病毒扫描程序,并且还可以捕获新的病毒,而您的防病毒扫描程序中尚不存在其签名。

有关此类 "文件扩展名" 的更完整列表,请访问:http://support.microsoft.com/default.aspx?scid=kb;EN-US;290497


2.6.6. 病毒扫描程序

有许多不同的服务器端病毒扫描程序可用。仅举几例

在您不愿意仅根据文件名阻止所有潜在危险文件的情况下(考虑 ".zip" 文件),此类扫描程序很有帮助。此外,它们将能够捕获未作为文件附件传输的病毒,例如 2004 年 3 月出现的 "Bagle.R" 病毒。

在大多数情况下,执行病毒扫描的机器不需要是您的邮件交换器。大多数这些防病毒扫描程序都可以在网络连接上的不同主机上调用。

防病毒软件主要根据已知病毒的一组签名(或病毒定义)来检测病毒。需要定期更新这些信息,因为会开发新的病毒。此外,为了获得最大的准确性,软件本身应始终保持最新状态。


2.6.7. 垃圾邮件扫描程序

同样,反垃圾邮件软件可以用于根据大量启发式方法对消息进行分类,包括其内容、标准合规性和各种网络检查,例如 DNS 黑名单垃圾邮件签名存储库。最后,此类软件通常为每条消息分配一个综合 "分数",指示该消息是垃圾邮件的可能性,如果分数高于某个阈值,则将其分类为垃圾邮件。

两种最流行的服务器端启发式反垃圾邮件过滤器是

当垃圾邮件发送者找到绕过其各种检查的方法时,这些工具会不断发展。例如,考虑 "创造性" 拼写,例如 "GR0W lO 1NCH35"。因此,就像防病毒软件一样,如果您使用反垃圾邮件软件,则应经常更新它以获得最高级别的准确性。

我使用 SpamAssassin,尽管为了最大限度地减少对机器资源的影响,它不再是我的第一道防线。在我个人地址每天大约 500 次垃圾邮件传递尝试中,大约有 50 次到达 SpamAssassin 检查的程度(主要是因为它们是从我的其他帐户之一转发的,因此上述检查无效)。在这 50 条消息中,大约每 2 或 3 天有一条消息最终出现在我的收件箱中。


2.7. 阻止附带垃圾邮件

附带垃圾邮件 (Collateral Spam) 使用目前描述的技术更难阻止,因为它通常来自使用标准邮件传输软件(例如 Sendmail、Postfix 或 Exim)的合法站点。 挑战在于区分这些消息和响应从您自己的用户发送的邮件而返回的有效投递状态通知 (Delivery Status Notification)。 以下是一些人们使用的方法:


2.7.1. 虚假病毒警告过滤器

大多数情况下,附带垃圾邮件是由防病毒扫描程序生成的病毒警告[13]。 反过来,在主题这些病毒警告的行,和/或其他特征中的措辞通常由防病毒软件本身提供。 因此,您可以创建一个更常见特征的列表,并过滤掉此类虚假病毒警告。

哇,您真幸运 - 已经有人为您做了这件事。 :-)

Tim Jackson维护着一个虚假病毒警告列表,供 SpamAssassin 使用。 该列表可在以下网址获得:http://www.timj.co.uk/linux/bogus-virus-warnings.cf


2.7.2. 为您的域名发布 SPF 信息

发件人策略框架 (Sender Policy Framework) 的目的正是为了防止 乔布工作 (Joe Job),即防止伪造有效的电子邮件地址。

如果您在域名的 DNS 区域中发布 SPF 记录,那么包含 SPF 检查的接收主机一开始就不会接受伪造的消息。 因此,他们不会向您的站点发送 投递状态通知 (Delivery Status Notification)


2.7.3. 信封发件人签名

我目前正在试验的另一种方法是在外发邮件的信封发件人 (Envelope Sender)地址的本地部分添加签名,然后在接受收到的投递状态通知 (Delivery Status Notification)之前检查信封收件人 (Envelope Recipient)地址中是否存在此签名。 例如,生成的发件人地址可能采用以下格式:

localpart=signature@domain

正常的邮件回复不受影响。 这些回复发送到消息中的地址发件人回复-至 (Reply-To)字段,这些字段保持不变。

听起来很简单,不是吗? 不幸的是,生成适合此目的的签名比听起来要复杂一些。 有几个相互冲突的考虑因素需要考虑:

  • 为了从此方法中获得任何好处,您生成的已签名信封发件人地址对于垃圾邮件发送者来说应该是无用的。 通常,这意味着签名包含一个最终会过期的时间戳

    sender=timestamp=hash@domain

  • 如果您向包含 灰名单 (Greylisting) 的站点发送邮件,您的信封发件人地址对于该特定收件人应保持不变。 否则,您的邮件将不断被灰名单。

    考虑到这一点,您可以根据 信封收件人 (Envelope Recipient) 地址生成 信封发件人 (Envelope Sender)

    sender=recipient=recipient.domain=hash@domain
    虽然此地址不会过期,但如果您开始看到发送到此地址的垃圾邮件,您至少会知道泄漏源 - 它包含在收件人地址中。 此外,您可以轻松阻止特定的收件人地址签名,而不会影响发送到同一收件人的正常邮件。

  • 邮件列表服务器还会出现另外两个问题。 通常,对请求邮件(例如 “订阅”/ “取消订阅”)的回复是在没有信封发件人的情况下发送的。

    • 第一个问题与将响应发送回请求邮件的信封发件人 (Envelope Sender)地址的服务器有关(如以下情况:)。 问题是邮件列表服务器的命令(例如 subscribeunsubscribe)通常发送到一个或多个不同的地址(例如, 分别) 不同于用于列表邮件的地址。 因此,订阅者地址将不同于发送到列表本身的消息中的发件人地址 - 并且在本例中,也不同于将为取消订阅请求生成的地址。 因此,您可能无法发布到列表或取消订阅。

      折衷方案是仅将收件人的包含在发件人签名中。 然后,发件人地址可能如下所示:

      subscribername=en.tldp.org=hash@subscriber.domain

    • 第二个问题与将响应发送回请求邮件的消息标头中的回复地址的服务器有关(例如)。 由于此地址未签名,因此来自列表服务器的响应将被您的服务器阻止。

      对此您无能为力,只能以允许他们将邮件返回到未签名的收件人地址的方式“列入白名单”这些特定服务器。

此时,这种方法开始失去一些优势。 此外,即使是合法的 DSN 也会被拒绝,除非原始邮件是通过您的服务器发送的。 因此,如果您的用户不漫游,或者通过您控制之外的服务器发送其外发邮件,您才应该考虑这样做。

也就是说,在上述任何问题都不适用于您的情况下,此方法为您提供了一种不仅可以消除附带垃圾邮件的好方法,还可以教育(大概是不知情地)生成它的站点所有者。 此外,作为一项额外的好处,执行 发件人呼叫验证 (Sender Callout Verification) 的站点只有在原始邮件确实是从您的站点发送时才会收到您的肯定响应。 本质上,您正在减少垃圾邮件发送者伪造发件人地址的风险。

您可以允许您的用户指定是否对传出邮件进行签名,如果是,则指定哪些主机应被允许将邮件返回到其地址的未签名版本。 例如,如果他们在您的邮件服务器上拥有系统帐户,您可以分别检查他们主目录中给定文件的存在和内容。


2.7.4. 仅接受真实用户的退信

即使您检查信封发件人签名,也可能存在一个漏洞,允许接受虚假退信。 具体来说,如果您的用户必须选择加入该计划,您可能不会在发送到系统别名(例如postmastermailer-daemon) 的邮件中检查此签名。 此外,由于这些用户不生成外发邮件,因此他们不应收到任何退信。

如果邮件发送到此类系统别名,您可以拒绝该邮件,或者,如果没有为提供的收件人地址创建邮箱,则可以拒绝该邮件。


第 3 章. 注意事项

由于系统范围的 SMTP 时间过滤,出现了一些具体的注意事项。 在这里,我们介绍其中的一些注意事项。


3.1. 多个传入邮件交换器

大多数域名列出了多个传入邮件交换器 (Mail Exchanger)(又名“MX 主机”)。 如果您这样做,请记住,为了产生任何效果,您在主 MX 上包含的任何 SMTP 时间过滤都必须包含在所有其他 MX 上。 否则,发送主机只需通过您的备份服务器重试邮件传递即可绕过过滤。

如果备份服务器不受您的控制,请首先问问自己是否需要多个 MX。 在这种情况下,它们很可能仅用作冗余邮件服务器,然后它们将邮件转发到您的主 MX。 如果您的主机碰巧停机了一段时间,那也没关系 - 行为良好的发送主机将在放弃前重试传递几天[9]

可能需要多个 MX 的一种情况是在多个服务器之间执行负载平衡 - 即,如果您收到大量邮件,以至于一台机器无法单独处理。 在这种情况下,看看您是否可以将一些任务(例如 病毒垃圾邮件扫描程序)卸载到其他机器,以减少或消除此需求。

同样,如果您决定继续使用多个 MX,您的备份服务器需要(至少)像主服务器一样具有限制性,否则主 MX 中的过滤将毫无用处。

另请参阅有关 灰名单 (Greylisting) 的部分,了解与多个 MX 主机相关的其他注意事项。


3.2. 阻止访问其他 SMTP 服务器

任何未在您的域的 DNS 区域中列为公共 邮件交换器 (Mail Exchanger) 的 SMTP 服务器都不应接受来自互联网的传入连接。 所有传入邮件流量都应通过您的传入邮件交换器。

此注意事项并非 SMTP 服务器所独有。 如果您有仅在您站点内用于内部目的的机器,请使用防火墙来限制对这些机器的访问。

这是一条规则,因此肯定有例外。 但是,如果您不知道它们是什么,那么以上内容就适用于您。


3.3. 转发邮件

如果邮件是从“友好”来源转发的,您应该注意不要由于垃圾邮件过滤而拒绝邮件,例如

  • 您的备份 MX 主机(如果有)。 据推测,这些主机已经过滤掉了大部分垃圾邮件(请参阅 多个传入邮件交换器)。

  • 您或您的用户订阅的邮件列表。 您仍然可以过滤此类邮件(如果最终出现在黑洞中,则可能不那么重要)。 但是,如果您拒绝邮件,您最终可能会导致列表服务器自动取消订阅收件人。

  • 属于收件人的其他帐户。 同样,拒绝将生成附带垃圾邮件,和/或给转发邮件的主机带来问题。

你可能会注意到最后两个来源存在一个逻辑问题:它们是特定于每个接收者的。如何允许每个用户指定他们想要加入白名单的主机,然后在系统范围内的SMTP时间过滤设置中使用这些单独的白名单?如果邮件被转发到你站点的多个接收者(在邮件列表的情况下经常如此),你如何决定使用哪个白名单?

这里没有神奇的解决方案。这是我们必须做一些工作的情况之一。你可以决定接受所有邮件,无论垃圾邮件分类如何,只要它从任何一个接收者的白名单中的主机发送即可。例如,为了响应每个 RCPT TO: 命令,我们可以将发送主机与相应用户的白名单进行匹配。如果找到,则设置一个标志,防止后续拒绝。实际上,你使用的是每个接收者白名单的聚合

实现附录将更详细地介绍这一点。


3.4. 用户设置和数据

在其他情况下,你可能希望支持站点上每个用户的设置和数据。例如,如果你使用SpamAssassin扫描传入邮件(参见 垃圾邮件扫描器),你可能希望允许个性化的垃圾邮件阈值、可接受的语言和字符集以及贝叶斯训练/数据。

一个棘手的问题是,传入邮件的SMTP时间过滤是在系统级别完成的,在邮件被传递给特定用户之前,因此不太适合于个性化偏好。单个邮件可能具有多个接收者;与转发邮件的情况不同,使用每个接收者偏好的聚合不是一个好选择。考虑一个你拥有来自不同语言背景的用户的情况。

但事实证明,这种说法可以修改。诀窍是将传入邮件中的接收者数量限制为一个,以便可以根据属于相应用户的设置和数据来分析邮件。

为此,你将接受第一个 RCPT TO:,然后向后续命令发出SMTP 451(延迟)响应。如果调用者是一个行为良好的MTA,它将知道如何解释此响应,并稍后重试。(如果它感到困惑,那么,嗯,它可能是一个你不希望接收邮件的发送者)。

显然,这是一个hack。发送给你站点的多个用户的每封邮件,每个接收者都会被延迟30分钟或更长时间。尤其是在企业环境中,内部人员和外部人员之间进行电子邮件讨论是很常见的,并且邮件传递的时间线至关重要,这可能根本不是一个好的解决方案。

另一个主要涉及企业和其他大型站点的问题是,传入邮件通常被转发到内部机器进行传递,并且接收者通常在邮件交换机上没有帐户。在这种情况下,仍然可以支持用户特定的设置和数据(例如,通过数据库查找或LDAP查询),但你可能还需要考虑是否值得这样做。

也就是说,如果你在一个小型站点上,并且不害怕延迟交付,那么这可能是允许每个用户微调其过滤标准的可接受方法。


第4章. 问答

在本节中,我尝试预测可能会出现的一些问题,并回答它们。如果你有未列出的问题,和/或想在本节中提供额外的输入,请提供 反馈

当垃圾邮件发送者适应时

Q: 当垃圾邮件发送者适应并试图绕过本文档中描述的技术时会发生什么?

Q: 当垃圾邮件发送者适应并试图绕过本文档中描述的技术时会发生什么?

A: 嗯,这取决于。 :-)

描述的一些检查(例如 SMTP检查灰名单)专门针对 *ratware* 行为。当然可以想象,如果足够多的站点采用这些检查,这种行为将会改变。Hatmut Danisch 指出:* Ratware 包含有bug的 SMTP 协议,因为他们不需要做得更好。它以这种方式工作,那么他们为什么要花更多时间?同时 "ratware" 具有更高的质量,甚至垃圾邮件消息的质量也得到了显着提高。一旦足够多的人通过检测不良的SMTP协议来拒绝垃圾邮件,垃圾邮件软件作者将简单地改进他们的软件。 *

也就是说,对于此类 ratware,仍然存在挑战

  • 为了绕过 SMTP事务延迟,他们需要等待来自接收SMTP服务器的每个响应。在那一点上,我们集体实现了给定垃圾邮件发送主机单位时间内能够传递的邮件速率的显着降低。由于垃圾邮件发送者正在与时间赛跑,以便在DNS黑名单和协作内容过滤器赶上之前尽可能多地传递邮件,因此我们正在提高这些工具的有效性。

    其效果类似于 微支付方案 的目标,其中发送者花费几秒钟来处理每个邮件接收者的计算挑战,并将生成的签名添加到电子邮件标头中,以供接收者验证。除了这些方案的复杂性之外,主要的区别在于,它们需要世界上几乎每个人的参与,才能有效地用于清除垃圾邮件,而 SMTP 事务延迟从第一个实现它的接收机器开始生效。

  • 为了绕过 HELO/EHLO 检查,他们需要提供正确的问候语,即使用有效的 完全限定域名 标识自己。这提供了更高的可追溯性,尤其是对于不自动将 rDNS 查找结果插入到消息的 Received: 标头中的接收 邮件传输代理

  • 为了通过所有 发件人地址检查,他们需要提供他们自己的有效发件人地址(或者,至少,一个 在他们自己的域名中的有效发件人地址)。无需多言。

  • 为了绕过 灰名单,他们需要在 1 小时后(但在 4 小时之前)重试将邮件传递到临时失败的接收者地址。(就实现而言,为了最大限度地减少机器资源,ratware 可能只保留临时失败的接收者列表,而不是保留每封临时失败的邮件的副本,并在 1 或 2 小时后对这些地址执行第二次扫描)。

    即便如此,灰名单 仍将与从 垃圾邮件陷阱 馈送的 DNS黑名单 结合使用,保持相当有效。这是因为强制性的一小时重试延迟将使这些列表有机会列出发送主机。

软件工具,例如 垃圾邮件扫描器病毒扫描器,都在不断发展。随着垃圾邮件发送者的发展,这些工具也在发展(反之亦然)。只要你使用这些工具的最新版本,它们将保持非常有效。

最后,本文档本身可能会发生更改。随着垃圾邮件性质的改变,人们会想出新的、创造性的方法来阻止它。


附录 A. Exim 实现

在这里,我们将介绍如何将本文档中描述的技术和工具集成到 Exim 邮件传输代理 中。


A.1. 先决条件

对于这些示例,你需要Exim 邮件传输代理,最好使用 Tom Kistner 的Exiscan-ACL补丁。预构建的Exim+Exiscan-ACL软件包存在于大多数流行的 Linux 发行版以及 FreeBSD 中;有关详细信息,请参见 Exiscan-ACL 主页[14]

结尾处的最终实现示例包含以下其他工具

  • SpamAssassin - 一种流行的垃圾邮件过滤工具,可根据大量高度复杂的启发式规则分析邮件内容。

  • greylistd - 一个简单的灰名单解决方案,由我本人编写,专门为 Exim 设计。

在整个示例中使用了其他可选软件。


A.2. Exim 配置文件

Exim 配置文件在顶部包含全局定义(我们将其称为main section),后跟其他几个部分[15]。这些其他部分中的每一个都以

begin section

我们将大部分时间花在acl部分中(即在begin acl之后);但我们也会在transportsrouters部分以及文件顶部的main section中添加和/或修改一些项目。


A.2.1. 访问控制列表

从版本 4.xx 开始,Exim 通过所谓的访问控制列表 (ACL) 整合了可能最复杂和灵活的 SMTP 时间过滤机制。

ACL 可用于评估是否接受或拒绝传入消息事务的某个方面,例如来自远程主机的初始连接,或 HELO/EHLOMAIL FROM:RCPT TO: SMTP 命令。因此,例如,你可能有一个名为acl_rcpt_to的 ACL,用于验证从对等方接收的每个 RCPT TO: 命令。

ACL 由一系列语句(或规则)组成。每个语句都以一个动作动词开头,例如accept, warn, require, deferdeny,后跟与该语句相关的条件、选项和其他设置的列表。每个语句都按顺序进行评估,直到采取明确的操作(除了warn之外)。在 ACL 的末尾有一个隐含的denyaccept

ACL 中上述示例语句可能如下所示acl_rcpt_todeny !hosts = +relay_from_hosts !domains = +local_domains : +relay_to_domains delay = 1m message = 550 You are not allowed to relay

  deny
    message  = relay not permitted
    !hosts   = +relay_from_hosts
    !domains = +local_domains : +relay_to_domains
    delay    = 1m

如果 RCPT TO: 命令不是由 "+relay_from_hosts" 主机列表中的主机传递的,并且接收者域不在 "+local_domains""+relay_to_domains" 域列表中,则此语句将拒绝该命令。但是,在向此命令发出 "550" SMTP 响应之前,服务器将等待一分钟。

为了在消息事务的给定阶段评估特定的 ACL,你需要将 Exim 的一个策略控制指向该 ACL。例如,为了使用上述acl_rcpt_toACL 评估 RCPT TO:,你的 Exim 配置文件的main section(在任何begin关键字之前)应包括

acl_smtp_rcpt = acl_rcpt_to

有关此类策略控制的完整列表,请参阅 Exim 规范中的第 14.11 节。


A.2.2. 扩展

可以使用大量的扩展项,包括运行时变量、查找函数、字符串/正则表达式操作、主机/域列表等等。可以在文件 "spec.txt" 中找到最新 x.x0 版本(即 4.20、4.30..)的详尽参考;ACL 在第 38 节中描述。

特别是,Exim 提供了二十个通用扩展变量,我们可以在 ACL 语句中为它们赋值

  • $acl_c0 - $acl_c9可以保存将在 SMTP 连接的生命周期内持续存在的值。

  • $acl_m0 - $acl_m9可以保存接收消息期间的值,但在之后会被重置。 HELOEHLOMAILRSET 命令也会重置它们。


A.3. 选项和设置

Exim 配置文件的主部分(第一个begin关键字之前)包含各种宏、策略控制和其他常规设置。 让我们首先定义一些稍后将使用的宏

# Define the message size limit; we will use this in the DATA ACL.
MESSAGE_SIZE_LIMIT = 10M

# Maximum message size for which we will run Spam or Virus scanning.
# This is to reduce the load imposed on the server by very large messages.
MESSAGE_SIZE_SPAM_MAX = 1M

# Macro defining a secret that we will use to generate various hashes.
# PLEASE CHANGE THIS!.
SECRET = some-secret

让我们调整一些 Exim 常规设置

# Treat DNS failures (SERVFAIL) as lookup failures.
# This is so that we can later reject sender addresses 
# within non-existing domains, or domains for which no
# nameserver exists.
dns_again_means_nonexist = !+local_domains : !+relay_to_domains

# Enable HELO verification in ACLs for all hosts
helo_try_verify_hosts = *

# Remove any limitation on the maximum number of incoming
# connections we can serve at one time.  This is so that while
# we later impose SMTP transaction delays for spammers, we
# will not refuse to serve new connections.
smtp_accept_max = 0

# ..unless the system load is above 10
smtp_load_reserve = 10

# Do not advertise ESMTP "PIPELINING" to any hosts.
# This is to trip up ratware, which often tries to pipeline
# commands anyway.
pipelining_advertise_hosts = :

最后,我们将一些 Exim 策略控制指向我们将创建的五个 ACL,以评估传入 SMTP 事务的各个阶段

acl_smtp_connect = acl_connect
acl_smtp_helo    = acl_helo
acl_smtp_mail    = acl_mail_from
acl_smtp_rcpt    = acl_rcpt_to
acl_smtp_data    = acl_data


A.4. 构建 ACL - 初步

在 acl 部分(

之后begin acl),我们需要定义这些 ACL。 在这样做时,我们将结合本文档前面描述的一些基本技巧,即DNS 检查SMTP 检查

在此阶段,我们将在 acl_rcpt_to 中执行大部分检查,而将其他 ACL 大部分留空。 这是因为大多数常用的恶意软件不理解 SMTP 事务早期的拒绝 - 它会不断尝试。 另一方面,如果 RCPT TO: 失败,大多数恶意软件客户端会放弃。

但是,我们创建所有这些 ACL,因为我们稍后将使用它们。


A.4.1. acl_connect

# This access control list is used at the start of an incoming
# connection.  The tests are run in order until the connection 
# is either accepted or denied.

acl_connect:

  # In this pass, we do not perform any checks here.
  accept


A.4.2. acl_helo

# This access control list is used for the HELO or EHLO command in
# an incoming SMTP transaction.  The tests are run in order until the
# greeting is either accepted or denied.

acl_helo:

  # In this pass, we do not perform any checks here.
  accept


A.4.3. acl_mail_from

# This access control list is used for the MAIL FROM: command in an
# incoming SMTP transaction.  The tests are run in order until the
# sender address is either accepted or denied.
#

acl_mail_from:

  # Accept the command.
  accept


A.4.4. acl_rcpt_to

# This access control list is used for every RCPT command in an
# incoming SMTP message.  The tests are run in order until the 
# recipient address is either accepted or denied.

acl_rcpt_to:

  # 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.
  #
  # Recipient verification is omitted here, because in many
  # cases the clients are dumb MUAs that don't cope well with 
  # SMTP error responses.
  #
  accept
    hosts       = : +relay_from_hosts


  # Accept if the message arrived over an authenticated connection,
  # from any host. Again, these messages are usually from MUAs, so
  # recipient verification is omitted.
  #
  accept
    authenticated = *


  ######################################################################
  # DNS checks
  ######################################################################
  #
  # The results of these checks are cached, so multiple recipients
  # does not translate into multiple DNS lookups.
  #

  # If the connecting host is in one of a select few DNSbls, then
  # reject the message.  Be careful when selecting these lists; many
  # would cause a large number of false postives, and/or have no
  # clear removal policy.
  #
  deny
    dnslists    = dnsbl.sorbs.net : \
                  dnsbl.njabl.org : \
                  cbl.abuseat.org : \
                  bl.spamcop.net
    message     = $sender_host_address is listed in $dnslist_domain\
                  ${if def:dnslist_text { ($dnslist_text)}}


  # If reverse DNS lookup of the sender's host fails (i.e. there is
  # no rDNS entry, or a forward lookup of the resulting name does not
  # match the original IP address), then reject the message.
  #
  deny
    message     = Reverse DNS lookup failed for host $sender_host_address.
    !verify     = reverse_host_lookup


  
  ######################################################################
  # Hello checks
  ######################################################################

  # If the remote host greets with an IP address, then reject the mail.
  # 
  deny
    message     = Message was delivered by ratware
    log_message = remote host used IP address in HELO/EHLO greeting
    condition   = ${if isip {$sender_helo_name}{true}{false}}


  # Likewise if the peer greets with one of our own names
  # 
  deny
    message     = Message was delivered by ratware
    log_message = remote host used our name in HELO/EHLO greeting.
    condition   = ${if match_domain{$sender_helo_name}\
                       {$primary_hostname:+local_domains:+relay_to_domains}\
                       {true}{false}}


  deny
    message     = Message was delivered by ratware
    log_message = remote host did not present HELO/EHLO greeting.
    condition   = ${if def:sender_helo_name {false}{true}}


  # If HELO verification fails, we add a X-HELO-Warning: header in
  # the message.
  #
  warn
    message     = 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.
    !verify     = helo

  

  ######################################################################
  # Sender Address Checks
  ######################################################################

  # If we cannot verify the sender address, deny the message.
  #
  # You may choose to remove the "callout" option.  In particular, 
  # if you are sending outgoing mail through a smarthost, it will not
  # give any useful information.
  #
  # Details regarding the failed callout verification attempt are
  # included in the 550 response; to omit these, change
  # "sender/callout" to "sender/callout,no_details".
  #
  deny
    message     = <$sender_address> does not appear to be a \
                  valid sender address. 
    !verify     = sender/callout



  ######################################################################
  # Recipent Address Checks
  ######################################################################

  # Deny if the local part contains @ or % or / or | or !. These are
  # rarely found in genuine local parts, but are often tried by people
  # looking to circumvent relaying restrictions.
  #
  # Also deny if the local part starts with a dot. Empty components
  # aren't strictly legal in RFC 2822, but Exim allows them because
  # this is common.  However, actually starting with a dot may cause
  # trouble if the local part is used as a file name (e.g. for a
  # mailing list).
  #
  deny
    local_parts = ^.*[@%!/|] : ^\\.


  # Drop the connection if the envelope sender is empty, but there is
  # more than one recipient address.  Legitimate DSNs are never sent
  # to more than one address.
  #
  drop
    message      = Legitimate bounces are never sent to more than one \
                   recipient.
    senders      = : postmaster@*
    condition    = $recipients_count


  # Reject the recipient address if it is not in a domain for
  # which we are handling mail.
  #
  deny
    message     = relay not permitted
    !domains    = +local_domains : +relay_to_domains


  # Reject the recipient if it is not a valid mailbox.
  # If the mailbox is not on our system (e.g. if we are a
  # backup MX for the recipient domain), then perform a
  # callout verification; but if the destination server is
  # not responding, accept the recipient anyway.
  #
  deny
    message     = unknown user
    !verify     = recipient/callout=20s,defer_ok


  # Otherwise, the recipient address is OK.
  #
  accept


A.4.5. acl_data

# This access control list is used for message data received via 
# SMTP.  The tests are run in order until the recipient address 
# is either accepted or denied.

acl_data:

  # Add Message-ID if missing in messages received from our own hosts.
  warn
    condition   = ${if !def:h_Message-ID: {1}}
    hosts       = : +relay_from_hosts
    message     = Message-ID: <E$message_id@$primary_hostname>


  # 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

  # Accept if the message arrived over an authenticated connection, from
  # any host.
  #
  accept
    authenticated = *


  # Enforce a message-size limit
  #
  deny
    message     = Message size $message_size is larger than limit of \
                  MESSAGE_SIZE_LIMIT
    condition   = ${if >{$message_size}{MESSAGE_SIZE_LIMIT}{true}{false}}


  # Deny unless the address list header is syntactically correct.
  #
  deny
    message     = Your message does not conform to RFC2822 standard
    log_message = message header fail syntax check
    !verify     = header_syntax


  # Deny non-local messages with no Message-ID, or no Date
  #
  # Note that some specialized MTAs, such as certain mailing list
  # servers, do not automatically generate a Message-ID for bounces.
  # Thus, we add the check for a non-empty sender.
  #
  deny
    message     = Your message does not conform to RFC2822 standard
    log_message = missing header lines
    !hosts      = +relay_from_hosts
    !senders    = : postmaster@*
    condition   = ${if or {{!def:h_Message-ID:}\
                           {!def:h_Date:}\
                           {!def:h_Subject:}} {true}{false}}
 

  # Warn unless there is a verifiable sender address in at least
  # one of the "Sender:", "Reply-To:", or "From:" header lines.
  #
  warn
    message     = X-Sender-Verify-Failed: No valid sender in message header
    log_message = No valid sender in message header
    !verify     = header_sender


  # Accept the message. 
  #
  accept


A.5. 添加 SMTP 事务延迟

A.5.1. 简单方法

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

  accept
    delay = 20s

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

  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 事务的早期阶段。 这样我们就可以在看到任何问题迹象时立即开始强制执行延迟,从而增加导致恶意软件的同步错误和其他问题的机会。

具体来说,我们希望

  • 将 DNS 检查移动到 acl_connect

  • 将 Hello 检查移动到 acl_helo。 一个例外:我们目前无法检查是否缺少 Hello 问候语,因为此 ACL 是响应 EHLO 或 HELO 命令而处理的。 我们将在 acl_mail_from ACL 中执行此检查。

  • 将发件人地址检查移动到 acl_mail_from

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

  • 如果我们决定拒绝传递,我们将存储一个错误消息,以便在即将到来的 550 响应中使用$acl_c0$acl_m0:

    • 如果我们在邮件传递开始之前识别出该条件(即在 acl_connectacl_helo 中),我们使用连接持久性变量$acl_c0

    • 一旦邮件事务开始(即在 MAIL FROM: 命令之后),我们将任何内容从$acl_c0复制到特定于消息的变量$acl_m0,并从此点开始使用后者。 这样,在此特定消息中识别出的任何条件都不会影响在同一连接中收到的任何后续消息。

    此外,我们在$acl_c1$acl_m1中以类似的方式存储相应的日志消息

  • 如果我们遇到不需要完全拒绝的条件,我们只在$acl_c1$acl_m1中存储警告消息。 一旦邮件事务开始(即在 acl_mail_from 中),我们也会将此变量中的任何内容添加到邮件标头中。

  • 如果我们决定接受一条消息,而不考虑任何后续检查的结果(例如 SpamAssassin 扫描),我们将在$acl_c0$acl_m0中设置一个标志,但$acl_c1$acl_m1为空。

  • 在每个 ACL 的开头(包括 acl_mail_from),我们记录当前时间戳$acl_m2。 在 ACL 的末尾,我们使用$acl_c1$acl_m1的存在来触发 SMTP 事务延迟,直到总共经过 20 秒。

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

表 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 中,将在后面介绍。


A.6. 添加灰名单支持

Exim 有几种替代的灰名单实现。 在这里,我们将介绍其中的几个。


A.6.1. greylistd

这是由开发的 Python 实现。 (因此,自然地,这是我将在最终 ACL 中包含的实现)。 它作为独立的守护程序运行,因此不依赖于任何外部数据库。 灰名单数据存储为简单的 32 位哈希,以提高效率。

您可以在 http://packages.debian.org/unstable/mail/greylistd 找到它。 Debian 用户可以通过 APT 获取它

# apt-get install greylistd

要咨询greylistd,我们在之前声明的 acl_rcpt_to ACL 中插入两个语句,紧挨着最终的accept语句

  # Consult "greylistd" to obtain greylisting status for this particular
  # peer/sender/recipient triplet.
  #
  # We do not greylist messages with a NULL sender, because sender 
  # callout verification would break (and we might not be able to
  # send mail to a host that performs callouts).
  #
  defer
    message     = $sender_host_address is not yet authorized to deliver mail \
                  from <$sender_address> to <$local_part@$domain>. \
                  Please try later.
    log_message = greylisted.
    domains     = +local_domains : +relay_to_domains
    !senders    = : postmaster@*
    set acl_m9  = $sender_host_address $sender_address $local_part@$domain
    set acl_m9  = ${readsocket{/var/run/greylistd/socket}{$acl_m9}{5s}{}{}}
    condition   = ${if eq {$acl_m9}{grey}{true}{false}} 

除非您包含信封发件人签名以阻止虚假的传递状态通知,否则您可能希望在您的acl_data 中添加类似的语句,以灰名单具有 NULL 发件人的消息。

我们在此处用于灰名单目的的数据将与上面略有不同。 除了$sender_address为空之外,在此时未定义$local_part$domain。 相反,变量$recipients包含所有收件人地址的逗号分隔列表。 对于合法的 DSN,应该只有一个地址。

  # Perform greylisting on messages with no envelope sender here.
  # We did not subject these to greylisting after RCPT TO: because
  # that would interfere with remote hosts doing sender callouts.
  #
  defer
    message     = $sender_host_address is not yet authorized to send \
                  delivery status reports to <$recipients>. \
                  Please try later.
    log_message = greylisted.
    senders     = : postmaster@*
    set acl_m9  = $sender_host_address $recipients
    set acl_m9  = ${readsocket{/var/run/greylistd/socket}{$acl_m9}{5s}{}{}}
    condition   = ${if eq {$acl_m9}{grey}{true}{false}}


A.6.2. MySQL 实现

以下内联实现由 Johannes Berg 贡献,部分基于

它不需要任何外部程序 - 整个实现都基于这些配置片段以及 MySQL 数据库。

包含最新配置片段的存档以及README文件可在以下网址获得:http://johannes.sipsolutions.net/wiki/Projects/exim-greylist

需要在您的系统上安装 MySQL。 在 MySQL 提示符下,创建一个exim4数据库,其中包含两个名为exim_greylistexim_greylist_log的表,如下所示

CREATE DATABASE exim4;
use exim4;

CREATE TABLE exim_greylist (
   id bigint(20) NOT NULL auto_increment,
   relay_ip varchar(80) default NULL,
   sender varchar(255) default NULL,
   recipient varchar(255) default NULL,
   block_expires datetime NOT NULL default '0000-00-00 00:00:00',
   record_expires datetime NOT NULL default '9999-12-31 23:59:59',
   create_time datetime NOT NULL default '0000-00-00 00:00:00',
   type enum('AUTO','MANUAL') NOT NULL default 'MANUAL',
   passcount bigint(20) NOT NULL default '0',
   blockcount bigint(20) NOT NULL default '0',
   PRIMARY KEY  (id)
);

CREATE TABLE exim_greylist_log (
   id bigint(20) NOT NULL auto_increment,
   listid bigint(20) NOT NULL,
   timestamp datetime NOT NULL default '0000-00-00 00:00:00',
   kind enum('deferred', 'accepted') NOT NULL,
   PRIMARY KEY (id)
);

在 Exim 配置文件的main部分中,声明以下宏

# if you don't have another database defined, then define it here
hide mysql_servers = localhost/exim4/user/password

# options
# these need to be valid as xxx in mysql's DATE_ADD(..,INTERVAL xxx)
# not valid, for example, are plurals: "2 HOUR" instead of "2 HOURS"
GREYLIST_INITIAL_DELAY = 1 HOUR
GREYLIST_INITIAL_LIFETIME = 4 HOUR
GREYLIST_WHITE_LIFETIME = 36 DAY
GREYLIST_BOUNCE_LIFETIME = 0 HOUR

# you can change the table names
GREYLIST_TABLE=exim_greylist
GREYLIST_LOG_TABLE=exim_greylist_log

# comment out to the following line to disable greylisting (temporarily)
GREYLIST_ENABLED=

# uncomment the following to enable logging
#GREYLIST_LOG_ENABLED=

# below here, nothing should normally be edited

.ifdef GREYLIST_ENABLED
# database macros
GREYLIST_TEST = SELECT CASE \
   WHEN now() > block_expires THEN "accepted" \
   ELSE "deferred" \
 END AS result, id \
 FROM GREYLIST_TABLE \
 WHERE (now() < record_expires) \
   AND (sender      = '${quote_mysql:$sender_address}' \
        OR (type='MANUAL' \
            AND (    sender IS NULL \
                  OR sender = '${quote_mysql:@$sender_address_domain}' \
                ) \
           ) \
       ) \
   AND (recipient   = '${quote_mysql:$local_part@$domain}' \
        OR (type = 'MANUAL' \
            AND (    recipient IS NULL \
                  OR recipient = '${quote_mysql:$local_part@}' \
                  OR recipient = '${quote_mysql:@$domain}' \
                ) \
           ) \
       ) \
   AND (relay_ip    = '${quote_mysql:$sender_host_address}' \
        OR (type='MANUAL' \
            AND (    relay_ip IS NULL \
                  OR relay_ip = substring('${quote_mysql:$sender_host_address}',1,length(relay_ip)) \
                ) \
           ) \
       ) \
 ORDER BY result DESC LIMIT 1

GREYLIST_ADD = INSERT INTO GREYLIST_TABLE \
  (relay_ip, sender, recipient, block_expires, \
   record_expires, create_time, type) \
 VALUES ( '${quote_mysql:$sender_host_address}', \
  '${quote_mysql:$sender_address}', \
  '${quote_mysql:$local_part@$domain}', \
  DATE_ADD(now(), INTERVAL GREYLIST_INITIAL_DELAY), \
  DATE_ADD(now(), INTERVAL GREYLIST_INITIAL_LIFETIME), \
  now(), \
  'AUTO' \
) 

GREYLIST_DEFER_HIT = UPDATE GREYLIST_TABLE \
                     SET blockcount=blockcount+1 \
                     WHERE id = $acl_m9

GREYLIST_OK_COUNT = UPDATE GREYLIST_TABLE \
                    SET passcount=passcount+1 \
                    WHERE id = $acl_m9

GREYLIST_OK_NEWTIME = UPDATE GREYLIST_TABLE \
                      SET record_expires = DATE_ADD(now(), INTERVAL GREYLIST_WHITE_LIFETIME) \
                      WHERE id = $acl_m9 AND type='AUTO'

GREYLIST_OK_BOUNCE = UPDATE GREYLIST_TABLE \
                     SET record_expires = DATE_ADD(now(), INTERVAL GREYLIST_BOUNCE_LIFETIME) \
                     WHERE id = $acl_m9 AND type='AUTO'

GREYLIST_LOG = INSERT INTO GREYLIST_LOG_TABLE \
               (listid, timestamp, kind) \
               VALUES ($acl_m9, now(), '$acl_m8')
.endif

现在,在 ACL 部分(在begin acl之后),声明一个名为 "greylist_acl" 的新 ACL

.ifdef GREYLIST_ENABLED
# this acl returns either deny or accept
# since we use it inside a defer with acl = greylist_acl,
# accepting here makes the condition TRUE thus deferring,
# denying here makes the condition FALSE thus not deferring
greylist_acl:
  # For regular deliveries, check greylist.

  # check greylist tuple, returning "accepted", "deferred" or "unknown"
  # in acl_m8, and the record id in acl_m9

  warn set acl_m8 = ${lookup mysql{GREYLIST_TEST}{$value}{result=unknown}}
       # here acl_m8 = "result=x id=y"

       set acl_m9 = ${extract{id}{$acl_m8}{$value}{-1}}
       # now acl_m9 contains the record id (or -1)

       set acl_m8 = ${extract{result}{$acl_m8}{$value}{unknown}}
       # now acl_m8 contains unknown/deferred/accepted

  # check if we know a certain triple, add and defer message if not
  accept
       # if above check returned unknown (no record yet)
       condition = ${if eq{$acl_m8}{unknown}{1}}
       # then also add a record
       condition = ${lookup mysql{GREYLIST_ADD}{yes}{no}}

  # now log, no matter what the result was
  # if the triple was unknown, we don't need a log entry
  # (and don't get one) because that is implicit through
  # the creation time above.
  .ifdef GREYLIST_LOG_ENABLED
  warn condition = ${lookup mysql{GREYLIST_LOG}}
  .endif

  # check if the triple is still blocked
  accept 
       # if above check returned deferred then defer
       condition = ${if eq{$acl_m8}{deferred}{1}}
       # and note it down
       condition = ${lookup mysql{GREYLIST_DEFER_HIT}{yes}{yes}}

  # use a warn verb to count records that were hit
  warn condition = ${lookup mysql{GREYLIST_OK_COUNT}}

  # use a warn verb to set a new expire time on automatic records,
  # but only if the mail was not a bounce, otherwise set to now().
  warn !senders = : postmaster@*
       condition = ${lookup mysql{GREYLIST_OK_NEWTIME}}
  warn senders = : postmaster@*
       condition = ${lookup mysql{GREYLIST_OK_BOUNCE}}

  deny
.endif

将此 ACL 合并到您的 acl_rcpt_to 中,以灰名单发件人地址为非空的元组。 这是为了允许发件人回溯验证

.ifdef GREYLIST_ENABLED
  defer !senders = : postmaster@*
        acl      = greylist_acl
        message  = greylisted - try again later
.endif

也将其合并到您的 acl_data 块中,但这次只有在发件人地址为空时才合并。 这是为了防止垃圾邮件发送者通过将发件人地址设置为 NULL 来绕过灰名单。

.ifdef GREYLIST_ENABLED
  defer senders  = : postmaster@*
        acl      = greylist_acl
        message  = greylisted - try again later
.endif


A.7. 添加 SPF 检查

在这里,我们介绍了使用 Exim 检查 发件人策略框架 记录的两种不同方式。 除了这些显式机制之外,SpamAssassin 套件在不久的将来(大约 2.70 版本)将通过为各种 SPF 结果分配加权分数来合并更复杂的 SPF 检查。

虽然我们可以早在 acl_mail_from ACL 中执行此检查,但有一个问题会影响此决定:SPF 与传统电子邮件转发不兼容。 除非转发主机实现 SRS,否则您可能会拒绝转发的邮件,因为您从一个未经授权的主机收到该邮件,根据 信封发件人地址中域的 SPF 策略。

为了避免这样做,我们需要查阅特定于用户的允许转发邮件的主机列表(如 豁免转发邮件中所述,将在后面介绍)。 这只有在 RCPT TO: 之后才有可能,当我们知道收件人的用户名时。

因此,我们将在任何灰名单检查和/或最终的accept语句之前,在 acl_rcpt_to 中添加此检查。


A.7.1. 通过 Exiscan-ACL 进行 SPF 检查

Tom Kistner 最近的版本Exiscan-ACL补丁(参见先决条件)具有对 SPF 的原生支持。 [16] 用法非常简单。 一个spf添加了 ACL 条件,可以与任何关键字进行比较pass, fail, softfail(软失败), none(无), neutral(中性), err_perm(永久性错误)err_temp(临时性错误).

在任何灰名单检查和/或最终accept语句之前,在 acl_rcpt_to 中插入以下代码片段

  # Query the SPF information for the sender address domain, if any,
  # to see if the sending host is authorized to deliver its mail.
  # If not, reject the mail.
  #
  deny
    message     = [SPF] $sender_host_address is not allowed to send mail \
                  from $sender_address_domain
    log_message = SPF check failed.
    spf         = fail


  # Add a SPF-Received: header to the message
  warn
    message     = $spf_received

如果发件人地址中的域的拥有者不允许来自调用主机的传递,则此语句将拒绝邮件。有些人认为这赋予了域拥有者过多的控制权,甚至到了搬起石头砸自己的脚的地步。一个建议的替代方案是将 SPF 检查与其他检查结合起来,例如发件人回拨验证(但请注意,与之前一样,如果您通过智能主机发送您的外发邮件,则执行此操作毫无意义)

  # Reject the mail if we cannot verify the sender address via callouts,
  # and if SPF information for the sending domain does not grant explicit
  # authority to the sending host.
  #
  deny
    message     = The sender address does not seem to be valid, and SPF \
                  information does not grant $sender_host_address explicit \
                  authority to send mail from $sender_address_domain
    log_message = SPF check failed.
    !verify     = sender/callout,random,postmaster
    !spf        = pass


  # Add a SPF-Received: header to the message
  warn
    message     = $spf_received


A.7.2. 通过 Mail::SPF::Query 进行 SPF 检查

Mail::SPF::Query是官方的 SPF 测试套件,可从 http://spf.pobox.com/downloads.html 获取。Debian 用户,请安装libmail-spf-query-perl.

Mail::SPF::Query包附带一个守护进程 (spfd),它监听 UNIX 域套接字上的请求。不幸的是,它没有附带一个 "init" 脚本来自动启动这个守护进程。因此,在以下示例中,我们将使用独立的 spfquery 实用程序来发出我们的 SPF 请求。

如上所述,在任何灰名单检查和/或最终accept语句之前,在 acl_rcpt_to 中插入以下代码片段

  # Use "spfquery" to obtain SPF status for this particular sender/host.
  # If the return code of that command is 1, this is an unauthorized sender.
  #
  deny
    message     = [SPF] $sender_host_address is not allowed to send mail \
                  from $sender_address_domain.
    log_message = SPF check failed.
    set acl_m9  = -ipv4=$sender_host_address \
                  -sender=$sender_address \
                  -helo=$sender_helo_name
    set acl_m9  = ${run{/usr/bin/spfquery $acl_m9}}
    condition   = ${if eq {$runrc}{1}{true}{false}}


A.8. 添加 MIME 和文件类型检查

这些检查依赖于 Tom Kistner 的补丁中的功能 - 有关详细信息,请参阅 先决条件Exiscan-ACLExiscan-ACL 包含对 MIME 解码和文件名后缀检查(或者使用 Windows 世界中的一个错误名称,"文件扩展名" 检查)的支持。 仅此检查就可以阻止大多数 Windows 病毒 - 但不能阻止那些以

.ZIP存档传输的病毒,也不能阻止那些利用 Outlook/MSIE HTML 渲染漏洞的病毒 - 请参阅有关 病毒扫描器 的讨论。

这些检查应该进入 acl_data,在最终accept语句

  # Reject messages that have serious MIME errors.
  #
  deny
    message     = Serious MIME defect detected ($demime_reason)
    demime      = *
    condition   = ${if >{$demime_errorlevel}{2}{1}{0}}


  # Unpack MIME containers and reject file extensions used by worms.
  # This calls the demime condition again, but it will return cached results.
  # Note that the extension list may be incomplete.
  #
  deny
    message     = We do not accept ".$found_extension" attachments here.
    demime      = bat:btm:cmd:com:cpl:dll:exe:lnk:msi:pif:prf:reg:scr:vbs:url

您会注意到,在上面的示例中,demime(解 MIME)条件被调用了两次。但是,结果被缓存了,因此消息实际上不会被处理两次。


A.9. 添加反病毒软件

Exiscan-ACL 直接插入到许多不同的病毒扫描器中,或者任何其他可以通过其命令行运行的扫描器中。cmdline(命令行)后端。

要使用此功能,您的 Exim 配置文件中的 main section(主要部分) 必须指定要使用的病毒扫描器,以及您希望传递给该扫描器的任何选项。基本语法是

av_scanner = scanner-type:option1:option:...

例如

av_scanner = sophie:/var/run/sophie
av_scanner = kavdaemon:/opt/AVP/AvpCtl
av_scanner = clamd:127.0.0.1 1234
av_scanner = clamd:/opt/clamd/socket
av_scanner = cmdline:/path/to/sweep -all -rec -archive %s:found:'(.+)'
...

在 DATA ACL 中,您需要使用malware(恶意软件)条件来执行实际扫描

  deny
    message  = This message contains a virus ($malware_name)
    demime   = *
    malware  = */defer_ok

包含的文件exiscan-acl-spec.txt包含完整的使用信息。


A.10. 添加 SpamAssassin

在 Exim 中,通常以两种方式之一在 SMTP 时间调用 SpamAssassin

  • 通过spam(垃圾邮件)条件提供Exiscan-ACL。这是我们将在此处介绍的机制。

  • 通过SA-Exim,这是 Marc Merlins 编写的另一个实用程序(),专门用于在 Exim 中在 SMTP 时间运行 SpamAssassin。该程序通过 Exim 的local_scan()接口运行,无论是直接修补到 Exim 源代码中,还是通过 Marc 自己的dlopen()插件(顺便说一句,该插件包含在 Debian 的exim4-daemon-lightexim4-daemon-heavy包中)。

    SA-Exim还提供了一些其他功能,即 *灰名单* 和 *teergrubing*。但是,由于扫描发生在收到消息数据之后,因此这两个功能可能不如在 SMTP 事务早期有用。

    SA-Exim可以在以下网址找到:http://marc.merlins.org/linux/exim/sa.html


A.10.1. 通过 Exiscan 调用 SpamAssassin

Exiscan-ACL"spam" 条件通过 SpamAssassin 或 Brightmail 传递消息,如果它们表明该消息是垃圾邮件,则会触发。 默认情况下,它连接到运行在spamd上的localhost(本地主机)的 SpamAssassin 守护程序 (spamd_address设置在 Exim 配置文件的 *main(主要)* 部分中。有关更多信息,请参阅exiscan-acl-spect.txt与补丁一起包含的文件。

在我们的实现中,我们将拒绝被分类为垃圾邮件的消息。但是,我们希望至少暂时将此类消息的副本保存在单独的邮件文件夹中。这样用户就可以定期扫描 误报

Exim 提供了可以应用于接受消息的*控制*,例如freeze(冻结)。Exiscan-ACL 补丁添加了另一个控制,即fakereject(虚假拒绝)。这会导致以下 SMTP 响应

550-FAKEREJECT id=message-id
550-Your message has been rejected but is being kept for evaluation.
550 If it was a legit message, it may still be delivered to the target recipient(s).

我们可以通过在 acl_data 中插入以下代码片段,在最终之前,将此功能合并到我们的实现中accept语句

  # Invoke SpamAssassin to obtain $spam_score and $spam_report.
  # Depending on the classification, $acl_m9 is set to "ham" or "spam".
  #
  # If the message is classified as spam, pretend to reject it. 
  #
  warn
    set acl_m9  = ham
    spam        = mail
    set acl_m9  = spam
    control     = fakereject
    logwrite    = :reject: Rejected spam (score $spam_score): $spam_report

  # Add an appropriate X-Spam-Status: header to the message.
  #
  warn
    message     = X-Spam-Status: \
                  ${if eq {$acl_m9}{spam}{Yes}{No}} (score $spam_score)\
                  ${if def:spam_report {: $spam_report}}
    logwrite    = :main: Classified as $acl_m9 (score $spam_score)

在此示例中,$acl_m9最初设置为 "ham"(正常邮件)。然后以用户mail(邮件)身份调用 SpamAssassin。如果该消息被分类为垃圾邮件,则$acl_m9设置为 "spam"(垃圾邮件),并且发出上述FAKEREJECT(虚假拒绝)响应。最后,将X-Spam-Status(X-垃圾邮件状态)标头添加到消息中。 想法是 邮件传递代理 或收件人的 邮件用户代理 可以使用此标头将垃圾邮件过滤到单独的文件夹中。


A.10.2. 配置 SpamAssassin

默认情况下,SpamAssassin 以冗长的表格格式显示其报告,主要适用于包含在消息正文中或作为消息正文的附件。在我们的例子中,我们需要一个简洁的报告,适用于上面例子中的X-Spam-Status(X-垃圾邮件状态)标头。为此,我们在其站点特定配置文件 (/etc/spamassassin/local.cf, /etc/mail/spamassassin/local.cf,或类似的文件) 中添加以下代码段

### Report template
clear_report_template
report "_TESTSSCORES(, )_"

此外,还内置了一个 贝叶斯 评分功能,并且默认情况下已打开。我们通常希望关闭此功能,因为它需要针对每个用户进行专门的训练,因此不适合系统范围的 SMTP 时间过滤

### Disable Bayesian scoring
use_bayes 0

要使这些更改生效,您必须重新启动 SpamAssassin 守护程序 (spamd)。


A.10.3. 用户设置和数据

假设您有许多用户想要指定他们个人的 SpamAssassin 首选项,例如垃圾邮件阈值、可接受的语言和字符集、白名单/黑名单发件人等等。或者也许他们真的希望能够使用 SpamAssassin 的本机贝叶斯评分(虽然我不明白为什么[17])。

正如文档前面 用户设置和数据 部分中所讨论的,有一种方法可以实现这一点。我们需要将每个传入邮件传递接受的收件人数量限制为一个。我们接受调用者发出的第一个 RCPT TO: 命令,然后使用 451 SMTP 响应推迟后续命令。与 灰名单 一样,如果调用者是一个行为良好的 MTA,它将知道如何解释此响应,并在稍后重试。


A.10.3.1. 告诉 Exim 每个传递只接受一个收件人

acl_rcpt_to 中,我们在验证收件人地址之后,但在与来自远程主机到本地用户的未经身份验证的传递相关的任何accept语句之前(即在任何灰名单检查、信封签名检查等之前)插入以下语句

  # Limit the number of recipients in each incoming message to one
  # to support per-user settings and data (e.g. for SpamAssassin).
  #
  # NOTE: Every mail sent to several users at your site will be
  #       delayed for 30 minutes or more per recipient.  This
  #       significantly slow down the pace of discussion threads
  #       involving several internal and external parties.
  #
  defer
    message      = We only accept one recipient at a time - please try later.
    condition    = $recipients_count


A.10.3.2. 将收件人用户名传递给 SpamAssassin

acl_data 中,我们修改前一节中给出的spam(垃圾邮件)条件,以便它将收件人地址的本地部分中指定的用户名传递给 SpamAssassin。

  # Invoke SpamAssassin to obtain $spam_score and $spam_report.
  # Depending on the classification, $acl_m9 is set to "ham" or "spam".
  #
  # We pass on the username specified in the recipient address,
  # i.e. the portion before any '=' or '@' character, converted
  # to lowercase.  Multiple recipients should not occur, since
  # we previously limited delivery to one recipient at a time.
  #
  # If the message is classified as spam, pretend to reject it. 
  #
  warn
    set acl_m9  = ham
    spam        = ${lc:${extract{1}{=@}{$recipients}{$value}{mail}}}
    set acl_m9  = spam
    control     = fakereject
    logwrite    = :reject: Rejected spam (score $spam_score): $spam_report

请注意,我们没有使用 Exim 的${local_part:...}函数来获取用户名,而是手动提取了任何 "@""=" 字符之前的部分。这是因为我们将在 信封签名 方案中使用后一个字符,稍后会介绍。


A.10.3.3. 在 SpamAssassin 中启用每用户设置

现在让我们再次看看 SpamAssassin。首先,您可以选择删除我们之前在其站点范围的配置文件中添加的use_bayes 0设置。无论如何,现在每个用户都将能够决定是否为自己覆盖此设置。

如果系统上的邮箱直接映射到具有主目录的本地 UNIX 帐户,那么您就完成了。默认情况下,SpamAssassin 守护程序 (spamd) 执行setuid()到我们传递给它的用户名,并将用户数据和设置存储在该用户的主目录中。

如果不是这种情况(例如,如果您的邮件帐户由 Cyrus SASL 或另一个服务器管理),则您需要告诉 SpamAssassin 在哪里可以找到每个用户的首选项和数据文件。 此外,spamd 需要保持以特定的本地用户身份运行,而不是尝试setuid()到不存在的用户。

我们通过指定在启动时传递给 spamd 的选项来执行这些操作

  • 在 Debian 系统上,编辑OPTIONS=中的/etc/default/spamassassin.

  • 在 RedHat 系统上,编辑SPAMDOPTIONS=中的/etc/sysconfig/spamassassin.

  • 其他人,自己弄清楚。

您需要的选项是

  • -u username(用户名)- 指定 spamd 将在其下运行的用户(例如mail(邮件))

  • -x- 禁用用户主目录中的配置文件。

  • --virtual-config-dir=/var/lib/spamassassin/%u- 指定每用户设置和数据的存储位置。 "%u" 替换为调用用户名。 spamd 必须能够创建或修改此目录

    # mkdir /var/lib/spamassassin
    # chown -R mail:mail /var/lib/spamassassin
    

毋庸置疑,在进行这些更改后,您需要重启 spamd


A.11. 添加信封发件人签名

在这里,我们将在发出的邮件中实现信封发件人签名,并在接受收到的"退信"(即,没有信封发件人的邮件)之前检查这些签名。

从您的主机发出的邮件的信封发件人地址将被修改如下:

sender=recipient=recipient.domain=hash@sender.domain

但是,由于此方案可能会产生意想不到的后果(例如,在邮件列表服务器的情况下),我们使其对您的用户可选。我们仅在发件人的主目录中找到名为".return-path-sign"的文件,并且仅当我们要发送到的域与该文件中的域匹配时,才会对发出的邮件的信封发件人地址进行签名。如果该文件存在但为空,则所有域都匹配。

同样,仅当收件人的主目录中存在相同的文件时,我们才要求收到的"退信"消息(即,没有信封发件人的消息)中的收件人地址被签名。用户可以通过其用户特定的白名单来免除对特定主机的此项检查,如免除转发邮件中所述。

此外,由于此方案除了ACL之外还涉及调整路由器和传输,因此我们不会将其包含在最终 ACL中。如果您能够遵循与这些部分有关的说明,那么您也应该能够添加此处描述的ACL部分。


A.11.1. 创建用于签名发件人地址的传输

首先,我们创建一个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}...}函数以及SECRET我们在main部分中声明的,[18]

    • 使用Exim的将结果哈希为8个小写字母${hash...}函数。

如果您需要对"智能主机"的传递进行身份验证,请添加适当的hosts_try_auth行。 (从您现有的智能主机传输中获取)。


A.11.2. 为远程传递创建新路由器

在当前处理您发出的邮件的现有路由器之前添加一个新的路由器。此路由器将使用上述传输进行远程传递,但前提是".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),也许可以模仿您现有的路由器。

请注意,我们不将此路由器用于没有信封发件人地址的邮件 - 我们不想篡改这些邮件! [19]


A.11.3. 为本地传递创建新的重定向路由器

接下来,您需要告诉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

包含等号的收件人地址将被重写,以使等号后面的本地部分被剥离。然后,再次处理所有路由器。


A.11.4. ACL 签名检查

此方案的最后一部分是告诉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}}

当您向在消息*标头*中的地址执行回拨验证的主机发送邮件时,您会遇到问题,例如发件人中的地址。这里的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>.

此外,即使收件人已选择在其发出的邮件中使用信封发件人签名,他们也可能希望免除特定主机在收到的邮件中提供此签名,即使邮件没有信封发件人地址。 这对于特定的邮件列表服务器可能是必需的,有关详细信息,请参见关于信封发件人签名的讨论。


A.12. 仅接受真实用户的退信

仅接受真实用户的退信中所述,现在存在一个漏洞,该漏洞阻止我们捕获发送给系统用户和别名(例如)的伪造的传递状态通知postmaster。在这里,我们将介绍两种替代方法,以确保仅接受实际发送邮件的用户的退信。


A.12.1. 检查收件人邮箱

第一种方法在acl_rcpt_to ACL中执行。在这里,我们检查收件人地址是否对应于本地邮箱

  # Deny mail for users that do not have a mailbox (i.e. postmaster,
  # webmaster...) if no sender address is provided.  These users do
  # not send outgoing mail, so they should not receive returned mail.
  #
  deny
    message     = This address never sends outgoing mail. \
                  You are responding to a forged sender address.
    log_message = bogus bounce for system user <$local_part@$domain>
    senders     = : postmaster@*
    domains     = +local_domains
    !mailbox check

不幸的是,我们如何执行邮箱检查将取决于您如何传递邮件(与之前一样,我们提取收件人地址的第一个"="符号之前的部分,以适应信封发件人签名

  • 如果邮箱映射到您服务器上的本地用户帐户,我们可以检查收件人姓名是否映射到与您系统上的"常规"用户对应的用户ID,例如,在500 - 60000范围内

      set acl_m9 = ${extract{1}{=}{${lc:$local_part}}}
      set acl_m9 = ${extract{2}{:}{${lookup passwd {$acl_m9}{$value}}}{0}}
      condition  = ${if and {{>={$acl_m9}{500}} {<${acl_m9}{60000}}} {true}}
    
    

  • 如果您将邮件传递到Cyrus IMAP套件,则可以使用提供的mbpath命令行实用程序来检查邮箱是否存在。 您将需要确保Exim用户有权检查邮箱(例如,您可以将其添加到cyrus组:# adduser exim4 cyrus)。

      set acl_m9 = ${extract{1}{=}{${lc:$local_part}}}
      condition  = ${run {/usr/sbin/mbpath -q -s user.$acl_m9} {true}}
      
    

  • 如果您将所有邮件转发到远程计算机进行传递,则可能需要执行收件人回拨验证,并让该计算机决定是否接受该邮件。 您需要在回拨中保持原始信封发件人不变

      verify = recipient/callout=use_sender
    
    

由于在本地传递的邮件的情况下,此邮箱检查会重复路由器中执行的某些逻辑,并且由于它特定于我们网站上的邮件传递机制,因此对于我们这些完美主义者来说,它可能有点笨拙。 因此,我们现在将提供一种替代方法。


A.12.2. 在别名路由器中检查空发件人

您可能有一个名为system_aliases或类似的路由器,以重定向诸如postmastermailer-demon之类的用户的邮件。通常,这些别名不用于发出的邮件的发件人地址。因此,您可以通过将以下条件添加到路由器来确保传入的传递状态通知不会通过它路由

!senders = : postmaster@*

现在,一个示例别名路由器可能如下所示

system_aliases:
  driver         = redirect
  domains        = +local_domains
  !senders       = : postmaster@*
  allow_fail
  allow_defer
  data           = ${lookup{$local_part}lsearch{/etc/aliases}}
  user           = mail
  group          = mail
  file_transport = address_file
  pipe_transport = address_pipe

尽管我们现在阻止了向某些系统别名的退信,但其他别名仅是遮蔽了现有的系统用户(例如"root""daemon"等)。如果您通过accept驱动程序传递本地邮件,并使用check_local_user来验证收件人地址,您现在可能会发现自己将邮件直接路由到这些系统帐户。

为了解决此问题,我们现在希望在处理本地邮件的路由器(例如local_user)中添加一个附加条件,以确保收件人不仅存在,而且是"常规"用户。例如,如上所述,我们可以检查用户ID是否在500 - 60000范围内

  condition  = ${if and {{>={$local_user_uid}{500}}\
                         {<{$local_user_uid}{60000}}}\
                    {true}}

现在,用于本地传递的示例路由器可能如下所示

local_user:
  driver           = accept
  domains          = +local_domains
  check_local_user
  condition        = ${if and {{>={$local_user_uid}{500}}\
                               {<{$local_user_uid}{60000}}}\
                              {true}}
  transport        = transport

请注意,如果您实施此方法,则您的服务器响应于系统用户的伪造退信的拒绝响应将与未知收件人的拒绝响应相同(在我们的例子中为550 Unknown User)。


A.13. 免除转发邮件

在SMTP事务中添加所有这些检查后,我们可能会发现自己间接创建了附带垃圾邮件,这是由于拒绝了从受信任的来源(例如邮件列表和其他站点上的邮件帐户)转发的邮件而导致的(有关详细信息,请参见关于转发邮件的讨论)。 现在,我们需要将这些主机列入白名单,以便免除它们进行SMTP拒绝 - 至少是那些由我们的垃圾邮件和/或病毒过滤引起的拒绝。

在此示例中,我们将针对每个RCPT TO:命令查询两个文件

  • 一个全局白名单在/etc/mail/whitelist-hosts中,包含备份MX主机和其他列入白名单的发件人[9],以及

  • 一个用户特定列表在/home/user/.forwarders中,指定该特定用户将从中接收转发邮件的主机(例如邮件列表服务器,其他地方的帐户的传出邮件服务器...)

如果您的邮件用户没有本地用户帐户和主目录,您可能需要修改文件路径和/或查找机制,使其更适合您的系统(例如数据库查找或LDAP查询)。

如果在其中一个白名单中找到发件人主机,我们会将单词"accept"保存在$acl_m0中,并清除$acl_m1的内容,如上一节关于选择性延迟中所述。 这将表明我们不应在后续语句中拒绝该邮件。

acl_rcpt_to 中,我们在验证收件人地址之后,但在与来自远程主机到本地用户的未经身份验证的传递相关的任何accept语句之前(即在任何灰名单检查、信封签名检查等之前)插入以下语句

  # Accept the mail if the sending host is matched in the global
  # whitelist file.  Temporarily set $acl_m9 to point to this  file. 
  # If the host is found, set a flag in $acl_m0 and clear $acl_m1 to 
  # indicate that we should not reject this mail later.
  # 
  accept
    set acl_m9  = /etc/mail/whitelist-hosts
    hosts       = ${if exists {$acl_m9}{$acl_m9}}
    set acl_m0  = accept
    set acl_m1  = 


  # Accept the mail if the sending host is matched in the ".forwarders" 
  # file in the recipient's home directory.  Temporarily set $acl_m9 to
  # point to this file.  If the host is found, set a flag in $acl_m0 and
  # clear $acl_m1 to indicate that we should not reject this mail later.
  #
  accept
    domains     = +local_domains
    set acl_m9  = /home/${extract{1}{=}{${lc:$local_part}}}/.forwarders
    hosts       = ${if exists {$acl_m9}{$acl_m9}} 
    set acl_m0  = accept
    set acl_m1  = 

acl_data ACL的各种语句中,我们检查$acl_m0的内容,以避免在按照上述设置设置的情况下拒绝邮件。 例如,为了避免由于缺少RFC2822标头而拒绝来自列入白名单的主机的邮件

  deny
    message     = Your message does not conform to RFC2822 standard
    log_message = missing header lines
    !hosts      = +relay_from_hosts
    !senders    = : postmaster@*
    condition   = ${if !eq {$acl_m0}{accept}{true}}
    condition   = ${if or {{!def:h_Message-ID:}\
                           {!def:h_Date:}\
                           {!def:h_Subject:}} {true}{false}}

适当的检查嵌入在最终 ACL中,接下来。


A.14. 最终 ACL

好的,醒醒! 这是一篇非常长的阅读文章 - 但恭喜您走到了这一步!

以下ACL包含了我们在此实现中描述的所有检查。 但是,由于以下原因,已注释掉一些检查

  • 灰名单。 这要么需要安装其他软件,要么通过Exim配置文件中的其他ACL和定义进行相当复杂的内联配置。 但是,我强烈推荐它。

  • 病毒扫描。 没有普遍存在的扫描仪,几乎每个人都在使用,类似于用于垃圾邮件识别的SpamAssassin。 另一方面,随附的文档Exiscan-ACL应该很容易遵循。

  • 每个用户的SpamAssassin设置。 这是一种权衡,对于许多人来说是不可接受的,因为它涉及将邮件推迟到除消息的第一个收件人之外的所有人。

  • 信封发件人签名。存在后果,例如对于漫游用户。此外,它涉及配置路由器和传输以及ACL。有关详细信息,请参见该部分。

  • 只接受真实用户的退信。有很多方法可以实现这一点,而确定哪些用户是真实用户取决于邮件的传递方式。

无需赘述,我们期待已久的最终结果终于来了。


A.14.1. acl_connect

# This access control list is used at the start of an incoming
# connection.  The tests are run in order until the connection is
# either accepted or denied.

acl_connect:
  # 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 over a local interface, and from hosts
  # for which we relay mail.
  accept
    hosts       = : +relay_from_hosts


  # If the connecting host is in one of several DNSbl's, then 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
    !hosts      = ${if exists {/etc/mail/whitelist-hosts} \
                              {/etc/mail/whitelist-hosts}}
    dnslists    = list.dsbl.org : \
                  dnsbl.sorbs.net : \
                  dnsbl.njabl.org : \
                  bl.spamcop.net : \
                  dsn.rfc-ignorant.org : \
                  sbl-xbl.spamhaus.org : \
                  l1.spews.dnsbl.sorbs.net
    set acl_c1  = X-DNSbl-Warning: \
                  $sender_host_address is listed in $dnslist_domain\
                  ${if def:dnslist_text { ($dnslist_text)}}


  # Likewise, if reverse DNS lookup of the sender's host fails (i.e.
  # there is no rDNS entry, or a forward lookup of the resulting name
  # does not match the original IP address), then generate a warning
  # message in $acl_c1.  We will later add this message to the mail
  # header.
  warn
    condition   = ${if !def:acl_c1 {true}{false}}
    !verify     = reverse_host_lookup
    set acl_m9  = Reverse DNS lookup failed for host $sender_host_address
    set acl_c1  = X-DNS-Warning: $acl_m9


  # 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


A.14.2. acl_helo

# This access control list is used for the HELO or EHLO command in 
# an incoming SMTP transaction. The tests are run in order until the
# greeting is either accepted or denied.

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


  # Likewise if the peer greets with one of our own names
  # 
  warn
    condition   = ${if match_domain{$sender_helo_name}\
                       {$primary_hostname:+local_domains:+relay_to_domains}\
                       {true}{false}}
    set acl_c0  = Message was delivered by ratware
    set acl_c1  = remote host used our name 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.


  # Accept the greeting, 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


A.14.3. acl_mail_from

# This access control list is used for the MAIL FROM: command in an
# incoming SMTP transaction.  The tests are run in order until the
# sender address is either accepted or denied.
#

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.
  #
  # Sender verification is omitted here, because in many cases
  # the clients are dumb MUAs that don't cope well with SMTP
  # error responses.
  #
  accept
    hosts     = : +relay_from_hosts


  # Accept if the message arrived over an authenticated connection,
  # from any host. Again, these messages are usually from MUAs.
  #
  accept
    authenticated = *


  # 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


  # If sender did not provide a HELO/EHLO greeting, then prepare a reject
  # message in $acl_m0, and a log message in $acl_m1.  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 def:sender_helo_name {0}{1}}
    set acl_m0  = Message was delivered by ratware
    set acl_m1  = remote host did not present HELO/EHLO greeting.


  # If we could not verify the sender address, create a warning message
  # in $acl_m1 and add it to the mail header.  The presence of this
  # message indicates that we should keep stalling the sender.
  #
  # You may choose to omit the "callout" option.  In particular, if
  # you are sending outgoing mail through a smarthost, it will not
  # give any useful information.
  #
  warn
    condition   = ${if !def:acl_m1 {true}{false}}
    !verify     = sender/callout
    set acl_m1  = Invalid sender <$sender_address>
    message     = X-Sender-Verify-Failed: $acl_m1
    log_message = $acl_m1


  # 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


A.14.4. acl_rcpt_to

# This access control list is used for every RCPT command in an
# incoming SMTP message.  The tests are run in order until the 
# recipient address is either accepted or denied.

acl_rcpt_to:

  # 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.
  #
  # Recipient verification is omitted here, because in many
  # cases the clients are dumb MUAs that don't cope well with 
  # SMTP error responses.
  #
  accept
    hosts       = : +relay_from_hosts


  # Accept if the message arrived over an authenticated connection,
  # from any host. Again, these messages are usually from MUAs, so
  # recipient verification is omitted.
  #
  accept
    authenticated = *


  # Deny if the local part contains @ or % or / or | or !. These are
  # rarely found in genuine local parts, but are often tried by people
  # looking to circumvent relaying restrictions.
  #
  # Also deny if the local part starts with a dot. Empty components
  # aren't strictly legal in RFC 2822, but Exim allows them because
  # this is common.  However, actually starting with a dot may cause
  # trouble if the local part is used as a file name (e.g. for a
  # mailing list).
  #
  deny
    local_parts = ^.*[@%!/|] : ^\\.


  # Deny if we have previously given a reason for doing so in $acl_m0.
  # Also stall the sender for another 20s first.
  #
  deny
    message     = $acl_m0
    log_message = $acl_m1
    condition   = ${if and {{def:acl_m0}{def:acl_m1}} {true}}
    delay       = 20s


  # If the recipient address is not in a domain for which we are handling
  # mail, stall the sender and reject.
  #
  deny
    message     = relay not permitted
    !domains    = +local_domains : +relay_to_domains
    delay       = 20s


  # If the address is in a local domain or in a domain for which are
  # relaying, but is invalid, stall and reject.
  #
  deny
    message     = unknown user
    !verify     = recipient/callout=20s,defer_ok,use_sender
    delay       = ${if def:sender_address {1m}{0s}}



  # Drop the connection if the envelope sender is empty, but there is
  # more than one recipient address.  Legitimate DSNs are never sent
  # to more than one address.
  #
  drop
    message      = Legitimate bounces are never sent to more than one \
                   recipient.
    senders      = : postmaster@*
    condition    = $recipients_count
    delay        = 5m


  # --------------------------------------------------------------------
  # Limit the number of recipients in each incoming message to one
  # to support per-user settings and data (e.g. for SpamAssassin).
  #
  # NOTE: Every mail sent to several users at your site will be
  #       delayed for 30 minutes or more per recipient.  This
  #       significantly slow down the pace of discussion threads
  #       involving several internal and external parties.
  #       Thus, it is commented out by default.
  #
  #defer
  #  message      = We only accept one recipient at a time - please try later.
  #  condition    = $recipients_count
  # --------------------------------------------------------------------


  # Accept the mail if the sending host is matched in the ".forwarders" 
  # file in the recipient's home directory.  Temporarily set $acl_m9 to
  # point to this file.  If the host is found, set a flag in $acl_m0 and
  # clear $acl_m1 to indicate that we should not reject this mail later.
  #
  accept
    domains     = +local_domains
    set acl_m9  = /home/${extract{1}{=}{${lc:$local_part}}}/.forwarders
    hosts       = ${if exists {$acl_m9}{$acl_m9}} 
    set acl_m0  = accept
    set acl_m1  = 


  # Accept the mail if the sending host is matched in the global
  # whitelist file.  Temporarily set $acl_m9 to point to this  file. 
  # If the host is found, set a flag in $acl_m0 and clear $acl_m1 to 
  # indicate that we should not reject this mail later.
  # 
  accept
    set acl_m9  = /etc/mail/whitelist-hosts
    hosts       = ${if exists {$acl_m9}{$acl_m9}}
    set acl_m0  = accept
    set acl_m1  = 


  # --------------------------------------------------------------------
  # Envelope Sender Signature Check.
  # This is commented out by default, because it requires additional
  # configuration in the 'transports' and 'routers' sections.
  #
  # 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}}
  # --------------------------------------------------------------------


  # --------------------------------------------------------------------
  # Deny mail for local users that do not have a mailbox (i.e. postmaster,
  # webmaster...) if no sender address is provided.  These users do
  # not send outgoing mail, so they should not receive returned mail.
  #
  # NOTE: This is commented out by default, because the condition is
  #       specific to how local mail is delivered.  If you want to
  #       enable this check, uncomment one and only one of the
  #       conditions below.
  #
  #deny
  #  message     = This address never sends outgoing mail. \
  #                You are responding to a forged sender address.
  #  log_message = bogus bounce for system user <$local_part@$domain>
  #  senders     = : postmaster@*
  #  domains     = +local_domains
  #  set acl_m9  = ${extract{1}{=}{${lc:$local_part}}}
  #
  # --- Uncomment the following 2 lines if recipients have local accounts:
  #  set acl_m9  = ${extract{2}{:}{${lookup passwd {$acl_m9}{$value}}}{0}}
  #  !condition  = ${if and {{>={$acl_m9}{500}} {<${acl_m9}{60000}}} {true}}
  # 
  # --- Uncomment the following line if you deliver mail to Cyrus:
  #  condition  = ${run {/usr/sbin/mbpath -q -s user.$acl_m9} {true}}
  # --------------------------------------------------------------------



  # Query the SPF information for the sender address domain, if any,
  # to see if the sending host is authorized to deliver its mail.
  # If not, reject the mail.
  #
  deny
    message     = [SPF] $sender_host_address is not allowed to send mail \
                  from $sender_address_domain
    log_message = SPF check failed.
    spf         = fail


  # Add a SPF-Received: line to the message header
  warn
    message     = $spf_received


  # --------------------------------------------------------------------
  # Check greylisting status for this particular peer/sender/recipient.
  # Before uncommenting this statement, you need to install "greylistd".
  # See:  http://packages.debian.org/unstable/main/greylistd
  #
  # Note that we do not greylist messages with NULL sender, because
  # sender callout verification would break (and we might not be able
  # to send mail to a host that performs callouts).
  #
  #defer
  #  message     = $sender_host_address is not yet authorized to deliver mail \
  #                from <$sender_address> to <$local_part@$domain>. \
  #                Please try later.
  #  log_message = greylisted.
  #  domains     = +local_domains : +relay_to_domains
  #  !senders    = : postmaster@*
  #  set acl_m9  = $sender_host_address $sender_address $local_part@$domain
  #  set acl_m9  = ${readsocket{/var/run/greylistd/socket}{$acl_m9}{5s}{}{}}
  #  condition   = ${if eq {$acl_m9}{grey}{true}{false}}
  #  delay       = 20s
  # --------------------------------------------------------------------

  # Accept the recipient.
  accept


A.14.5. acl_data

# This access control list is used for message data received via 
# SMTP.  The tests are run in order until the recipient address 
# is either accepted or denied.

acl_data:
  # Log some header lines
  warn
    logwrite    = Subject: $h_Subject:


  # Add Message-ID if missing in messages received from our own hosts.
  warn
    condition   = ${if !def:h_Message-ID: {1}}
    hosts       = +relay_from_hosts
    message     = Message-ID: <E$message_id@$primary_hostname>


  # 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

  # Accept if the message arrived over an authenticated connection, from
  # any host.
  #
  accept
    authenticated = *


  # Deny if we have previously given a reason for doing so in $acl_m0.
  # Also stall the sender for another 20s first.
  #
  deny
    message     = $acl_m0
    log_message = $acl_m1
    condition   = ${if and {{def:acl_m0}{def:acl_m1}} {true}{false}}
    delay       = 20s


  # enforce a message-size limit
  #
  deny
    message     = Message size $message_size is larger than limit of \
                  MESSAGE_SIZE_LIMIT
    condition   = ${if >{$message_size}{MESSAGE_SIZE_LIMIT}{yes}{no}}


  # Deny unless the addresses in the header is syntactically correct.
  #
  deny
    message     = Your message does not conform to RFC2822 standard
    log_message = message header fail syntax check
    !verify     = header_syntax


  # Uncomment the following to deny non-local messages without
  # a Message-ID:, Date:, or Subject: header.
  #
  # Note that some specialized MTAs, such as certain mailing list 
  # servers, do not automatically generate a Message-ID for bounces.
  # Thus, we add the check for a non-empty sender.
  #
  #deny
  #  message     = Your message does not conform to RFC2822 standard
  #  log_message = missing header lines
  #  !hosts      = +relay_from_hosts
  #  !senders    = : postmaster@*
  #  condition   = ${if !eq {$acl_m0}{accept}{true}}
  #  condition   = ${if or {{!def:h_Message-ID:}\
  #                         {!def:h_Date:}\
  #                         {!def:h_Subject:}} {true}{false}}


  # Warn unless there is a verifiable sender address in at least
  # one of the "Sender:", "Reply-To:", or "From:" header lines.
  #
  warn
    message     = X-Sender-Verify-Failed: No valid sender in message header
    log_message = No valid sender in message header
    !verify     = header_sender



  # --------------------------------------------------------------------
  # Perform greylisting on messages with no envelope sender here.
  # We did not subject these to greylisting after RCPT TO: because
  # that would interfere with remote hosts doing sender callouts.
  # Note that the sender address is empty, so we don't bother using it.
  #
  # Before uncommenting this statement, you need to install "greylistd".
  # See:  http://packages.debian.org/unstable/main/greylistd
  #
  #defer
  #  message     = $sender_host_address is not yet authorized to send \
  #                delivery status reports to <$recipients>. \
  #                Please try later.
  #  log_message = greylisted.
  #  senders     = : postmaster@*
  #  condition   = ${if !eq {$acl_m0}{accept}{true}}
  #  set acl_m9  = $sender_host_address $recipients
  #  set acl_m9  = ${readsocket{/var/run/greylistd/socket}{$acl_m9}{5s}{}{}}
  #  condition   = ${if eq {$acl_m9}{grey}{true}{false}}
  #  delay       = 20s
  # --------------------------------------------------------------------



  # --- BEGIN EXISCAN configuration ---

  # Reject messages that have serious MIME errors.
  #
  deny
    message     = Serious MIME defect detected ($demime_reason)
    demime      = *
    condition   = ${if >{$demime_errorlevel}{2}{1}{0}}


  # Unpack MIME containers and reject file extensions used by worms.
  # This calls the demime condition again, but it will return cached results.
  # Note that the extension list may be incomplete.
  #
  deny
    message     = We do not accept ".$found_extension" attachments here.
    demime      = bat:btm:cmd:com:cpl:dll:exe:lnk:msi:pif:prf:reg:scr:vbs:url


  # Messages larger than MESSAGE_SIZE_SPAM_MAX are accepted without
  # spam or virus scanning
  accept
    condition   = ${if >{$message_size}{MESSAGE_SIZE_SPAM_MAX} {true}}
    logwrite    = :main: Not classified \
                  (message size larger than MESSAGE_SIZE_SPAM_MAX)


  # --------------------------------------------------------------------
  # Anti-Virus scanning
  # This requires an 'av_scanner' setting in the main section.
  #
  #deny
  #  message  = This message contains a virus ($malware_name)
  #  demime   = *
  #  malware  = */defer_ok
  # --------------------------------------------------------------------



  # Invoke SpamAssassin to obtain $spam_score and $spam_report.
  # Depending on the classification, $acl_m9 is set to "ham" or "spam".
  #
  # If the message is classified as spam, and we have not previously
  # set $acl_m0 to indicate that we want to accept it anyway, pretend
  # reject it.
  #
  warn
    set acl_m9  = ham
    # ------------------------------------------------------------------
    # If you want to allow per-user settings for SpamAssassin,
    # uncomment the following line, and comment out "spam = mail".
    # We pass on the username specified in the recipient address,
    # i.e. the portion before any '=' or '@' character, converted
    # to lowercase.  Multiple recipients should not occur, since
    # we previously limited delivery to one recipient at a time.
    #
    # spam        = ${lc:${extract{1}{=@}{$recipients}{$value}{mail}}}
    # ------------------------------------------------------------------
    spam        = mail
    set acl_m9  = spam
    condition   = ${if !eq {$acl_m0}{accept}{true}}
    control     = fakereject
    logwrite    = :reject: Rejected spam (score $spam_score): $spam_report



  # Add an appropriate X-Spam-Status: header to the message.
  #
  warn
    message     = X-Spam-Status: \
                  ${if eq {$acl_m9}{spam}{Yes}{No}} (score $spam_score)\
                  ${if def:spam_report {: $spam_report}}
    logwrite    = :main: Classified as $acl_m9 (score $spam_score)


  # --- END EXISCAN configuration ---


  # Accept the message. 
  #
  accept

词汇表

这些是本文档中使用的某些单词和术语的定义。

B

贝叶斯过滤器

一种根据消息中单词(或最近是单词组合/短语)的重复出现概率来分配垃圾邮件概率的过滤器。

您最初通过向过滤器输入已知的垃圾邮件(spam)和已知的合法邮件(ham)来训练过滤器。然后,将贝叶斯评分分配给每封邮件中的每个单词(或短语),指示该特定单词或短语最常出现在ham中还是在spam中。该单词及其评分存储在贝叶斯索引中。

此类过滤器可能会捕获人工程序员尝试手动创建基于关键字的过滤器时可能遗漏的指标。至少,它们可以自动执行此任务。

贝叶斯单词索引无疑特定于他们接受训练的语言。此外,它们特定于各个用户。因此,它们可能更适合于单个内容过滤器(例如,在 邮件用户代理中),而不是用于系统范围的,SMTP时间的过滤。

此外,垃圾邮件发送者已经开发出一些技术来击败简单的贝叶斯过滤器,方法是在其消息中包含随机字典单词和/或短篇小说。这降低了baynesian过滤器分配的垃圾邮件概率,并且从长远来看,会降低贝叶斯索引的质量。

另请参阅:http://www.everything2.com/index.pl?node=Bayesian

C

附带损害

由于DNS黑名单中的条目而阻止了合法的发件人主机。

一些黑名单(如SPEWS)通常会列出ISP的整个IP地址空间,如果他们认为ISP对滥用投诉没有反应,从而影响所有客户。

另请参阅:误报

附带垃圾邮件

自动发送的消息,以响应原始消息(主要是垃圾邮件或恶意软件),其中发件人地址是伪造的。附带垃圾邮件的典型示例包括病毒扫描报告("您有病毒")或其他 传递状态通知)。

D

域名系统

缩写:DNS)用于获取有关互联网域名的信息的实际标准。此类信息的示例包括其服务器的IP地址(所谓的A记录),传入邮件交换机的专用(MX记录),通用服务器信息(SRV记录)以及其他文本信息(TXT记录)。

DNS是分层的分布式系统;每个域名都与一组或多个DNS服务器相关联,这些服务器提供有关该域的信息 - 包括其子域的名称服务委派。

例如,顶级域"org"由公共利益注册管理机构运营;其DNS服务器将域名"tldp.org"的查询委派给Linux文档项目的特定名称服务器。反过来,TLDP的名称服务器(实际上由UNC运营)可能会或可能不会委派对第三级名称(例如"www.tldp.org")的查询。

DNS查找通常由转发名称服务器执行,例如Internet服务提供商提供的那些(例如,通过DHCP)。

传递状态通知

缩写:DSN)由MTA或MDA自动创建的消息,用于通知原始消息(通常包含在DSN中)的发件人有关其状态。例如,DSN可以通知原始消息的发件人由于临时或永久问题而无法传递消息,并且/或者是否以及将持续多久的传递尝试。

传递状态通知以空的 信封发件人 地址发送。

E

信封发件人

在使用 MAIL FROM: 命令的SMTP事务期间,作为消息发件人给出的电子邮件地址。这可能与消息本身的 "From:" 标头中提供的地址不同。

一个特殊情况是 传递状态通知(退回邮件,回执,休假消息..)。对于此类邮件,信封发件人 为空。这是为了防止 邮件循环,通常能够将这些与 "常规" 邮件区分开。

另请参阅:SMTP事务

信封收件人

发送消息的电子邮件地址。这些地址在使用 RCPT TO 命令的SMTP事务期间提供。这些地址可能与消息本身的 "To:""Cc:" 标头中提供的地址不同。

另请参阅:SMTP事务

F

假阴性

被错误分类为合法邮件的垃圾邮件(垃圾邮件,病毒,恶意软件)(因此,未被过滤掉)。

误报

被错误分类为垃圾邮件的合法邮件(因此,被阻止)。

另请参阅:附带损害

完全限定域名

(又名"FQDN")。一个完整的,全局唯一的互联网名称,包括DNS域。例如:"www.yahoo.com"

FQDN 并不总是指向单个主机。例如,诸如 "www" 之类的常用服务名称通常指向许多IP地址,以便在服务器上提供一些负载平衡。但是,给定机器的主要主机名应始终对该机器是唯一的。例如:"p16.www.scd.yahoo.com"

FQDN 始终包含句点(".")。第一个句点之前的部分是非限定名称,并且不是全局唯一的。

J

Joe Job

一种垃圾邮件,旨在使其看起来来自其他人的有效地址,通常是为了恶意尝试生成来自第三方的投诉和/或对该地址的所有者造成其他损害。

另请参阅:http://www.everything2.com/index.pl?node=Joe%20Job

M

邮件传递代理

缩写:MDA)在用户邮箱所在的计算机上运行的软件,用于将邮件传递到该邮箱中。通常,该传递直接由MTA 邮件传输代理执行,然后它充当MDA的辅助角色。单独的邮件传递代理的示例包括:Deliver,Procmail,Cyrmaster和/或Cyrdeliver(来自Cyrus IMAP套件)。

邮件循环

一种情况,其中一个自动消息触发另一个消息,该消息直接或间接地再次触发第一个消息,依此类推。

想象一下一个邮件列表,其中一个订阅者是列表本身的地址。通常,列表服务器通过在消息标头中添加一个 "X-Loop:" 行来处理这种情况,并且不处理已经具有该行的邮件。

另一个等效的术语是Ringing

邮件传输代理

缩写:MTA)在邮件服务器上运行的软件,例如互联网域的邮件交换机,用于将邮件发送到其他主机并从其他主机接收邮件。流行的MTA包括:Sendmail,Postfix,Exim,Smail。

邮件用户代理

缩写:MUA; 又名邮件阅读器)用户软件,用于访问,下载,读取和发送邮件。示例包括Microsoft Outlook/Outlook Express,Apple Mail.app,Mozilla Thunderbird,Ximian Evolution。

邮件交换机

缩写:MX)专用于(发送和/或)接收互联网域邮件的机器。

互联网域的DNS区域信息通常包含 完全限定域名s的列表,这些名称充当该域的传入邮件交换机。每个此类列表都称为 "MX记录",并且还包含一个数字,指示其在多个 "MX记录" 中的 "优先级"。数字最小的列表具有最高的优先级,并被认为是该域的 "主邮件交换机"

小额支付计划

(又名发件人付费计划)。消息的发件人消耗一些机器资源来为消息的每个收件人创建一个虚拟邮资印花 - 通常通过解决一个需要大量内存读/写操作,但对CPU速度相对不敏感的数学难题。然后,将此印花添加到消息的标头中,收件人将通过更简单的解码操作来验证该印花。

其想法是,由于该消息需要为每个收件人地址提供邮资印花,因此一次向成百上千个用户发送垃圾邮件的“成本”将会高得令人望而却步。

其中两个系统是

O

开放代理

一种 代理,可以从任何地方公开接受TCP/IP连接,并将其转发到任何地方。

垃圾邮件发送者和病毒通常会利用它们来隐藏自己的IP地址,和/或更有效地在多个主机和网络上分配传输负载。

另请参阅:僵尸主机

开放中继

一种 中继,可以从任何地方公开接受邮件,并将其转发到任何地方。

在1980年代,几乎每个公共SMTP服务器都是 开放中继。消息通常会在到达预期收件人之前在多个第三方机器之间传输。现在,合法的邮件几乎都是直接从发件人端的传出 邮件传输代理 发送到收件人域的传入 邮件交换机

相反,互联网上仍然存在的 开放中继 服务器几乎完全被垃圾邮件发送者利用,以隐藏自己的身份,并在发送数百万条消息的任务上执行一些负载平衡,大概是在DNS黑名单有机会将所有这些机器都列出之前。

另请参阅有关 防止开放中继的讨论。

P

代理

代表他人行事的机器。它可以转发例如HTTP请求或TCP/IP连接,通常是发送到互联网或从互联网发送。例如,公司 - 或有时是整个国家 - 通常使用 "Web代理服务器" 来过滤来自其内部网络的传出HTTP请求。这可能或可能对最终用户透明。

另请参阅:开放代理中继

R

Ratware

垃圾邮件发送者使用的大规模发送病毒和电子邮件软件,专门设计用于在很短的时间内传递大量邮件。

大多数ratware实现仅包含在最佳情况下传递邮件绝对必要的SMTP客户端代码。他们在与接收主机的SMTP对话框中提供虚假或不准确的信息。他们在发出命令之前不会等待接收者的响应,并且如果在几秒钟内未收到响应,则会断开连接。如果出现暂时性故障,他们不会遵循正常的重试机制。

中继

通常是转发到互联网或从互联网转发电子邮件的机器。中继的一个示例是ISP为其客户提供的用于发送传出邮件的 "smarthost"

另请参阅:开放中继代理

请求评论

缩写:RFC)来自 http://www.rfc-editor.org/"请求评议(RFC)文档系列是一组关于互联网的技术和组织说明 [...]。 RFC 系列备忘录讨论了计算机网络的许多方面,包括协议、过程、程序和概念,以及会议记录、意见,有时还有幽默。"

这些文档构成了互联网行为的"规则",包括对协议和数据格式的描述。邮件传递方面特别重要的是:

S

垃圾邮件陷阱 (Spam Trap)

一种电子邮件地址,通过公开的位置植入到地址收集机器人中,然后用于提供给协同工具,例如 DNS 黑名单垃圾邮件签名存储库

发送到这些地址的邮件通常是垃圾邮件或恶意软件。但是,其中一些将是附带损害,即发送到伪造发件人地址的 传递状态通知。因此,除非垃圾邮件陷阱有适当的保障措施来忽略此类消息,否则生成的工具可能不完全可靠。

Z

僵尸主机 (Zombie Host)

一台连接到互联网并感染了群发病毒或蠕虫的机器。 这样的机器总是运行某种版本的 Microsoft Windows 操作系统,并且几乎总是在"住宅" IP 地址段中。 他们的所有者要么不知道,要么不在乎机器是否被感染,而且通常,他们的 ISP 不会采取任何措施来关闭它们。

幸运的是,有各种 DNS 黑名单,例如"dul.dnsbl.sorbs.net",其中包含此类“住宅”地址块。 您应该能够使用这些黑名单来拒绝传入邮件。 来自住宅用户的合法邮件通常应通过其 ISP 的"智能主机"


附录 B. GNU 通用公共许可证

B.1. 序言

大多数软件的许可证旨在剥夺您共享和更改它的自由。 相比之下,GNU 通用公共许可证旨在保证您共享和更改自由软件的自由——确保该软件对所有用户都是免费的。 本通用公共许可证适用于自由软件基金会的大多数软件以及任何其他作者承诺使用它的程序。(自由软件基金会的某些其他软件受 GNU 库通用公共许可证的保护。)您也可以将其应用于您的程序。

当我们谈论自由软件时,我们指的是自由,而不是价格。 我们的通用公共许可证旨在确保您有分发自由软件副本的自由(并且如果您愿意,可以为此服务收费),您可以收到源代码,或者如果您需要可以获得它,您可以更改软件或在新自由程序中使用它的片段;并且您知道您可以做这些事情。

为了保护您的权利,我们需要做出限制,禁止任何人否认您这些权利或要求您放弃这些权利。 如果您分发软件副本或修改软件,这些限制将转化为您必须承担的某些责任。

例如,如果您分发此类程序的副本,无论是免费的还是收费的,您都必须将您拥有的所有权利授予接收者。 您必须确保他们也收到或可以获得源代码。 并且您必须向他们展示这些条款,以便他们了解自己的权利。

我们通过两个步骤保护您的权利:

  1. 对软件进行版权保护,以及

  2. 为您提供此许可证,该许可证赋予您复制、分发和/或修改软件的合法许可。

此外,为了保护每位作者的权益和我们的权益,我们希望确保每个人都明白,此自由软件不提供任何保证。 如果软件被其他人修改并传递下去,我们希望它的接收者知道他们拥有的不是原始版本,以便其他人引入的任何问题都不会影响原始作者的声誉。

最后,任何自由程序都不断受到软件专利的威胁。 我们希望避免自由程序的再分发者单独获得专利许可,从而实际上使该程序成为专有程序的危险。 为了防止这种情况,我们已经明确规定,任何专利都必须获得许可供所有人免费使用,或者根本不获得许可。

以下是复制、分发和修改的精确条款和条件。


B.2. 复制、分发和修改的条款和条件

B.2.1. 第 0 节

本许可证适用于任何程序或其他作品,其中包含版权所有者发出的声明,表明可以根据本通用公共许可证的条款分发该程序或其他作品。 下面的“程序”是指任何此类程序或作品,并且“基于程序的作品”是指根据版权法的程序或任何衍生作品:也就是说,包含程序或其一部分的作品,无论是逐字记录还是经过修改和/或翻译成另一种语言。(此后,翻译无限制地包含在术语“修改”中。)每个被许可人都被称为“您”

复制、分发和修改以外的活动不受本许可证的约束;它们不在其范围内。 运行程序的行为不受限制,并且程序产生的输出仅在其内容构成基于程序的作品(独立于通过运行程序而生成)的情况下才受约束。 这是否真实取决于程序的作用。


B.2.2. 第 1 节

您可以复制和分发您收到的程序源代码的逐字副本,以任何媒介提供,前提是您在每个副本上显眼且适当地发布适当的版权声明和免责声明;完整地保留所有引用本许可证和不存在任何保证的声明;并将本许可证的副本连同程序一起提供给程序的任何其他接收者。

您可以收取转移副本的实际费用,并且您可以选择提供有偿保修保护。


B.2.3. 第 2 节

您可以修改您的程序副本或其中的任何部分,从而形成基于该程序的作品,并根据上述 第 1 节的条款复制和分发此类修改或作品,前提是您还满足以下所有条件:

  1. 您必须使修改后的文件带有显眼的声明,说明您更改了这些文件以及任何更改的日期。

  2. 您必须使您分发或发布的任何全部或部分包含程序或由程序或其任何部分衍生的作品,根据本许可证的条款,免费许可给所有第三方。

  3. 如果修改后的程序在运行时通常以交互方式读取命令,则您必须使其在以最普通的方式开始运行以进行此类交互式使用时,打印或显示一条公告,包括适当的版权声明和不存在保证的声明(或者,声明您提供保证),以及用户可以在这些条件下重新分发该程序的声明,并告诉用户如何查看本许可证的副本。

    Note例外
     

    如果程序本身是交互式的,但通常不打印此类公告,则不需要基于该程序的工作打印公告。)

这些要求适用于整个修改后的作品。 如果该作品中可识别的部分不是从程序派生的,并且可以合理地被视为独立的和单独的作品,则当您将它们作为单独的作品分发时,本许可证及其条款不适用于这些部分。 但是,当您将相同的部分作为整体的一部分分发,该整体是基于程序的作品时,整个作品的分发必须符合本许可证的条款,本许可证对其他被许可人的许可扩展到整个作品,从而扩展到每个部分,无论谁编写的。

因此,本节的目的不是声明权利或质疑您对完全由您编写的作品的权利;相反,目的是行使控制基于程序的衍生作品或集体作品的分发的权利。

此外,仅仅将另一个不基于程序的作品与程序(或基于程序的作品)聚合在存储或分发介质的卷上,不会使另一个作品受到本许可证的约束。


B.2.4. 第 3 节

您可以根据上述 第 1 节第 2 节的条款复制和分发程序(或基于它的作品,根据 第 2 节)以目标代码或可执行形式,前提是您还执行以下一项操作:

  1. 附带完整的相应的机器可读源代码,该源代码必须根据上述第 1 节和第 2 节的条款在通常用于软件交换的介质上分发;或者,

  2. 附带一份书面要约,有效期至少为三年,以不高于您实际执行源代码分发成本的价格,向任何第三方提供相应的源代码的完整机器可读副本,以便根据上述第 1 节和第 2 节的条款在通常用于软件交换的介质上分发;或者,

  3. 附带您收到的有关分发相应源代码的要约的信息。(只有对于非商业分发,并且只有当您按照上述 b 小节以目标代码或可执行形式收到带有此类要约的程序时,才允许使用此替代方案。)

对于一项作品而言,其源代码指的是修改该作品的首选形式。对于可执行作品而言,完整的源代码指的是它所包含的所有模块的全部源代码,加上任何相关的接口定义文件,以及用于控制可执行文件的编译和安装的脚本。但是,作为一种特殊的例外情况,分发的源代码不需要包含通常与运行该可执行文件的操作系统(编译器、内核等等)的主要组件一起分发的任何东西(无论是源代码形式还是二进制形式),除非该组件本身随可执行文件一起分发。

如果通过提供从指定地点复制的访问权限来分发可执行代码或目标代码,那么提供从同一地点复制源代码的等效访问权限也算是源代码的分发,即使第三方没有义务连同目标代码一起复制源代码。


B.2.5. 第4节

除非本许可证明确规定,否则您不得复制、修改、再许可或分发本程序。任何以其他方式复制、修改、再许可或分发本程序的行为均属无效,并将自动终止您在本许可证下的权利。但是,根据本许可证从您那里收到副本或权利的各方,只要他们保持完全合规,他们的许可证就不会被终止。


B.2.6. 第5节

您没有义务接受本许可证,因为您没有签署它。但是,如果没有本许可证,没有其他任何东西授予您修改或分发本程序或其衍生作品的权限。如果您不接受本许可证,法律禁止这些行为。因此,通过修改或分发本程序(或任何基于本程序的作品),您表明您接受本许可证,以及所有关于复制、分发或修改本程序或基于它的作品的条款和条件。


B.2.7. 第6节

每次您重新分发本程序(或任何基于本程序的作品)时,接收者都会自动收到来自原始许可人的许可,以复制、分发或修改本程序,但须遵守这些条款和条件。您不得对接收者行使此处授予的权利施加任何进一步的限制。您不负责强制第三方遵守本许可证。


B.2.8. 第7节

如果由于法院判决或专利侵权指控或任何其他原因(不限于专利问题),您被施加的条件(无论是通过法院命令、协议或其他方式)与本许可证的条件相矛盾,这些条件并不能免除您遵守本许可证的条件。如果您无法分发以同时满足您在本许可证下的义务和任何其他相关义务,那么您可能根本无法分发本程序。例如,如果专利许可不允许所有直接或间接通过您收到副本的人免版税地重新分发本程序,那么您能够同时满足它和本许可证的唯一方法就是完全避免分发本程序。

如果本节的任何部分在任何特定情况下被认为是无效或不可执行的,本节的其余部分应继续适用,并且本节作为一个整体应在其他情况下适用。

本节的目的不是诱导您侵犯任何专利或其他财产权主张,也不是质疑任何此类主张的有效性;本节的唯一目的是保护自由软件分发系统的完整性,该系统通过公共许可证实践来实施。许多人对通过该系统分发的各种软件做出了慷慨的贡献,他们依赖于该系统的一致应用;是否愿意通过任何其他系统分发软件由作者/捐赠者决定,许可人不能强加这种选择。

本节旨在彻底明确人们认为的本许可证其余部分的推论。


B.2.9. 第8节

如果某些国家/地区由于专利或受版权保护的接口而限制了本程序的分发和/或使用,则将本程序置于本许可证下的原始版权所有者可以添加明确的地理分发限制,排除这些国家/地区,以便仅在未如此排除的国家/地区内或之间允许分发。在这种情况下,本许可证将该限制纳入其中,如同写在本许可证的正文中一样。


B.2.10. 第9节

自由软件基金会可能会不时发布通用公共许可证的修订版和/或新版本。这些新版本在精神上将与当前版本相似,但可能在细节上有所不同,以解决新的问题或担忧。

每个版本都有一个独特的版本号。如果本程序指定了适用于它的本许可证的版本号和“任何后续版本”,您可以选择遵循该版本或自由软件基金会发布的任何后续版本的条款和条件。如果本程序没有指定本许可证的版本号,您可以选择自由软件基金会发布的任何版本。


B.2.11. 第10节

如果您希望将本程序的部分内容合并到分发条件不同的其他自由程序中,请写信给作者请求许可。对于自由软件基金会拥有版权的软件,请写信给自由软件基金会;我们有时会为此做出例外。我们的决定将以维护我们自由软件的所有衍生品的自由状态以及促进软件的共享和重用这两个目标为指导。


B.2.12. 无担保 第11节

由于本程序是免费许可的,因此在适用法律允许的范围内,本程序不提供任何担保。除非另有书面说明,否则版权所有者和/或其他各方“按原样”提供本程序,不提供任何形式的担保,无论是明示的还是暗示的,包括但不限于对适销性和适用于特定目的的暗示担保。本程序质量和性能的全部风险由您承担。如果本程序被证明有缺陷,您将承担所有必要的维修、修理或更正的费用。


B.2.13. 第12节

在任何情况下,除非适用法律要求或书面同意,任何版权所有者或任何其他可能按照上述允许修改和/或重新分发本程序的各方均不对您承担任何损害赔偿责任,包括因使用或无法使用本程序而引起的任何一般性、特殊性、附带性或后果性损害赔偿(包括但不限于数据丢失或数据变得不准确,或您或第三方遭受的损失,或本程序无法与任何其他程序一起运行),即使此类持有者或其他各方已被告知可能发生此类损害赔偿。

条款和条件结束


B.3. 如何将这些条款应用于您的新程序

如果您开发了一个新程序,并且希望它对公众发挥最大的作用,那么实现这一目标的最佳方法是使其成为自由软件,每个人都可以在这些条款下重新分发和更改它。

为此,请将以下声明附加到程序中。最安全的方法是将它们附加到每个源文件的开头,以最有效地传达排除担保的信息;并且每个文件至少应包含“版权”行以及指向完整声明位置的指针。

<用一行文字给出程序的名称以及对其功能的简要说明。> 版权 (C) <年份> <作者姓名>

本程序是自由软件;您可以按照自由软件基金会发布的 GNU 通用公共许可证的条款重新分发和/或修改它;无论是许可证的第 2 版,还是(由您选择)任何后续版本。

发布本程序是希望它有用,但没有任何担保;甚至没有对适销性或适用于特定目的的暗示担保。有关更多详细信息,请参见 GNU 通用公共许可证。

您应该已经收到了 GNU 通用公共许可证的副本以及本程序;如果不是,请写信给自由软件基金会,地址:59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

另外,添加有关如何通过电子和纸质邮件与您联系的信息。

如果程序是交互式的,请使其在以交互模式启动时输出类似这样的简短通知

Gnomovision 版本 69,版权 (C) 年份 作者姓名 Gnomovision 绝对不提供任何担保;有关详细信息,请键入“show w”。这是自由软件,欢迎您在某些条件下重新分发它;键入“show c”查看详细信息。

假设的命令“show w”和“show c”应显示通用公共许可证的相应部分。当然,您使用的命令可以称为“show w”和“show c”以外的其他名称;它们甚至可以是鼠标点击或菜单项 - 无论哪种方式适合您的程序。

如果必要,您还应该让您的雇主(如果您是程序员)或您的学校签署一份程序的“版权免责声明”。这是一个示例;更改名称

Yoyodyne, Inc. 特此放弃对 James Hacker 编写的程序“Gnomovision”(对编译器进行传递)的所有版权权益。

<Ty Coon 的签名>, 1989 年 4 月 1 日 Ty Coon,副总裁

本通用公共许可证不允许将您的程序合并到专有程序中。如果您的程序是一个子例程库,您可能会认为允许将专有应用程序与该库链接更有用。如果您想这样做,请使用 GNU 库通用公共许可证代替本许可证。

注释

[1]

如果您拒绝邮件,不受信任的第三方主机可能仍然会生成附带垃圾邮件。但是,除非该主机是 开放代理开放中继,否则它大概只会发送来自合法发件人的邮件,这些发件人的地址是有效的。如果它*确实*是一个开放代理或 SMTP 中继 - 那么最好拒绝邮件并让它在*他们的*外发邮件队列中冻结,而不是让它在你的队列中冻结。最终,这应该会让该服务器的所有者有所了解。

[2]

就我个人而言,我认为挑战/响应系统在任何情况下都不是一个好主意。它们会生成 附带垃圾邮件,对于从每月银行对账单等自动来源发送的邮件,它们需要特别注意,并且它们会降低电子邮件的可用性,因为人们需要跳过障碍才能相互联系。很多时候,合法邮件的发送者不会费心或不知道他们需要跟进确认请求,并且邮件丢失了。

[3]

我的观点与许多 “垃圾邮件行动主义者” 的观点形成鲜明对比,例如 SPEWS 黑名单 的维护者。该列表的既定目标之一正是造成 附带损害,以此向 ISP 施加压力,要求他们对滥用投诉做出反应。通常,列表投诉会遇到诸如 “找你的 ISP,别找我们”“换一个 ISP” 之类的膝跳反应。

通常,这些不是可行的选择。考虑到发展中国家。就此而言,考虑到几乎在任何地方,宽带提供商都是受监管的垄断企业。我相信这些态度说明了信任这些团体的问题所在。

简而言之,有更好、更准确的方法来过滤垃圾邮件。

[4]

请注意,当您阻止传入的 SMTP 邮件时,您也在服务器上占用了一个 TCP 套接字,以及内存和其他服务器资源。如果您的服务器通常很忙,则施加 SMTP 事务延迟将使您更容易受到拒绝服务 (Denial-of-Service) 攻击。一个更具"可扩展性"的选择可能是,一旦您有确凿的证据表明发件人是恶意软件客户端,就断开连接。

[5]

存在类似用途的不同列表。例如,"bondedsender.org" 是一个DNS 白名单 (DNSwl),包含"可信任的" IP 地址,这些 IP 地址的所有者已发布财务保证金,如果从该地址发出垃圾邮件,该保证金将被扣除。其他列表包含特定国家/地区、特定 ISP 等使用的 IP 地址。

[6]

例如,世界上最大的互联网服务提供商 (ISP) comcast.net 的传出邮件交换器("智能主机")在撰写本文时已包含在 SPEWS 的 Level 1 列表中。从 Comcast 需要更有效地执行其自身 AUP 的角度来看,并非完全不应该,但此列表确实影响了 30% 的美国互联网用户,其中大多数是"无辜的"用户,例如我自己。

更糟糕的是,SPEWS FAQ 中发布的信息指出: Level 1 列表的大部分由垃圾邮件发送者或垃圾邮件支持运营者本身拥有的网段组成,几乎没有检测到其他合法客户。 从技术上讲,如果 (a) 您认为 Comcast 是"垃圾邮件支持运营",并且 (b) 注意到单词"其他",则此信息是准确的。撇开文字解析不谈,此信息显然具有误导性。

[7]

虽然此检查通常能有效剔除垃圾邮件,但有报告称,有缺陷的 L-Soft listserv 安装会以列表服务器的纯 IP 地址迎接。

[8]

一个特殊情况是 NULL 信封发件人地址(即 MAIL FROM: <>),它用于传递状态通知和其他自动生成的响应。 应该始终接受此地址。

[9]

虽然很少见,但一些"合法的"批量邮件发送者,例如groups.yahoo.com,不会重试暂时失败的传递。 Evan Harris 编制了此类发件人列表,适用于列入白名单:http://cvs.puremagic.com/viewcvs/greylisting/schema/whitelist_ip.txt?view=markup

[10]

大型站点通常使用多个服务器来处理外发邮件。例如,一个服务器或服务器池可能用于立即传递。如果第一次传递尝试失败,则邮件将交给已针对大型队列进行调整的备用服务器。因此,从此类站点发出的前两次传递尝试将失败。

[11]

某些专门的 MTA,例如某些邮件列表服务器,不会自动生成消息 ID"退回"邮件的标头(传递状态通知)。这些消息通过空的信封发件人来标识。

[12]

IMAP 协议不允许将 NUL 字符传输到邮件用户代理,因此 Cyrus 开发人员认为,处理包含 NUL 字符的邮件的最简单方法是拒绝它们。

[13]

反病毒软件的作者为何如此愚蠢地信任包含病毒的电子邮件中的发件人地址,这可能是一个更深入的心理分析研究的主题。

[14]

特别是,Exim 在 Debian GNU/Linux 用户中可能最受欢迎,因为它是该发行版中的默认 MTA。 如果您使用 Debian("Sarge" 或更高版本),则可以通过安装exim4-daemon-heavy软件包

# apt-get install exim4-daemon-heavy

[15]

获得 Exim+Exiscan-ACLDebian 用户:exim4-config软件包让您可以在将 Exim 配置拆分为分布在/etc/exim4/conf.d

下的子目录中的几个小块,或者将整个配置保留在单个文件中之间进行选择。Debian 用户:如果您选择了前一个选项(我推荐这个!),您可以通过在这些子目录中创建新文件,而不是修改现有文件,使您的自定义与软件包提供的库存配置很好地分离。 例如,您可以创建一个名为/etc/exim4/conf.d/acl/80_local-config_rcpt_to

的文件来声明您自己的 RCPT TO: 命令的 ACL(请参阅下文)。Exim "init" 脚本 (/etc/init.d/exim4

[16]

) 将在您下次(重新)启动时自动将所有这些文件合并到一个大型运行时配置文件中。exim4-daemon-heavyDebian 用户:截至 2004 年 7 月 14 日,libmail-spf-query-perl.

[17]

软件包中包含的 Exiscan-ACL 版本尚不支持 SPF。 在此期间,您可以选择其他 SPF 实现;安装

[18]

虽然贝叶斯训练确实特定于每个用户,但应该指出的是,恕我直言,SpamAssassin 的贝叶斯分类器在任何情况下都不是那么出色。 特别是我发现这种情况尤其如此,因为垃圾邮件发送者已经学会通过在其邮件中播种随机字典单词或故事(例如,在 HTML 消息的元数据中)来击败此类系统。如果您认为这是过度杀伤,我倾向于在表面上同意。 在本文档的早期版本中,我只是使用${hash_8:SECRET=....}${hash...}来生成签名的最后一个组成部分。 但是,有了这个,从技术上来说,只要对 Exim 的<mbm (at) colondot.net>

[19]

指出: 您正在编写的文档希望很多人直接复制。 鉴于此,kerchoff 原则开始适用,您的所有秘密都应该在密钥中。 如果密钥可以被逆向出来,这似乎可以通过一些返回路径来实现,那么垃圾邮件发送者可以再次开始从该域发出有效的返回路径,而您又回到了起点。 [...] 更好的是,IMO,从一开始就让它变得强大。在上面的例子中,senders条件实际上是多余的,因为文件/home//.return-path-sign