Sendmail 8.15.2 配置参数详解
Sendmail 8.15.2 配置体系全景:m4 宏语法五规则、sendmail.mc 实测全文逐段解读、11 个 FEATURE 与 conf 参数语义、五类映射库(access/aliases/virtusertable/genericstable/mailertable)实操语法、Debian 定制层与三种生效路径,全部片段与回显实测照抄。
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 ──(重启/重载)──> 运行行为
三个关键铁律:
- 永远改 .mc,不改 .cf——直接改 sendmail.cf 在下次 m4 重新生成时会被无声覆盖;
- 改完 .mc 必须重新生成 .cf 并触发生效,只改文本源不会有任何效果(排障实录见「十、生效流程与验证」问题 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.mc | MTA 配置宏源(4412 字节) | 管理员 | m4 |
sendmail.cf | MTA 最终配置(64641 字节) | m4 生成 | sendmail 守护 |
submit.mc / submit.cf | MSP 提交侧配置对 | 管理员 / m4 生成 | 本机提交的 sendmail 进程 |
access / access.db | 访问控制文本源 / 哈希库 | 管理员 / makemap | sendmail 规则集 |
aliases -> ../aliases | 系统别名(符号链接到 /etc/aliases) | 管理员 | sendmail(读 aliases.db) |
aliases.db | 别名哈希库(12288 字节) | newaliases | sendmail |
virtusertable(.db) | 虚拟用户映射 | 管理员 / makemap | sendmail |
genericstable(.db) | 发件地址改写 | 管理员 / makemap | sendmail |
mailertable(.db) | 按域路由 | 管理员 / makemap | sendmail |
local-host-names | 本机收信域名清单 | 管理员 | sendmail |
sendmail.conf | Debian 层守护参数(12235 字节) | 管理员 | /usr/share/sendmail/sendmail 脚本 |
Makefile | mc→cf/db 重生成入口(12530 字节) | 包管理器 | make |
databases | 包级映射库声明单(3379 字节) | sendmailconfig 维护 | make |
trusted-users | 可代发用户列表(实测空文件) | 管理员 | sendmail |
service.switch | 名称服务查询顺序 | 管理员 | sendmail |
helpfile | SMTP 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_msa | FEATURE(no_default_msa')dnl` | 不生成默认 MSA(消息提交代理)监听,改用下方显式 DAEMON_OPTIONS 控制 |
use_cw_file | FEATURE(use_cw_file')dnl` | 启用 /etc/mail/local-host-names 作为本机收信域名清单($w 扩展) |
access_db | FEATURE(access_db', , skip')dnl | 启用访问控制库。第二参空走默认映射规格(hash 类型 /etc/mail/access 源),skip 表示库文件缺失时不判致命(排障期友好) |
blacklist_recipients | FEATURE(blacklist_recipients')dnl` | 把 access 查询扩展到收件人方向——这是 access 文件里 To: 条目能生效的前提 |
virtusertable | FEATURE(virtusertable')dnl` | 启用虚拟用户映射库(默认 hash /etc/mail/virtusertable) |
mailertable | FEATURE(mailertable')dnl` | 启用按域路由库(默认 hash /etc/mail/mailertable) |
genericstable | FEATURE(genericstable')dnl` | 启用发件地址改写库(默认 hash /etc/mail/genericstable) |
greet_pause | FEATURE(greet_pause', 1000')dnl | 发 220 横幅前静默 1000 毫秒——标准客户端会等横幅,早期垃圾邮件群发器不等就直接发数据,由此暴露。例外写 access 表 GreetPause:<地址> 0 |
delay_checks | FEATURE(delay_checks', friend', n')dnl` | 调整 access 检查顺序(friend=发件人例外优先语义,可选第三参控制例外查询行为,细节见 cf/README delay_checks 节) |
conncontrol | FEATURE(conncontrol', nodelay', terminate')dnl` | 并发连接数控制(查 access 表 ClientConn: 条目);terminate=超限直接断连而非延迟 |
ratecontrol | FEATURE(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_as | MASQUERADE_AS(example.com')dnl` | 出站信统一伪装成主域(配合 FEATURE(masquerade_envelope) 连信封一起改) |
always_add_domain | FEATURE(always_add_domain')dnl` | 本机投递的信也补全域名 |
relay_hosts_only | FEATURE(relay_hosts_only')dnl` | access 中继授权从网段粒度收紧到主机名粒度 |
smrsh | FEATURE(smrsh', path')dnl | 别名/forward 管道命令经 smrsh 白名单执行(上游通用模板常用;实测 Debian 默认配置未启用——sendmail.cf 中无 smrsh 字样,本地投递由 procmail 承担) |
local_procmail | FEATURE(local_procmail')dnl` | 本地投递改经 procmail(Debian 层亦可经 sendmail.conf 开关) |
dnsbl | FEATURE(dnsbl', zen.spamhaus.org')dnl | 接入 DNS 实时黑名单 |
五、define 参数详解
define(confXXX, 值')设置一个 conf 参数(最终落在 sendmail.cf 的O/D` 指令上)。实测启用的 5 个:
| 参数 | 实测值 | 语义 |
|---|---|---|
confPRIVACY_FLAGS | needmailhelo,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_THROTTLE | 15 | 每 10 分钟窗口内单 IP 最大新连接数(与 confCONNECTION_RATE_WINDOW_SIZE 配对) |
confCONNECTION_RATE_WINDOW_SIZE | 10m | 速率统计窗口长度 |
confBAD_RCPT_THROTTLE | 3 | 单连接收到第 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_NAME | mail.example.com | 规范域名($j),决定 SMTP 横幅与 Message-Id;不设则取内核主机名 |
confMAX_MESSAGE_SIZE | 10485760 | 单信最大字节(对应 SMTP SIZE 扩展上限) |
confMAX_RCPTS_PER_MESSAGE | 25 | 单信最大收件人数,超出拆分投递 |
confQUEUE_LA | 12 | 系统 1 分钟负载高于此值时暂停队列跑动 |
confREFUSE_LA | 18 | 负载高于此值时拒收新连接 |
confDELAY_LA | 0 | 负载高于此值即在响应中加人为延迟(4 为常用起点) |
confTO_QUEUERETURN | 5d | 队列滞留上限,超时退信 |
confTO_QUEUEWARN | 4h | 队列滞留多久后向发件人发延迟警告 |
confSMART_HOST | smtp.example.net | 所有外部信改交指定中继(双跳架构必配) |
confSAFE_FILE_ENV | /home | 写文件前 chroot 到指定目录(Debian mc 注释段预留的防逃逸开关) |
confLOG_LEVEL | 9(默认) | 日志详细度,排障期可提到 14+ |
confPRIVACY_FLAGS(补位) | goaway | 一键收紧(等效 needmailhelo+noexpn+novrfy 等组合) |
六、MAILER 与 MAILER_DEFINITIONS
MAILER(名称) 引入一个投递代理的完整定义(投递路径、参数、地址规则)。实测启用两个,且必须放在 mc 文件末尾(cf 体系的硬约定——MAILER 之后不应再有 FEATURE/define,Debian mc 用 MAILER_DEFINITIONS 标记此段):
| MAILER | 职责 | 对应投递 |
|---|---|---|
local | 本地投递代理:落箱 /var/mail/<用户> 或转交 procmail | mailer=local(投递日志中可见) |
smtp | SMTP 家族代理: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:/路径(展开列表文件)。三个必知点:
- 改完必须跑
newaliases(等效sendmail -bi),重编译 aliases.db;只改文本源不生效; - Debian 下 postmaster/mailer-daemon 等基础别名由基础段提供(
postmaster: root链是 RFC 2142 的合规底线,删掉会收到管理告警); 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——让内网用户的裸用户名出站变成规范地址。两个硬前提(缺一不改写):
- mc 中启用
FEATURE(genericstable)(实测已启用); - 发件域列入改写范围:实测用
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-names | localhost、mail.example.com、example.com 三行(39 字节) | 本机视为最终收信地的域名清单(FEATURE(use_cw_file) 启用)。某地址若不在此清单且不匹配 virtusertable/aliases 特殊目标,就会被当成外部信走转发而非本地落箱 |
trusted-users | 空文件(0 字节) | 可代其他用户发信、可 EXPN 的信任用户清单(FEATURE(use_ct_file) 语义族)。加入用户名一行一个;实测保持为空即仅 root |
submit.mc | Debian MSP 原型:OSTYPE(debian) + DOMAIN(debian-msp),头注释给出与 sendmail.mc 相同的三种重生成方式 | MSP 提交体系的配置源。改它影响的是本机命令提交链路(clientmqueue),与 MTA 收发互不干扰。有 dnl MASQUERADE_AS()dnl 预留行 |
helpfile | 5659 字节(2021-12-09 包日期) | SMTP HELP 命令的文案库 |
address.resolve | 281 字节(包日期) | 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_OPTIONS | sendmail.mc | make -C /etc/mail(或 sendmailconfig,或手工 m4 sendmail.mc > sendmail.cf)+ 服务重载 | sendmail -d0.4 -bt < /dev/null 看 $j/$w;SMTP 横幅看版本与域名 |
| 访问控制 | access | makemap hash /etc/mail/access < /etc/mail/access | 实际发信看 550/RELAY 行为 |
| 系统别名 | /etc/aliases | newaliases | sendmail -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/