跳转到主要内容

于 2025年04月22日 摘录自 Postfix Built-in Content Inspection

内置内容检查简介

Postfix 支持一个内置过滤机制,该机制会在消息存储到 Postfix 队列之前,逐行检查消息头和消息正文内容。该过滤器通常使用 POSIX 或 PCRE 正则表达式实现,具体详见 header_checks(5) 手册页。

内置过滤器的原始目的是阻止特定电子邮件蠕虫或病毒的爆发,且在此任务中表现出色。该过滤器还帮助阻止了退回的垃圾邮件、蠕虫或病毒引起的退回邮件,以及病毒检测系统发出的通知。有关此次要应用程序的详细信息,请参阅BACKSCATTER_README文档。

由于内置过滤器专为阻止特定蠕虫和病毒爆发而优化,因此它存在限制,使其不适合用于一般垃圾邮件和病毒检测。为此,您应使用在FILTER_READMESMTPD_PROXY_READMEMILTER_README 文档中描述的外部内容检测方法。

下图展示了 Postfix 内置内容检查的整体工作流程:

 ;Postmaster
通知
 ;|
v
网络或
本地用户
->; ;内置
过滤器
->; ;Postfix
队列
->; ;投递
代理
->; ;网络或
本地邮箱
 ;^
| ;
 ;|
v ;
 ;无法投递的邮件
转发的邮件

图片清楚地表明,当 Postfix 正在接收新邮件时,过滤器正在工作。这意味着 Postfix 可以拒绝来自网络的邮件,而无需将无法投递的邮件退回到发件人地址(发件人地址通常是伪造的)。然而,这种能力是有代价的:如果邮件检查耗时过长,远程客户端将超时,客户端可能会重复发送同一条消息。

本文件涵盖的主题:

哪些邮件会受到头部/正文检查

Postfix 头部/正文检查由 cleanup(8) 服务器在将邮件注入 入站队列 之前实施。下图放大显示了cleanup(8)服务器,并展示了该服务器处理来自多个不同来源的邮件。为了保持图表的可读性,未显示postmaster通知的来源,因为这些通知可能由多个Postfix守护进程生成。

 ;bounce(8)
(无法投递)
smtpd(8)
(网络)
\ ;|
v ;
 ; ;
qmqpd(8)
(网络)
-\
-/ ;
cleanup(8)->; ;incoming
queue ;
pickup(8)
(本地)
/ ;^
| ;
 ;local(8)
(转发)

出于效率考虑,仅对从 Postfix 外部进入的邮件进行头部/正文检查。再次过滤已过滤的邮件会降低效率,且不希望阻塞 postmaster 通知。下表总结了哪些邮件会接受头部/正文检查,哪些不会。

消息类型来源检查头部/正文?
无法投递的邮件bounce(8)
网络邮件smtpd(8)可配置
网络邮件qmqpd(8)可配置
本地提交pickup(8)可配置
本地转发local(8)
邮局管理员通知多个

Postfix 如何决定哪些邮件需要过滤?在 cleanup(8) 服务器中做出此决定会很笨拙,因为该程序会从许多不同来源接收邮件。相反,源会请求进行头部/正文检查。以下是针对通过 smtpd(8) 接收的邮件关闭头部/正文检查的示例,qmqpd(8)pickup(8) 接收的邮件,请参见下方"仅对外部用户邮件配置头部/正文检查"、 #mx_submission">MX 服务和提交服务的不同头部/正文检查配置",以及 "仅对发往某些域的邮件配置头部/正文检查"。

Postfix 头部/正文检查的限制

  • 头部/正文检查不会解码邮件头部或邮件正文内容。例如,如果邮件正文中的文本是 BASE64 编码(RFC 2045),则您的正则表达式必须匹配 BASE64 编码形式。同样,包含编码非 ASCII 字符的邮件头(RFC 2047)也需要匹配其编码形式。
  • 头部/正文检查无法基于邮件头或正文行的组合进行过滤。头部/正文检查会逐个检查消息头部或消息正文行,无法将决策结果传递到下一个消息头部或正文行。
  • 头部/正文检查不能依赖于消息的收件人。
    • 一条消息可能有多个收件人,且所有收件人将获得相同处理。已提出通过选择性延迟多收件人邮件的部分收件人来实现的变通方案,但这会导致 SMTP 性能下降且不适用于非 SMTP 邮件。
    • 部分邮件来源会在发送收件人信息前先发送标题和内容。在决定是否需要过滤前缓冲整个邮件会效率低下,而先过滤邮件再缓冲所有操作直至确定是否执行这些操作则显得笨拙。
  • 尽管有警告,有些人仍试图使用内置过滤功能来处理普通垃圾邮件和/或病毒拦截,使用数百甚至数千个正则表达式。这可能导致灾难性的性能故障。症状如下:

    • cleanup(8) 进程会耗尽所有可用 CPU 时间来处理正则表达式,并/或耗尽所有可用内存,导致系统开始交换。这会导致所有 incoming 邮件投递速度变慢。
    • 由于 Postfix 需要越来越多的时间来接收电子邮件,同时进行的 SMTP 会话数量增加到达到 SMTP 服务器进程限制的程度。
    • 当所有 SMTP 服务器进程都在等待 cleanup(8) 服务器完成时,新的 SMTP 客户端必须等待直到有 SMTP 服务器进程可用。这导致邮件投递在开始前就超时。

    解决此类性能问题的办法很简单:不要对普通垃圾邮件和/或病毒拦截使用头部/正文检查,也不要在邮件入队列前进行过滤。当性能是关注点时,请使用在邮件入队列后运行的外部内容过滤器,具体请参阅FILTER_README文档。

防止每日邮件状态报告被阻塞

以下内容摘自 Jim Seymour 的 Pflogsumm FAQ,网址为 http://jimsun.linxnet.com/downloads/pflogsumm-faq.txt。Plogsumm 是一个分析 Postfix 日志的程序,包括被拒绝邮件的日志记录。如果这些日志包含被 Postfix 的 body_checks 模式拒绝的文本,那么这些日志也可能被相同的 body_checks 模式。此问题不存在于 header_checks 模式中,因为这些模式不适用于邮件状态报告中的文本。

您配置 Postfix 进行正文检查,Postfix 执行其操作,Plogsumm 报告结果,Postfix 在 Pflogsumm 报告中捕获相同的字符串。对此有几种解决方案。

Wolfgang Zeikat 贡献了以下内容:

#!/usr/bin/perl
use MIME::Lite;
### 创建新消息:
$msg = MIME::Lite->new(
From => '[email protected]',
To => '[email protected]',
# Cc => '[email protected][email protected]',
Subject => 'pflogsumm',
Date => `date`,
Type => 'text/plain',
Encoding => 'base64',
Path => '/tmp/pflogg',
);
$msg->send;

其中 "/tmp/pflogg" 是 Pflogsumm 的输出路径。这将 Pflogsumm 的输出以 base64 MIME 附件的形式保存。

注释:如果在可被未授权用户访问的机器上运行此代码,建议将Plogsumm报告存储在非世界可写目录中。

在 postfix-users 邮件列表的一个帖子后续讨论中,Ralf Hildebrandt 指出:

"mpack 实现相同功能。"

确实如此。选择使用哪个工具取决于个人偏好。

其他解决方案涉及添加额外的body_checks规则,以对每日邮件状态报告做出例外处理,但不推荐此方法。此类规则会减慢所有邮件传输并增加 Postfix 维护复杂性。

仅对来自外部用户的邮件配置头部/正文检查

以下信息适用于 Postfix 2.1 及更高版本。较早版本的 Postfix 不支持 receive_override_options 功能。

最简单的方法是在master.cf中配置一个 Postfix 实例,并为其指定多个 SMTP 服务器 IP 地址:

  • 两个 SMTP 服务器 IP 地址,仅用于内部用户发送的邮件,且关闭了头部/正文过滤,同时启用本地邮件拾取服务并关闭头部/正文过滤。

    /etc/postfix.master.cf:
    # ==================================================================
    # 服务类型 权限 是否启用 chroot 唤醒 最大进程数 命令
    # (是) (是) (是) (从不) (100)
    # ==================================================================
    1.2.3.4:smtp inet n - n - - smtpd
    -o receive_override_options=no_header_body_checks
    127.0.0.1:smtp inet n - n - - smtpd
    -o receive_override_options=no_header_body_checks
    pickup unix n - n 60 1 pickup
    -o receive_override_options=no_header_body_checks
    
  • 添加一些防火墙规则以防止从外部访问 1.2.3.4:smtp。
  • 一个用于外部用户邮件的 SMTP 服务器地址,通过 main.cf 启用头部/正文过滤。

    /etc/postfix.master.cf:
    # =================================================================
    # 服务类型 权限 是否启用 chroot 唤醒 最大进程数 命令
    # (是) (是) (是) (从不) (100)
    # =================================================================
    1.2.3.5:smtp inet n - n - - smtpd
    

为 MX 服务和提交服务配置不同的头部/正文检查

如果授权用户的提交需要与远程 MTA 发送的邮件不同的头部/正文检查,那么只要您为授权用户和 MX 服务分别设置了独立的邮件流,就可以实现这一点。

下面的示例假设授权用户连接到 TCP 端口 587(提交)或 465(smtps),而远程 MTA 连接到 TCP 端口 25(smtp)。

首先,我们定义几个"用户定义"参数,这些参数将覆盖提交和 smtps 服务的设置。

/etc/postfix/main.cf:
msa_cleanup_service_name = msa_cleanup
msa_header_checks = pcre:/etc/postfix/msa_header_checks
msa_body_checks = pcre:/etc/postfix/msa_body_checks

接下来,我们将 msa_cleanup 定义为一个专用的清理服务,仅由提交和 smtps 服务使用。该服务使用上述定义的 header_checksbody_checks 覆盖设置。

/etc/postfix.master.cf:
# =================================================================
# 服务类型 权限 是否启用 chroot 唤醒 最大进程数 命令
# (是) (是) (是) (从不) (100)
# =================================================================
smtp inet n - n - - smtpd
msa_cleanup unix n - n - 0 cleanup
-o header_checks=$msa_header_checks
-o body_checks=$msa_body_checks
submission inet n - n - - smtpd
-o cleanup_service_name=$msa_cleanup_service_name
-o syslog_name=postfix/submission
...[参见示例 master.cf 文件以获取更多信息]...
smtps inet n - n - - smtpd
-o cleanup_service_name=$msa_cleanup_service_name
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
...[参见示例 master.cf 文件以获取更多信息]...

通过将 "msa_xxx" 参数设置保留在 main.cf 中,您可以保持 master.cf 文件简洁,并最大限度地减少重复内容。

仅对发往某些域的邮件配置头部/正文检查

以下信息适用于 Postfix 2.1。较早版本的 Postfix 不支持 receive_override_options 功能。

如果您是 MX 服务提供商,并希望仅对某些域启用头部/正文检查,可以在 master.cf 中配置一个 Postfix 实例,并为其分配多个 SMTP 服务器 IP 地址。每个地址提供不同的服务。

/etc/postfix.master.cf:
# =================================================================
# 服务类型 权限 是否启用 chroot 唤醒 最大进程数 命令
# (是) (是) (是) (从不) (100)
# =================================================================
# 启用头部/正文检查的域的 SMTP 服务。
1.2.3.4:smtp inet n - n - - smtpd
# 禁用头部/正文检查的域的 SMTP 服务。
1.2.3.5:smtp inet n - n - - smtpd
-o receive_override_options=no_header_body_checks

配置完成后,您可以在 DNS 中设置 MX 记录,将每个域名路由到相应的 SMTP 服务器实例。