使用Postfix,Dovecot,MySQL搭建的邮件服务器

前阵子和几位朋友一同建起了一个小社区网站,期间搭建了一个团队服务器用同步代码和收发邮件,搭建邮件服务还是头一回。现在现成的邮件服务器解决方案也是有的,不过还是自己搭建了一遍,熟悉了一下具体流程。这两天在自己的服务器上也部署了,大致记录下:

结构:

Postfix 提供 SMTP,submission 服务,并对请求进行过滤,转发。SMTP 是邮件投递的基础。Postfix 各种功能可以添加外部实现,比如这里会将 Virtual Mail 通过 LMTP 转给Dovecot,Email 信息查询部分由 MySQL 提供,通过 pypolicyd-spf 进行过滤,用户验证也由 Dovecot 完成。

Dovecot 负责 IMAP 等服务的请求和用户验证,管理虚拟用户的邮箱内容等,和Postfix通过 LMTP 链接。

使用 MySQL 存储邮箱用户的账户信息,MySQL是可选的存储方式之一。

为了安全起见所有链接均用了加密的方式,普通 STMP,IMAP 链接强制必须使用 STARTTLS 升级为安全链接,STMPS,IMAPS 链接和 HTTPS 一样一开始就为加密链接。

安装Postfix、Dovecot、MySQL、pypolicyd-spf

视不同发行版安装相应的包即可。

 

配置:

系统:

建立vmail用户,vmail文件夹

useradd -s /sbin/nologin -d /var/vmail -m -r -g mail vmail

chown vmail:mail /var/vmail

chmod o-rwx vmail /var/vmail

grep vmail /etc/passwd | awk -F : '{print $3}'

建立并获得的vmail的uid,稍后会用到。

也可以指定uid建立vmail用户:

useradd -s /sbin/nologin -d /var/vmail -m -r -u <uid> vmail

建立 aliases.db(如果不存在的话),Postfix会用到

touch /etc/alises

newaliases

MySQL:

初次开启的话记得运行一下mysql_secure_installation。

通过 mysql -u root -p 输入密码进入 Mysql,参考以下步骤建表,插入数据。

create user vmail@localhost identified by 'vmailpassword';

create database vmail;

grant select on vmail.* to vmail@localhost identified by 'vmailpassword';

use vmail;

create table domains (name char(30) not null primary key);

create table users (email char(64) not null primary key, password char(128) not null, domain char(30) not null, foreign key (domain) references domains(name));

create table aliases (source char(64) not null primary key, destination char(64) not null, foreign key (destination) references users(email));

insert into domains values('----.com');

insert into users values('----@----.com', ENCRYPT('--password--here--', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))),'----.com');

insert into aliases values ('--alias--@----.com', '----@----.com');

domains表存放virtual domian,user表存放可登陆的用户邮箱和密码。

Postfix 配置:

配置文件:

注意修改前备份,方便恢复和参考。主要修改以下参数:

设置服务器信息,有单个服务器的情况下设置比较简单,domain为服务器域名:

mydestination 不可与 virtual main domain 冲突,否则发往 Dovecot 的邮件会被 Postfix 截下来,一般会找不到用户而 Reject,如果正好有重名的用户的话应该会发往错误的用户。

mydestination = localhost
mydomain = <domain>
myorigin = <domain>
myhostname = <domain>
mynetworks = 127.0.0.0/8
mynetworks_style = host
relay_domains =

TLS证书设置:

smtpd_tls_cert_file = /etc/ssl/private/----crt----
smtpd_tls_key_file = /etc/ssl/private/----key----
smtpd_tls_CApath = /etc/ssl/certs
smtpd_use_tls = yes

SASL支持设置,设置为不兼容老旧客户端:

smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous, noactive, nodictionary
smtpd_sasl_local_domain = $mydomain
broken_sasl_auth_clients = no

一些安全强化选项,使用高强度的加密算法,和启用TLS,以及设置空的readme文件夹等。

smtpd_tls_mandatory_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CDC3-SHA, KRB5-DE5, CBC3-SHA
smtpd_tls_mandatory_ciphers = high
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, TLSv1, TLSv1.1, TLSv1.2
smtpd_tls_exclude_ciphers = $smtpd_tls_mandatory_exclude_ciphers
smtpd_tls_ciphers = $smtpd_tls_mandatory_ciphers
smtpd_tls_protocols = $smtpd_tls_mandatory_protocols
smtpd_tls_auth_only = yes
smtpd_tls_dh1024_param_file = /etc/postfix/dh2048.pem
smtpd_tls_dh512_param_file = /etc/postfix/dh512.pem
smtpd_tls_eecdh_grade = ultra
smtpd_tls_security_level = may
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes

# outgoing
smtp_tls_mandatory_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CDC3-SHA, KRB5-DE5, CBC3-SHA
smtp_tls_mandatory_ciphers = high
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, TLSv1, TLSv1.1, TLSv1.2
smtp_tls_exclude_ciphers = $smtp_tls_mandatory_exclude_ciphers
smtp_tls_ciphers = $smtp_tls_mandatory_ciphers
smtp_tls_protocols = $smtp_tls_mandatory_protocols
smtp_tls_security_level = may
smtp_tls_note_starttls_offer = yes
smtp_tls_loglevel = 1


tls_random_source = dev:/dev/urandom
tls_preempt_cipherlist = yes
# Log the hostname of a remote SMTP server that offers STARTTLS, when TLS is not already enabled for that server. 

# No readme for more security
readme_directory = no
sample_directory = no

生成对应的文件,在Shell中执行以下语句:

openssl dhparam -out /etc/postfix/dh2048.pem 2048
openssl dhparam -out /etc/postfix/dh512.pem 512

如果强制启用TLS的话,将上面的 *_level = may 改为 enforce。不过会导致和一些邮箱不兼容,比如126,163。

一些行为配置,酌情修改:

在找不到收信人的情况下返回永久错误,让发信服务器不再重试投递:

unknown_local_recipient_reject_code = 550

超时和错误次数限制:

# Time before log a delayed warning
delay_warning_time = 4h
# how long to keep message on queue before return as failed.
maximal_queue_lifetime = 7d
# max and min time in seconds between retries if connection failed
minimal_backoff_time = 1000s
maximal_backoff_time = 8000s
# how long to wait when servers connect before receiving rest of data
smtp_helo_timeout = 60s
# how many address can be used in one message.
# effective stopper to mass spammers, accidental copy in whole address list
# but may restrict intentional mail shots.
smtpd_recipient_limit = 16
# how many error before back off.
smtpd_soft_error_limit = 3
# how many max errors before blocking it.
smtpd_hard_error_limit = 12

配置和Dovecot的链接参数:

通过lmtp协议连接到dovecot,设置vmail的文件夹,用户等参数。

其中 uid 为 vmail 的 uid,gid 为 mail 的 gid,如果不是12的话酌情修改。(grep mail /etc/group)

virtual_transport = lmtp:unix:private/dovecot-lmtp
address_verify_virtual_transport = lmtp:unix:private/dovecot-lmtp
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_mailbox_base = /var/vmail
virtual_uid_maps = static:998
virtual_gid_maps = static:12

其中一些位置可以写死,减少SQL调用,比如只有一个 virtual_mailbox_domain:

virtual_mailbox_domains = ----.com

其中需要三个SQL查询用的配置文件:

/etc/postfix/mysql-virtual-mailbox-domains.cf:

vim mysql-virtual-mailbox-domains.cf

user = vmail
password = vmailpassword
hosts = 127.0.0.1
dbname = vmail
query = SELECT 1 FROM domains WHERE name='%s'

mysql-virtual-mailbox-maps.cf:

vim /etc/postfix/mysql-virtual-mailbox-maps.cf

user = vmail
password = vmailpassword
hosts = 127.0.0.1
dbname = vmail
query = SELECT 1 FROM users WHERE email='%s'

/etc/postfix/mysql-virtual-alias-maps.cf:

vim /etc/postfix/mysql-virtual-alias-maps.cf

user = vmail
password = vmailpassword
hosts = 127.0.0.1
dbname = vmail
query = SELECT destination FROM aliases WHERE source='%s'

最后邮件发送/接受限制,阻止一些简单的垃圾/骚扰邮件:

强制 Client 发送 HELO,启用 REJECT DELAY 和下面的 client 中的 reject_unknown_client_hostname 过滤配合,只让有MX记录/通过验证的用户使用SMTP,禁用VRFY:

smtpd_helo_required = yes
smtpd_delay_reject = yes
disable_vrfy_command = yes

restriction 规则:

tpd_client_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, warn_if_reject, reject_plaintext_session, reject_unknown_client_hostname, permit
smtpd_relay_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, warn_if_reject, reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unauth_destination, check_policy_service unix:private/policyd-spf, permit
smtpd_helo_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, warn_if_reject, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_helo_hostname, permit
smtpd_sender_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, warn_if_reject, reject_non_fqdn_sender, reject_unknown_sender_domain, reject_unverified_sender, permit
smtpd_recipient_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, warn_if_reject, reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unauth_destination, check_policy_service unix:private/policyd-spf, permit
smtpd_data_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, warn_if_reject, reject_multi_recipient_bounce, permit
smtpd_end_of_data_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, warn_if_reject, reject_multi_recipient_bounce, permit
smtpd_etrn_restrictions = reject

SPF设置:延长SPF验证超时时间:

policy_time_limit = 3600

 

编辑 /etc/postfix/master.cf 加入以下来开启SPF服务,配合上面的过滤:

policyd-spf  unix  -       n       n       -       0       spawn
  user=tpe argv=/usr/bin/policyd-spf /etc/policyd-spf/policyd-spf.conf

继续编辑 /etc/postfix/master.cf ,开启SMTP服务:

smtp      inet  n       -       n       -       -       smtpd
...

submission inet n       -       n       -       -       smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
....

smtps     inet  n       -       n       -       -       smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
....

smpts工作于wraper模式,即不要STARTTLS,直接建立TLS链接,其他参数按照main.cf为默认不修改。

执行:

postfix set-permissions

service postfix start

启动 postfix。

 

Dovecot:

配置文件,修改前注意备份:

编辑 /etc/dovecot/dovecot.conf,开启 imap lmtp。 imap 提供外部访问,lmtp给postfix用。

protocols = imap lmtp

需要这一行来加载conf.d中的配置文件。

!include conf.d/*.conf

 

编辑 /etc/dovecot/conf.d/10-mail.conf,指定vmail的文件位置和UID,GID,同时限制Dovecot能使用的GID,UID。如果inbox没有启用的话,取消注释启用。

mail_location = maildir:/var/vmail/%d/%n

mail_uid = vmail
mail_gid = mail

first_valid_uid = 998
last_valid_uid = 998

first_valid_gid = 12
last_valid_gid = 12

 

编辑: /etc/dovecot/conf.d/10-auth.conf,设置验证:

disable_plaintext_auth = yes

auth_mechanisms = plain login

注释除了sql以外的验证途径,仅保留:

!include auth-sql.conf.ext

编辑 conf.d/auth-sql.conf.ext,配置SQL验证的参数:

密码通过MySQL验证,用户登陆后使用的GID,UID统一使用vmail,mail,文件位置统一为/var/mail/。

passdb {
driver = sql

# Path for SQL configuration file, see example-config/dovecot-sql.conf.ext
args = /etc/dovecot/dovecot-sql.conf.ext
}

userdb {
driver = static
args = uid=vmail gid=vmail home=/var/vmail/%d/%n
}

编辑 /etc/dovecot/dovecot-sql.conf.ext,配置查询语句:

driver = mysql

connect = host=localhost dbname=vmail user=vmail password=vmailpassword

default_pass_scheme = SHA512-CRYPT

password_query = \
SELECT email as user, password FROM users WHERE email='%u';

 

编辑 /etc/dovecot/conf.d/10-logging.conf,显示更多日志:

auth_verbose = yes

 

编辑 /etc/dovecot/conf.d/10-master.conf,配置 Dovecot 提供的各种服务参数:

LMTP 和 Auth 服务均以 unix socket 形式与 Postfix 链接。worker 进程以 dovecot 身份运行。socket的位置均放在 /var/spool/postfix/private/ 中:

default_internal_user = dovecot

service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0666
}

# Create inet listener only if you can't use the above UNIX socket
#inet_listener lmtp {
# Avoid making LMTP visible for the entire internet
#address =
#port =
#}
}

service auth {
# auth_socket_path points to this userdb socket by default. It's typically
# used by dovecot-lda, doveadm, possibly imap process, etc. Users that have
# full permissions to this socket are able to get a list of all usernames and
# get the results of everyone's userdb lookups.
#
# The default 0666 mode allows anyone to connect to the socket, but the
# userdb lookups will succeed only if the userdb returns an "uid" field that
# matches the caller process's UID. Also if caller's uid or gid matches the
# socket's uid or gid the lookup succeeds. Anything else causes a failure.
#
# To give the caller full permissions to lookup all users, set the mode to
# something else than 0666 and Dovecot lets the kernel enforce the
# permissions (e.g. 0777 allows everyone full permissions).
unix_listener auth-userdb {
mode = 0666
#user =
#group =
}

# Postfix smtp-auth
unix_listener /var/spool/postfix/private/auth {
mode = 0666
}

# Auth process is run as this user.
user = $default_internal_user
}

default_internal_user = dovecot

service auth-worker {
# Auth worker process is run as root by default, so that it can access
# /etc/shadow. If this isn't necessary, the user should be changed to
# $default_internal_user.
user = $default_internal_user
}

 

编辑 /etc/dovecot/conf.d/10-ssl.conf,管理IMAP服务的链接安全性:

ssl = required

ssl_cert = </etc/ssl/private/<cert_file>
ssl_key = </etc/ssl/private/<key_file>

ssl_dh_parameters_length = 2048

ssl_protocols = !SSLv2 !SSLv3 TLSv1 TLSv1.1 TLSv1.2

ssl_cipher_list = DEFAULT:!EXPORT:!LOW:!MEDIUM:!MD5

ssl_prefer_server_ciphers = yes

其中将dh key长度设置为了2048,启动dovecot 后 dovecot 会去生成新的 ssl-parameters,如果设备性能不高的话会花费很长时间。如果ssl-parameters出现问题可以删除旧的 ssl-parameters (/var/lib/ssl-parameters.dat) 之后重启dovecot。

 

编辑 15-lda.conf,配置LMTP参数:

postmaster_address = postmaster@--domain.com--

hostname = --domain.com--

 

防火墙:

使用Iptables的话,需要打开以下端口:

参考设置(没启用POP3):

#imap/imaps
-A INPUT -p tcp -m tcp --dport 143 -m conntrack --ctstate NEW -j ACCEPT
-A INPUT -p tcp -m tcp --dport 993 -m conntrack --ctstate NEW -j ACCEPT
#smtp/smpts
-A INPUT -p tcp -m tcp --dport 25 -m conntrack --ctstate NEW -j ACCEPT
-A INPUT -p tcp -m tcp --dport 465 -m conntrack --ctstate NEW -j ACCEPT

重新加载iptables,postfix,dovecot,之后服务器应该就可以用了。

 

配置域名:

首先要有个域名,添加MX记录,SPF记录,MX记录用于在接收邮件的时候解析,这样其他邮件服务器可以知道给你投递邮件的话投递到什么地方,SPF记录用来判断一个IP是否有权利以此域名的名义发送邮件,防止别人冒充你。

MX记录为服务器IP即可。

TXT记录可以参考:

v=spf1 mx ~all

意为允许MX记录中的IP地址使用你的域名发信,其他地址均不能以你的名义发信。

 

完成:

邮件服务器建好了,启动服务,之后可以用Thunderbird之类的客户端登陆。

 

TODO:

反垃圾,Docker,性能,Header checker