像 UNIX 时代一样架设 SMTP 服务器

最近需要架设一台 SMTP 服务器用来发件,我起初觉得这不简单。但当你像 UNIX 时代一样架设 SMTP 服务器,它是非常简单的。今天,我就把这篇教程当成我几个小时折腾的总结。

基础知识

首先,UNIX 时代是啥样的?UNIX 时代计算机还十分昂贵,所以大家共享同一台计算机。不同的用户使用终端登入同一台计算机。用户们一般都结成社区。现在,这样的社区还存在但因为大家人手一台计算机就改用 ssh 连接到同一台主机了。这样的社区叫做 tilde 其中 tilde.club 算比较有名的。

所以,起初电子邮件还只是在一台主机中传输。像这样:

$ mail bob
Subject: Hello World

Hello, how are you doing?
EOT

然后 Bob 的终端就会提醒他有新的邮件,使用 mail 命令就可以查看。

现在的电子邮件通过互联网使用 SMTP 协议投递到别的主机。比如 noreply@want.reply.com 中 noreply 就是要投递到的用户,want.reply.com 就是投递到的目标主机。在域名系统还未成熟时,这种投递方式就已经存在。

一封邮件可以由任何人投递,而信封(envelope)也无非就像一个 HTTP 请求一样。以中国的第一封邮件举例(部分地方加上了换行):

Received: from Peking by unika1; Sun, 20 Sep 87 16:55 (MET dst)
Date: Mon, 14 Sep 87 21:07 China Time
From: Mail Administration for China
To: Zorn@germany, Rotert@germany, Wacker@germany, Finken@unika1
CC: lhl@parmesan.wisc.edu, farber@udel.edu, jennings%irlean.bitnet@germany, cic%relay.cs.net@germany, Wang@ze1, RZLI@ze1
Subject: First Electronic Mail from China to Germany
Date: Mon, 14 Sep 87 21:07 China Time

"Ueber die Grosse Mauer erreichen wir alle Ecken der Welt"
"Across the Great Wall we can reach every corner in the world"

Dies ist die erste ELECTRONIC MAIL, die von China aus ueber Rechnerkopplung in die internationalen Wissenschaftsnetze geschickt wird.
This is the first ELECTRONIC MAIL supposed to be sent from China into the international scientific networks via com-puter interconnection between Beijing and Karlsruhe, West Germany (using CSNET/PMDF BS2000 Version).

University of Karlsruhe Institute for Computer Application of
Informatik State Commission of Rechnerabteilung - Machine Industry (IRA) (ICA)

Prof. Dr. Werner Zorn Prof. Wang Yuen Fung
Michael Finken Dr. Li Cheng Chiung
Stephan Paulisch Qui Lei Nan
Michael Rotert Ruan Ren Cheng
Gerhard Wacker Wei Bao Xian
Hans Lackner Zhu Jiang
Zhao Li Hua

可以看到,上面的邮件由 Mail Administration for China 发给德国的 Zorn, Rotert, Wacker 和 unika1 的 Finken。现在的邮件与这封 1987 年的邮件没什么不同,只是邮件头稍微复杂点而已。

电子邮件怎么投递?一封电子邮件的起点是发件人,终点是收件人的收件箱。 中间还要经过服务器的转发,这样的服务器叫做中继(Relay)。一个主机上的内部邮件会被 SMTPd 直接投递到对应用户的收件箱,SMTPd 也可以接收外界的投递然后运送到对应用户的收件箱。SMTPd 还要发件,如果邮件不发往本地用户那么就要充当中继,投递给对方。

开始搭建

UNIX 时代一样架设 SMTP 服务器,所以我选择来自 UNIX 时代的系统——OpenBSD。OpenBSD 安装时自带了 OpenSMTPd httpd 和 acme-client,可以大大简化要做的工作。

第一步,你需要确保你服务器的主机名和你的发件域名一致。比如,如果你的主机名是 tilde.fsfans.club,那么如果主机上的 root 用户向外发件,电子邮件地址就会变成 root@tilde.fsfans.club。即 @ 后面的是主机,前面的是主机上的用户。

第二步,获取 TLS 证书。我们在远程连接 SMTP 服务器时要加密数据,所以需要获得一个 TLS 证书。这个证书可以是自签名的(会有安全告警),也可以是 Let’s Encrypt 上申请的。下面我就演示怎么用 OpenBSD 自带的 acme-client 生成证书。

先把 /etc/examples/httpd.conf 复制到 /etc,这是我们的 HTTP 服务器配置(用于验证)。然后把里面的 example.com 替换成主机名,像这样:

# $OpenBSD: httpd.conf,v 1.22 2020/11/04 10:34:18 denis Exp $

server "tilde.fsfans.club" {
        listen on * port 80
        location "/.well-known/acme-challenge/*" {
                root "/acme"
                request strip 2
        }
        location * {
                block return 302 "https://$HTTP_HOST$REQUEST_URI"
        }
}

server "tilde.fsfans.club" {
        listen on * tls port 443
        tls {
                certificate "/etc/ssl/tilde.fsfans.club.fullchain.pem"
                key "/etc/ssl/private/tilde.fsfans.club.key"
        }
        location "/pub/*" {
                directory auto index
        }
        location "/.well-known/acme-challenge/*" {
                root "/acme"
                request strip 2
        }
}

然后把 /etc/examples/acme-client.conf 复制到 /etc,修改掉里面的 example.com 然后去掉 alternative names 一行:

#
# $OpenBSD: acme-client.conf,v 1.4 2020/09/17 09:13:06 florian Exp $
#
authority letsencrypt {
        api url "https://acme-v02.api.letsencrypt.org/directory"
        account key "/etc/acme/letsencrypt-privkey.pem"
}

authority letsencrypt-staging {
        api url "https://acme-staging-v02.api.letsencrypt.org/directory"
        account key "/etc/acme/letsencrypt-staging-privkey.pem"
}

authority buypass {
        api url "https://api.buypass.com/acme/directory"
        account key "/etc/acme/buypass-privkey.pem"
        contact "mailto:me@example.com"
}

authority buypass-test {
        api url "https://api.test4.buypass.no/acme/directory"
        account key "/etc/acme/buypass-test-privkey.pem"
        contact "mailto:me@example.com"
}

domain tilde.fsfans.club {
        domain key "/etc/ssl/private/tilde.fsfans.club.key"
        domain full chain certificate "/etc/ssl/tilde.fsfans.club.fullchain.pem"
        sign with letsencrypt
}

然后就可以启动 HTTPd 然后获取 TLS 证书了:

rcctl enable httpd
rcctl start httpd
acme-client tilde.fsfans.club
rcctl reload httpd

此后要检查证书并续签只需要定时运行 acme-client。

第三步,配置 OpenSMTPd。在安装后,OpenSMTPd 就应该已经启动了(用于本地邮件系统)。在
/etc/mail/smtpd.conf 的上面,配置 TLS 证书:

pki tilde.fsfans.club cert "/etc/ssl/tilde.fsfans.club.fullchain.pem"
pki tilde.fsfans.club key "/etc/ssl/private/tilde.fsfans.club.key"

然后在 listen lo0 的下面,配置 SMTPd 的端口监听:

listen on all tls pki "tilde.fsfans.club" auth
listen on all port submission tls-require pki "tilde.fsfans.club" auth

这样 SMTPd 就监听在 smtp 端口和 submission 端口下了,后面的 auth 代表需要身份验证。验证的机制就像 ssh 登录一样,用户名是 Unix 用户名,密码是 Unix 用户的密码。

如果还想收件,就取消那行注释(配置文件里有解释):

match from any for domain "tilde.fsfans.club" action "local_mail"
match from any for domain "fsfans.club" action "local_mail"

现在,我们只做到了让用户可以从网络连接 SMTP 服务器,但没有写如何处置他们的消息。你会发现,配置文件下面有这两行:

match from local for local action "local_mail"
match from local for any action "outbound"

意思不言而喻。我们要做的就是再加两行,把 local 换成 auth:

match from auth for local action "local_mail"
match from auth for any action "outbound"

意思是,验证过的连接到本地的邮件使用 local_mail,到其它的地方使用 outbound。(你可以留意到 action “outbound” 实际上就是中继)

注意,如果你想要把这个服务器供别人使用就不能这样写。这样会允许所有验证过的用户发送任何 `From’ 的邮件(下文会说为什么)。如果要禁止这种行为就得在 match 上做文章,详见 stmpd.conf(8)

添加后就可以用 rcctl reload smtpd 重新加载配置了。

最后一步,配置一系列记录。因为上面介绍过,投递实际上可以由任何主机进行,不管它们是否是「真主机」。所以目标服务器通常会用一系列 DNS 记录判定你是否是真的。

IP 的 PTR 记录(反向解析)

验证的方式之一就是查询发件 IP 的 PTR 记录。PTR 记录可以把 IP 解析到一个域名上,所以也叫反向解析。如果反向解析和 `From’ 的主机一样,收件服务器就可能认为你是真的。反向解析的配置通常需要咨询 IP/VPS 的提供商。在这里,我就需要把对应 IP 解析到 tilde.fsfans.club。

域名的 A 记录

收件服务器可以查询发件主机名的 A 记录(或者 CNAME),如果 IP 与发件 IP 匹配,收件服务器就可能认为你是真的。在这里,我就需要把 tilde.fsfans.club 指向对应 IP。

域名的 MX 记录

根据维基百科,MX 记录用于指定负责处理发往收件人域名的邮件服务器。这里我把 tilde.fsfans.club 指向 tilde.fsfans.club、fsfans.club 指向 tilde.fsfans.club。

域名的 SPF 记录

根据维基百科,发件人策略框架(英语:Sender Policy Framework;简称SPF)是一套电子邮件认证机制,可以确认电子邮件确实是由网域授权的邮件服务器寄出,防止有人伪冒身份网络钓鱼或寄出垃圾电邮。SPF允许管理员设定一个DNS TXT记录或SPF记录设定发送邮件服务器的IP范围,如有任何邮件并非从上述指明授权的IP地址寄出,则很可能该邮件并非确实由真正的寄件者寄出(邮件上声称的“寄件者”为假冒)。

我们只用添加一条 TXT 记录以表明 SPF 规则。在这里,我就需要把 tilde 指向 “v=spf1 a mx ip4:a.b.c.d ~all” (a.b.c.d 是你的 IP 地址),表示接收到来自 a.b.c.d 发送的 tilde.fsfans.club 的邮件都是真实的。

域名的 DMARC 记录

域名密钥识别邮件(DomainKeys Identified Mail,DKIM)是一套电子邮件认证机制,使用公开密钥加密的基础提供了数字签名与身份验证的功能,以检测寄件者、主旨、内文、附件等部分有否被伪冒或窜改。

一般来说,发送方会在电子邮件的标头插入DKIM-Signature及电子签名信息。而接收方则透过DNS查询得到公开密钥后进行验证。

在这里,我们不使用 DKIM,但在 DNS 记录中我们也可以声明我们不用 DKIM。我需要把 _dmarc.tilde 指向 “v=DMARC1; p=none”。

发件测试

在执行上述步骤了以后,就可以测试发件了。我们可以先从本机开始:

打开 mail-tester.com,你会看到一个电子邮件地址。在 Shell 中使用命令 `mail test-???@srv1.mail-tester.com’ 给其发邮件,用 Ctrl+D 结束消息。然后点击 Then check your score,你会得到邮件的评分(评分越高越容易投递):

tilde$ mail test-34bounepd@srv1.mail-tester.com
Subject: Lorem Ipsum
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.
EOT
www.mail-tester.com 的检测结果

你应该会因为没有启用 DKIM 签名扣一分,但无关紧要。

然后你可以远程发信,服务器是中继的主机名端口是 587(submission),加密选择 STARTTLS。用户名是 Unix 用户的用户名,密码同理。

调试信息

首先,你应该确保你的服务商没有 Ban 你的 smtp 端口。

然后,你可以看 /var/log/maillog 文件,里面是日志。

参考材料:SMTPD(8)SMTPD.CONF(5)ACME-CLIENT.CONF(5)HTTPD.CONF(5)RCCTL(8)