Exim 4.95 配置参数详解

吃透 Ubuntu 22.04 仓库版 Exim 4.95 的 Debian 配置框架:update-exim4.conf.conf 部署型变量、单一/分片双模式的宏注入差异、MAIN 宏与主配置选项、ACL 中继三态控制、路由器与传输器、别名与 .forward、SMTP AUTH 认证,全部按「修改 → 重新生成 → 验证」闭环实测,附生产配置模板与排障 FAQ。

EximExim配置参数详解邮件服务器2026/10/26

Exim 4.95 配置参数详解

目标:吃透 Ubuntu 22.04 仓库版 Exim 4.95 的 Debian 配置框架——update-exim4.conf.conf 部署型变量、MAIN 宏、ACL、路由器、传输器与别名/转发/认证,每个参数按「修改 → 重新生成 → 验证」闭环实测。所有命令与回显均来自实测(实测日期:2026-10-26)。

一、环境信息

项目取值
操作系统Ubuntu 22.04(jammy,x86_64)
软件版本Exim 4.95(4.95-4ubuntu2.12)
主 MTAmail1.example.org(10.89.61.11),邮件域 example.org,分片配置(use_split_config=true)
次级 MTAmail2.example.net(10.89.61.12),邮件域 example.net,单一配置文件(use_split_config=false)
服务端口25/tcp(SMTP,STARTTLS 可用)
配置文件/etc/exim4/update-exim4.conf.conf、/etc/exim4/conf.d/(分片)或 /etc/exim4/exim4.conf.template(单一)
生成配置/var/lib/exim4/config.autogenerated
日志/var/log/exim4/mainlog、/var/log/exim4/rejectlog
测试账号root 与 admin(口令 q1w2e3r4)

两台机器特意保持两种配置形态:mail1 走分片(便于逐文件讲解 conf.d),mail2 走单一文件(便于讲解模板与宏),覆盖两种运维形态。

二、前置条件

两台机器已按《Ubuntu 22.04 安装 Exim 4.95 指南》完成安装与 internet site 模式配置。验证基线:

root@mail1:~# exim4 --version | head -1
Exim version 4.95 #2 built 22-Sep-2026 18:29:28
root@mail1:~# systemctl is-active exim4
active
root@mail1:~# exim4 -bV | tail -1
Configuration file is /var/lib/exim4/config.autogenerated

exim4 -bV 的最后一行永远显示当前实际生效的配置文件路径,是排查「改了配置没生效」的第一入口。

三、配置文件体系总览

3.1 配置文件清单

文件作用
/etc/exim4/update-exim4.conf.confDebian 框架的部署型变量(dc_*),framework 的「开关面板」
/etc/exim4/conf.d/分片配置的片段目录(40 个文件,按序号拼接)
/etc/exim4/exim4.conf.template单一配置模式的模板(与 conf.d 内容同源)
/var/lib/exim4/config.autogeneratedupdate-exim4.conf 的生成物,exim4 实际加载的配置
/etc/exim4/exim4.conf.localmacros单一模式下的本地宏注入文件
/etc/exim4/conf.d/main/000_localmacros分片模式下的本地宏注入文件(文件名以 000 开头保证最先拼接)
/etc/exim4/hubbed_hostshubby 路由器的手工路由表(域名: 目标主机)
/etc/aliases系统别名表(system_aliases 路由器读取)
/etc/exim4/passwd.client作为客户端发往 smarthost 时的认证口令库

3.2 生成与生效流程

任何配置修改后的生效链路固定为三步:

root@mail1:~# update-exim4.conf && systemctl reload exim4
  1. 编辑源文件(dc_* 变量 / conf.d 片段 / 模板 / 宏文件);
  2. update-exim4.conf 把源文件拼装为 /var/lib/exim4/config.autogenerated(语法错误时直接失败,不会生成坏配置);
  3. systemctl reload exim4(或 restart)让守护进程重新加载。

3.3 单一配置 vs 分片配置

由 dc_use_split_config 决定。mail1 从单一模式切换到分片模式:

root@mail1:~# sed -i "s/^dc_use_split_config=.*/dc_use_split_config='true'/" /etc/exim4/update-exim4.conf.conf
root@mail1:~# update-exim4.conf && systemctl restart exim4
root@mail1:~# ls /etc/exim4/conf.d/
acl  auth  main  retry  rewrite  router  transport
root@mail1:~# for d in /etc/exim4/conf.d/*/; do echo "$d $(ls $d | wc -l) files"; done
/etc/exim4/conf.d/acl/ 5 files
/etc/exim4/conf.d/auth/ 2 files
/etc/exim4/conf.d/main/ 4 files
/etc/exim4/conf.d/retry/ 2 files
/etc/exim4/conf.d/rewrite/ 2 files
/etc/exim4/conf.d/router/ 13 files
/etc/exim4/conf.d/transport/ 12 files
root@mail1:~# exim4 -bV | tail -1
Configuration file is /var/lib/exim4/config.autogenerated

两种模式下「实际生效」的文件不同:

维度单一模式(mail2)分片模式(mail1)
配置源/etc/exim4/exim4.conf.template/etc/exim4/conf.d/* 按文件名序拼接
本地宏文件/etc/exim4/exim4.conf.localmacros/etc/exim4/conf.d/main/000_localmacros
适用人群喜欢一个文件改到底需要精确修改某个 ACL/路由器/传输器

⚠️ 关键坑:两种模式的宏注入文件互不相认(见 3.4 的排障实录)。

3.4 宏注入文件差异排障实录(自签证书兜底)

mail1 切分片模式后,跨机投递日志里出现了陌生的证书 DN:

2026-10-26 11:07:36 1xLIf4-0003rM-TT => admin@example.org R=smarthost T=remote_smtp_smarthost H=mail1.example.org [10.89.61.11] X=TLS1.3:... CV=no DN="C=UK,O=Exim Developers,CN=mail1.example.org" K C="250 OK id=1xLIf4-0003rQ-Uq"

此前 s_client 验证过的证书明明是 O=Example Lab。检查运行值定位:

root@mail1:~# exim4 -bP tls_advertise_hosts tls_certificate tls_privatekey
tls_advertise_hosts = *
tls_certificate =
tls_privatekey =
root@mail1:~# grep -rn 'MAIN_TLS_ENABLE' /etc/exim4/conf.d/ /etc/exim4/exim4.conf.localmacros
/etc/exim4/exim4.conf.localmacros:MAIN_TLS_ENABLE = yes

根因:MAIN_TLS_ENABLE = yes 只写在了 exim4.conf.localmacros,而该文件只在单一模式被模板读取;分片模式根本不看它。于是 tls_certificate 为空——但 Debian 模板默认广播 STARTTLS(tls_advertise_hosts = *),Exim 4.95 在无证书可用的运行期自动生成内部自签证书兜底,其主体恰为 C=UK,O=Exim Developers。

修复(分片模式改用 conf.d/main/000_localmacros):

root@mail1:~# rm -f /etc/exim4/exim4.conf.localmacros
root@mail1:~# printf 'MAIN_TLS_ENABLE = yes\n' > /etc/exim4/conf.d/main/000_localmacros
root@mail1:~# update-exim4.conf && systemctl reload exim4
root@mail1:~# exim4 -bP tls_advertise_hosts tls_certificate tls_privatekey
tls_advertise_hosts = *
tls_certificate = /etc/exim4/exim.crt
tls_privatekey = /etc/exim4/exim.key

对端验证恢复:

root@mail2:~# echo | openssl s_client -connect mail1.example.org:25 -starttls smtp 2>/dev/null | openssl x509 -noout -subject
subject=CN = mail1.example.org, O = Example Lab, C=CN

四、部署型参数详解(update-exim4.conf.conf)

文件本体(mail1 最终态):

root@mail1:~# cat /etc/exim4/update-exim4.conf.conf
# /etc/exim4/update-exim4.conf.conf
#
# Edit this file and call 'update-exim4.conf' for changes to take effect.
# See update-exim4.conf(8) for details.

dc_eximconfig_configtype='internet'
dc_other_hostnames='mail1.example.org:example.org'
dc_local_interfaces='0.0.0.0 ; ::0'
dc_readhost=''
dc_relay_domains=''
dc_minimaldns='false'
dc_relay_nets='10.89.61.0/24'
dc_smarthost=''
CFILEMODE='644'
dc_use_split_config='true'
dc_hide_mailname=''
dc_mailname_in_oh='true'
dc_localdelivery='maildir_home'

(mail2 同结构,dc_other_hostnames='mail2.example.net:example.net'、dc_use_split_config='false'。)

4.1 dc_eximconfig_configtype:部署模式

五种取值与行为见下表:

取值行为典型场景
internet直接收发互联网邮件独立邮件服务器
smarthost收信可本机投递/接收,外发全部经上游内网统一出口
satellite无本地投递,全部经上游只发不收的边缘机
local只投本机用户(全新安装默认)单机系统邮件
unconfigured最小骨架尚未规划

smarthost 实测(在 mail2 上把外发改走 mail1):移除 hubbed_hosts 的干扰项后切换:

root@mail2:~# sed -i "s/^dc_eximconfig_configtype=.*/dc_eximconfig_configtype='smarthost'/; s/^dc_smarthost=.*/dc_smarthost='mail1.example.org::25'/" /etc/exim4/update-exim4.conf.conf
root@mail2:~# update-exim4.conf && systemctl reload exim4
root@mail2:~# printf 'Pure smarthost route test.\n' | mail -s 'smart-02' admin@example.org
root@mail2:~# grep '1xLIf4-0003rM-TT' /var/log/exim4/mainlog
2026-10-26 11:07:36 1xLIf4-0003rM-TT => admin@example.org R=smarthost T=remote_smtp_smarthost H=mail1.example.org [10.89.61.11] X=TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256 CV=no DN="C=UK,O=Exim Developers,CN=mail1.example.org" K C="250- 480 byte chunk, total 480\n250 OK id=1xLIf4-0003rQ-Uq"
2026-10-26 11:07:36 1xLIf4-0003rM-TT Completed

路由器变为 R=smarthost、传输器变为 T=remote_smtp_smarthost。收完实验改回:

root@mail2:~# sed -i "s/^dc_eximconfig_configtype=.*/dc_eximconfig_configtype='internet'/; s/^dc_smarthost=.*/dc_smarthost=''/" /etc/exim4/update-exim4.conf.conf
root@mail2:~# update-exim4.conf && systemctl reload exim4
root@mail2:~# grep -E 'configtype|smarthost' /etc/exim4/update-exim4.conf.conf
dc_eximconfig_configtype='internet'
dc_smarthost=''

💡 说明:dc_smarthost 语法为 主机::端口(双冒号分隔);remote_smtp_smarthost 传输器默认启用对上游证书的校验,自签证书会触发「校验失败回退明文」(日志含 delivering unencrypted to H=...,见 10.3)。另注意 hubbed_hosts 路由器(分片序号 150)排在 smarthost 路由器之前,同一目标域两条路由并存时 hubby 优先生效——本实验前先移除了 hubbed_hosts 条目才能观察到 R=smarthost。

4.2 dc_local_interfaces:监听地址

取值效果
127.0.0.1 ; ::1仅回环(local 模式默认)
0.0.0.0 ; ::0所有 IPv4/IPv6 接口(internet 站点常用)
10.89.61.11 ; 127.0.0.1只绑业务网卡与回环

修改后用 ss -ltn | grep ':25 ' 与 exim4 -bP local_interfaces 双重验证(本文所有监听切换均按此闭环执行,第七章不再重复)。

4.3 dc_other_hostnames:本机承接邮件域

分号分隔的域名列表,最终并入 +local_domains 域名表。mail1 当前值 mail1.example.org:example.org,验证:

root@mail1:~# exim4 -bP '+local_domains'
+local_domains = @ : localhost : example.org : mail1.example.org

非本机域名的对照(mail1 上查询 mail2 的域):

root@mail1:~# exim4 -bt admin@example.net
R: hubbed_hosts for admin@example.net
admin@example.net
  router = hubbed_hosts, transport = remote_smtp
  host mail2.example.net [10.89.61.12]

4.4 dc_relay_nets:允许中继的客户端网段

该变量填充 +relay_from_hosts 主机表,决定哪些客户端可以借本机向第三方域转发邮件:

root@mail1:~# exim4 -bP +relay_from_hosts
hostlist relay_from_hosts = 10.89.61.0/24 : 127.0.0.1 : ::::1

语义与 ACL 实验见第六章 6.3(允许/拒绝/认证放行三态实测)。

4.5 dc_localdelivery:本地投递格式

取值投递目标路由器/传输器
mail_spool/var/mail/<user>(mbox)local_user / mail_spool
maildir_home$home/Maildir/(Maildir)local_user / maildir_home

两种格式的完整投递与目录证据见《Ubuntu 22.04 安装 Exim 4.95 指南》7.4。

4.6 其余 dc_* 变量

变量作用实测状态
dc_relay_domains允许代收并转发的目标域(非本地域)空;如设 example.com 则本机为 example.com 的二级 MX
dc_minimaldns最小化 DNS 查询(跳过主机名反查等)false
dc_hide_mailname / dc_readhost仅 smarthost 模式有意义:改写外发信封域名空
dc_postmasterpostmaster 别名指向root
CFILEMODE生成配置的权限644
dc_mailname_in_oh/etc/mailname 归属提示true

五、MAIN 宏与主配置选项

5.1 宏机制

Debian 模板中所有可定制点都以 .ifdef MAIN_XXX 预留,本地宏文件只需赋值即可打开对应分支,无需改动模板本体。模板的宏注入位置在 conf.d/main/01_exim4-config_listmacrosdefs(即单一模式的模板头部):

# Macro defining the main configuration directory.
# We do not use absolute paths.
.ifndef CONFDIR
CONFDIR = /etc/exim4
.endif

# debconf-driven macro definitions get inserted after this line

5.2 MAIN_TLS_ENABLE:启用服务器证书

分片模式写入 /etc/exim4/conf.d/main/000_localmacros,单一模式写入 /etc/exim4/exim4.conf.localmacros,内容与验证:

root@mail1:~# cat /etc/exim4/conf.d/main/000_localmacros
MAIN_TLS_ENABLE = yes
root@mail1:~# exim4 -bP tls_advertise_hosts tls_certificate tls_privatekey
tls_advertise_hosts = *
tls_certificate = /etc/exim4/exim.crt
tls_privatekey = /etc/exim4/exim.key

其作用是打开 conf.d/main/03_exim4-config_tlsoptions 中的条件块(文件原文节选):

.ifdef MAIN_TLS_ENABLE
# Defines what hosts to 'advertise' STARTTLS functionality to. The
# default, *, will advertise to all hosts that connect with EHLO.
.ifndef MAIN_TLS_ADVERTISE_HOSTS
MAIN_TLS_ADVERTISE_HOSTS = *
.endif
tls_advertise_hosts = MAIN_TLS_ADVERTISE_HOSTS

未定义该宏时 tls_certificate 为空,Exim 4.95 会以运行期自签证书兜底(排障实录见 3.4)。

5.3 MAIN_HOST_LOOKUP:连接方反查

模板默认全量反查(conf.d/main/02_exim4-config_options 原文):

.ifndef DC_minimaldns
.ifndef MAIN_HOST_LOOKUP
MAIN_HOST_LOOKUP = *
.endif
host_lookup = MAIN_HOST_LOOKUP
.endif

实验网段没有 PTR 记录,反查只拖慢会话并产生内部命名回显,置空关闭:

root@mail1:~# printf 'MAIN_TLS_ENABLE = yes\nMAIN_HOST_LOOKUP =\n' > /etc/exim4/conf.d/main/000_localmacros
root@mail1:~# update-exim4.conf && systemctl restart exim4
root@mail1:~# exim4 -bP host_lookup
host_lookup =

关闭后 SMTP 会话回显从 Hello <反查名> [10.89.61.12] 变为 Hello lab [10.89.61.12](lab 为客户端 EHLO 宣告名)。

5.4 MESSAGE_SIZE_LIMIT:单信大小上限

默认 50MB(EHLO 能力 250-SIZE 52428800)。压到 10MB 做闭环验证:

root@mail2:~# printf 'MAIN_TLS_ENABLE = yes\nMESSAGE_SIZE_LIMIT = 10M\n' > /etc/exim4/exim4.conf.localmacros
root@mail2:~# update-exim4.conf && systemctl reload exim4
root@mail2:~# exim4 -bP message_size_limit
message_size_limit = 10M
root@mail2:~# printf 'EHLO lab\r\nQUIT\r\n' | timeout 5 bash -c 'exec 3<>/dev/tcp/127.0.0.1/25; cat <&3 & cat >&3; wait' | tr -d '\r' | grep SIZE
250-SIZE 10485760

EHLO 能力值同步变为 10485760,验证后恢复默认(从宏文件删除该行并重新生成)。

5.5 主配置选项速查(exim4 -bP)

root@mail1:~# exim4 -bP primary_hostname qualify_domain message_size_limit daemon_smtp_ports
primary_hostname = mail1.example.org
qualify_domain = example.org
message_size_limit = 50M
daemon_smtp_ports = 25
root@mail1:~# exim4 -bP '+local_domains' '+relay_to_domains' '+relay_from_hosts'
+local_domains = @ : localhost : example.org : mail1.example.org
+relay_to_domains =
+relay_from_hosts = 10.89.61.0/24 : 127.0.0.1 : ::::1

六、ACL 详解

6.1 ACL 入口

conf.d/main/02_exim4-config_options 把三个 ACL 挂到 SMTP 阶段:

acl_smtp_mail = MAIN_ACL_CHECK_MAIL
acl_smtp_rcpt = MAIN_ACL_CHECK_RCPT
acl_smtp_data = MAIN_ACL_CHECK_DATA

宏默认值指向 acl_check_mail / acl_check_rcpt / acl_check_data,实现分别在 conf.d/acl/ 下五个文件(00_exim4-config_header、20_exim4-config_local_deny_exceptions、30_exim4-config_check_mail、30_exim4-config_check_rcpt、40_exim4-config_check_data)。

6.2 acl_check_rcpt 逐段解读

conf.d/acl/30_exim4-config_check_rcpt 是中继控制的核心,关键段落原文(节选):

  accept
    hosts = +relay_from_hosts
    control = submission/sender_retain
    control = dkim_disable_verify


  # Accept if the message arrived over an authenticated connection, from
  # any host. ...
  accept
    authenticated = *
    control = submission/sender_retain
    control = dkim_disable_verify

  # Insist that any other recipient address that we accept is either in one of
  # our local domains, or is in a domain for which we explicitly allow
  # relaying. Any other domain is rejected as being unacceptable for relaying.
  require
    message = relay not permitted
    domains = +local_domains : +relay_to_domains

要点:来自 +relay_from_hosts 的连接直接放行(并打上 submission 修正标记);已认证连接放行;其余收件域必须属于本机域或显式中继域,否则 require 失败返回 relay not permitted。

6.3 中继控制三态实验(mail1)

状态一:relay_nets 在位,明文中继放行

root@mail2:~# { printf 'EHLO lab\r\nMAIL FROM:<root@example.net>\r\nRCPT TO:<someone@example.com>\r\nRSET\r\nQUIT\r\n'; sleep 2; } | timeout 10 bash -c 'exec 3<>/dev/tcp/mail1.example.org/25; cat <&3 & cat >&3; wait' | tr -d '\r'
220 mail1.example.org ESMTP Exim 4.95 Ubuntu Mon, 26 Oct 2026 11:22:55 +0000
250-mail1.example.org Hello lab [10.89.61.12]
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250-PIPE_CONNECT
250-AUTH PLAIN
250-CHUNKING
250-STARTTLS
250-PRDR
250 HELP
250 OK
250 Accepted
250 Reset OK
221 mail1.example.org closing connection

RCPT 返回 250 Accepted(mail1 的 dc_relay_nets='10.89.61.0/24' 覆盖了 mail2 的地址)。

状态二:清空 relay_nets,同会话被拒

root@mail1:~# sed -i "s/^dc_relay_nets=.*/dc_relay_nets=''/ /etc/exim4/update-exim4.conf.conf
root@mail1:~# update-exim4.conf && systemctl reload exim4
root@mail2:~# { printf 'EHLO lab\r\nMAIL FROM:<root@example.net>\r\nRCPT TO:<someone@example.com>\r\nRSET\r\nQUIT\r\n'; sleep 2; } | timeout 10 bash -c 'exec 3<>/dev/tcp/mail1.example.org/25; cat <&3 & cat >&3; wait' | tr -d '\r' | grep -E '^(250 [A-Z]|550|221)'
250 OK
550 relay not permitted
221 mail1.example.org closing connection

mail1 的拒绝日志:

2026-10-26 11:31:57 H=(lab) [10.89.61.12] F=<root@example.net> rejected RCPT <someone@example.com>: relay not permitted

状态三:认证放行(accept authenticated = *)

口令库与认证器(分片模式新增 /etc/exim4/conf.d/auth/170_lab-plain):

root@mail1:~# openssl passwd -6 'q1w2e3r4'
$6$roFh37CXl.VZTQyP$T5nYBi1eQE7l8umJbWKzOqR3WrLX/oW8gb2jNJlunhyvABCBWEVZNTtlrq6UkxWaEt9ugBFtGpwCIdVJZEXi/
root@mail1:~# printf 'admin:$6$roFh37CXl.VZTQyP$T5nYBi1eQE7l8umJbWKzOqR3WrLX/oW8gb2jNJlunhyvABCBWEVZNTtlrq6UkxWaEt9ugBFtGpwCIdVJZEXi/\n' > /etc/exim4/smtp.users
root@mail1:~# cat /etc/exim4/conf.d/auth/170_lab-plain
plain_server:
  driver = plaintext
  public_name = PLAIN
  server_condition = ${if crypteq{$auth3}{${lookup{$auth2}lsearch{CONFDIR/smtp.users}{$value}fail}}{yes}{no}}
  server_set_id = $auth2
root@mail1:~# update-exim4.conf && systemctl restart exim4

口令正确:明文会话直接认证成功并放行中继:

root@mail2:~# B64=$(printf '\0admin\0q1w2e3r4' | base64)
root@mail2:~# { printf "EHLO lab\r\nAUTH PLAIN %s\r\nMAIL FROM:<root@example.net>\r\nRCPT TO:<someone@example.com>\r\nRSET\r\nQUIT\r\n" "$B64"; sleep 2; } | timeout 10 bash -c 'exec 3<>/dev/tcp/mail1.example.org/25; cat <&3 & cat >&3; wait' | tr -d '\r' | grep -E '^(235|250 [A-Z]|550)'
235 Authentication succeeded
250 OK
250 Accepted

口令错误:535 且进入 rejectlog:

root@mail2:~# B64B=$(printf '\0admin\0wrongpass' | base64)
root@mail2:~# { printf "EHLO lab\r\nAUTH PLAIN %s\r\nQUIT\r\n" "$B64B"; sleep 2; } | timeout 10 bash -c 'exec 3<>/dev/tcp/mail1.example.org/25; cat <&3 & cat >&3; wait' | tr -d '\r' | grep -E '^(235|535|221)'
535 Incorrect authentication data
221 mail1.example.org closing connection
2026-10-26 11:32:01 plain_server authenticator failed for (lab) [10.89.61.12]: 535 Incorrect authentication data (set_id=admin)

恢复状态:dc_relay_nets='10.89.61.0/24' 写回并 reload,exim4 -bP +relay_from_hosts 复核三表项(见 4.4 回显)。

6.4 acl_check_data 与 check_mail

40_exim4-config_check_data 负责报文头检查(如头部语法、大小限制),30_exim4-config_check_mail 校验 MAIL FROM 阶段。本实验环境未引入内容扫描器,两段保持包默认配置即可;如需附件拦截/垃圾评分,应配合 heavy 守护进程与 SpamAssassin/ClamAV 在这两段扩展,本文不展开。

七、路由器与传输器详解

7.1 路由器清单(conf.d/router/,13 个文件)

文件路由器驱动作用
100_exim4-config_domain_literaldomain_literalipliteral收件域为字面 IP 的处理
150_exim4-config_hubbed_hostshubbed_hostsmanualroute按 hubbed_hosts 表手工路由(本实验双机互发的关键)
200_exim4-config_primarydnslookupdnslookup非本地域走 DNS MX 投递
300_exim4-config_real_localreal_localredirectreal- 前缀本地直投
400_exim4-config_system_aliasessystem_aliasesredirect/etc/aliases 别名展开
500_exim4-config_hubuserhubuserredirecthubuser 表展开
600_exim4-config_userforwarduserforwardredirect$home/.forward 用户转发/过滤
700_exim4-config_procmailprocmailaccept存在 ~/.procmailrc 时交 procmail
800_exim4-config_maildropmaildropaccept存在 ~/.mailfilter 时交 maildrop
850_exim4-config_lowuidlowuid_aliasesredirect低 UID 用户的别名
900_exim4-config_local_userlocal_useraccept常规本地用户投递入口
mmm_mail4rootmail4rootredirectroot 邮件落到 /var/mail/mail

文件名前缀即拼接顺序,路由器按此顺序自上而下尝试。

7.2 关键路由器原文解读

hubbed_hosts(手工路由,双机互发的桥梁):

hubbed_hosts:
  debug_print = "R: hubbed_hosts for $domain"
  driver = manualroute
  domains = "${if exists{CONFDIR/hubbed_hosts}\
                   {partial-lsearch;CONFDIR/hubbed_hosts}\
              fail}"
  same_domain_copy_routing = yes
  route_data = ${lookup{$domain}partial-lsearch{CONFDIR/hubbed_hosts}}
  transport = remote_smtp

dnslookup(默认外发路由):

dnslookup:
  debug_print = "R: dnslookup for $local_part@$domain"
  driver = dnslookup
  domains = ! +local_domains
  transport = remote_smtp
  same_domain_copy_routing = yes
  ignore_target_hosts = ROUTER_DNSLOOKUP_IGNORE_TARGET_HOSTS
  no_more

domains = ! +local_domains 表明它只处理「非本机域」;no_more 意味着查不到路由目标后不再下探。

system_aliases:

system_aliases:
  debug_print = "R: system_aliases for $local_part@$domain"
  driver = redirect
  domains = +local_domains
  allow_fail
  allow_defer
  data = ${lookup{$local_part}lsearch{/etc/aliases}}

userforward:

userforward:
  debug_print = "R: userforward for $local_part@$domain"
  driver = redirect
  domains = +local_domains
  check_local_user
  file = $home/.forward
  require_files = $local_part_data:$home/.forward
  no_verify
  no_expn
  check_ancestor
  allow_filter
  directory_transport = address_directory
  file_transport = address_file
  pipe_transport = address_pipe
  reply_transport = address_reply
  skip_syntax_errors

local_user 与 mail4root:

local_user:
  debug_print = "R: local_user for $local_part@$domain"
  driver = accept
  domains = +local_domains
  check_local_user
  local_parts = ! root
  transport = LOCAL_DELIVERY
  cannot_route_message = Unknown user

mail4root:
  debug_print = "R: mail4root for $local_part@$domain"
  driver = redirect
  domains = +local_domains
  data = /var/mail/mail
  file_transport = address_file
  local_parts = root
  user = mail
  group = mail

local_user 用 LOCAL_DELIVERY 宏(由 dc_localdelivery 决定指向哪个传输器);root 被 local_parts = ! root 排除后由 mail4root 兜底——这就是 root 邮件落在 /var/mail/mail 的配置层原因。

7.3 传输器清单(conf.d/transport/,12 个文件)

文件传输器作用
30_exim4-config_address_fileaddress_file投到任意文件(含 mbox)
30_exim4-config_address_pipeaddress_pipe管道命令投递
30_exim4-config_address_replyaddress_reply自动回信
30_exim4-config_maildir_homemaildir_homeMaildir 到家目录
30_exim4-config_mail_spoolmail_spoolmbox 到 /var/mail
30_exim4-config_maildrop_pipemaildrop_pipemaildrop 管道
30_exim4-config_procmail_pipeprocmail_pipeprocmail 管道
30_exim4-config_remote_smtpremote_smtp常规 SMTP 外发
30_exim4-config_remote_smtp_smarthostremote_smtp_smarthostsmarthost 外发(含上游认证/校验)
35_exim4-config_address_directoryaddress_directory投到目录

7.4 关键传输器原文解读

maildir_home:
  debug_print = "T: maildir_home for $local_part@$domain"
  driver = appendfile
  directory = $home/Maildir
  create_directory
  delivery_date_add
  envelope_to_add
  return_path_add
  maildir_format
  directory_mode = 0700
  mode = 0600
  mode_fail_narrower = false

remote_smtp:
  debug_print = "T: remote_smtp for $local_part@$domain"
  driver = smtp

address_file:
  debug_print = "T: address_file for $local_part@$domain"
  driver = appendfile
  delivery_date_add
  envelope_to_add
  return_path_add

(maildir_home 的 .ifdef 分支为可覆盖的宏参数,此处为默认展开值。)日志中的 T= 字段即实际命中的传输器,R= 是路由器,二者是判读投递路径的第一线索。

7.5 系统别名(/etc/aliases)

追加 billing 别名并验证路由链:

root@mail1:~# printf 'billing: admin\n' >> /etc/aliases
root@mail1:~# tail -2 /etc/aliases
ops-list: admin, root
billing: admin
root@mail1:~# exim4 -bt billing@example.org
R: system_aliases for billing@example.org
R: system_aliases for admin@example.org
R: userforward for admin@example.org
R: system_aliases for admin@example.org
R: procmail for admin@example.org
R: maildrop for admin@example.org
R: lowuid_aliases for admin@example.org (UID 1000)
R: local_user for admin@example.org
R: hubbed_hosts for example.net
admin@example.org
    <-- admin@example.org
    <-- billing@example.org
  router = local_user, transport = maildir_home
admin@example.net
    <-- admin@example.org
    <-- billing@example.org
  router = hubbed_hosts, transport = remote_smtp
  host mail2.example.net [10.89.61.12]

别名展开(billing → admin)→ admin 的 .forward(见 7.6)→ 两个最终投递地址。别名修改即时生效,无需重启服务。

7.6 用户转发(.forward)

mail1 的 admin 配置「保留本地副本 + 转发到 mail2」:

root@mail1:~# printf '\\admin\nadmin@example.net\n' > /home/admin/.forward
root@mail1:~# chown admin:admin /home/admin/.forward
root@mail1:~# cat -A /home/admin/.forward
\admin$
admin@example.net$
root@mail1:~# printf 'Forward test: local copy + remote copy to mail2.\n' | mail -s 'fwd-02' admin@example.org
root@mail1:~# tail -4 /var/log/exim4/mainlog
2026-10-26 11:32:41 1xLIwT-0005Tm-L3 <= root@example.org U=root P=local S=485
2026-10-26 11:32:41 1xLIwT-0005Tm-L3 => admin <admin@example.org> R=local_user T=maildir_home
2026-10-26 11:32:41 1xLIwT-0005Tm-L3 => admin@example.net <admin@example.org> R=hubbed_hosts T=remote_smtp H=mail2.example.net [10.89.61.12] X=TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256 CV=no DN="CN=mail2.example.net,O=Example Lab,C=CN" K C="250- 500 byte chunk, total 500\n250 OK id=1xLIwT-0005Uq-Lk"
2026-10-26 11:32:41 1xLIwT-0005Tm-L3 Completed

\admin 的反斜杠表示「继续本地投递、避免二次别名展开」,因此同一封信一式两份:本地 Maildir 一份、mail2 一份。

八、SMTP AUTH 认证

8.1 认证器与口令库

见 6.3 的 plain_server 定义与 smtp.users 生成。crypteq 支持系统 crypt 哈希(openssl passwd -6 产生的 SHA-512),口令不以明文落盘。

8.2 会话验证

正确口令 235 Authentication succeeded、错误口令 535 Incorrect authentication data(回显见 6.3 状态三)。认证成功后 server_set_id = $auth2 把身份写入日志。

8.3 认证中继实录

以认证身份经 mail1 中继一封信到 mail2(两段式发送,尊重 354 同步点):

root@mail2:~# B64=$(printf '\0admin\0q1w2e3r4' | base64)
root@mail2:~# { printf "EHLO lab\r\nAUTH PLAIN %s\r\nMAIL FROM:<root@example.net>\r\nRCPT TO:<admin@example.net>\r\nDATA\r\n" "$B64"; sleep 2; printf "Subject: auth-relay-03\r\nFrom: root@example.net\r\n\r\nTwo-stage authenticated relay body.\r\n.\r\nQUIT\r\n"; sleep 2; } | timeout 14 bash -c 'exec 3<>/dev/tcp/mail1.example.org/25; cat <&3 & cat >&3; wait' | tr -d '\r'
235 Authentication succeeded
250 OK
250 Accepted
354 Enter message, ending with "." on a line by itself
250 OK id=1xLIw1-0005TI-WD
221 mail1.example.org closing connection

mail1 日志(P=esmtpa A=plain_server:admin 即认证标记):

2026-10-26 11:32:15 1xLIw1-0005TI-WD <= root@example.net H=(lab) [10.89.61.12] P=esmtpa A=plain_server:admin S=371
2026-10-26 11:32:16 1xLIw1-0005TI-WD => admin@example.net R=hubbed_hosts T=remote_smtp H=mail2.example.net [10.89.61.12] X=TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256 CV=no DN="CN=mail2.example.net,O=Example Lab,C=CN" K C="250- 383 byte chunk, total 383\n250 OK id=1xLIw4-0005Ub-0d"
2026-10-26 11:32:16 1xLIw1-0005TI-WD Completed

mail2 的 admin 收到主题为 auth-relay-03 的邮件(Maildir new 目录文件核对通过)。

8.4 安全注记

本实验认证器未设置 server_advertise_condition,明文会话也广播 250-AUTH PLAIN;生产环境应加 server_advertise_condition = ${if def:tls_in_cipher}(或等价配置)强制「先 STARTTLS 再认证」,避免口令明文过网。

九、生产配置模板(实验室单机 MTA 参考)

以下为本文实验收敛后的最终配置,可直接套用(替换主机名/域名/网段三处即可):

# /etc/exim4/update-exim4.conf.conf
dc_eximconfig_configtype='internet'
dc_other_hostnames='mail1.example.org:example.org'
dc_local_interfaces='0.0.0.0 ; ::0'
dc_readhost=''
dc_relay_domains=''
dc_minimaldns='false'
dc_relay_nets='10.89.61.0/24'
dc_smarthost=''
CFILEMODE='644'
dc_use_split_config='true'
dc_hide_mailname=''
dc_mailname_in_oh='true'
dc_localdelivery='maildir_home'
# /etc/exim4/conf.d/main/000_localmacros(分片模式)
MAIN_TLS_ENABLE = yes
MAIN_HOST_LOOKUP =

# /etc/exim4/exim4.conf.localmacros(单一模式,二选一)
MAIN_TLS_ENABLE = yes
MAIN_HOST_LOOKUP =

配套文件:/etc/exim4/hubbed_hosts(跨域手工路由)、/etc/aliases(系统别名)、/etc/exim4/smtp.users + conf.d/auth/170_lab-plain(如需认证中继)、/etc/exim4/exim.crt|exim.key(TLS)。

十、常见问题

10.1 update-exim4.conf 报错退出

向本地宏文件写入非法内容后重新生成,会在生成阶段即失败(不会污染运行配置):

root@mail2:~# printf 'MAIN_TLS_ENABLE = yes\nMESSAGE_SIZE_LIMIT = 10M\nBAD_DIRECTIVE_HERE\n' >> /etc/exim4/exim4.conf.localmacros
root@mail2:~# update-exim4.conf
2026-10-26 11:15:32 Exim configuration error in line 3 of /etc/exim4/exim4.conf.localmacros:
  main option "bad_directive_here" unknown

处理:删除非法行后重新 update-exim4.conf(本实验修正后生成成功)。

10.2 改了配置不生效

检查三件事:是否跑了 update-exim4.conf、是否 reload/restart、exim4 -bV 最后一行是否指向预期文件(见第二章)。宏文件形态与配置模式不匹配(3.4)是最常见的隐形原因。

10.3 smarthost 外发报 TLS certificate verification failed

remote_smtp_smarthost 默认校验上游证书,自签证书会被拒绝并回退明文投递:

2026-10-26 11:07:36 1xLIf4-0003rM-TT TLS session: (certificate verification failed): certificate invalid: delivering unencrypted to H=mail1.example.org [10.89.61.11] (not in hosts_require_tls)

处理:为上游签发受信 CA 证书,或为该上游主机配置指纹/CA 信任(tls_verify_certificates),不建议长期明文。

10.4 hubbed_hosts 与 smarthost 谁生效

hubbed_hosts 路由器(序号 150)先于 smarthost 路由器拼接,两处同时命中时 R=hubbed_hosts 生效。要观察 smarthost 行为需先移除对应 hubbed 条目(4.1 实验即按此进行)。

10.5 SMTP protocol synchronization error

脚本一次灌入 DATA 与正文触发流水线同步保护,报文样例与处理见《Ubuntu 22.04 安装 Exim 4.95 指南》10.3。

十一、参考链接