MTA的反垃圾邮件功能,实际上就是在MTA处理过程中对会话进行过滤。这个过滤不但过滤了发往自身的垃圾邮件,而且还防止了自身被恶意利用发送垃圾邮件。Postfix实现了目前所有主要的MTA过滤技术。。
MTA过滤分为两类:邮件数据发送前过滤和邮件数据发送后过滤。
一、数据前过滤
数据前过滤是指在SMTP会话中,DATA指令发送前进行的过滤。在这个阶段,有四种不同子阶段的过滤:SMTP连接时过滤、HELO/EHLO指令过滤、MAIL FROM指令过滤和RCPT TO指令过滤。根据这四个子阶段接收到的信息的不同,它们也分别称作SMTP客户端限制、HELO/EHLO主机名限制、发送者地址限制和接收者地址限制。
过滤默认是在RCPT TO指令后生效的,这是因为一些Windows上的邮件客户端不处理在RCPT TO指令前的过滤动作。可以通过将smtpd_delay_reject设置为no来使过滤动作立刻生效。这个参数还影响了在不同的指令上可以使用的过滤规则参数。
1、过滤规则
这四个子阶段的过滤是分别通过四个配置语句来指定过滤规则的。它们都接收一系列的规则参数列表,参数间可以用空格或逗号分隔开。在默认状态下smtpd_delay_reject的值是yes,它们可以接受所有支持的规则,不过需在RCPT TO指令后才能全部生效;如果把smtpd_delay_reject设置为no,它们只可以接受五个公共的规则参数、之前子阶段的过滤规则参数和该子阶段的规则参数。
它们接受的公共的规则参数如下:
permit
允许该连接进行。该规则通常置于规则列表的最后面使规则更清晰。
defer
通知客户端现在不能继续会话,稍后再进行SMTP连接请求。这常用于服务器需要进行一些DNS检查,但是(由于DNS查询超时)没有及时获得结果时,通知客户端稍后再进行连接。该规则通常置于规则列表的最后面使规则更清晰。
reject
拒绝该连接请求。在这个阶段就断开了连接,有效的节约了垃圾邮件造成的带宽和处理能力的浪费。该规则通常置于规则列表的最后面使规则更清晰。
拒绝动作默认不会在匹配了拒绝规则后就立刻断开连接,而是在rcpt to指令处理完之后再断开的,这是由于一些windows上有缺陷的邮件程序不处理在rcpt to指令前的发回的拒绝状态码。可以通过smtpd_delay_reject设置为no来立刻发送拒绝状态码断开连接。
reject_code指定了拒绝的返回状态码(默认是554)。
warn_if_reject
改变其后规则的拒绝动作为警告,即如果其后存在满足拒绝的条件,并不实际拒绝,而是发出一条警告信息(reject_warning)到日志文件中(通常是/var/log/maillog)。它常用于在实际运行的邮件服务器上测试邮件过滤规则。
reject_unauth_pipelining
拒绝在Postfix支持指令流水线前发送SMTP指令流水线的客户端连接。指令流水线是一些邮件客户端为了快速发送邮件所采用的技术。
以下就四个子阶段分别讲述过滤规则
A、SMTP连接时过滤(SMTP客户端限制)
Postfix可以在接受客户端的SMTP连接请求时进行过滤检查。
通过Postfix的smtpd_client_restrictions指令可以指定这个阶段的过滤规则。这个阶段可用的过滤规则除公共规则外还有:
reject_unknown_client
拒绝客户的地址没有对应的DNS的A记录或PTR记录的连接。通常有些机器,尤其是个人拨号用户的机器没有对应的A记录或PTR记录,所以要注意漫游用户的使用(漫游用户是指不在$mynetworks中,比如在别的ISP拨号上网的用户。通常用SMTP认证来解决这个问题)。
unknown_client_reject_code指定了拒绝的返回状态码(默认是450)。
permit_mynetworks
允许来自其IP地址属于$mynetworks所定义网络的客户端的连接。通常可用于ISP为自己的拨号用户提供SMTP服务时,通过$mynetworks参数指定自己的网络并允许自己的网络内的机器发送邮件。
reject_rbl_client domain.tld和reject_rhsbl_client domain.tld
拒绝来自属于RBL和RHSBL列表中的地址进行连接。通过检查一个IP地址或域名是否存在于domain.tld的RBL或RHSBL中,可以判断该客户端是否被列入了domain.tld的实时黑名单,从而决定是否接受连接。有关实时黑名单可以参考本站的【反垃圾邮件技术参考>>实时黑名单技术】。
maps_rbl_reject_code指定了拒绝的返回状态码(默认是554)。
check_client_access maptype:mapname
搜索名为mapname的maptype类型的访问数据库。可以根据客户端的主机名、父域、IP地址或部分IP地址来匹配。关于访问数据库请参阅下面的附录。
例子:
smtpd_client_restrictions = hash:/etc/postfix/access,
reject_rbl_client relays.ordb.org,
reject_rhsbl_client dsn.rfc-ignorant.org,
permit_mynetworks,
reject_unknown_client
其中relays.ordb.org和dsn.rfs-ignorant.org都是国外比较权威的免费RBL和RHSBL服务器。
B、HELO/EHLO指令过滤(HELO/EHLO主机名限制)
在接受了SMTP连接后,可以对HELO或EHLO指令所发送的信息进行过滤检查。
有些邮件客户端在通讯时并不发送HELO/EHLO指令,可以通过smtpd_helo_required设置为yes强制要求发送HELO/EHLO指令(默认Postfix不要求发送HELO/EHLO)。
通过Postfix的smtpd_helo_restrictions指令可以指定这个阶段的过滤规则。这个阶段可用的过滤规则除公共规则和smtpd_client_restrictions的规则外还有:
reject_invalid_hostname
拒绝无效格式的主机名的连接。
invalid_hostname_reject_code指定了拒绝的返回状态码(默认是501)。
reject_unknown_hostname
拒绝未知的主机名的连接。所谓未知的主机名是指该主机没有DNS的A记录或MX记录。由于很多拨号用户的机器并没有对应的A记录或MX记录,所以要注意漫游用户的使用。
unknown_hostname_reject_code指定了拒绝的返回状态码(默认是450)。
reject_non_fqdn_hostname
拒绝主机名不是FQDN格式(完全限定域名格式,即用点分隔开的包括域名和主机名的主机全名)的连接。
non_fqdn_reject_code指定了拒绝的返回状态码(默认时504)。
permit_naked_ip_address
允许直接使用IP地址的连接。通常在HELO/EHLO中使用主机名而不是IP地址。
check_client_access maptype:mapname
搜索名为mapname的maptype类型的访问数据库。可以根据HELO/EHLO发送的主机名、父域来匹配。关于访问数据库请参阅下面的附录。
C、MAIL FROM指令过滤(发送者地址限制)
在接受了SMTP连接,客户端发送了HELO/EHLO指令后(该指令可选),应该通过MAIL FROM指令声明发送者的身份。可以对发送者身份进行过滤检查。
按照RFC规范,在MAIL FROM指令和下面的RCPT TO指令中应该使用RFC 821格式的邮件地址(例如:<user@domain.tld>),但是由于有许多的邮件客户端的不规范,往往不使用标准的RFC 821格式。Postfix默认接受任何可以理解的邮件地址,如:丢失了地址里的一对尖括号、可以包含RFC 822格式的注释等。如果希望打开对RFC 821格式的限制,可以将strict_rfc821_envelopes设置为yes。
通过Postfix的smtpd_sender_restrictions指令可以指定这个阶段的过滤规则。这个阶段可用的过滤规则除了公共规则和smtpd_client_restrictions的规则和smtpd_helo_restrictions的规则外还有:
reject_unknown_sender_domain
拒绝发送者邮件的域没有DNS的A记录或MX记录的连接。
unknown_address_reject_code指定了拒绝的返回状态码(默认是450)。当进行DNS查询出现临时错误时(如查询超时)也总是返回450。
reject_rhsbl_sender domain.tld
拒绝发送者邮件的域属于RHSBL黑名单的连接。通过检查一个域名是否存在于domain.tld的RHSBL中,可以判断该客户端是否被列入了domain.tld的实时黑名单,从而决定是否接受连接。
maps_rbl_reject_code指定了拒绝的返回状态码(默认是554)。
check_sender_access maptype:mapname
搜索名为mapname的maptype类型的访问数据库。可以根据发送者邮件的邮件地址、名字、域和父域来匹配。关于访问数据库请参阅下面的附录。
reject_non_fqdn_sender
拒绝发送者邮件的域不是FQDN格式的连接。
non_fqdn_reject_code指定了拒绝的返回状态码(默认时504)。
reject_sender_login_mismatch
拒绝发送者在$smtpd_sender_owner_maps中所匹配的用户名和SASL登录名不一致的连接。
D、RCPT TO指令过滤(接收者地址限制)
在MAIL FROM指令后要通过RCPT TO指令指定邮件接收者。可以对接收者身份进行过滤检查。
通过Postfix的smtpd_recipient_restrictions指令可以指定这个阶段的过滤规则。同以上的检查指令不同,为了避免开放转发,这个指令有默认值:permit_mynetworks,reject_unauth_destination。这个阶段可用的过滤规则除了公共规则和smtpd_client_restrictions的规则和smtpd_helo_restrictions的规则和smtpd_sender_restrictions的规则外还有:
permit_auth_destination
允许发往默认转发和默认接收的连接。
Postfix默认转发以下的邮件:
来自$mynetworks中地址发送的邮件
发往$relay_domains中的域或其子域的邮件。但是不能包含邮件路由(如user@elsewhere@domain.tld)。
Postfix默认接收最终投递目标符合如下条件的邮件:
目标在$inet_interfaces
目标在$mydestinations
目标在$virtual_alias_domains
目标在$virtual_mailbox_domains
reject_unauth_destination
拒绝不是发往默认转发和默认接收的连接。
relay_domain_reject_code指定了拒绝的返回状态码(默认是554)。
permit_mx_backup
允许接收本地主机是邮件投递目标的MX地址的邮件。但是不能包含邮件路由(如user@elsewhere@domain.tld)。
check_recipient_access maptype:mapname
搜索名为mapname的maptype类型的数据库。可以根据接收者邮件的邮件地址、名字、域和父域来匹配。 关于访问数据库请参阅下面的附录。
check_recipient_maps
拒绝接收者不匹配如下列表的连接:
$local_recipient_maps($mydestinations和$inet_interfaces)
$virtual_alias_maps($virtual_alias_domains)
$virtual_mailbox_maps($virtual_mailbox_domains)
$relay_recipient_maps($relay_domains)
空的$local_recipient_maps和$local_recipient_maps表示不对接收者地址进行过滤检查。
Postfix默认在接收者检查列表的最后做check_recipient_maps检查。
reject_unknown_recipient_domain
拒绝接收者邮件的域没有DNS的A记录或MX记录的连接。
unknown_address_reject_code指定了拒绝的返回状态码(默认是450)。当进行DNS查询出现临时错误时(如查询超时)也总是返回450。
reject_rhsbl_recipient domain.tld
拒绝接收者邮件的域属于RHSBL黑名单的连接。通过检查一个域名是否存在于domain.tld的RHSBL中,可以判断该客户端是否被列入了domain.tld的实时黑名单,从而决定是否接受连接。
maps_rbl_reject_code指定了拒绝的返回状态码(默认是554)。
reject_non_fqdn_recipient
拒绝接收者邮件的域不是FQDN格式的连接。
non_fqdn_reject_code指定了拒绝的返回状态码(默认时504)。
permit_sasl_authenticated
允许通过了SASL认证的用Х⑺陀始?Mü?ASL协议(包括SASL1和SASL2)实现的SMTP认证功能需要在编译Postfix时编译进SASL支持,并在main.cf中将smtpd_sasl_auth_enable设置为yes。有关SASL及如何在Postfix中实现SASL认证请参阅下面的附录。通过SASL认证可以对漫游用户提供发信支持,是关闭Open-Relay的重要手段。
一个smtpd_recipient_restrictions的例子:
smtpd_recipient_restrictions = reject_invalid_hostname,
reject_non_fqdn_hostname,
reject_non_fqdn_sender,
reject_non_fqdn_recipient,
reject_unknown_sender_domain,
reject_unknown_recipient_domain,
reject_unauth_pipelining,
permit_mynetworks,
reject_unauth_destination,
check_client_access hash:/etc/postfix/client_checks,
check_client_access regexp:/etc/postfix/client_checks.re,
check_helo_access hash:/etc/postfix/helo,
check_sender_access hash:/etc/postfix/sender,
check_recipient_access regexp:/etc/postfix/recipient.re,
reject_rbl_client relays.ordb.org,
reject_rbl_client list.dsbl.org,
reject_rbl_client sbl.spamhaus.org,
reject_rbl_client blackholes.easynet.nl,
reject_rhsbl_client dsn.rfc-ignorant.org,
permit
这里要注意,有可能由于对reject_non_fqdn_*和reject_unknown_*的检查而导致一些主机名没有正常解析的机器不能正确发信。另外,在应用这个规则前,可以在规则列表头加上warn_if_reject来检查一下。
2、多阶段过滤和单阶段过滤
由于一些windows上有缺陷的邮件程序不处理在RCPT TO指令前的发回的拒绝状态码,所以Postfix默认不会在匹配了拒绝规则后就立刻断开连接,而是在RCPT TO指令处理完之后再断开的。可以通过smtpd_delay_reject设置为no来立刻发送拒绝状态码断开连接(默认是yes)。
视乎smtpd_delay_reject设置为yes或no的不同,Postfix的过滤有多阶段过滤和单阶段过滤两种形式。
A、多阶段过滤
当选项smtpd_delay_reject设置为yes时,过滤是分为多个阶段进行的。比如,即便在上一个子阶段该连接被判定为需要被拒绝,但是由于拒绝被延迟了,所以会接着进行本子阶段的过滤,如果在本子阶段又允许了该连接,那么上一个子阶段的拒绝结果就会覆盖。
从结果上看,这相当于无论上一个子阶段的过滤结果如何,都会进行所有定义了的过滤规则。所以我们称之为多阶段过滤。其逻辑模型如下:
STATUS = CLIENT:{rule1 || rule2 || ...} * HELO:{rule1 || rule2 || ...} * SENDER:{rule1 || rule2 || ...} * RECIEPIENT:{rule1 || rule2 || ...} * permit
这里的“||”表示如果一个规则有返回值(即进行了匹配,无论返回permit还是reject)那么就不进行该子阶段的其后的规则检查。
B、单阶段过滤
当选项smtpd_delay_reject设置为no时,相当于把这四个子阶段的过滤规则都依序排列连接成一个单一的过滤规则列表。比如,如果在某个阶段该连接被判定为需要被拒绝,那么会立刻拒绝,而根本不处理其后规则和其后子阶段的规则。
从结果上看,这相当于如果某个规则发生了匹配,那么其后的规则和子阶段都被短路了,就像只有一个子阶段的检查一样。所以我们称之为单阶段过滤。其逻辑模型如下:
STATUS = CLIENT:{rule1 || rule2 || ...} || HELO:{rule1 || rule2 || ...} || SENDER:{rule1 || rule2 || ...} || RECIEPIENT:{rule1 || rule2 || ...} || permit
这里的“||”表示如果一个规则或子阶段有返回值(即进行了匹配,无论返回permit还是reject)那么就不进行该子阶段的其后的规则和子阶段检查。
在单阶段过滤中,由于smtpd_recipient_restrictions可以接受所有的数据前过滤的规则,所以通常会把所有的过滤规则都放在这里。注意在单阶段过滤,所有排在前面的过滤规则被先处理,而且会形成短路。比如,如果你在smtpd_client_restrictions里就应用了RBL服务,但是RBL里面有一个地址是你希望接收来自那里的邮件的,那么根本没有机会来检查发送者地址就会被拒绝了。解决地方法就是在smtpd_sender_restrictions(或smtpd_recipient_restrictions)中先做一个check_sender_access,然后再做RBL检查。
二、数据后过滤
有时候仅仅通过在邮件数据发送前的信息还不足以判断一封邮件是否是垃圾邮件。那么更进一步可以通过邮件数据中包含的信息来判断是否是垃圾邮件而决定如何处理。虽然在SMTP会话已经完成了数据接收后进行过滤并不能节省下被浪费的带宽和处理能力,但是这样减少了垃圾邮件占用的空间、用户判断删除垃圾邮件而浪费的精力,而且对于一些现在流行的通过邮件传递的蠕虫病毒也减少了用户误中并继续扩散的可能性。
数据后过滤一般分为信头过滤和信体过滤两种。
1、信头过滤
简单地说,在SMTP会话中,DATA指令发送的数据以一个空行分隔开,前面的部分称为信头,后面的称为信体。通常在邮件中,包括三种类型的信头:
邮件主信头
MIME头(包括主信头中的和多部分分隔串之后的)
邮件中附带的邮件的信头
信头过滤可以通过以下配置来配置:
head_checks = maptype:mapname
它匹配所有的三种信头。
mime_head_checks = maptype:mapname
它仅仅匹配MIME头。
nested_head_checks = maptype:mapname
它仅仅匹配邮件中附带的邮件的信头。
信头过滤模板有两种类型:ISO标准正则表达式regexp和Perl兼容正则表达式pcre,这两者在语法上有一定的差异,使用任何一种都可以。过滤模板中的过滤规则格式如下:
/模式表达式/ 动作
模式表达式是用regexp或pcre写的用来匹配邮件头的表达式。
动作有如下几种(和访问数据库中所支持的动作不完全一致,比如不支持[45]NN、数字、DUNNO和规则等):
REJECT [text]
拒绝该邮件,可选的消息被发给发信人并记录到maillog中。
OK
对于这个信头行不再进行其他的匹配规则处理,继续处理下一行信头。
IGNORE
从信头中删除该信头行。常被用来删除一些特定的信头行,如Received。
WARNING [text]
将该信头行和可选的消息记录到maillog中。常用来测试过滤规则。
HOLD [text]
将该邮件放入hold队列中,并记录该信头行和可选的消息到maillog中。放入hold队列的邮件可以通过postcat命令来查看,或通过postsuper来删除或递交。
DISCARD [text]
成功接收邮件后并不递交,而是悄悄地将该邮件丢弃,并记录该信头行和可选的消息到maillog中。
FILTER transport:nexthop
将该邮件发到一个邮件过滤器中进行处理。
另外注意,信头过滤在对Subject处理时,中文(8位编码)会被编码为MIME编码或UUENCODE编码,请做相应转换处理。
2、信体过滤
信体是除了主信头之外的邮件内容(包括MIME头,不过将多行的MIME头作为多个单行处理)。信体过滤通常不检查全部的信体,只检查body_checks_max_size所指定的大小(以字节为单位,默认是50K)。信体过滤是针对行来处理的,对于超长的行,只检查line_length_limi所指定的长度(默认2048字节)。
信体过滤可以通过以下配置来配置:
body_checks = maptype:mapname
信体过滤模板同信头过滤模板一样。
三、附录
1、访问数据库
访问数据库是Postfix用来判断拒绝或接受邮件的数据库。通过访问数据库可以拒绝或接收特定的主机名、域名、网络地址和邮件地址。
通常访问数据库是一个由postmap命令从一个文本文件输入生成的db或dbm格式的HASH文件。也有NIS,LDAP,SQL和正则表达式方式的其它数据库,它们和HASH文件以相同的方式工作,但是不需要用postmap来生成。
数据库的格式如下:
模式表达式 动作
当匹配了模式表达式时,就触发了相应的动作。
数据库中的空行和以“#”开头的行被忽略掉。以空格开始的行是上一行的续行。
A、邮件地址
非正则表达式用来表示邮件地址时有如下格式:
user@domain.tld
匹配特定的邮件地址
domain.tld
匹配特定邮件的域。如果在parent_domain_matches_subdomains指定了smtpd_access_maps(默认),那么也会匹配该域的子域;否则就需要使用.doamin.tld格式(注意前面的点)来单独匹配子域。
user@
匹配所有本地部分(邮件用户名)是user的邮件地址
<>
匹配无邮件地址。这个值是通过smtpd_null_access_lookup_key来指定的。
B、主机名和地址
非正则表达式用来表示主机名和地址时有如下格式:
domain.tld
匹配特定邮件的域。如果在parent_domain_matches_subdomains指定了smtpd_access_maps(默认),那么也会匹配该域的子域;否则就需要使用.doamin.tld格式(注意前面的点)来单独匹配子域。
net.work.addr.ess
net.work.addr
net.work
net
匹配网络地址。注意CIDR(无类网络地址,network/netmask格式)不被支持。
C、动作
访问数据库支持如下动作:
[45]NN text
拒绝该邮件并返回数字的拒绝状态码和消息。
REJECT [text]
拒绝该邮件并返回拒绝状态码和消息,如果没有指定消息就返回通用的消息。
OK
接受该邮件。
数字
同OK一样,接受该邮件。
DUNNO
跳过该规则,继续处理下面的规则。
HOLD [text]
将该邮件放入hold队列中,并记录可选的消息或通用的消息到maillog中。放入hold队列的邮件可以通过postcat命令来查看,或通过postsuper来删除或递交。
DISCARD [text]
成功接收邮件后并不递交,而是悄悄地将该邮件丢弃,并记录可选的消息或通用的消息到maillog中。
FILTER transport:nexthop
将该邮件发到一个邮件过滤器中进行处理。
UCE规则
应用UCE规则,如permit、reject_unauth_destination和smtpd_restriction_classes定义的规则类。
2、SMTP认证(SASL认证)
如上所述,漫游用户需要通过SMTP认证方式来使用发信服务。Postfix默认是不支持SASL协议,即不支持SMTP认证,需要通过额外指定SASL库的位置并打开SASL认证功能才能支持SMTP认证。以下以Postfix-2.0.13和Cyrus-Sasl-2.1.1为例简单说明如何使用SMTP认证。
首先下载Postfix-2.0.13和Cyrus-Sasl-2.1.1,并安装cyrus-sasl-2.1.1。默认cyrus-sasl安装到了/usr/local下,它的库在/usr/local/lib下,它的头文件在/usr/local/include/sasl下。
然后解开Postfix-2.0.13的源代码包,并使用如下命令编译:
% make tidy
% make makefiles CCARGS="-DUSE_SASL_AUTH -I/usr/local/include/sasl"
AUXLIBS="-L/usr/local/lib -lsasl2"
如果需要你还可以加上其他的编译选项,如对MySQL的支持等。
编译完成后安装,然后修改/etc/postfix/main.cf,添加如下配置:
smtpd_sasl_auth_enable = yes
来打开SASL认证功能,然后在smtpd_recipient_restrictions中添加permit_sasl_authenticated规则:
smtpd_recipient_restrictions = permit_sasl_authenticated
来允许通过了SASL认证的用户发信,你还可以在这里增加其它需要的规则。
修改/usr/local/lib/sasl2/smtpd.conf(如没有请创建),设定你需要的认证方式,如auxprop、pwcheck、saslauthd等,这依赖于你的具体需求。
在配置好Postfix后,重新启动Postfix。
然后就可以通过支持SMTP认证的邮件客户端进行认证发信了。你也可以通过Telnet方式来测试你SMTP认证是否成功。
有关SASL认证的更详细的细节,请参阅Postfix源代码包中的README_FILES目录中的SASL_README文件。