【OPS.0x05】用Postfix+Dovecat+MariaDB搭建邮件系统

本文最后更新于:2025年4月14日 凌晨

在 Debian 上用 MySQL 跟™杀人了一样

0x00. 一切开始之前

一切说来话长但长话短说,总而言之笔者正在搭建一个独立可用的邮件系统,因为有一个自己的域名作为后缀来发送邮件的邮箱是非常炫酷的一件事情(?)

在技术选型上笔者经过考虑之后还是选择传统的 Postfix + Dovecot + MariaDB 方案,因为这是被广泛应用在各个企业当中的成熟解决方案,至于为什么选择 MariaDB 而不是 MySQL 则是因为笔者服务器使用的 Debian 发行版对 MySQL 的支持不太好,导致配置过程中出现了一系列事故,使得笔者最终转向 Debian 推荐的 MariaDB :(

话不多说,下面我们来看配置过程:)

至于邮件系统相关原理还有 SMTP、IMAP 协议一类的就不是这篇博客所包含的内容了,请同学们课后自行了解

0x01. 邮件系统搭建

这里我们选择使用传统的 Postfix + Dovecot 方案,服务器用的是笔者之前屯的一台便宜 Debian VPS

DNS 配置

我们主要需要添加三条记录:

  • A 记录:邮件服务器的域名,指向邮件服务器的 IP,一般可以是 mail.主域名
  • MX 记录:指向邮件服务器的域名(上面的 A 记录),用于设定邮件交给哪个服务器处理,例如名称设为 mail 、内容设为 mail-server.域名 则意味着 mail.域名 的邮件交给 mail-server.域名 处理;这里笔者的选择是将 主域名 的邮件交给 mail.主域名 处理
  • TXT(SPF) 记录:发信使用,用于证明该 IP 送出的该 domain 的信不是伪造的,内容应当为 v=spf1 ip4:服务器IP -all

安装 MariaDB

首先安装 MariaDB,用来存放邮件和用户数据

注意这里 极度不推荐使用 MySQL,因为会遇到非常难以解决的问题在 Debian 上用 MySQL 跟™杀人了一样

1
2
$ sudo apt-get update
$ sudo apt install mariadb-server mariadb-client

然后就可以使用了

在 MariaDB 中创建对应的数据库

首先登录 root 账户:

1
$ sudo mariadb -u root

创建一个新的数据库并使用:

1
2
CREATE DATABASE IF NOT EXISTS mail_db;
USE mail_db;

创建一个表存放认证的域名,并插入一条新记录:

1
2
3
4
5
6
7
8
9
# 创建一个表
CREATE TABLE `virtual_domains` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(256) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

# 添加域名
INSERT INTO virtual_domains VALUE(1,'test.com');

创建一个表存放邮箱与密码(推荐存放哈希值,例如使用 sha256),并添加上一个用户,注意这里的 domain_id 为前面的表中域名的序号(下同):

1
2
3
4
5
6
7
8
9
10
11
12
13
# 创建一个表
CREATE TABLE `virtual_users` (
`id` INT NOT NULL AUTO_INCREMENT,
`domain_id` INT NOT NULL,
`password` VARCHAR(128) NOT NULL,
`email` VARCHAR(256) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

# 创建一个新用户
INSERT INTO virtual_users VALUE(1, 1, "后续使用doveadm pw -s SHA256-CRYPT -p '密码' 创建的密码", "arttnba3@test.com");

创建一个表存放邮箱别名,主要用于邮件转发:

1
2
3
4
5
6
7
8
9
10
11
12
# 创建一个表
CREATE TABLE `virtual_aliases` (
`id` INT NOT NULL auto_increment,
`domain_id` INT NOT NULL,
`source` VARCHAR(256) NOT NULL,
`destination` VARCHAR(256) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

# 如果要创建转发,参照下面的写法
# INSERT INTO virtual_aliases VALUE(1, 1, "rat3bant@test.com", "arttnba3@test.com");

最后创建一个新的用户并给予这些表的权限,后续我们使用该用户访问数据库而非直接使用 root:

1
2
3
4
CREATE USER 'mailuser'@'localhost' IDENTIFIED BY '你的密码';
GRANT ALL PRIVILEGES ON mail_db.* TO 'mailuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Postfix + Dovecot 安装

首先安装 Postfix:

1
$ sudo apt-get install -y postfix postfix-mysql libmariadb-dev libmariadb3

这里会出现一个选择配置的选项,我们先选 Internet Site ,也就是由该服务器通过 SMTP 协议直接收发邮件:

然后配置 system mail name,简而言之就是邮件所使用的域名,以 arttnba3@example.com 为例这里应当填 example.com

接下来安装 Dovecot:

1
$ sudo apt-get install -y dovecot-core dovecot-pop3d dovecot-imapd dovecot-lmtpd dovecot-mysql

配置 Postfix

配置 SSL 证书

笔者直接用的 Cloudflare 提供的证书,将其保存到服务器本地中,例如:

  • /etc/ssl/certs/证书.pem
  • /etc/ssl/private/证书.key

然后修改 Postfix 的主配置文件 /etc/postfix/main.cf ,确保如下配置:

1
2
3
4
5
6
7
8
smtpd_tls_cert_file=/etc/ssl/certs/你的证书.pem
smtpd_tls_key_file=/etc/ssl/private/你的密钥.key
smtpd_use_tls=yes
smtpd_tls_security_level=encrypt
smtpd_tls_mandatory_protocols = TLSv1, TLSv1.1, TLSv1.2, TLSv1.3
smtpd_tls_mandatory_ciphers = high

smtp_tls_security_level=encrypt

配置使用 Dovecot 进行身份认证

修改 Postfix 的主配置文件 /etc/postfix/main.cf ,确保如下配置:

1
2
3
4
5
6
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_tls_auth_only = yes
smtpd_recipient_restrictions = permit_sasl_authenticated permit_mynetworks reject_unauth_destination
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination

域名等常规配置

修改 Postfix 的主配置文件 /etc/postfix/main.cf ,确保如下配置:

1
2
3
4
5
myhostname = 你的mail域名
myorigin = $myhostname
mydomain = $myhostname
mydestination = localhost
smtp_helo_name = $myhostname

配置 MariaDB 与 Dovecot 相关

暂时先不考虑 SQL 注入…因为👴太懒了,反正问了问 ChatGPT 大概是没问题的:(

修改 Postfix 的主配置文件 /etc/postfix/main.cf ,添加这些项:

1
2
3
4
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf
virtual_transport = lmtp:unix:private/dovecot-lmtp

/etc/postfix/mysql-virtual-mailbox-domains.cf 中按照前面的配置写入如下内容:

1
2
3
4
5
user = mailuser 
password = 前面设置的密码
hosts = 127.0.0.1:3306
dbname = mail_db
query = SELECT 1 FROM virtual_domains WHERE name='%s'

/etc/postfix/mysql-virtual-mailbox-maps.cf 中写入如下内容:

1
2
3
4
5
user = mailuser 
password = 前面设置的密码
hosts = 127.0.0.1:3306
dbname = mail_db
query = SELECT 1 FROM virtual_users WHERE email='%s'

/etc/postfix/mysql-virtual-alias-maps.cf 中写入如下内容:

1
2
3
4
5
user = mailuser 
password = 前面设置的密码
hosts = 127.0.0.1:3306
dbname = mail_db
query = SELECT destination FROM virtual_aliases WHERE source='%s'

最后重启 Postfix:

1
$ sudo systemctl restart postfix

此时我们执行如下指令进行测试时,结果都应当返回 1

1
2
3
4
$ sudo postmap -q 前面添加的域名 mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
$ sudo postmap -q 前面添加的邮箱 mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
# 如果你添加了邮箱别名,运行下面的测试命令:
$ sudo postmap -q 前面添加的邮箱 mysql:/etc/postfix/mysql-virtual-alias-maps.cf

开启邮件提交

简而言之用来给客户端向服务器提交请求,一个是常规的 587 端口的提交,另一个是如 outlook 等邮件客户端通过 465 端口的 SMTPS 提交邮件

编辑 /etc/postfix/master.cf 文件,添加如下配置:

1
2
3
4
5
6
7
8
9
submission inet n       -       y       -       -       smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_tls_auth_only=yes
smtps inet n - y - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes

配置 Dovecot

配置协议

/etc/dovecot/dovecot.conf 中添加如下内容:

1
protocols = imap lmtp pop3

创建虚拟用户

接下来我们创建一个新的虚拟用户作为 Dovecot 的后端,这里我们创建一个 vmail 用户:

1
2
3
$ sudo groupadd -g 5000 vmail
$ sudo useradd --home-dir /var/mail/ --shell /usr/sbin/nologin -g vmail -u 5000 vmail
$ sudo chown -R vmail:vmail /var/mail

修改 /etc/dovecot/conf.d/10-mail.conf ,确保如下两条配置:

1
2
mail_location = maildir:/var/mail/vhosts/%d/%n
mail_privileged_group = mail

配置认证方式

修改 /etc/dovecot/conf.d/10-auth.conf ,配置如下:

1
auth_mechanisms = plain login

来到文件末尾,注释掉其他 auth 方式,开启 SQL:

1
2
3
4
5
6
#!include auth-system.conf.ext
!include auth-sql.conf.ext
#!include auth-ldap.conf.ext
#!include auth-passwdfile.conf.ext
#!include auth-checkpassword.conf.ext
#!include auth-static.conf.ext

不开启明文密码登录:

1
disable_plaintext_auth = yes

接下来修改 /etc/dovecot/conf.d/auth-sql.conf.ext 文件,确保如下配置:

1
2
3
4
5
6
7
8
9
10
passdb {
driver = sql
args = /etc/dovecot/dovecot-sql.conf.ext
}

userdb {
driver = sql
args = /etc/dovecot/dovecot-sql.conf.ext
}

配置 SQL 认证过程

接下来修改 /etc/dovecot/dovecot-sql.conf.ext ,确保如下配置:

1
2
3
4
5
driver = mysql
connect = host=127.0.0.1 port=3306 dbname=mail_db user=mailuser password=前面设置的密码
default_pass_scheme = SHA256-CRYPT
user_query = SELECT email AS username, '/var/mail/vhosts/%d/%n' AS home, 5000 AS uid, 5000 AS gid FROM virtual_users WHERE email = '%n@%d';
password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';

配置 lmtp、auth、postfix 相关

修改 /etc/dovecot/conf.d/10-master.conf ,确保如如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0600
user = postfix
group = postfix
}
}

service auth {
unix_listener auth-userdb {
mode = 0600
user = vmail
}

unix_listener /var/spool/postfix/private/auth {
mode = 0666
user = postfix
group = postfix
}

user = dovecot

}

service auth-worker {
user = vmail
}

配置 SSL 认证

接下来修改 /etc/dovecot/conf.d/10-ssl.conf 文件,确保如下配置:

1
2
3
ssl = required
ssl_cert = <证书位置
ssl_key = <密钥位置

配置文件夹权限

修改 /etc/dovecot 的权限:

1
2
$ sudo chown -R vmail:dovecot /etc/dovecot
$ sudo chmod -R o-rwx /etc/dovecot

最后重启 postfix 与 dovecot:

1
2
$ sudo systemctl restart postfix
$ sudo systemctl restart dovecot

配置 OpenDKIM

首先安装 OpenDKIM:

1
$ sudo apt install opendkim opendkim-tools

接下来修改 /etc/opendkim.conf ,确保如下配置:

原文件中可能会有一条 Socket 开头的默认配置,需要注释掉

1
2
3
4
Domain                  你的域名
Selector dkim
KeyFile /etc/dkimkeys/dkim.private
SOCKET inet:8891@localhost

接下来修改 /etc/default/opendkim ,确保如下配置:

1
SOCKET="inet:8891@localhost"

接下来生成 dkim 密钥对:

1
2
3
$ sudo opendkim-genkey -t -s dkim -d 你的域名
$ sudo mv dkim.private /etc/dkimkeys/
$ sudo chown opendkim:opendkim /etc/dkimkeys/dkim.private

接下来修改 /etc/postfix/main.cf ,确保如下配置:

1
2
3
4
smtpd_milters = inet:localhost:8891
non_smtpd_milters = inet:localhost:8891
milter_protocol = 2
milter_default_action = accept

接下来重启 opendkim 与 postfix:

1
2
$ sudo systemctl restart opendkim
$ sudo systemctl restart postfix

最后添加一条格式为 "v=DKIM1;k=rsa;t=y;p=一个字符串" 的 DNS 记录,类型为 TXT, 记录名为 dkim._domainkey ,p 的内容参照前面 opendkim-genkey 命令在当前目录下生成的 dkim.txt

0x02. 测试与登录邮件系统

使用 Outlook/Thunderbird 等软件进行登录

Thunderbird 是一个开源的邮件客户端,可以直接识别并登录,比较方便:

Outlook 电脑端也是可以直接识别出 IMAP 服务器并登录,不过你可能还会需要根据自己的服务器配置手动修改一些东西:

不过 Outlook 会把你的邮件给偷一份到微软自己的 Outlook 服务器上,这个就酌情考虑了:

移动端同电脑端

使用 OpenSSL 单独测试本地端口

🕊

0x03. 可能遇到的问题

没有办法接收/发送邮件

一般是云服务器厂商做了限制,例如 VMISS 就限制了不少端口:

可以 在另一台设备上 用 nmap 检查端口可访问状态,例如笔者在 VMISS 上买的服务器的 25 端口就有点问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ nmap -Pn 你的邮件域名
Starting Nmap 7.93 ( https://nmap.org ) at 2025-04-12 18:40 EDT
Nmap scan report for 你的邮件域名 (你的IP)
Host is up (0.16s latency).
Not shown: 992 closed tcp ports (conn-refused)
PORT STATE SERVICE
22/tcp open ssh
25/tcp filtered smtp
110/tcp open pop3
143/tcp open imap
465/tcp open smtps
587/tcp open submission
993/tcp open imaps
995/tcp open pop3s

Nmap done: 1 IP address (1 host up) scanned in 10.55 seconds

这个时候就只能要么换服务器要么想办法让厂商开放端口了,没啥好的解决方案:(

发信时遇到 451 4.3.0 Temporary lookup failure

这个问题往往出现在你尝试在 Debian 上使用 MySQL 而非 MariaDB 作为你的数据库的场景下

,这个时候你查看邮件日志可能如下:

1
2
3
4
5
6
7
8
$ cat /var/log/mail.log 
# ...
Apr 12 16:49:42 mail postfix/trivial-rewrite[41805]: warning: connect to mysql server 127.0.0.1:3306: Plugin caching_sha2_password could not be loaded: /usr/lib/x86_64-linux-gnu/libmariadb3/plugin/caching_sha2_password.so: cannot open shared object file: No such file or directory
Apr 12 16:49:42 mail postfix/trivial-rewrite[41805]: warning: virtual_mailbox_domains: mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf: table lookup problem
Apr 12 16:49:42 mail postfix/trivial-rewrite[41805]: warning: virtual_mailbox_domains lookup failure
Apr 12 16:49:42 mail postfix/trivial-rewrite[41805]: warning: virtual_mailbox_domains: mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf: table lookup problem
Apr 12 16:49:42 mail postfix/trivial-rewrite[41805]: warning: virtual_mailbox_domains lookup failure
Apr 12 16:49:42 mail postfix/submission/smtpd[41799]: NOQUEUE: reject: RCPT from localhost[::1]: 451 4.3.0 <arttnba3@👴的域名>: Temporary lookup failure; from=<邮箱地址> to=<邮箱地址> proto=ESMTP helo=<主机名>

简而言之就是 Debian 的 Postfix 默认链接到 MariaDB 相关的库而非 MySQL ,因此默认使用 MariaDB Client(哪怕你安装了 libmysqlclient 包,Postfix 也不会去用),MySQL 默认用的 chaching_sha2_password 在 MariaDB 里 没有 ,自然连接不上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$ ldd /usr/lib/postfix/postfix-mysql.so
linux-vdso.so.1 (0x00007fff9f9b5000)
libmariadb.so.3 => /lib/x86_64-linux-gnu/libmariadb.so.3 (0x00007fcb2df44000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcb2dd63000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fcb2dd44000)
libssl.so.3 => /lib/x86_64-linux-gnu/libssl.so.3 (0x00007fcb2dc9b000)
libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007fcb2d800000)
/lib64/ld-linux-x86-64.so.2 (0x00007fcb2dfab000)
$ ldd /usr/lib/postfix/sbin/trivial-rewrite
linux-vdso.so.1 (0x00007ffd4faf3000)
libpostfix-master.so => /usr/lib/postfix/libpostfix-master.so (0x00007f709beff000)
libpostfix-global.so => /usr/lib/postfix/libpostfix-global.so (0x00007f709beb2000)
libpostfix-util.so => /usr/lib/postfix/libpostfix-util.so (0x00007f709be68000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f709bc83000)
libdb-5.3.so => /lib/x86_64-linux-gnu/libdb-5.3.so (0x00007f709bac1000)
libnsl.so.2 => /lib/x86_64-linux-gnu/libnsl.so.2 (0x00007f709baa4000)
libicuuc.so.72 => /lib/x86_64-linux-gnu/libicuuc.so.72 (0x00007f709b8a6000)
/lib64/ld-linux-x86-64.so.2 (0x00007f709bf17000)
libtirpc.so.3 => /lib/x86_64-linux-gnu/libtirpc.so.3 (0x00007f709b878000)
libicudata.so.72 => /lib/x86_64-linux-gnu/libicudata.so.72 (0x00007f7099a00000)
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f7099600000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f7099920000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f709b856000)
libgssapi_krb5.so.2 => /lib/x86_64-linux-gnu/libgssapi_krb5.so.2 (0x00007f709b803000)
libkrb5.so.3 => /lib/x86_64-linux-gnu/libkrb5.so.3 (0x00007f7099846000)
libk5crypto.so.3 => /lib/x86_64-linux-gnu/libk5crypto.so.3 (0x00007f709b7d6000)
libcom_err.so.2 => /lib/x86_64-linux-gnu/libcom_err.so.2 (0x00007f7099840000)
libkrb5support.so.0 => /lib/x86_64-linux-gnu/libkrb5support.so.0 (0x00007f7099832000)
libkeyutils.so.1 => /lib/x86_64-linux-gnu/libkeyutils.so.1 (0x00007f709982b000)
libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007f709981a000)

最简单的解决方案就是 不使用 MySQL 而是使用 MariaDB,这样一切问题便能迎刃而解,笔者不建议大家去折腾其他解决方案,因为会非常麻烦

可能会有人想到获取可以更改 MySQL 的认证插件为 mysql_native_password 使得 MariaDB 能兼容,但是 Debian 上会遇到一系列的各种问题,简而言之笔者想了很多方法都未能成功:(

以及似乎在 Debian 上也没有简单的办法能让 Postfix 默认使用 MySQL 而非 MariaDB, 这时就体现出 Gentoo 的 USE flag 的优越性所在了

Undelivered Mail Returned to Sender

出错了,但我们做对了.jpg

有的时候在你发完邮件之后可能会收到这样一封邮件:

这种情况下一般而言 你的配置是没有问题的,只是你的服务器供应商的网段被对方给拉黑了 ,因此你发出去的邮件会被拒收

解决方案除了通过换 VPS 供应商的方式更换到不同 IP 段以外应该没有别的办法:(


【OPS.0x05】用Postfix+Dovecat+MariaDB搭建邮件系统
https://arttnba3.github.io/2025/03/27/OPS-0X05-POSTFIX_DOVECOT_MARIADB_DEBIAN/
作者
arttnba3
发布于
2025年3月27日
许可协议