简介
本文件要求使用 Postfix 版本 2.1 或更高版本。
通常,Postfix 接收邮件、将其存储在邮件队列中,然后进行投递。通过本文所述的外部内容过滤器,邮件将在队列中存储后再进行过滤。这种方法将邮件接收过程与邮件过滤过程解耦,使您能够最大程度地控制同时运行的过滤进程数量。
队列后内容过滤器应按以下方式使用:
网络或
本地用户-> Postfix
队列-> 内容
过滤器-> Postfix
队列-> 网络或
本地邮箱
本文档描述了使用单个 Postfix 实例处理所有任务的实现方式:接收、过滤和投递邮件。使用两个独立 Postfix 实例的应用程序将在本文档的后续版本中进行说明。
队列后内容过滤器不应与 SMTPD_PROXY_README 或 MILTER_README 文档中描述的方法混淆,其中入站 SMTP 邮件在存储到 Postfix 队列之前会被过滤。
本文档描述了两种过滤所有电子邮件的方法,以及几种选择性过滤邮件的选项:
- 工作原理
- 简单内容过滤器
- 高级内容过滤
- 选择性内容过滤
工作原理
一个队列后内容过滤器从 Postfix 接收未过滤的邮件(如下面进一步描述),并可以执行以下操作之一:
- 将邮件重新注入 Postfix,可能在更改内容和/或目的地后。
- 丢弃或隔离邮件。
- 拒绝邮件(通过向 Postfix 发送适当的状态代码)。Postfix 将邮件发回发件人地址。
注意:在当前邮件蠕虫和伪造垃圾邮件泛滥的时代,将病毒发送回发件人地址是一个非常糟糕的主意,因为发件人地址几乎肯定不是病毒的来源。最好丢弃已知的病毒,并将可疑内容隔离,以便人工决定如何处理。
简单内容过滤示例
第一个示例设置简单,但存在重大限制,将在第二个示例中解决。Postfix 通过 smtpd(8) 服务器接收未过滤的邮件,并通过 Postfix 的 pipe(8) 交付代理将未过滤的邮件传递给内容过滤器。内容过滤器使用 Postfix 的 sendmail(1) 命令将过滤后的邮件重新注入 Postfix,以便 Postfix 将其传递到最终目的地。
这意味着通过 Postfix sendmail(1) 命令提交的邮件无法进行内容过滤。
在下图中,带数字的名称代表 Postfix 命令或守护进程。请参阅 OVERVIEW 文档,以了解 Postfix 架构的概述。
未过滤
->
smtpd(8)
pickup(8)>- cleanup(8) -> qmgr(8)
Postfix
队列-< local(8)
smtp(8)
pipe(8)->
->
过滤
过滤
^
||
vmaildrop
queue<- Postfix
postdrop(1)<- Postfix
sendmail(1)<- 内容
过滤器
内容过滤器可以是一个简单的 shell 脚本,例如:
1 #!/bin/sh
2
3 # 简单的 shell 基于过滤器。它旨在以以下方式调用:
4 # /path/to/script -f 发件人 收件人...
5
6 # 根据实际情况调整这些选项。在 Postfix 2.3 之前,-G 选项没有作用。
7 INSPECT_DIR=/var/spool/filter
8 SENDMAIL="/usr/sbin/sendmail -G -i" # 切勿在此处使用 "-t"。
9
10 # 退出代码来自 <sysexits.h>
11 EX_TEMPFAIL=75
12 EX_UNAVAILABLE=69
13
14 # 完成或中止时清理。
15 trap "rm -f in.$$" 0 1 2 3 15
16
17 # 开始处理。
18 cd $INSPECT_DIR || {
19 echo $INSPECT_DIR 不存在;退出 $EX_TEMPFAIL; }
20
21 cat >in.$$ || {
22 echo 无法将邮件保存到文件;退出 $EX_TEMPFAIL; }
23
24 # 在此指定您的内容过滤器。
25 # filter <in.$$ || {
26 # echo 消息内容被拒绝;退出 $EX_UNAVAILABLE; }
27
28 $SENDMAIL "$@" <in.$$
29
30 exit $?
注释:
- 第 8 行: -G 选项表示过滤器输出不是本地邮件提交: 不要做一些愚蠢的事情,比如在消息头中的地址后附加本地域名。此选项在 Postfix 2.3 版本之前不起作用。
- 第 8 行:-i 选项表示当行仅包含 "." 时不要停止读取输入。
- 第 8 行:切勿在此处使用 "-t" 命令行选项。它会导致邮件投递错误,例如将邮件列表中的消息发回邮件列表。
- 第 21 行:该方案是先将邮件内容捕获到文件中,再通过第三方内容过滤程序处理内容。
- 第 22 行:若无法将邮件内容捕获到文件,则通过退出状态码 75(EX_TEMPFAIL)终止邮件投递。Postfix 将邮件放入延迟投递队列并稍后重试。
- 第25行:您需要在此处指定一个实际的内容过滤程序,该程序通过标准输入接收内容。
- 第26行:如果内容过滤程序发现问题,邮件将通过退出状态69(EX_UNAVAILABLE)被退回。Postfix会将邮件作为无法投递的邮件发送回发件人。
- 注意:在当前邮件蠕虫和垃圾邮件泛滥的时代,将已知病毒或垃圾邮件退回到发件人是一个糟糕的主意,因为该地址很可能已被伪造。更安全的做法是丢弃已知病毒,并将可疑内容隔离以便人工审查。
- 第 28 行:如果内容正常,则将其作为输入传递给 Postfix sendmail 命令,过滤器命令的退出状态即为 Postfix sendmail 命令的退出状态。Postfix 将按常规方式投递邮件。
- 第 30 行:Postfix 返回 Postfix sendmail 命令的退出状态。
建议您先手动运行此脚本直至对结果满意。使用真实邮件(包含头部和正文)作为输入运行:
% /path/to/script -f sender -- recipient... <message-file
一旦对内容过滤脚本满意:
- 创建一个名为"filter"的专用本地用户账户。该用户负责处理所有潜在危险的邮件内容,因此必须使用独立账户。切勿使用"nobody",更不要使用"root"或"postfix"。
- 创建仅对"filter"用户可访问的目录/var/spool/filter。该目录用于存储内容过滤脚本的临时文件。
配置 Postfix 使用 pipe(8) 传递代理将邮件转发至内容过滤器(请参阅 pipe(8) 手册页以了解命令语法)。
/etc/postfix/master.cf: # ============================================================= # 服务类型 权限 是否启用 chroot 唤醒 最大进程数 命令 # (是) (是) (是) (从不) (100) # ============================================================= filter unix - n n - 10 pipe flags=Rq user=filter null_sender= argv=/path/to/script -f ${sender} -- ${recipient}
这将并行运行最多 10 个内容过滤器。建议使用系统可承受的最大进程数替代 10 的限制。内容检测软件可能消耗大量系统资源,因此不应同时运行过多实例。空的 null_sender 设置在 Postfix 2.3 及更高版本中是必需的。
要仅对通过 SMTP 接收的邮件启用内容过滤,请在 master.cf 条目中,该条目定义了 Postfix SMTP 服务器:
/etc/postfix/master.cf: # ============================================================= # 服务类型 权限 chroot 唤醒 最大进程数 命令 # (yes) (yes) (yes) (never) (100) # ============================================================= smtp inet ...其他内容在此,请勿修改... smtpd -o content_filter=filter:dummy
"-o content_filter" 这行配置会导致 Postfix 在每个传入邮件中添加一个内容过滤请求记录,内容为"filter:dummy"。该记录会覆盖正常的邮件路由,并将邮件转发给内容过滤器处理。
content_filter 配置参数期望的值格式为 transport:destination。transport 名称指定了 master.cf 中邮件投递代理定义的第一个字段;下一跳 destination 的语法在对应投递代理的手册页中描述。
空的下一跳过滤器 destination 的含义取决于版本。Postfix 2.7 及更高版本将使用收件人域名;较早版本将使用 $myhostname。为兼容 Postfix 2.6 或更早版本,或指定一个非空的下一跳过滤器 destination。
content_filter 设置的优先级低于在 access(5)、header_checks(5) 或 body_checks(5) 表中指定的 FILTER 操作。
- 执行 "postfix reload" 以完成更改。
简单内容过滤性能
使用上述 shell 脚本,通过 SMTP 传输的邮件在 Postfix 性能上将损失四倍。对于内容过滤过程中创建和删除的每个临时文件,转发性能还会再降低一个数量级。对于本地提交或投递的邮件,性能影响较小,因为此类投递本身就比 SMTP 转发邮件更慢。
简单内容过滤器限制
上述内容过滤器的问题在于其不够健壮。原因是软件与 Postfix 之间未采用明确定义的协议。如果过滤器 shell 脚本因内存分配问题而异常终止,脚本将不会返回 /usr/include/sysexits.h 文件中定义的正常退出状态。此时邮件不会进入延迟队列,而是直接被退回。同样的健壮性问题也可能在内容过滤软件自身遇到资源问题时发生。
简单内容过滤方法不适用于通过 header_checks 或 body_checks 模式触发的内容过滤操作。这些模式会在邮件通过 Postfix sendmail 命令重新注入后再次应用,导致邮件过滤循环。高级内容过滤方法(见下文)允许关闭 header_checks 或 body_checks 模式。
关闭简单内容过滤器
要关闭"简单"内容过滤:
- 编辑 master.cf 文件,从定义 Postfix SMTP 服务器的条目中删除 "-o content_filter=filter:dummy" 文本。
- 执行 "postsuper -r ALL" 以从现有队列文件中删除内容过滤请求记录。
- 执行另一个 "postfix reload"。
高级内容过滤示例
第二个示例更为复杂,但性能更好,且在机器遇到资源问题时更少出现邮件弹回。此内容过滤器通过本地主机端口 10025 接收未过滤的 SMTP 邮件,并通过本地主机端口 10026 将过滤后的邮件发送回 Postfix。
对于不支持SMTP的内容过滤软件,Bennett Todd的SMTP代理实现了一个不错的PERL/SMTP内容过滤框架。参见: https://web.archive.org/web/20151022025756/http://bent.latency.net/smtpprox/。
在下图中,名称后跟数字代表 Postfix 命令或守护进程程序。请参阅 OVERVIEW 文档以了解 Postfix 架构的概述。
未过滤
未过滤->
->smtpd(8)
pickup(8)>- cleanup(8) -> qmgr(8)
Postfix
队列-< smtp(8)
local(8)->
->过滤
过滤^
||
vsmtpd(8)
10026smtp(8)
^
||
v内容过滤器 10025
此处给出的示例会过滤所有邮件,包括通过 SMTP 到达的邮件以及通过 Postfix 的 sendmail 命令本地提交的邮件(本地提交通过 pickup(8) 服务器进入 Postfix;为了简化示例,我们省略了本地提交的详细信息)。请参阅本文档末尾的示例,了解如何排除本地用户免受过滤,或如何配置基于目标的內容过滤器。
对于通过 SMTP 到达和离开的邮件,Postfix 的性能大约会降低一倍,前提是内容过滤器不创建临时文件。内容过滤器创建的每个临时文件都会使性能损失增加一倍。
高级内容过滤器:要求所有邮件均经过过滤
要为所有邮件启用高级内容过滤器方法,请在 main.cf 中指定:
/etc/postfix/main.cf: content_filter = scan:localhost:10025 receive_override_options = no_address_mappings
- "receive_override_options"这一行禁用内容过滤器前的地址操作,因此内容过滤器看到的是原始邮件地址,而非虚拟别名展开、规范映射、自动抄送、地址伪装等操作的结果。
- "content_filter"这一行会导致 Postfix 在每个传入邮件中添加一个内容过滤请求记录,内容为"scan:localhost:10025"。内容过滤请求记录由smtpd(8)和pickup(8) 服务器(以及 qmqpd(8),如果你启用了此服务)添加。
- 内容过滤请求存储在队列文件中;这是 Postfix 跟踪需要过滤邮件的方式。当队列文件中包含内容过滤请求时,队列管理器会将邮件交付给指定的内容过滤器,无论其最终目的地为何。
- content_filter 配置参数期望的值为 transport:destination 格式。transport 名称指定了 master.cf 中邮件投递代理定义的第一个字段;下一跳 destination 的语法在对应投递代理的手册页中描述。
- 空的下一跳过滤器 destination 的含义取决于版本。Postfix 2.7 及更高版本将使用收件人域名;较早版本将使用 $myhostname。为兼容 Postfix 2.6 或更早版本,或指定一个非空的下一跳过滤器 destination。
- content_filter 设置的优先级低于在 access(5)、header_checks(5) 或 body_checks(5) 表中指定的 FILTER 操作。
高级内容过滤器:将未过滤的邮件发送至内容过滤器
在此示例中,"scan" 是 Postfix SMTP 客户端的一个实例,其配置参数略有不同。以下是在 Postfix master.cf 文件中设置该服务的方式:
/etc/postfix/master.cf: # ============================================================= # 服务类型 权限 是否启用 启动方式 最大进程数 命令 # (是) (是) (是) (从不) (100) # ============================================================= scan unix - - n - 10 smtp -o smtp_send_xforward_command=yes -o disable_mime_output_conversion=yes -o smtp_generic_maps=
- 这将同时运行最多 10 个内容过滤器。建议使用系统可承受的进程限制,而非默认的 10 个并发进程。内容检测软件可能占用大量系统资源,因此不应让过多进程同时运行。
- 若设置 "-o smtp_send_xforward_command=yes",扫描传输将尝试通过内容过滤器将原始客户端名称和 IP 地址转发给后过滤器 SMTP 进程,以便过滤后的邮件以真实客户端名称和 IP 地址进行日志记录。参见 smtp(8) 和 XFORWARD_README 以获取更多信息。
- "-o disable_mime_output_conversion=yes"是一个解决方法,可防止域密钥和其他数字签名被破坏。这是因为某些基于 SMTP 的内容过滤器不会声明支持 8BITMIME,尽管它们实际上可以处理 8 位邮件。
- "-o smtp_generic_maps=" 是一个临时解决方案,用于防止使用 generic(5) 映射进行本地地址重写。此类重写仅应在邮件发送到互联网时发生。
高级内容过滤器:运行内容过滤器
内容过滤器可通过 Postfix 进程启动服务(相当于 inetd)进行配置。例如,要在本地主机端口 10025 上启动最多 10 个内容过滤进程:
/etc/postfix/master.cf: # =================================================================== # 服务类型 权限 是否启用 运行环境 最大进程数 命令 # (是) (是) (是) (从不) (100) # =================================================================== localhost:10025 inet n n n - 10 spawn user=filter argv=/path/to/filter localhost 10026
- "filter" 是一个专用的本地用户账户。该用户不会登录,可以设置 "*" 密码,并使用不存在的 shell 和 home 目录。该用户负责处理所有潜在危险的邮件内容,因此应作为独立账户。
- 默认情况下,Postfix 会终止运行时间超过 command_time_limit 秒(默认:1000 秒)的命令。这是为了防止过滤器无限运行而采取的安全措施。
如果您希望过滤器监听端口 localhost:10025 而不是 Postfix,则必须将过滤器作为独立程序运行,并且不得使用 Postfix 的 spawn 服务。
高级过滤器:将邮件重新注入 Postfix
内容过滤器的任务是,要么以适当的诊断信息弹回邮件,要么通过端口 localhost 10026 上的专用监听器将邮件重新注入 Postfix。
最简单的内容过滤器只是在输入和输出之间复制 SMTP 命令和数据。如果出现问题,它只需对 Postfix 发来的 `.' 输入回复 `550 content rejected`,并在将邮件重新注入 Postfix 的连接上断开连接而不发送 `.`。
/etc/postfix/master.cf: # =================================================================== # 服务类型 权限 是否启用 启动方式 最大进程数 命令 # (是) (是) (是) (从不) (100) # =================================================================== localhost:10026 inet n - n - 10 smtpd -o content_filter= -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_milters -o smtpd_helo_restrictions= -o smtpd_client_restrictions= -o smtpd_sender_restrictions= # Postfix 2.10 及更高版本:指定空的 smtpd_relay_restrictions。 -o smtpd_relay_restrictions= -o smtpd_recipient_restrictions=permit_mynetworks,reject -o mynetworks=127.0.0.0/8 -o smtpd_authorized_xforward_hosts=127.0.0.0/8
- 注意:请勿在 "=" 或 "," 字符周围添加空格。
- 注意:SMTP 服务器的进程限制不得小于 "filter" master.cf 中的设置。
- "-o content_filter="会覆盖main.cf中的设置,并要求对来自内容过滤器的邮件不进行内容过滤。这是必需的,否则邮件会循环。
"-o receive_override_options" 覆盖 main.cf 设置,以避免在内容过滤器之前已经完成的工作被重复执行。这些选项与在 main.cf 中指定的选项互补:
- 我们指定 "no_unknown_recipient_checks" 以禁用尝试确定收件人是否未知。
- 我们指定 "no_header_body_checks" 以禁用头部/正文检查。
- 我们指定 "no_milters" 以禁用 Milter 应用程序(此选项仅在 Postfix 2.3 及更高版本中可用)。
- 我们在此处未指定 "no_address_mappings"。这将启用虚拟别名展开、规范映射、地址伪装以及内容过滤器之后的其他地址映射。main.cf 中的 "receive_override_options" 设置会在内容过滤器之前禁用这些映射。
这些接收覆盖选项要么由 SMTP 服务器本身实现,要么传递给清理服务器。
- "-o smtpd_xxx_restrictions" 和 "-o mynetworks=127.0.0.0/8" 覆盖 main.cf 中的设置。它们会关闭在此处仅会浪费时间的垃圾邮件控制。
- 使用 "-o smtpd_authorized_xforward_hosts=127.0.0.0/8",扫描传输将尝试将原始客户端名称和 IP 地址转发给后过滤器 SMTP 进程,以便过滤后的邮件使用真实客户端名称和 IP 地址进行日志记录。参见 XFORWARD_README 和 smtpd(8)。
高级内容过滤性能
采用本文所述的"三明治"内容过滤方法时,需确保过滤器并发数与可用 CPU、内存和 I/O 资源相匹配。内容过滤进程过少,即使在低流量情况下,邮件也会在活动队列中积压;并发过多则会导致Postfix因资源不足而失败,进而推迟发送本应进入内容过滤的邮件。
目前,内容过滤器的性能调优是一个试错过程;由于过滤和未过滤的消息共享同一队列,分析受到限制。如本文档开头所述,使用多个 Postfix 实例进行内容过滤将在未来版本中覆盖。
关闭高级内容过滤器
要关闭"高级"内容过滤:
删除或注释掉以下两行main.cf中的内容。关闭内容过滤后,为高级内容过滤所做的其他更改将无效。
/etc/postfix/main.cf: content_filter = scan:localhost:10025 receive_override_options = no_address_mappings
- 执行 "postsuper -r ALL" 以从现有队列文件中删除内容过滤请求记录。
- 执行另一个 "postfix reload"。
仅过滤来自外部用户的邮件
最简单的方法是在 master.cf 中为一个 Postfix 实例配置多个 SMTP 服务器 IP 地址:
两个 SMTP 服务器 IP 地址,仅用于内部用户的邮件,且内容过滤已关闭。
/etc/postfix.master.cf: # ================================================================== # 服务类型 权限 是否启用 chroot 唤醒 最大进程数 命令 # (是) (是) (是) (从不) (100) # ================================================================== 1.2.3.4:smtp inet n - n - - smtpd -o smtpd_client_restrictions=permit_mynetworks,reject 127.0.0.1:smtp inet n - n - - smtpd -o smtpd_client_restrictions=permit_mynetworks,reject
用于外部用户发送邮件的单个 SMTP 服务器地址,启用了内容过滤。
/etc/postfix.master.cf: # ================================================================= # 服务类型 权限 是否启用 chroot 唤醒 最大进程数 命令 # (是) (是) (是) (从不) (100) # ================================================================= 1.2.3.5:smtp inet n - n - - smtpd -o content_filter=filter-service:filter-destination -o receive_override_options=no_address_mappings
之后,您可以按照上述"高级"或"简单"内容过滤示例中的步骤操作,但必须确保不指定"content_filter" 或 "receive_override_options" 在 main.cf 文件中。
不同域名的不同过滤器
如果您是 MX 服务提供商,并希望为不同域名应用不同的内容过滤器,可以在 master.cf 中配置一个 Postfix 实例,并为其分配多个 SMTP 服务器 IP 地址。每个地址提供不同的内容过滤服务。
/etc/postfix.master.cf: # ================================================================= # 服务类型 权限 是否启用 chroot 唤醒 最大进程数 命令 # (是) (是) (是) (从不) (100) # ================================================================= # 用于通过 service1:dest1 过滤的域的 SMTP 服务 1.2.3.4:smtp inet n - n - - smtpd -o content_filter=service1:dest1 -o receive_override_options=no_address_mappings # 用于过滤服务2:dest2的域的SMTP服务 1.2.3.5:smtp inet n - n - - smtpd -o content_filter=service2:dest2 -o receive_override_options=no_address_mappings
之后,您可以按照上述"高级"或"简单"内容过滤示例中的步骤操作,但必须不要指定"content_filter" 或 "receive_override_options"。 cf 文件中。
在 DNS 中设置 MX 记录,将每个域名路由到正确的 SMTP 服务器实例。
访问或标头/正文表中的过滤操作
上述过滤配置是静态的。遵循给定路径的邮件要么始终被过滤,要么从未被过滤。从 Postfix 2.0 开始,您还可以动态启用内容过滤。
要通过 access(5) 表规则启用内容过滤:
/etc/postfix/access: whatever FILTER foo:bar
要通过 header_checks(5) 或 body_checks(5) 表模式启用内容过滤:
/etc/postfix/header_checks: /whatever/ FILTER foo:bar
您还可以在 smtpd 访问映射以及清理服务器的 header/body_checks 中进行此操作。此功能必须谨慎使用:您必须在 after-filter smtpd 和清理守护进程中禁用所有 UCE 功能,否则将导致内容过滤循环。
限制:
- 来自 smtpd 访问映射和 header/body_checks 的 FILTER 操作优先于通过 main.cf content_filter 参数指定的过滤器。
- 如果一条消息触发了多个过滤器操作,则仅最后一个操作生效。
- 同一内容过滤器将应用于给定消息的所有收件人。