最近做项目需要通过 Postfix 实现针对邮件的特殊转发需求,网上找到这篇文章对 Postfix 的整个转发运作原理讲解得非常清楚透彻,几乎涵盖了所有与邮件中继转发邮件的技术点,特地转载过来做一下记录备忘;年纪大了以后记忆不行的时候还可以再回来看看参考!
Postfix 邮件转发详解
到目前为止,我们主要的讨论焦点,都是 postfix 于邮递路径末端所扮演的角色。也就是说,抵达 postfix 服务器的邮件,主要是被投递到本机系统。但是,postfix 也常扮演另一种角色--位于邮递路径中的中继器。
备用交换器
在 DNS 的术语中, MX 代表 mail exchanger。一个MX
记录代表一个网域承接外来邮件的主机,以及该主机的优先度。同一个网域可以同时有好几个 MX 记录,优先度最高的主机称为主交换器,其余主机称为备用交换器。
备用交换器的任务,是在主交换器离线时,承接该网域的外来邮件;当优先度高于自己的其他备用交换器或主交换器恢复上线时,已收下的邮件必须传给主交换器来处理,备用交换器不能自己擅作主张,将收到的邮件直接投递到最终目的地。架设备用交换器时,不必特别设定如何传信给主交换器,因为 postfix 自己能从 DNS 查出如何转信给主交换器。不过,前提是你必须让 postfix 知道自己是哪一个网域的备用交换器。
你的 postfix 系统是哪些网域的备用交换器,那些网域的名称就必须被列在relay_domains
参数中。当寄信方的 MTA 发觉收信网域的主交换器离线了,它会试着联系优先度次高的交换器,直到有一个备用交换器能够收下邮件地址为止。如果你的 postfix 系统是某个网域的备用交换器,而且该网域被列在relay_domains
参数中,则 postfix 会收下该网域的邮件,并将其排入队列。
每隔一段时间, postfix 会扫描它的队列,并检查是否有优先度更高的邮件交换器恢复连接,如果有,则交出先前代收的邮件。如果不能交出邮件,postfix 会持续尝试,直到邮件在队列里等待的时间超过maximal_queue_lifetime
所指定的时限为止。此参数的默认值为五天,如果邮件在等待队列里待的时间超过此上限,postfix 会发出退信通知函给寄件人。如果你事先知道主交换器停机的时间会超过五天,可以适度提高时限。
转发列表
postfix 可从relay_domains
参数与 DNS 系统知道自己的备用交换器角色,但是它如何知道转发的网域中有哪些有效收件人?如果备用交换器无法事先知道主交换器有哪些合法邮箱,它势必被迫盲目收下所有邮件,等到主交换器恢复连接时,才会发现有哪些邮件无法投递并退信给原寄件人。因此,我们郑重建议你在备用交换器设置一份列表,记录受理网域有哪些合法用户,并定期与主交换器同步更新。
当备用交换器上的 postfix server 收到寄给 relay_domains 所列网域的邮件时,它会检查relay_recipient_maps
参数所指定的查询表,借此判断是否应该收下邮件,如不应收下则直接拒收。
relay_recipient_maps
参数的设定方式如下:
relay_recipient_maps = hash:/etc/postfix/relay_recipients
relay_recipients
查询表应该记录所有合法收件人的邮件地址。postfix 只需要此表的索引键,所以对应值可以任意填写(但不可不填)对于人事变动频繁的网域,我们建议你应该研制一个同步程序,利用 rsync、ssh、crond 等工具,自动从主交换器下载最新列表。
如果备用交换器的 postfix 不过滤转发网域的收件地址,会有什么后果?答案是备用交换器会收到一大堆无法投递的垃圾邮件,并产生一大堆寄不出去的退信通知函。因为捏造收件人是垃圾邮件发送者常用的手段之一,他们事先不知道你的邮件交换器有哪些用户,于是以常用的人名来捏造收件地址,试图蒙混过关。此外,垃圾邮件也不可能会提供有效的回信地址。
假如备用交换器与主交换器位于同一个局域网,除了使用 rsync、scp、ssh、crond等传统工具之外,还有更好的方法来进行列表的同步更新工作。比方说,将用户账号储存在某种数据库中,像mysql、ldap等,让postfix能够进行实时查询。
在你设定了relay_recipient_maps
之后,还必须面对一个潜在的问题;你必须将所有转发网域的所有合法邮件地址都列入查询表,因为 postfix 将拒收任何收件地址没出现在查询表的邮件。如果你不知道某些网域的合法邮件地址,你可以为该网域设置一个无限别名地址;
快速清空
为许多网域承接邮件的网络,时常会面临无法立刻送出邮件的困境,因为他们的顾客的服务器不见得永远保持连接。在客户离线的情况下,ISP只能将收到的邮件暂时存放在队列里,等到客户的服务器恢复连接时,再使用 SMTP 的 ETRN 命令,一次清空队列里的所有邮件;
当一个网域的主交换器恢复连接并完成收信准备时,我们的队列里已囤积了大量要传给该网域的邮件,让 postfix
逐一检查每一个队列文件的收信网域为何,将会损耗许多时间。
为此, postfix 提供了一种称为“快速清空”(fast flush)的功能,可用来在队列中找出寄给特定网域的所有邮件。快速清空功能是由 flush daemon 控制管理的,对于 postfix 所代理的每一个收信网域, flush 各准备了一份列表,记录该网域邮件在队列里的编号。如此,在发出 ETRN 命令之后,postfix 可以迅速找出所有要投递到该网域的邮件。
默认情况下,flush 只管理 relay_domains 所列的网域。如果还有其他网域也需要快速清空服务,你可以将它们的网域名称在fast_flush_domains
参数中,像这样:
fast_flush_domains = $relay_domains, example.com
此例中,example.com 是一个没列在 relay_domains
中的网域。你可以使用 postqueue -s
命令来通知 postfix ,某个快速清空网域已经准备好接收先前累积的邮件了:
postqueue -s example.com
传输表
当你想改变默认的邮递流程时,可利用传输表(transport map
)来达成愿望。也就是说,如果你希望postfix以你指定的方式来处理特定网域的邮件,而不管DNS MX记录是怎么设定的,你可将相关网域与传送方式写在一二传输表中,然后将transport_maps
参数指向此传输表。
本节讨论transport_maps
参数的基本用法,后面几章会陆续谈到此参数在其他方面的应用。transport_maps
参数可指向一个或多个传输表,例如:
transport_maps = hash:/etc/postfix/transport
传输表的索引键可以是完整的邮件地址、网域名称或子网域名称。当收件地址或网域名称符合传输表某个记录的索引键时,则以该记录的对应值所指定的传输法来投递该邮件。
传输表内容
example.com smtp:[192.168.23.56]:20025
oreilly.com relay:[gateway.oreilly.com]
oreillynet.com smtp
ora.com maildrop
kdent@ora.com error:no mail accepted for kdent
传输表对应值的格式,随传输方法而异,但大体上符合transport:nexthop
这样的格式。某些传输方法的 nexthop
可表示成host:port
形式,表示递送路径下一站的主机名与通信端口。
以下分别说明可组成对应值的三种元素:
transport
传输方法的名称。此名称必须是master.cf所定义的传输类型之一。如果你增加了新的传输方法,则必须先在master.cf定义其名称与传输类型。
host
收信主机或网域。host 只能搭配 inet 传输类型(smtp或lmtp)。postfix 按照一般收信网域的处理流程 host:
先查询 mx 记录来决定邮件的去处,如果没有 mx 记录,则传到 a 记录所指的 ip 地址。如果将主机名称放在一对方括号内,则 postfix 会直接传信到 host 的 a 记录所指的 ip 地址。但如果你直接使用 ip 地址,则一定要加方括号,例如 [192.168.23.56]
port
收信主机的通信端口。通常只有inet类型的传输服务才会指定通信端口。port 的格式可以是十进制数,也可以是/etc/services
文件定义的服务名称。
上例列出的传输表内容,展示了 transport:host:port
的多种可能组合,分别解释如下:
example.com smtp:[192.168.23.56]:20025
收下所有写给example.com
的邮件,然后使用 smtp MDA 传送到位于 192.168.23.56 的主机,而且 smtp MDA 必须连接到该主机的port 20025,而非默认的 smtp port25。请注意,由于我们直接使用了ip地址,所以必须加上方括号。
oreilly.com relay:[gateway.oreilly.com]
收下所有要寄到 oreilly.com 的邮件,然后使用 relay MDA 转寄给 gateway.oreilly.com 主机。由于没指定通信端口,所以relay使用默认的port25。由于主机名称被放在方括号内,所以邮件是直接传到 gateway.oreilly.com 的 A 记录所指的 ip 地址,而非mx记录所指的ip地址。
relay MDA 是 postfix2.0 版以后才引进的,它修正了队列调度算法可能引起的潜在效能瓶颈。当你要将入站邮件送到内部系统时,应该直接通过relay MDA,以避免这类邮件与出站邮件竞争资源。
oreillynet.com smtp
收下所有目的地为 oreillynet.com 网域的邮件,然后交给 smtp MDA 执行投递操作。由于没指定 host:port
所以 smtp 依照 oreillynet.com 网域的 DNS MX
或 A
记录来决定目的地,并使用 port 25
来联系收信服务器。实际上这个例子实属多余,因为只要将 oreillynet.com 列在 relay_hosts
或 relay_domains
参数,就可以达到相同效果。
ora.com maildrop
收下所有写给 ora.com 网域的邮件,然后交给 maildrop
处理。maildrop
的运作方式必须被明确定义在master.cf
中。由于maildrop
不需要inet socket
,所以不必指定host:port
。
kdent@ora.com error:No mail accepted for kdent
error
是一种特殊的传输服务,它唯一的作用是当场拒收邮件。冒号之后的字符串是回复给传送方的错误信息。
传输表不一定用来将邮件传递到外界,也可以用于将特定邮件交给本地系统,以便进行特殊处理。比方说,过滤邮件内容、暂时扣留某个网域的所有邮件等。