Sendmail 8.15.2 配置参数详解

Sendmail 8.15.2 配置体系全景:m4 宏语法五规则、sendmail.mc 实测全文逐段解读、11 个 FEATURE 与 conf 参数语义、五类映射库(access/aliases/virtusertable/genericstable/mailertable)实操语法、Debian 定制层与三种生效路径,全部片段与回显实测照抄。

SendmailSendmailUbuntu 22.04邮件服务器2026/10/27

Sendmail 8.15.2 配置参数详解

目标:吃透 Sendmail 的配置体系——sendmail.mc(m4 宏源)、m4 宏语法、FEATURE/MAILER/define 参数、五类地址映射库(access/aliases/virtusertable/genericstable/mailertable)以及 Debian/Ubuntu 定制层。全文以 Ubuntu 22.04 服务器上实测的 Sendmail 8.15.2-22ubuntu3 为基准(2026-10-27 取证),所有配置片段、文件内容与命令回显均为现场照抄。

一、配置体系总览

Sendmail 的最终生效配置是 /etc/mail/sendmail.cf(规则集与宏定义,语法晦涩),而管理员维护的是它的「源码」——m4 宏文件 sendmail.mc。二者的关系是一条单向流水线:

sendmail.mc ──(m4 预处理器 + cf.m4 宏库)──> sendmail.cf ──(重启/重载)──> 运行行为

三个关键铁律:

  1. 永远改 .mc,不改 .cf——直接改 sendmail.cf 在下次 m4 重新生成时会被无声覆盖;
  2. 改完 .mc 必须重新生成 .cf 并触发生效,只改文本源不会有任何效果(排障实录见「十、生效流程与验证」问题 3);
  3. 别名与映射库改的是「文本源 + .db 编译产物」两份文件,文本源是给人看的,.db 才是 sendmail 运行时读的。

运行时实际参与工作的配置体系有两套:MTA 体系(sendmail.cf + /var/spool/mqueue 队列,端口 25 收发)与 MSP 提交体系(submit.cf + /var/spool/clientmqueue 队列,本机命令行提交)。两套各有独立的 mc/cf 源对,改配置时别改错对象。

实测服务器的 /etc/mail 目录全貌(ls -la /etc/mail 现场照抄,含属主与时间戳):

total 280
drwxr-sr-x 7 smmta smmsp  4096 Oct 27 01:57 .
drwxr-xr-x 1 root  root   4096 Oct 27 00:25 ..
-rwxr-xr-- 1 root  smmsp 12530 Oct 27 01:53 Makefile
-rw------- 1 root  root    317 Oct 27 01:57 access
-rw-r----- 1 smmta smmsp 12288 Oct 27 01:57 access.db
-rw-r--r-- 1 root  root    281 Dec  9  2021 address.resolve
lrwxrwxrwx 1 root  smmsp    10 Oct 26 17:22 aliases -> ../aliases
-rw-r----- 1 smmta smmsp 12288 Oct 27 01:53 aliases.db
-rw-r--r-- 1 root  smmsp  3379 Oct 27 01:53 databases
-rw-r--r-- 1 root  smmsp    25 Oct 27 00:22 genericstable
-rw-r----- 1 smmta smmsp 12288 Oct 27 00:22 genericstable.db
-rw-r--r-- 1 root  root   5659 Dec  9  2021 helpfile
-rw-r--r-- 1 root  smmsp    39 Oct 27 00:22 local-host-names
drwxr-sr-x 2 smmta smmsp  4096 Oct 27 00:25 m4
-rw-r--r-- 1 root  smmsp   122 Oct 27 02:04 mailertable
-rw-r----- 1 smmta smmsp 12288 Oct 27 02:04 mailertable.db
drwxr-xr-x 2 root  root   4096 Oct 26 17:22 peers
drwxr-xr-x 2 root  smmsp  4096 Dec  9  2021 sasl
-rw-r--r-- 1 root  smmsp 64641 Oct 27 01:53 sendmail.cf
-rw-r--r-- 1 root  root  12235 Oct 26 17:22 sendmail.conf
-rw-r--r-- 1 root  smmsp  4412 Oct 27 00:25 sendmail.mc
-rw-r--r-- 1 root  root    148 Dec  9  2021 service.switch
-rw-r--r-- 1 root  root    179 Dec  9  2021 service.switch-nodns
drwxr-sr-x 2 smmta smmsp  4096 Oct 26 17:22 smrsh
-rw-r--r-- 1 root  smmsp 44566 Oct 26 17:22 submit.cf
-rw-r--r-- 1 root  smmsp  2383 Oct 26 17:22 submit.mc
drwxr-xr-x 2 smmta smmsp  4096 Oct 26 17:22 tls
-rw-r--r-- 1 root  smmsp     0 Oct 26 17:22 trusted-users
-rw-r--r-- 1 root  smmsp    77 Oct 27 01:57 virtusertable
-rw-r----- 1 smmta smmsp 12288 Oct 27 01:57 virtusertable.db

核心文件速查表:

文件角色谁写谁读
sendmail.mcMTA 配置宏源(4412 字节)管理员m4
sendmail.cfMTA 最终配置(64641 字节)m4 生成sendmail 守护
submit.mc / submit.cfMSP 提交侧配置对管理员 / m4 生成本机提交的 sendmail 进程
access / access.db访问控制文本源 / 哈希库管理员 / makemapsendmail 规则集
aliases -> ../aliases系统别名(符号链接到 /etc/aliases)管理员sendmail(读 aliases.db)
aliases.db别名哈希库(12288 字节)newaliasessendmail
virtusertable(.db)虚拟用户映射管理员 / makemapsendmail
genericstable(.db)发件地址改写管理员 / makemapsendmail
mailertable(.db)按域路由管理员 / makemapsendmail
local-host-names本机收信域名清单管理员sendmail
sendmail.confDebian 层守护参数(12235 字节)管理员/usr/share/sendmail/sendmail 脚本
Makefilemc→cf/db 重生成入口(12530 字节)包管理器make
databases包级映射库声明单(3379 字节)sendmailconfig 维护make
trusted-users可代发用户列表(实测空文件)管理员sendmail
service.switch名称服务查询顺序管理员sendmail
helpfileSMTP HELP 文案(5659 字节)包管理器sendmail
m4/、peers/、sasl/、smrsh/、tls/子目录:本地宏片段/SMTP 认证对端/SASL/smrsh 程序白名单/TLS 证书——

.db 与文本源的时间戳对应关系本身就是排障线索:实测中 access 01:57 → access.db 01:57(同步更新),sendmail.mc 00:25 → sendmail.cf 01:53(mc 改过但 cf 后来又重生成过一次)——若 .db/.cf 时间戳早于对应文本源,就是「改了没生效」的第一现场(现场判定法见「十、生效流程与验证」)。

二、sendmail.mc 实测全文与逐段解读

实测服务器 /etc/mail/sendmail.mc(Debian 原型 debproto.mc 派生,已按实验需求定制,全文照抄,注释也保留):

divert(-1)dnl
#-----------------------------------------------------------------------------
# $Sendmail: debproto.mc,v 8.15.2 2021-12-09 00:18:01 cowboy Exp $
#
# Copyright (c) 1998-2010 Richard Nelson.  All Rights Reserved.
#
# cf/debian/sendmail.mc.  Generated from sendmail.mc.in by configure.
#
# sendmail.mc prototype config file for building Sendmail 8.15.2
#
# Note: the .in file supports 8.15.0 - 9.0.0, but the generated
#	file is customized to the version noted above.
#
# This file is used to configure Sendmail for use with Debian systems.
#
# If you modify this file, you will have to regenerate /etc/mail/sendmail.cf
# by running this file through the m4 preprocessor via one of the following:
#	* make   (or make -C /etc/mail)
#	* sendmailconfig
#	* m4 /etc/mail/sendmail.mc > /etc/mail/sendmail.cf
# The first two options are preferred as they will also update other files
# that depend upon the contents of this file.
#
# The best documentation for this .mc file is:
# /usr/share/doc/sendmail-doc/cf.README.gz
#
#-----------------------------------------------------------------------------
divert(0)dnl
#
#   Copyright (c) 1998-2005 Richard Nelson.  All Rights Reserved.
#
#  This file is used to configure Sendmail for use with Debian systems.
#
define(`_USE_ETC_MAIL_')dnl
include(`/usr/share/sendmail/cf/m4/cf.m4')dnl
VERSIONID(`$Id: sendmail.mc, v 8.15.2-22ubuntu3 2021-12-09 00:18:01 cowboy Exp $')
OSTYPE(`debian')dnl
DOMAIN(`debian-mta')dnl
dnl # Items controlled by /etc/mail/sendmail.conf - DO NOT TOUCH HERE
undefine(`confHOST_STATUS_DIRECTORY')dnl        #DAEMON_HOSTSTATS=
dnl # Items controlled by /etc/mail/sendmail.conf - DO NOT TOUCH HERE
dnl #
dnl # General defines
dnl #
dnl # SAFE_FILE_ENV: [undefined] If set, sendmail will do a chroot()
dnl #	into this directory before writing files.
dnl #	If *all* your user accounts are under /home then use that
dnl #	instead - it will prevent any writes outside of /home !
dnl #   define(`confSAFE_FILE_ENV',             `')dnl
dnl #
dnl # Daemon options - restrict to servicing LOCALHOST ONLY !!!
dnl # Remove `, Addr=' clauses to receive from any interface
dnl # If you want to support IPv6, switch the commented/uncommentd lines
dnl #
FEATURE(`no_default_msa')dnl
dnl DAEMON_OPTIONS(`Family=inet6, Name=MTA-v6, Port=smtp, Addr=::1')dnl
DAEMON_OPTIONS(`Family=inet,  Name=MTA-v4, Port=smtp')dnl
dnl DAEMON_OPTIONS(`Family=inet6, Name=MSP-v6, Port=submission, M=Ea, Addr=::1')dnl
DAEMON_OPTIONS(`Family=inet,  Name=MSP-v4, Port=submission, M=Ea, Addr=127.0.0.1')dnl
dnl #
dnl # Be somewhat anal in what we allow
define(`confPRIVACY_FLAGS',dnl
`needmailhelo,needexpnhelo,needvrfyhelo,restrictqrun,restrictexpand,nobodyreturn,authwarnings')dnl
dnl #
dnl # Define connection throttling and window length
define(`confCONNECTION_RATE_THROTTLE', `15')dnl
define(`confCONNECTION_RATE_WINDOW_SIZE',`10m')dnl
dnl #
dnl # Features
dnl #
dnl # use /etc/mail/local-host-names
FEATURE(`use_cw_file')dnl
dnl #
dnl # The access db is the basis for most of sendmail's checking
FEATURE(`access_db', , `skip')dnl
FEATURE(`blacklist_recipients')dnl
FEATURE(`virtusertable')dnl
FEATURE(`mailertable')dnl
FEATURE(`genericstable')dnl
GENERICS_DOMAIN(`mail.example.com')dnl
INPUT_MAIL_FILTER(`opendkim', `S=inet:8891@localhost, F=T')dnl
define(`confMILTER_MACROS_CONNECT',confMILTER_MACROS_CONNECT`, {j}')dnl
define(`confMILTER_MACROS_HELO',confMILTER_MACROS_HELO`, {verify}')dnl
dnl #
dnl # The greet_pause feature stops some automail bots - but check the
dnl # provided access db for details on excluding localhosts...
FEATURE(`greet_pause', `1000')dnl 1 seconds
dnl #
dnl # Delay_checks allows sender<->recipient checking
FEATURE(`delay_checks', `friend', `n')dnl
dnl #
dnl # If we get too many bad recipients, slow things down...
define(`confBAD_RCPT_THROTTLE',`3')dnl
dnl #
dnl # Stop connections that overflow our concurrent and time connection rates
FEATURE(`conncontrol', `nodelay', `terminate')dnl
FEATURE(`ratecontrol', `nodelay', `terminate')dnl
dnl #
dnl # If you're on a dialup link, you should enable this - so sendmail
dnl # will not bring up the link (it will queue mail for later)
dnl define(`confCON_EXPENSIVE',`True')dnl
dnl #
dnl # Dialup/LAN connection overrides
dnl #
include(`/etc/mail/m4/dialup.m4')dnl
include(`/etc/mail/m4/provider.m4')dnl
dnl #
dnl # Default Mailer setup
MAILER_DEFINITIONS
MAILER(`local')dnl
MAILER(`smtp')dnl

这份 mc 是理解配置体系的最佳标本——宏语句分七类,逐段解读如下:

段宏语句作用
① 骨架divert(-1) / divert(0) / include(cf.m4) / VERSIONID / OSTYPE(debian) / DOMAIN(debian-mta)m4 流控 + 引入宏库 + 选择操作系统域模板(见「三、四」)
② Debian 移交项undefine(confHOST_STATUS_DIRECTORY)标注 DO NOT TOUCH:该参数由 sendmail.conf 层管理,在此处 undefine 以免双头控制
③ 监听定义FEATURE(no_default_msa) + 两条 DAEMON_OPTIONS定义 MTA 监听(Port=smtp 即 25,未加 Addr= 时监听所有接口)与 MSP 监听(Port=submission 即 587,Addr=127.0.0.1 仅回环、M=Ea 强制认证+错误回显)
④ 隐私与限流define(confPRIVACY_FLAGS, ...) + confCONNECTION_RATE_THROTTLE(15) + confCONNECTION_RATE_WINDOW_SIZE(10m)收紧 SMTP 行为(详见「五、define 参数详解」)+ 每 10 分钟窗口内每 IP 限 15 连接
⑤ 特性启用七个 FEATURE + GENERICS_DOMAIN + INPUT_MAIL_FILTER + 两条 confMILTER_MACROS_*开启映射库/黑名单/ greet_pause 等(详见「四、FEATURE 详解」)
⑥ 防滥用限速define(confBAD_RCPT_THROTTLE, 3) + FEATURE(conncontrol...) + FEATURE(ratecontrol...)单连接 3 个坏收件后延迟响应;并发/速率超限直接断连(terminate)
⑦ 投递代理MAILER_DEFINITIONS + MAILER(local) + MAILER(smtp) + 两条 include(m4/*.m4)声明本地与 SMTP 投递代理(必须是 mc 最后的语句,顺序错误会生成失败的 cf)

三、m4 宏语法规则

sendmail.mc 不是 shell 脚本也不是 ini 配置,而是 m4 宏处理器的输入。五条核心语法规则:

规则 1:m4 引号是「反引号开头、单引号结尾」的成对引号(不是 shell 的双引号):

OSTYPE(`debian')dnl

错误写法 OSTYPE('debian') 或 OSTYPE("debian") 都会产生难以理解的 m4 报错(排障实录见「十、生效流程与验证」问题 1)。反引号 ` 开引、直引号 ' 闭引,m4 把引号内容作为「不加展开的字面量」传给宏。

规则 2:dnl(delete to newline)吃掉当前行剩余部分,用于抑制注释行后的空行。惯例是每条宏语句末尾都带 dnl;纯注释行前缀 dnl #(被注释掉的配置就是这形态,如上面的 IPv6 DAEMON_OPTIONS)。

规则 3:divert(-1) / divert(0) 控制输出流——divert(-1) 后 m4 输出被丢弃(用于文件头大段版权注释),divert(0) 恢复正常输出。这就是为什么文件头 27 行注释不会出现在 sendmail.cf 里。

规则 4:include() 内联另一个 m4 文件。include(``/usr/share/sendmail/cf/m4/cf.m4') 引入全部 cf 宏定义,是所有 FEATURE/MAILER/define 得以识别的前提;include(``/etc/mail/m4/dialup.m4') 与 include(``/etc/mail/m4/provider.m4') 是 Debian 层的拨号/托管环境开关(默认为空操作,见「七、Debian 定制层」)。

规则 5:三大结构宏决定模板骨架:

宏语义实测值
OSTYPE()选操作系统模板:定路径(队列/帮助文件/投递代理路径)与系统特性debian
DOMAIN()选站点域模板:一组通用策略(Debian 的 debian-mta 内含别名文件路径、smrsh、本地投递等约定)debian-mta
VERSIONID()纯标识注释,无行为$Id: sendmail.mc, v 8.15.2-22ubuntu3 ...

💡 技术拓展:m4 是 1977 年诞生的宏处理器,Sendmail 借用它实现「配置的配置」——.mc 是给人维护的领域语言,.cf 是给 sendmail 消费的中间表示。cf/README(/usr/share/doc/sendmail-doc/cf.README.gz,需装 sendmail-doc 包)是这套体系的权威手册,Debian mc 文件头注释也指向它。

四、FEATURE 详解

FEATURE(名称, 参数1, 参数2...) 启用一个打包好的配置特性(在 cf.m4 宏库中对应一段预置规则)。实测启用的 11 个:

FEATURE实测写法语义
no_default_msaFEATURE(no_default_msa')dnl`不生成默认 MSA(消息提交代理)监听,改用下方显式 DAEMON_OPTIONS 控制
use_cw_fileFEATURE(use_cw_file')dnl`启用 /etc/mail/local-host-names 作为本机收信域名清单($w 扩展)
access_dbFEATURE(access_db', , skip')dnl启用访问控制库。第二参空走默认映射规格(hash 类型 /etc/mail/access 源),skip 表示库文件缺失时不判致命(排障期友好)
blacklist_recipientsFEATURE(blacklist_recipients')dnl`把 access 查询扩展到收件人方向——这是 access 文件里 To: 条目能生效的前提
virtusertableFEATURE(virtusertable')dnl`启用虚拟用户映射库(默认 hash /etc/mail/virtusertable)
mailertableFEATURE(mailertable')dnl`启用按域路由库(默认 hash /etc/mail/mailertable)
genericstableFEATURE(genericstable')dnl`启用发件地址改写库(默认 hash /etc/mail/genericstable)
greet_pauseFEATURE(greet_pause', 1000')dnl发 220 横幅前静默 1000 毫秒——标准客户端会等横幅,早期垃圾邮件群发器不等就直接发数据,由此暴露。例外写 access 表 GreetPause:<地址> 0
delay_checksFEATURE(delay_checks', friend', n')dnl`调整 access 检查顺序(friend=发件人例外优先语义,可选第三参控制例外查询行为,细节见 cf/README delay_checks 节)
conncontrolFEATURE(conncontrol', nodelay', terminate')dnl`并发连接数控制(查 access 表 ClientConn: 条目);terminate=超限直接断连而非延迟
ratecontrolFEATURE(ratecontrol', nodelay', terminate')dnl`连接速率控制(查 access 表 ClientRate: 条目),参数语义同上

三个配合性语句(与 FEATURE 同族的裸宏):

  • GENERICS_DOMAIN(mail.example.com')dnl:声明哪些域名的发件地址要过 genericstable 改写。只有列入(或经 GENERICS_DOMAIN_FILE 列入文件)的域才被改写,防止把外部域的发件人也改了。等价文件形式:GENERICS_DOMAIN_FILE(/etc/mail/genericsdomain');
  • INPUT_MAIL_FILTER(opendkim', S=inet:8891@localhost, F=T')dnl:挂接 milter 过滤器——连接本机 8891 端口的 opendkim 服务做 DKIM 签名/验签,F=T 表示过滤器故障时临时失败(tempfail)。实测该机 opendkim 进程随 sendmail 一起运行(opendkim -x /etc/opendkim.conf);
  • 两条 define(confMILTER_MACROS_CONNECT/HELO, ...):向过滤器追加传递的宏——CONNECT 阶段加传 {j}(本机规范域名)、HELO 阶段加传 {verify}(TLS 校验结果),DKIM 签名头需要这些值。

常配而未启用的 FEATURE 速查(写法均实测语法,启用即加一行):

FEATURE典型写法用途
masquerade_asMASQUERADE_AS(example.com')dnl`出站信统一伪装成主域(配合 FEATURE(masquerade_envelope) 连信封一起改)
always_add_domainFEATURE(always_add_domain')dnl`本机投递的信也补全域名
relay_hosts_onlyFEATURE(relay_hosts_only')dnl`access 中继授权从网段粒度收紧到主机名粒度
smrshFEATURE(smrsh', path')dnl别名/forward 管道命令经 smrsh 白名单执行(上游通用模板常用;实测 Debian 默认配置未启用——sendmail.cf 中无 smrsh 字样,本地投递由 procmail 承担)
local_procmailFEATURE(local_procmail')dnl`本地投递改经 procmail(Debian 层亦可经 sendmail.conf 开关)
dnsblFEATURE(dnsbl', zen.spamhaus.org')dnl接入 DNS 实时黑名单

五、define 参数详解

define(confXXX, 值')设置一个 conf 参数(最终落在 sendmail.cf 的O/D` 指令上)。实测启用的 5 个:

参数实测值语义
confPRIVACY_FLAGSneedmailhelo,needexpnhelo,needvrfyhelo,restrictqrun,restrictexpand,nobodyreturn,authwarnings(7 个,跨两行书写)隐私/行为旗标集合:needmailhelo 强制 MAIL 前先 HELO;needexpnhelo/needvrfyhelo 未 HELO 禁 EXPN/VRFY;restrictqrun 仅 root 可跑队列(防普通用户 sendmail -q);restrictexpand 仅 root/信任用户可用 EXPN 展开别名;nobodyreturn 退信不带原文正文(防信息泄露);authwarnings 加 X-Authentication-Warning 头
confCONNECTION_RATE_THROTTLE15每 10 分钟窗口内单 IP 最大新连接数(与 confCONNECTION_RATE_WINDOW_SIZE 配对)
confCONNECTION_RATE_WINDOW_SIZE10m速率统计窗口长度
confBAD_RCPT_THROTTLE3单连接收到第 3 个未知收件人后,后续 RCPT 响应人为延迟——拖垮坏机器人,不影响正常客户端
confMILTER_MACROS_CONNECT / confMILTER_MACROS_HELO追加 {j} / {verify}见「四」milter 段

改写监听地址/端口的正是 DAEMON_OPTIONS(不算 conf 参数但同属 mc 定型语句)。实测两条的语法拆解:

DAEMON_OPTIONS(`Family=inet,  Name=MTA-v4, Port=smtp')dnl
DAEMON_OPTIONS(`Family=inet,  Name=MSP-v4, Port=submission, M=Ea, Addr=127.0.0.1')dnl
  • Family=inet:IPv4(inet6 为 IPv6,实测中 IPv6 行被 dnl 注释);
  • Name=:守护进程名标识(ps 里显示用);
  • Port=smtp / submission:服务名或端口号(25 / 587);
  • Addr=:省略时监听全部接口(MTA 行即如此,对内网可达);Addr=127.0.0.1(MSP 行)则仅本机可连;
  • M=Ea:MSP 专属——E 断连时回报错误、a 要求认证。

常用 conf 参数扩展速查表(本机未启用、生产高频,写法与语义按 cf/README 对齐):

参数典型值语义
confDOMAIN_NAMEmail.example.com规范域名($j),决定 SMTP 横幅与 Message-Id;不设则取内核主机名
confMAX_MESSAGE_SIZE10485760单信最大字节(对应 SMTP SIZE 扩展上限)
confMAX_RCPTS_PER_MESSAGE25单信最大收件人数,超出拆分投递
confQUEUE_LA12系统 1 分钟负载高于此值时暂停队列跑动
confREFUSE_LA18负载高于此值时拒收新连接
confDELAY_LA0负载高于此值即在响应中加人为延迟(4 为常用起点)
confTO_QUEUERETURN5d队列滞留上限,超时退信
confTO_QUEUEWARN4h队列滞留多久后向发件人发延迟警告
confSMART_HOSTsmtp.example.net所有外部信改交指定中继(双跳架构必配)
confSAFE_FILE_ENV/home写文件前 chroot 到指定目录(Debian mc 注释段预留的防逃逸开关)
confLOG_LEVEL9(默认)日志详细度,排障期可提到 14+
confPRIVACY_FLAGS(补位)goaway一键收紧(等效 needmailhelo+noexpn+novrfy 等组合)

六、MAILER 与 MAILER_DEFINITIONS

MAILER(名称) 引入一个投递代理的完整定义(投递路径、参数、地址规则)。实测启用两个,且必须放在 mc 文件末尾(cf 体系的硬约定——MAILER 之后不应再有 FEATURE/define,Debian mc 用 MAILER_DEFINITIONS 标记此段):

MAILER职责对应投递
local本地投递代理:落箱 /var/mail/<用户> 或转交 procmailmailer=local(投递日志中可见)
smtpSMTP 家族代理:esmtp(外域转发)、smtp8、relay(智能主机)、smtpu 等mailer=esmtp / mailer=relay

日志印证:B 系列端到端投递实录中,本地落箱记录为 mailer=local ... stat=Sent——这个 mailer=local 即来自 MAILER(local) 的定义。

顺序铁律:mc 中 MAILER(local) 与 MAILER(smtp) 的相对顺序无关紧要,但相对其他语句的位置必须靠后。若把 MAILER 写在 FEATURE 前面,m4 生成虽不报错,但部分特性规则会失效或生成损坏的 cf。

七、Debian/Ubuntu 定制层(sendmail.conf 与 Makefile)

Debian/Ubuntu 在上游 Sendmail 之上加了一层「配置的配置」,理解这层才不会被 Ubuntu 的行为迷惑。

1)sendmail.conf——守护参数层(/etc/mail/sendmail.conf,12235 字节,头部现场照抄):

#------------------------------------------------------------------------------
#
# /etc/mail/sendmail.conf
#
# Copyright (c) 2001-2010 Richard Nelson.  All Rights Reserved.
# Version:	  8.15.2
# Time-stamp: <2021-12-09 00:18:01 cowboy>
#
# Parameter file for sendmail (sourced by /usr/share/sendmail/sendmail)
# Make all changes herein, instead of altering /etc/init.d/sendmail.
#
# After making changes here, you'll need to run /usr/sbin/sendmailconfig
# or /usr/share/sendmail/update_conf to have the changes take effect -
# If you change DAEMON_MODE, QUEUE_MODE, or QUEUE_INTERVAL, you'll also
# need to run /etc/init.d/sendmail restart.
#
# Changes made herein will be kept across upgrades - except for comments!
# Some comment lines have special significance ...
#
# **** **** **** **** DO NOT EDIT THE COMMENTS **** **** **** ****
#
# Supported parameters (and defaults) are listed herein.
#
# Notes:
#	* This setup allows sendmail to run in several modes:
#	  - listener and queue runner..DAEMON_MODE="daemon".QUEUE_MODE="daemon"
#	  - listener only..............DAEMON_MODE="daemon".QUEUE_MODE="none"
#	  - queue runner only..........DAEMON_MODE="none"...QUEUE_MODE="daemon"
#	  - *NOTHING* ?!?..............DAEMON_MODE="none"...QUEUE_MODE="none"
#
#	* You can also run the listener from inetd:
#	  - listener and queue runner..DAEMON_MODE="inetd"..QUEUE_MODE="daemon"
#	  - listener only..............DAEMON_MODE="inetd"..QUEUE_MODE="none"
#
#	* You can also run the queue runner from cron:
#	  - listener and queue runner..DAEMON_MODE="....."..QUEUE_MODE="cron"
#	  - queue runner only..........DAEMON_MODE="none"...QUEUE_MODE="cron"
#
#	* _PARMS entries herein are shown in precedence order, any later _PARMS
#	  field will, if applicable, override any previous _PARMS fields.
#
#	* Values *MUST* be surrounded with double quotes ("), single quotes
#	  will *NOT* work !
#
#------------------------------------------------------------------------------
#				SMTP Listener Configuration
#
# DAEMON_NETMODE="Static";  Keyword  SMTP network mode
#	static:  Do not monitor any network interfaces for changes
#	dynamic: Monitor one or more interfaces for changes

关键点:这是 shell 语法的参数文件(由 /usr/share/sendmail/sendmail 脚本 source),值必须双引号(单引号无效)。核心开关 DAEMON_MODE(监听器形态:daemon/inetd/none)与 QUEUE_MODE(队列跑动形态:daemon/none/cron)。改后需跑 sendmailconfig 应用,且 DAEMON_MODE/QUEUE_MODE/QUEUE_INTERVAL 三项变更还需重启服务。实测进程印证 daemon+daemon 模式(sendmail: MTA: accepting connections 常驻 + 队列跑动随服务管理)。

这层解释了「为什么 Debian 的 mc 里有一行 undefine(confHOST_STATUS_DIRECTORY)」——hoststat 状态目录开关被移交到 sendmail.conf 的 DAEMON_HOSTSTATS 参数,mc 里先 undefine 防止双头。

2)sendmailconfig——Debian 配置向导:/usr/sbin/sendmailconfig 会读取 sendmail.conf、重建 mc(把 conf 参数翻译进 mc 段落)、跑 m4 重生成 cf、并重编译全部映射库。Debian 体系下改完任何配置,跑一次 sendmailconfig 是最省心的生效方式(等价于手工 make 全家桶)。

3)Makefile——make 驱动的重生成入口。/etc/mail/Makefile(12530 字节,setgid smmsp)把「改什么就重生什么」自动化。实测关键规则(grep 现场照抄):

all: sendmail /var/run/sendmail/stampdir/reload
...
/etc/mail/sendmail.cf : /etc/mail/sendmail.mc /usr/share/sendmail/cf/m4/cf.m4 /usr/share/sendmail/cf/ostype/debian.m4 /usr/share/sendmail/cf/debian/autoconf.m4 /usr/share/sendmail/cf/domain/debian-mta.m4 /usr/share/sendmail/cf/debian/autoconf.m4 /etc/mail/m4/dialup.m4 /etc/mail/m4/provider.m4
	m4 /etc/mail/sendmail.mc > /etc/mail/sendmail.cf.new ...
	@echo "### /etc/mail/sendmail.mc ###" ...
	@sed -e 's/^/# /' /etc/mail/sendmail.mc ...
/etc/mail/databases : /etc/mail/sendmail.mc ...(同上依赖)

三条情报:目标 all 触发 sendmail.cf 重生成 + 服务 reload;.cf 的依赖链把 cf.m4、debian.m4、debian-mta.m4、dialup.m4、provider.m4 全部纳入——改任何一个依赖文件,make 都会自动重生成;重生成时用 sed -e 's/^/# /' 把旧 mc 以注释形式留档在新 cf 头部(audit 友好)。所以 Debian 机器上的标准生效命令就是一行:

make -C /etc/mail

4)databases 文件(3379 字节):make 的聚合依赖清单,由 sendmailconfig 根据启用的映射库自动维护——access/virtusertable/genericstable/mailertable 等库的 makemap 重编译都从这条链走。

5)service.switch:sendmail 自己的名称服务查询顺序(148 字节,全文照抄):

# /etc/mail/nsswitch.conf
#
# This is the format that sendmail expects (no colon after the service)
#
passwd		files
hosts		files dns
aliases		files

hosts files dns 表示主机名解析先查本地再查 DNS;aliases files 表示别名只认本地文件(不查 NIS/LDAP)。调试「别名不生效」时先看这份文件(排障实录问题 3 的深因之一)。

八、地址映射库与别名详解

五类映射库 + 一套系统别名是 Sendmail 行为控制的主战场。所有条目均为实测服务器现场照抄。

8.1 access——访问控制库

/etc/mail/access(317 字节,全文照抄):

# 默认放行本机回环
Connect:127.0.0.1        RELAY
Connect:::1              RELAY
# C1 实验:按来源 IP 控制中继
Connect:10.89.57.21      RELAY
Connect:10.89.57.99      REJECT
# 按信封发件人拒绝
From:spammer@spam.example.net  REJECT
# 按收件人静默丢弃
To:discard@example.com   DISCARD

语法 <标签>:<键> <动作> 四要素:

标签匹配对象本文件实例
Connect:连接来源(IP/网段/主机名)10.89.57.21
From:信封发件人(地址/域)spammer@spam.example.net
To:收件人(需 FEATURE(blacklist_recipients))discard@example.com
(无标签)发件人地址或域名(旧写法)—
GreetPause:/ClientConn:/ClientRate:greet_pause/并发/速率例外与限额(配合「四」的三个 FEATURE)未用

常用动作:RELAY 放行中继 / OK 放行收信 / REJECT 拒绝(550)/ DISCARD 静默吞掉(不回错)/ ERROR:550 自定义文案 定制拒绝码 / SKIP 中断后续查找。

改 access 的三步闭环(实测流程):

# 1) 编辑文本源
vi /etc/mail/access
# 2) 重编译哈希库( makemap 哈希类型 + 源文件,产物 access.db)
makemap hash /etc/mail/access < /etc/mail/access
# 3) 无需重启——规则集每次 RCPT 都查库;要让排队中的信重走检查可补一发定向跑队列
sendmail -qR@example.com

实测 sendmail -qR@example.com 的回显(日志行照抄):

Oct 27 02:10:26 mail sm-mta[5978]: running queue: /var/spool/mqueue

-qR<串> 只重跑收件人含该串的队列项——改 access 后让滞留邮件重走新规则的精准手段。

8.2 aliases——系统别名

/etc/mail/aliases 是指向 /etc/aliases 的符号链接(目录清单可见 aliases -> ../aliases)。实测新增段(tail -20 /etc/aliases 现场照抄):

# C1 实验别名
support: admin, root
helpdesk: help@example.com
webmaster: root

语法 别名: 目标1, 目标2, ...,目标可以是本地用户、其他别名、外部地址、|命令(若启用 smrsh 则经其白名单执行)、/文件(追加落盘)、:include:/路径(展开列表文件)。三个必知点:

  1. 改完必须跑 newaliases(等效 sendmail -bi),重编译 aliases.db;只改文本源不生效;
  2. Debian 下 postmaster/mailer-daemon 等基础别名由基础段提供(postmaster: root 链是 RFC 2142 的合规底线,删掉会收到管理告警);
  3. newaliases 校验失败(如目标为空)会保留旧库——这也是误删别名后快速回滚的兜底。

权限印证(实测 stat):aliases.db 属主 smmta:smmsp、权限 640——运行中的 MTA 以 smmta 身份读库,root 执行 newaliases 重写库后属主不变,权限模型自洽。

别名校验:sendmail -bv <地址> 只做地址解析不投递。实测:

sendmail -bv admin

回显:

admin... deliverable: mailer local, user admin

注意一个反直觉现象:sendmail -bv postmaster 回显 postmaster... User unknown——postmaster 只存在于别名库,而 -bv 的解析不展开别名(别名展开发生在入队阶段),所以别名目标会报 unknown。别把这一现象误判为「别名坏了」。

8.3 virtusertable——虚拟用户映射

/etc/mail/virtusertable(77 字节,全文照抄):

webmaster@example.com  admin
@example.com  admin
sale@example.com      admin

语法 <虚拟地址或域> <真实目标>,自上而下精确匹配、通配兜底:

  • webmaster@example.com:精确地址 → 本地用户 admin(webmaster 不是系统用户也能收信,这正是「虚拟用户」含义);
  • @example.com:域通配 → 该域其他地址全投 admin(放最后一行做兜底)。

与 aliases 的分工:virtusertable 管虚拟地址→真实目标(收件方向),aliases 管真实收件人→多目标展开。生成命令:

makemap hash /etc/mail/virtusertable < /etc/mail/virtusertable

启用前提是 mc 里 FEATURE(virtusertable)(实测已启用)。Debian 默认映射规格即 hash /etc/mail/virtusertable.db。

8.4 genericstable——发件地址改写

/etc/mail/genericstable(25 字节,全文照抄):

admin  admin@example.com

语义:本地用户 admin 对外发信时,发件地址自动改写为 admin@example.com——让内网用户的裸用户名出站变成规范地址。两个硬前提(缺一不改写):

  1. mc 中启用 FEATURE(genericstable)(实测已启用);
  2. 发件域列入改写范围:实测用 GENERICS_DOMAIN(mail.example.com')(或改用 GENERICS_DOMAIN_FILE`)。

生成命令:

makemap hash /etc/mail/genericstable < /etc/mail/genericstable

8.5 mailertable——按域路由

/etc/mail/mailertable(122 字节,全文照抄):

remote.example  error:550 Delivery to remote.example is blocked
example.net error:553 mailbox blocked by mailertable-demo

语法 <域或主机> <投递代理>:<代理参数>,在 MX 解析之前决定「这个域的邮件怎么送」:

  • error:<码> <文案>:直接以指定 SMTP 码拒绝——本例把两个域封死(550/553),是「策略性拒收某域」的干净实现;
  • 常规用法如 example.net esmtp:[192.0.2.10](方括号禁 MX 查询直投 IP)、example.org relay:smarthost.example.com(交给中继)。

生成命令:

makemap hash /etc/mail/mailertable < /etc/mail/mailertable

实测服务器上 access/virtusertable/genericstable/mailertable 四种映射库的 .db 均为 12288 字节(Berkeley DB hash 固定页大小),属主统一 smmta:smmsp 640——与运行身份对齐,属主漂移即 read-fail,是映射库排障的固定检查项。

九、辅助配置文件

文件实测内容/状态说明
local-host-nameslocalhost、mail.example.com、example.com 三行(39 字节)本机视为最终收信地的域名清单(FEATURE(use_cw_file) 启用)。某地址若不在此清单且不匹配 virtusertable/aliases 特殊目标,就会被当成外部信走转发而非本地落箱
trusted-users空文件(0 字节)可代其他用户发信、可 EXPN 的信任用户清单(FEATURE(use_ct_file) 语义族)。加入用户名一行一个;实测保持为空即仅 root
submit.mcDebian MSP 原型:OSTYPE(debian) + DOMAIN(debian-msp),头注释给出与 sendmail.mc 相同的三种重生成方式MSP 提交体系的配置源。改它影响的是本机命令提交链路(clientmqueue),与 MTA 收发互不干扰。有 dnl MASQUERADE_AS()dnl 预留行
helpfile5659 字节(2021-12-09 包日期)SMTP HELP 命令的文案库
address.resolve281 字节(包日期)DNS 解析教育性说明(no-resolve 提示文件)
smrsh/ 目录setgid smmsp 目录smrsh 白名单程序的链接池(仅启用 smrsh 特性后生效;实测 Debian 默认本地投递走 procmail,本目录为空壳备用)
tls/、sasl/、peers/目录(root/smmsp 属主)TLS 证书、SASL 配置、STARTTLS 对端策略的默认落点(本机未启用 STARTTLS,故为空壳)

十、生效流程与验证

10.1 三类配置、三种生效路径

改动对象文本源生效动作验证命令
mc/FEATURE/define/DAEMON_OPTIONSsendmail.mcmake -C /etc/mail(或 sendmailconfig,或手工 m4 sendmail.mc > sendmail.cf)+ 服务重载sendmail -d0.4 -bt < /dev/null 看 $j/$w;SMTP 横幅看版本与域名
访问控制accessmakemap hash /etc/mail/access < /etc/mail/access实际发信看 550/RELAY 行为
系统别名/etc/aliasesnewaliasessendmail -bv <本地用户>(非别名目标)
虚拟用户/改写/路由virtusertable/genericstable/mailertable各自 makemap hash ...对应方向发信验证

时间戳核对法(最快速的「改了没生效」诊断):ls -l /etc/mail/*.db /etc/mail/*.cf /etc/mail/*.mc ——凡 .db/.cf 的时间戳早于其文本源,就是没跑生效动作;反之若时间戳已更新但行为未变,才需要往「服务未重载」「规则集不查此库」方向查。

10.2 排障实录

以下三则均来自本次实测过程,现象与解法照实记录:

问题 1:m4 报 syntax error, unexpected $undefined,sendmail.cf 生成中断。

现象:手工往 sendmail.mc 追加宏时引号用错(用了直引号开头或双引号),make -C /etc/mail 时 m4 直接报语法错误退出。Makefile 规则先写 .cf.new 中间产物(现场规则照抄:m4 /etc/mail/sendmail.mc > /etc/mail/sendmail.cf.new),m4 失败时旧 sendmail.cf 原样保留,服务不受影响。解法:mc 中所有字符串实参一律「反引号开、单引号闭」,含特殊字符的值(如带逗号的 confPRIVACY_FLAGS)整个值仍是一对引号包裹、跨行书写合法(实测 mc 中 confPRIVACY_FLAGS 就是两行写法)。修好引号后 make -C /etc/mail 一次通过。

问题 2:外域发信报 451 4.1.8 Domain of sender address ... does not resolve。

现象与定位:SMTP 会话在 MAIL FROM 阶段被拒,回显照抄:

451 4.1.8 Domain of sender address root@client.example.net does not resolve

根因:发件域 DNS 解析失败,sendmail 拒收(默认反向校验策略)。解法:给发件方换用可解析的信封发件域(实测改用 example.com 域内地址后正常)。教训:测试环境的「伪造域」要至少在本机 DNS 或 /etc/hosts(配合 service.switch 的 hosts 查询序)可解析。

问题 3:改了 aliases 但投递仍按旧别名走。

现象:新增别名后发信,收件行为与改动前一致。定位:ls -l /etc/mail/aliases.db 时间戳早于 /etc/aliases——只改了文本源,没跑 newaliases。解法:newaliases 重编译后立即生效(实测库时间戳更新到执行时刻)。延伸:sendmail -bv postmaster 报 User unknown(见 8.2)是 -bv 不展开别名的正常现象,不要与别名失效混淆。

问题 4(记录在案):」对外中继测试 550 5.7.1 ... Relaying denied。

这不是故障而是默认防线:access 库没有放行的来源一律拒中继。放行方式即 8.1 的 Connect:<网段> RELAY + makemap。与之相对的 Relaying denied 何时算异常——当你明明加了 Connect 条目仍被拒:多为 makemap 忘跑(.db 未更新,用时间戳核对法)或键写错(网段掩码写法 10.89.57 不带掩码时按前缀匹配整段,写 10.89.57.0/24 需 8.12+ 支持)。

十一、参考链接

  • 本系列认知篇:《Sendmail 8.18.2 介绍与选型指南》(版本谱系、许可证与 MTA 选型对比)
  • 本系列安装篇:《Ubuntu 22.04 安装 Sendmail 8.15.2 指南》(apt 与源码编译双方案、ACCEPT_BROKEN_BDB_LOCKING 排障实录)
  • 本系列部署篇:《Sendmail 8.18.2 Docker Compose 部署指南》(自建镜像与双节点编排,含 8.18.2 的 cf 生成流程)
  • 本系列命令篇:《Sendmail 8.18.2 常用 CLI 命令与工具详解》(makemap/newaliases/sendmail -bv 等命令实操,发布后此处将替换为站内明细链接)
  • cf/README 宏配置权威手册(本机路径):/usr/share/doc/sendmail-doc/cf.README.gz(需安装 sendmail-doc 包);在线版 https://fossies.org/linux/sendmail/cf/README
  • Debian sendmail 维护脚本与文档:/usr/share/doc/sendmail/(README.Debian.gz 讲 sendmail.conf 全参数)
  • Sendmail 官方发布与 RELEASE_NOTES(查询日期 2026-10-27):https://ftp.sendmail.org/