搭建 SMTP 邮件服务器

使用 Postfix, Dovecot, SQLite 搭建邮件服务器。

名次解释:

  • FQDN完全限定域名。完整域名由主机名称与母域名两部分所组成。
  • rDNS反向 DNS 解析。查询 DNS 来确定与 IP 地址关联的域名。

约定:

  • 本文中所用域名为:example.com,FQDN 为 mail.example.com。

基本的准备

依次完成如下工作:

  • 打开服务器相关端口:
    • 25 (SMTP)
    • 465 (SMTPS)
    • 143 (IMAP)
    • 993 (IMAPS)
  • 设置一条 A 记录,使你的 FQDN 指向服务器 ip。
  • 给你的域名设置一条 MX 记录
  • 给你的服务器设置反向 DNS 解析,使你的服务器 ip 指向你的 FQDN。
  • 设置服务器主机名 (hostname) 为你的 FQDN。

获取域名 SSL 证书

使用 acme.sh 签发即可。下面这篇文章可能会有一点帮助。

利用 GitHub Action 实现 SSL 证书的自动续签

将证书文件放置在如下位置:

/opt/ssl_certificate/fullchain.pem/opt/ssl_certificate/key.pem

安装软件包

包括 Postfix、Dovecot、SQLite3 以及一些辅助性的软件。安装完成后,备份配置文件。

1
2
3
4
5
# apt install postfix postfix-sqlite -y
# apt install dovecot-imapd dovecot-lmtpd dovecot-sqlite -y
# apt install sqlite3 -y
# apt install postgrey postfix-policyd-spf-python opendkim opendkim-tools fail2ban -y
# tar -czvf backup.tar.gz /etc/postfix /etc/dovecot /etc/opendkim.conf

顺便新建用户与用户组 vmail

1
2
3
# groupadd -g 5000 vmail
# useradd -g vmail -u 5000 vmail -d /var/mail -s /sbin/nologin
# chown -R vmail:vmail /var/mail

设置 DKIM记录并配置 opendkim 服务

编辑配置文件。

1
# vim /etc/opendkim.conf

在配置文件中找到如下几行,取消这几行的注释,并将 simple 改为 relaxed/simple

1
2
3
Canonicalization   simple
Mode               sv
SubDomains         no

接着找到这一行 (#ADSPAction continue),并将下面的内容添加到这行后面。如果没有找到这一行,只需要将下面的内容添加到这一行 (SubDomains no) 后面即可。

1
2
3
4
5
AutoRestart         yes
AutoRestartRate     10/1M
Background          yes
DNSTimeout          5
SignatureAlgorithm  rsa-sha256

将如下内容添加到文件末尾。在 Ubuntu 18.04 上,UserID 已经设置为 opendkim,无需重复添加。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#OpenDKIM user
# Remember to add user postfix to group opendkim
UserID             opendkim

# Map domains in From addresses to keys used to sign messages
KeyTable           refile:/etc/opendkim/key.table
SigningTable       refile:/etc/opendkim/signing.table

# Hosts to ignore when verifying signatures
ExternalIgnoreList  /etc/opendkim/trusted.hosts

# A set of internal hosts whose mail should be signed
InternalHosts       /etc/opendkim/trusted.hosts

然后找到这一行:

1
Socket                  local:/var/run/opendkim/opendkim.sock

把它替换为:

1
Socket                  local:/var/spool/postfix/opendkim/opendkim.sock

保存文件并退出 vim。

执行如下命令,创建所需文件夹并编辑配置文件。

1
2
# mkdir -p /etc/opendkim/keys/example.com
# vim /etc/opendkim/signing.table

写入如下内容:

1
*@example.com    default._domainkey.example.com

保存。注意上述内容中的域名要替换为你要部署的真实域名,后续不再提示。

编辑文件。

1
# vim /etc/opendkim/key.table

写入如下内容:

1
default._domainkey.example.com     example.com:default:/etc/opendkim/keys/example.com/default.private

保存。注意,上面的内容为一整行,而不是两行分开。

编辑文件。

1
# vim /etc/opendkim/trusted.hosts

写入如下内容:

1
2
3
4
127.0.0.1
localhost

*.example.com

保存。

生成 opendkim key 并输出。

1
2
3
# opendkim-genkey -b 2048 -d example.com -D /etc/opendkim/keys/example.com -s default -v
# chown -R opendkim:opendkim /etc/opendkim
# cat /etc/opendkim/keys/example.com/default.txt

将输出的 opendkim key 按如下格式设置域名的 TXT 记录。注意删除记录值中多余的双引号。

TXT default._domainkey v=DKIM1; K=rsa; p=xxxxxx;

使用如下命令测试是否配置成功。

1
# opendkim-testkey -d example.com -s default -vvv

执行如下命令,使 Postfix 可以使用 opendkim 服务。

1
2
3
4
# gpasswd -a postfix opendkim
# mkdir /var/spool/postfix/opendkim
# chown opendkim:postfix /var/spool/postfix/opendkim
# systemctl restart opendkim

设置 SPF 记录

按如下格式设置域名的 TXT 记录

TXT @ v=spf1 mx ~all

设置 DMARC 记录

按如下格式设置域名的 TXT 记录

TXT _dmarc v=DMARC1; p=none; pct=100; sp=none; adkim=r; aspf=r; fo=1; rua=mailto:your_[email protected]; ruf=mailto:your_[email protected];

配置拦截政策

白名单

1
# vim /etc/postfix/rbl_override
1
2
dripemail2.com  OK          //drip.com
mlsend.com      OK          //mailerlite email marketing service
1
# postmap /etc/postfix/rbl_override

HELO/EHLO 主机名

1
# vim /etc/postfix/helo_access
1
2
optimus-webapi-prod-2.localdomain       OK
va-massmail-02.rakutenmarketing.com     OK
1
# postmap /etc/postfix/helo_access

灰名单

1
# vim /etc/default/postgrey

找到下面这一行:

1
POSTGREY_OPTS="--inet=10023"

把它改为:

1
POSTGREY_OPTS="--inet=127.0.0.1:10023 --delay=60"

然后重启 postgrey 服务。

1
# systemctl restart postgrey

配置 Fail2ban 工具

fail2ban 能有效防止常见的洪水攻击。

编辑 postfix 规则。

1
# vim /etc/fail2ban/jail.d/postfix.conf

内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[postfix]
enabled     = true
logpath     = %(syslog_mail)s
maxretry    = 3
bantime     = 1h
filter      = postfix

[postfix-flood-attack]
enabled     = true
logpath     = %(syslog_mail)s
bantime     = 1h
filter      = postfix-flood-attack
port        = smtp,smtps,imap,imaps,sieve

保存。然后编辑 dovecot 规则。

1
# vim /etc/fail2ban/jail.d/dovecot.conf

内容如下:

1
2
3
4
5
6
[dovecot]
enabled     = true
logpath     = %(syslog_mail)s
maxretry    = 3
bantime     = 1h
filter      = dovecot

保存。然后编辑 postfix 过滤器。

1
# vim /etc/fail2ban/filter.d/postfix-flood-attack.conf

内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[INCLUDES]
before = common.conf

[Definition]
_daemon = postfix(-\w+)?/(?:submission/|smtps/)?smtp[ds]
failregex = ^%(__prefix_line)slost connection after AUTH from \S+\[<HOST>\]$
ignoreregex =

[Init]
journalmatch = _SYSTEMD_UNIT=postfix.service

保存。重启 fail2ban 服务。

1
# systemctl restart fail2ban

创建数据库表

创建并调用 sqlite 数据库。

1
# sqlite3 /etc/postfix/postfix.sqlite

调整格式化输出。

1
2
3
sqlite> .header on
sqlite> .mode column
sqlite> .timer on

依次执行如下 SQL 语句,创建数据库表。

 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
CREATE TABLE domains (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  domain varchar(255) NOT NULL UNIQUE,
  description varchar(255) NOT NULL default '',
  active tinyint(1) NOT NULL default '1'
);

CREATE TABLE users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username varchar(255) NOT NULL,
  password varchar(255) NOT NULL,
  domain_id INTEGER NOT NULL,
  quota bigint(20) NOT NULL default '0',
  created datetime NOT NULL default (datetime('now', 'localtime')),
  active tinyint(1) NOT NULL default '1',
  FOREIGN KEY(domain_id) REFERENCES domains(id)
);

CREATE TABLE aliases (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  domain_id int(11) NOT NULL,
  source varchar(255) NOT NULL,
  destination varchar(255) NOT NULL,
  active tinyint(1) NOT NULL default '1',
  FOREIGN KEY(domain_id) REFERENCES domains(id)
);

INSERT INTO domains (domain) VALUES ('example.com');
INSERT INTO users (username, password, domain_id) VALUES ('i', '{CRYPT}$2y$05$....', 1);
INSERT INTO aliases (domain_id, source, destination) VALUES (1, '[email protected]', '[email protected]');

注:邮箱用户密码可暂时使用如下命令生成:

1
# doveadm pw -p "passwd"

配置 Postfix

编辑 /etc/postfix/main.cf

1
# vim /etc/postfix/main.cf

替换为如下内容:

  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
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#----- General -----

myhostname = mail.example.com
myorigin = /etc/mailname
mydestination = localhost
relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all
mynetworks_style = host
append_dot_mydomain = no
readme_directory = no
compatibility_level = 2


#----- SMTPD parameters -----

biff = no
#delay_warning_time = 4h
disable_vrfy_command = yes
strict_rfc821_envelopes = yes
#smtputf8_enable = no

smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
smtpd_delay_reject = yes
smtpd_helo_required = yes
smtpd_timeout = 30s
smtpd_recipient_limit = 40
#smtpd_etrn_restrictions = reject
#smtpd_reject_unlisted_sender = yes
#smtpd_reject_unlisted_recipient = yes
#smtpd_hard_error_limit = 1

smtp_always_send_ehlo = yes
smtp_helo_timeout = 15s
smtp_rcpt_timeout = 15s

#maximal_queue_lifetime = 1d
#bounce_queue_lifetime = 1d
minimal_backoff_time = 180s
maximal_backoff_time = 3h


#----- TLS parameters -----
smtpd_use_tls = yes
smtpd_tls_auth_only = yes
smtpd_tls_cert_file = /opt/ssl_certificate/fullchain.pem
smtpd_tls_key_file = /opt/ssl_certificate/key.pem
smtpd_tls_ciphers = high
smtpd_tls_security_level = may
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_loglevel = 0
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtpd_tls_received_header = yes
smtp_tls_security_level = may
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_loglevel = 0
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache


#----- SASL parameters -----
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
#smtpd_sasl_local_domain =
#smtpd_sasl_authenticated_header = no
#broken_sasl_auth_clients = no


#----- Virtual domains -----

alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
virtual_transport = lmtp:unix:private/dovecot-lmtp
virtual_mailbox_domains = sqlite:/etc/postfix/sqlite_mailbox_domains.cf
virtual_mailbox_maps = sqlite:/etc/postfix/sqlite_mailbox_maps.cf
virtual_alias_maps = sqlite:/etc/postfix/sqlite_alias_maps.cf
#virtual_uid_maps = static:5000
#virtual_gid_maps = static:5000


#----- Restrictions -----

smtpd_recipient_restrictions =
  permit_mynetworks,
  permit_sasl_authenticated,
  reject_non_fqdn_recipient,
  reject_unknown_recipient_domain,
  reject_unlisted_recipient,
  reject_unauth_destination,
  reject_unauth_pipelining,
  check_policy_service unix:private/policyd-spf,
  check_policy_service inet:127.0.0.1:10023,
  check_client_access hash:/etc/postfix/rbl_override,
  reject_rhsbl_helo dbl.spamhaus.org,
  reject_rhsbl_reverse_client dbl.spamhaus.org,
  reject_rhsbl_sender dbl.spamhaus.org
smtpd_sender_restrictions =
  permit_mynetworks,
  permit_sasl_authenticated,
  reject_sender_login_mismatch,
  reject_non_fqdn_sender,
  reject_unknown_sender_domain,
  reject_unknown_reverse_client_hostname,
  reject_unknown_client_hostname,
  reject_unauth_pipelining
smtpd_helo_restrictions =
  permit_mynetworks,
  permit_sasl_authenticated,
  check_helo_access hash:/etc/postfix/helo_access,
  reject_invalid_helo_hostname,
  reject_non_fqdn_helo_hostname,
  reject_unknown_helo_hostname
smtpd_relay_restrictions =
  permit_mynetworks,
  permit_sasl_authenticated,
  defer_unauth_destination,
  check_helo_access hash:/etc/postfix/helo_access,
  reject_invalid_helo_hostname,
  reject_non_fqdn_helo_hostname,
  reject_unknown_helo_hostname
smtpd_client_restrictions =
  permit_mynetworks,
  permit_sasl_authenticated,
  reject_unknown_client_hostname,
  reject_rbl_client bl.spamcop.net,
  reject_rbl_client zen.spamhaus.org,
  reject_rbl_client blackholes.easynet.nl
smtpd_data_restrictions =
  reject_unauth_pipelining


#----- policyd-spf -----

policyd-spf_time_limit = 3600


#----- Opendkim -----

milter_default_action = accept
milter_protocol = 6
smtpd_milters = local:/opendkim/opendkim.sock
non_smtpd_milters = $smtpd_milters

保存。然后编辑 /etc/postfix/master.cf

1
# vim /etc/postfix/master.cf

取消 smtps 小节的注释,并调整为如下几行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
smtps     inet  n       -       y       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_security_level=encrypt
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_sasl_type=dovecot
  -o smtpd_sasl_path=private/auth
  -o smtpd_sasl_security_options=noanonymous
  -o smtpd_sasl_local_domain=$myhostname
  -o smtpd_reject_unlisted_recipient=no
  -o smtpd_sender_login_maps=sqlite:/etc/postfix/sqlite_mailbox_maps.cf
  -o smtpd_sender_restrictions=reject_sender_login_mismatch
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o smtpd_recipient_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_mynetworks,permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

在文件末尾添加下面几行:

1
2
policyd-spf  unix  -       n       n       -       0       spawn
  user=policyd-spf argv=/usr/bin/policyd-spf

保存。然后编辑 sqlite_mailbox_domains.cf

1
# vim /etc/postfix/sqlite_mailbox_domains.cf

内容为:

1
2
3
dbpath = /etc/postfix/postfix.sqlite
query = SELECT domain FROM domains WHERE domain='%s'
result_format = %s

保存。然后编辑 sqlite_mailbox_maps.cf

1
# vim /etc/postfix/sqlite_mailbox_maps.cf

内容为:

1
2
3
dbpath = /etc/postfix/postfix.sqlite
query = SELECT username||'@'|| FROM users INNER JOIN domains on users.domain_id=domains.id WHERE username='%u'
result_format = %s

保存。然后编辑 sqlite_alias_maps.cf

1
# vim /etc/postfix/sqlite_alias_maps.cf

内容为:

1
2
3
dbpath = /etc/postfix/postfix.sqlite
query = SELECT destination FROM aliases WHERE source = '%s'
result_format = %s

配置 Dovecot

编辑 /etc/dovecot/dovecot.conf

protocols = imap lmtp 添加到 # Enable installed protocols 的后面。

保存。然后编辑 /etc/dovecot/conf.d/10-auth.conf

修改文件内容为如下几行:

1
2
3
disable_plaintext_auth = yes
auth_mechanisms = plain login
!include auth-sql.conf.ext

保存。然后编辑 /etc/dovecot/conf.d/10-mail.conf

修改文件内容为如下几行:

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

保存。然后编辑 /etc/dovecot/conf.d/10-master.conf

编辑 service auth 块为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
service auth {
  unix_listener auth-userdb {
    mode = 0600
    user = vmail
    group = vmail
  }
  unix_listener /var/spool/postfix/private/auth {
    mode = 0660
    user = postfix
    group = postfix
  }
}

编辑 service lmtp 块为:

1
2
3
4
5
6
7
service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    mode = 0600
    user = postfix
    group = postfix
  }
}

编辑 service auth-worker 块为:

1
2
3
service auth-worker {
  #user = vmail
}

保存。然后编辑 /etc/dovecot/conf.d/10-ssl.conf

修改文件内容为如下几行:

1
2
3
4
ssl = required
ssl_cert = </opt/ssl_certificate/fullchain.pem
ssl_key = </opt/ssl_certificate/key.pem
ssl_min_protocol = TLSv1.2

保存。然后编辑 /etc/dovecot/conf.d/auth-sql.conf.ext

修改文件内容为如下几行:

1
2
3
4
5
6
7
8
passdb {
  driver = sql
  args = /etc/dovecot/dovecot-sql.conf.ext
}
userdb {
  driver = static
  args = uid=vmail gid=vmail home=/var/mail/%d/%n
}

保存。然后编辑 /etc/dovecot/dovecot-sql.conf.ext

修改文件内容为如下几行:

1
2
3
4
5
6
7
driver = sqlite
connect = /etc/postfix/postfix.sqlite
default_pass_scheme = SHA512-CRYPT
password_query = \
  SELECT username, domain, password \
  FROM users INNER JOIN domains ON users.domain_id=domains.id \
  WHERE username = '%n' AND domain = '%d'

测试

Newsletters spam test

参考

以上。


作者

iMaple

发布于

2020-04-02

更新于

2020-06-06

许可协议

CC BY-NC 4.0

评论