跳转到主要内容

于 2025年04月22日 摘录自  Postfix Architecture Overview

简介

本文档概述了Postfix的架构,并提供了每个Postfix命令或服务器程序的描述链接。本文档介绍了每个命令或服务器程序的使用背景,并提供了包含具体使用示例和背景信息的文档链接。

本文档涵盖的主题:

Postfix 如何接收邮件

当一条消息进入 Postfix 邮件系统时,其内部的第一站是 入站队列。下图展示了处理新邮件的主要流程。带编号的名称是 Postfix 命令或服务器程序,而阴影区域内的未编号名称代表 Postfix 队列。

 trivial-
rewrite(8)
网络-> smtpd(8) 
^
|
 
网络-> qmqpd(8)-> cleanup(8)-> incoming 
 
 pickup(8)<- maildrop 
 ^
本地-> sendmail(1)-> postdrop(1)
  • 网络邮件通过 smtpd(8)qmqpd(8) 服务器进入 Postfix。这些服务器会移除 SMTP 或 QMQP 协议封装,执行一些基本检查以保护 Postfix,并将发件人、收件人和邮件内容传递给 cleanup(8) 服务器。smtpd(8) 服务器可配置为阻止不需要的邮件,具体请参阅 SMTPD_ACCESS_README 文档。
  • 本地提交通过 Postfix 的 sendmail(1) 兼容命令接收,并排入 maildrop 队列 中。这种安排即使在 Postfix 邮件系统未运行时也有效。本地 pickup(8) 服务器会收集本地提交的邮件,执行一些基本检查以保护 Postfix,并将发件人、收件人和邮件内容传递给 cleanup(8) 服务器。
  • 来自内部源的邮件直接传递给cleanup(8)服务器。这些源在图中未显示,包括:由local(8) 投递代理转发的邮件(见下一节)、由 bounce(8) 服务器退回给发件人的邮件(见再下一节),以及 Postfix 问题通知。
  • cleanup(8) 服务器实现邮件入队列前的最终处理阶段。它添加缺失的 From: 及其他邮件头信息,并按照 ADDRESS_REWRITING_README 文档中描述的方式转换地址。可选地,cleanup(8) 服务器可配置为使用正则表达式进行轻量级内容检查,具体请参阅 BUILTIN_FILTER_README 文档。cleanup(8) 服务器将结果作为单个文件放置在 incoming queue,并通知队列管理器(见下一节)有新邮件到达。
  • trivial-rewrite(8) 服务器将地址重写为标准的 "[email protected]"格式,具体说明请参阅ADDRESS_REWRITING_README文档。Postfix目前未实现重写语言,但可通过表查找实现大部分功能,必要时可使用正则表达式。

Postfix 如何投递邮件

当消息到达 入站队列 后,下一步是投递该消息。下图展示了 Postfix 邮件投递机制的主要组件。带数字的名称是 Postfix 命令或服务器程序,而阴影区域内的未编号名称代表 Postfix 队列。

 trivial-
rewrite(8)
 smtp(8)-> 网络
 
^
|
lmtp(8)-> 网络
incoming -> active -> qmgr(8)--- local(8)-> 文件,命令
 
^
|
 
virtual(8)-> 文件
 deferred  
 pipe(8)-> 命令
  • 队列管理器(图中的qmgr(8)服务器进程)是Postfix邮件投递的核心。它联系 smtp(8)lmtp(8)local(8)virtual(8)pipe(8)discard(8)error(8) 投递代理,并为一个或多个收件人地址发送投递请求。discard(8)error(8) 投递代理是特殊的:它们会丢弃或退回所有邮件,并且不在上图中显示。

    队列管理器维护一个有限的活动队列,其中包含已打开用于投递的消息。活动队列 作为一个有限的窗口,用于查看潜在的大型 incomingdeferred queues 中。有限的活动队列可防止队列管理器在高负载下耗尽内存。

    队列管理器维护一个独立的延迟队列,用于存放无法投递的邮件,从而避免大量邮件积压影响正常队列访问速度。队列管理器对延迟邮件投递尝试的策略在QSHAPE_READMETUNING_README文档中描述。

  • trivial-rewrite(8) 服务器根据收件人地址的本地或远程地址类进行解析,具体定义在 ADDRESS_CLASS_README 文档中。可通过可选的 transport(5) 表指定额外的路由信息。trivial-rewrite(8) 服务器可选地查询 relocated(5) 表,以查找地址已更改的收件人;发给此类收件人的邮件将附带解释信息退回发件人。
  • smtp(8) 客户端在投递请求中查找目的地对应的 SMTP 服务器列表,按优先级排序,并依次尝试每个服务器,直至投递或退回投递请求中的所有收件人。它根据 SMTP 协议的要求封装发件人、收件人和消息内容;这包括将消息正文从 8 位 MIME 转换为 7 位编码,但不包括 RFC 2047 头部编码。
  • lmtp(8) 客户端使用一种与 SMTP 类似的协议,该协议经过优化,适用于向 Cyrus 等邮件服务器投递邮件。此配置的优势在于,一台 Postfix 服务器可以通过 LMTP 向多个邮件服务器投递邮件。反之亦然:一台邮件服务器可以通过 LMTP 由多个 Postfix 服务器投递邮件。
  • 本地(local(8))投递代理支持 UNIX 风格的邮件箱、与 qmail 兼容的 maildir 文件、Sendmail 风格的全局 aliases(5) 数据库,以及 Sendmail 风格的用户级 .forward 文件。多个本地投递代理可以并行运行,但通常限制对同一用户的并行投递。

    local(8)交付代理具有替代本地交付形式的钩子:您可以配置它将邮件交付到用户主目录中的邮箱文件,您可以配置它将邮箱交付委托给外部命令(如 procmail),或将交付委托给不同的 Postfix 交付代理。

  • virtual(8) 投递代理是一个精简版的投递代理,仅支持投递到 UNIX 风格的邮件箱或 qmail 风格的 maildir 文件。该投递代理可为多个域投递邮件,因此特别适合在单台机器上托管大量小型域。此内容在VIRTUAL_README文档中有所描述。
  • pipe(8) 邮件发送程序是与其他邮件处理系统通信的出站接口(Postfix 的 sendmail(1) 命令是入站接口)。该接口与 UNIX 兼容:pipe(8) 邮件发送程序向子进程的命令行、环境变量和标准输入流提供信息,并期望子进程返回 <sysexits.h> 中定义的退出状态代码。通过pipe(8)邮件发送器的示例在FILTER_READMEMAILDROP_READMEUUCP_README 文档中。

Postfix 后台工作原理

前面的章节概述了 Postfix 服务器进程如何发送和接收邮件。这些服务器进程依赖于其他在后台运行的服务器进程。下文试图在各自的上下文中可视化每个服务。与之前一样,带数字的名称是 Postfix 命令或服务器程序,而阴影区域内的未编号名称代表 Postfix 队列。

  • 驻留的 master(8) 服务器是监督 Postfix 邮件系统运行状况的监视程序。它通常在系统启动时通过 "postfix start" 命令启动,并持续运行直至系统关闭。master(8) 服务器负责启动 Postfix 服务器进程以接收和发送邮件,并重新启动因某些问题而提前终止的服务器。master(8) 服务器还负责执行在 master.cf 配置文件中指定的服务器进程数量限制。下图展示了 Postfix 启动时的程序层次结构。仅显示了部分邮件处理守护进程。

     postfix(1)  
     |
    |
      
     postfix-script(1)  
     
     /
    / 
    |
    |
    \ 
     \
     
    postsuper(1) master(8) postlog(1)
     
     /
    / 
    |
    |
    \ 
     \
     
    smtpd(8) qmgr(8) local(8)
  • anvil(8) 服务器为所有 smtpd(8) 服务器实现客户端连接和请求速率限制。TUNING_README 文档提供了处理行为异常的 SMTP 客户端的指导。anvil(8) 服务在 Postfix 2.2 及更高版本中可用。

    网络-> 
    smtpd(8)

     
    <-> 
    anvil(8)

     
  • bounce(8)defer(8)trace(8) 服务各自维护自己的队列目录树,并为每条消息生成日志文件。Postfix 在向发件人发送"失败"、"延迟"或"成功"的投递状态通知时会使用这些信息。

    trace(8) 服务还实现了对 Postfix "sendmail -bv" 和 "sendmail -v" 命令的支持,这些命令会生成关于 Postfix 如何投递邮件的报告,并从 Postfix 2.1 版本开始可用。请参阅 DEBUG_README 以获取示例。

    cleanup(8)-> qmgr(8)
    Postfix
    队列
    -> 传递
    代理
    ^
     |
     |
    (非-)
    配送
    通知
    <- bounce(8)
    defer(8)
    trace(8)
    <- 队列 ID,
    接收者,
    状态
     
    ^
    |
      
     每条消息
    日志文件
    日志文件
      
  • flush(8) 服务器维护按目的地分类的日志,并实现 "sendmail -qRsite", "sendmail -qIqueueid" "postqueue -s site", "postqueue -i queueid", 以及 ETRN,具体请参阅 ETRN_README 文档。这将选定的队列文件从延迟队列移动回传入队列并请求其投递。flush(8) 服务在 Postfix 1.0 及更高版本中可用。

     incoming 

    deferred 
        
     ^
    |
        
    smtpd(8)
    sendmail(1)
    postqueue(1)
    目的地
    要清空
    -> flush(8)<- 延迟
    目标,
    队列 ID
    交付
    代理,
    qmgr(8)
     
    ^
    |
        
     按目的地
    日志
    日志
        
  • proxymap(8) 服务器为 Postfix 进程提供只读和可读写表查找服务。这克服了 chroot 限制,通过在多个进程间共享一个打开的表来减少打开的查找表数量,并实现了单更新器表。
  • scache(8) 服务器维护 Postfix smtp(8) 客户端的连接缓存。当为选定的目的地启用连接缓存时,smtp(8) 客户端在邮件传输完成后不会立即断开连接,而是将连接交由连接缓存服务器,该服务器会保持连接打开一段时间。smtp(8) 客户端将继续处理其他邮件投递请求。同时,任何 smtp(8) 进程均可向 scache(8) 服务器请求该缓存连接并重复使用其进行邮件投递。作为安全措施,Postfix 限制了连接可被重用的次数。

    在向具有多个邮件服务器的目的地投递邮件时,连接缓存可帮助跳过未响应的服务器,从而大幅提升投递速度。SMTP 连接缓存功能在 Postfix 2.2 及更高版本中可用。有关此功能的更多信息,请参阅 CONNECTION_CACHE_README 文档。

     /-- smtp(8)--> Internet
    qmgr(8) |
    |
    |
    |
    v
         
     \-- smtp(8) 
      ^
    |
       
     scache(8)   

    Postfix smtp(8) 客户端可以复用一个 TLS 加密连接(通过 "smtp_tls_connection_reuse = yes")。这可以大大减少连接建立的开销并提高消息投递率。Postfix smtp(8) 客户端连接到远程 SMTP 服务器并发送明文 EHLO 和 STARTTLS 命令后,smtp(8) 客户端会在连接中插入一个 tlsproxy(8) 进程,如图所示。

     /-- smtp(8)--> tlsproxy(8)--> 互联网
    qmgr(8) |
    |
    |
    |
    v
           
     \-- smtp(8)   
      ^
    |
         
     scache(8)     

    邮件传输完成后,Postfix smtp(8) 客户端会将 smtp(8)-to-tlsproxy(8) 连接到 scache(8) 服务器,该服务器会保持连接打开一段时间。smtp(8) 客户端继续处理其他邮件投递请求。同时,任何 Postfix smtp(8) 客户端都可以向 scache(8) 服务器请求该缓存连接并重复使用它进行邮件投递。

  • showq(8) 服务器列出 Postfix 队列状态。这是为 mailq(1)postqueue(1) 命令提供队列列表服务的程序。

    输出<- mailq(1)
    post-
    queue(1) 
     
    <- showq(8)<-Postfix
    队列
  • spawn(8) 服务器根据请求执行非 Postfix 命令,客户端通过套接字或 FIFO 连接到命令的标准输入、输出和错误流。您可以在SMTPD_POLICY_README文档中找到其使用示例。
  • tlsmgr(8) 服务器在 Postfix 的 TLS(传输层安全,原名 SSL)启用时运行。该进程可作为 smtp(8) 客户端或 smtpd(8) 服务器中启用 TLS(传输层安全,原名 SSL)时运行。该进程有两个职责:

    • 维护用于为 Postfix smtp(8) 客户端或 smtpd(8) 服务器进程中的 TLS 引擎提供初始化的伪随机数生成器(PRNG)。该 PRNG 的状态会定期保存到文件中,并在 tlsmgr(8) 启动时读取。
    • 维护可选的 Postfix smtp(8) 客户端或 smtpd(8) 服务器缓存,其中包含 TLS 会话密钥。保存的密钥可通过减少 TLS 会话初始化时的计算量来提升性能。

    Postfix 2.2 及更高版本支持 TLS。有关 Postfix TLS 实现的详细信息,请参阅 TLS_README 文档。

    网络-> 
    smtpd(8) 
     
    <---种子---

    <-会话-> 

    tlsmgr(8) 
     
    ---种子--->

    <-会话-> 

    smtp(8) 
     
    ->网络
     
     /
    / 
    |
    |
    \ 
     \
      
     smtpd
    session
    cache
     PRNG
    state 
    file
     smtp
    session
    cache
      
  • verify(8) 服务器在 smtpd(8) 服务器接受邮件之前,验证发件人或收件人地址是否可投递。verify(8) 服务器会查询一个包含地址验证结果的缓存。如果未找到结果,verify(8) 服务器会向 Postfix 队列中插入一个探测消息,并处理来自投递代理或队列管理器的状态更新。此过程在 ADDRESS_VERIFICATION_README 文档中描述。verify(8) 服务在 Postfix 2.1 及更高版本中可用。

     -> 探测
    消息
    -> Postfix
    邮件
    队列
      
      
    网络-> smtpd(8)<-> verify(8)  
     |
    v
      
    <- probe
    status
    <- Postfix
    delivery
    agents
    -> 本地
    -> 网络
     
     ^
    |
    v
     
     
       
     地址
    验证
    缓存
          
  • postscreen(8) 服务器可以部署在 Postfix smtpd(8) 进程的前端。其目的是接受来自网络的连接,并决定哪些 SMTP 客户端可以与 Postfix 通信。根据 2008 年 MessageLabs 的年度报告,81% 的所有电子邮件是垃圾邮件,其中 90% 由僵尸网络发送;到 2010 年,这些数字分别升至 92% 和 95%。虽然postscreen(8)负责阻止僵尸进程,但仍有更多smtpd(8)进程可供合法客户端使用。

    postscreen(8) 维护一个临时允许列表,用于通过其测试的客户端;通过允许允许列表中的客户端跳过测试,postscreen(8) 最大限度地减少了对合法电子邮件流量的影响。

    postscreen(8) 服务器自 Postfix 2.8 及更高版本起可用。为了保持实现的简单性,postscreen(8) 将 DNS 允许/拒绝列表的查询委托给 dnsblog(8) 服务器进程,并将 TLS 加密/解密委托给 tlsproxy(8) 服务器进程。此委托对远程 SMTP 客户端不可见。

     僵尸      
          
    僵尸tlsproxy(8)   smtpd(8)
         
     其他--- postscreen(8)    
         
     其他   smtpd(8)
          
     僵尸      
  • postlogd(8) 服务器提供了一种替代 syslog 日志记录的方案,后者仍为默认设置。此功能自 Postfix 3.4 版本起可用,并支持以下模式:

    • 将日志记录到文件,这解决了 MacOS 的可用性问题,并消除了由 systemd 速率限制导致的信息丢失。

      命令
      或守护进程
         
       -> postlogd(8)-> /path/to/file
       
    • 将日志记录到标准输出,这消除了 Postfix 在容器中运行时对 syslog 的依赖。

      命令
      或守护进程
       stdout 继承
      自 "postfix start-fg"
      -> postlogd(8)-> 
       

    请参阅 MAILLOG_README 以获取详细信息和限制。

Postfix 支持命令

Postfix 架构概述以日常使用 Postfix 邮件系统的命令行工具摘要结束。除了与 Sendmail 兼容的 sendmail(1)mailq(1)newaliases(1) 命令外,Postfix 系统还自带了一组命令行实用程序。为了保持一致性,这些命令均以 postsomething 命名。

  • postfix(1) 命令用于控制邮件系统的运行。它是启动、停止和重启邮件系统以及执行其他管理操作的接口。此命令仅限超级用户使用。
  • postalias(1) 命令维护 Postfix aliases(5) 类型的数据库。这是为 newaliases(1) 命令执行实际操作的程序。
  • postcat(1) 命令显示 Postfix 队列文件的内容。这是一个功能有限的初步工具。该程序很可能被更强大的工具取代,这些工具不仅能显示,还能编辑 Postfix 队列文件。
  • postconf(1) 命令显示或更新 Postfix main.cf 参数,并显示与系统相关的信息,包括支持的文件锁定方法和支持的查找表类型。
  • postdrop(1) 命令是 Postfix 通过 sendmail(1) 命令,用于将邮件存入 maildrop 队列 目录。
  • postkick(1) 命令使 Postfix 的内部通信通道可用于 shell 脚本等场景。
  • postlock(1) 命令为 Postfix 兼容的邮箱锁定提供支持,可在 shell 脚本等场景中使用。
  • postlog(1) 命令为 shell 脚本提供 Postfix 兼容的日志记录功能。
  • postmap(1) 命令维护 Postfix 查找表,例如 canonical(5)virtual(5) 等。它是 UNIX makemap 命令的变体。
  • postmulti(1) 命令会为每个 Postfix 实例重复执行 "postfix start" 等命令,并支持创建、删除等 Postfix 实例操作。有关教程,请参阅 MULTI_INSTANCE_README
  • postqueue(1) 命令是 Postfix 通过 sendmail(1)mailq(1) 运行的特权命令,用于清空或列出邮件队列。
  • postsuper(1) 命令用于维护 Postfix 队列。它会删除旧的临时文件,并在队列目录的哈希深度发生变化后,将队列文件移动到正确的目录。该命令在邮件系统启动时以及 Postfix 重启时执行。