使用 Postfix、Dovecot 和 Roundcube 的虚拟用户邮件系统
本文档描述了如何设置一个虚拟用户邮件系统,即发件人和收件人不必对应Linux系统用户。
大致上,本文使用的组件包括:邮件服务器Postfix,IMAP服务器Dovecot,Webmail界面Roundcube,以及用于管理这一切的PostfixAdmin管理界面。
最终,本文提供的解决方案将允许您使用当前可用的最佳安全机制,您将能够通过SMTP和SMTPS发送邮件,并通过POP3、POP3S、IMAP和IMAPS接收邮件。此外,由于PostfixAdmin的存在,配置将变得简单,用户还可以通过Roundcube登录。
安装
开始之前,您必须拥有一个正常工作的MySQL服务器(如MySQL中所述)和一个正常工作的Postfix服务器(如Postfix中所述)。
安装postfix-mysql、dovecot和roundcubemail软件包。
配置
用户
出于安全原因,应创建一个新用户来存储邮件。
# groupadd -g 5000 vmail # useradd -u 5000 -g vmail -s /usr/bin/nologin -d /home/vmail -m vmail
在这两种情况下,都使用了5000的gid和uid,以避免与普通用户发生冲突。您所有的邮件都将存储在/home/vmail中。您可以将主目录更改为类似/var/mail/vmail的位置,但请注意也要在下面的所有配置中进行相应的更改。
数据库
您需要创建一个空的数据库和相应的用户。在本文档中,用户postfix_user将拥有对数据库postfix_db的读/写访问权限,密码为hunter2。您需要自行创建数据库和用户,并授予用户使用该数据库的权限,如下面的代码所示。
$ mysql -u root -p
CREATE DATABASE postfix_db; GRANT ALL ON postfix_db.* TO 'postfix_user'@'localhost' IDENTIFIED BY 'hunter2'; FLUSH PRIVILEGES;
现在您可以访问PostfixAdmin的设置页面,让PostfixAdmin创建所需的表,并在其中创建用户。
PostfixAdmin
请参阅 PostfixAdmin。
SSL证书
您需要一个SSL证书来保护所有加密的邮件通信(SMTPS/IMAPS/POP3S)。如果您没有,请创建一个。
# cd /etc/ssl/private/ # openssl req -new -x509 -nodes -newkey rsa:4096 -keyout vmail.key -out vmail.crt -days 1460 #days are optional # chmod 400 vmail.key # chmod 444 vmail.crt
或者,使用Let's Encrypt创建一个免费受信任的证书。私钥将在/etc/letsencrypt/live/yourdomain/privkey.pem中,证书将在/etc/letsencrypt/live/yourdomain/fullchain.pem中。您可以相应地更改配置,或将密钥符号链接到/etc/ssl/private。
# ln -s /etc/letsencrypt/live/yourdomain/privkey.pem /etc/ssl/private/vmail.key # ln -s /etc/letsencrypt/live/yourdomain/fullchain.pem /etc/ssl/private/vmail.crt
Postfix
在复制粘贴下面的配置之前,请检查relay_domains是否已设置。如果您保留多个活动项,将在运行时收到警告。
relay_domains可能很危险。通常您不希望Postfix转发陌生人的邮件。$mydestination是一个合理的默认值。在运行postfix之前,请仔细检查其值!请参阅https://www.postfix.org/BASIC_CONFIGURATION_README.html#relay_to。同样,请按照Postfix#安全SMTP(接收)的说明,指向您在#SSL证书中创建的文件。
设置Postfix
向/etc/postfix/main.cf追加以下内容:
relay_domains = $mydestination virtual_alias_maps = proxy:mysql:/etc/postfix/virtual_alias_maps.cf virtual_mailbox_domains = proxy:mysql:/etc/postfix/virtual_mailbox_domains.cf virtual_mailbox_maps = proxy:mysql:/etc/postfix/virtual_mailbox_maps.cf virtual_mailbox_base = /home/vmail virtual_mailbox_limit = 512000000 virtual_minimum_uid = 5000 virtual_transport = virtual virtual_uid_maps = static:5000 virtual_gid_maps = static:5000 local_transport = virtual local_recipient_maps = $virtual_mailbox_maps transport_maps = lmdb:/etc/postfix/transport smtpd_sasl_auth_enable = yes smtpd_sasl_type = dovecot smtpd_sasl_path = /var/run/dovecot/auth-client smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination smtpd_sasl_security_options = noanonymous smtpd_sasl_tls_security_options = $smtpd_sasl_security_options smtpd_tls_security_level = may smtpd_tls_auth_only = yes smtpd_tls_received_header = yes smtpd_tls_cert_file = /etc/ssl/private/vmail.crt smtpd_tls_key_file = /etc/ssl/private/vmail.key smtpd_sasl_local_domain = $mydomain smtpd_tls_loglevel = 1 smtp_tls_security_level = may smtp_tls_loglevel = 1
- 在上面的配置中,
virtual_mailbox_domains是您希望接收邮件的域列表。此列表不能包含在mydestination中设置的域。这就是为什么我们将mydestination保留为localhost的原因。
virtual_mailbox_maps将包含虚拟用户及其邮箱位置的信息。我们使用一个哈希文件来存储更持久的映射,这些映射将覆盖MySQL数据库中的转发。
virtual_mailbox_base是虚拟邮箱存储的基础目录。
virtual_uid_maps和virtual_gid_maps是虚拟邮件所属的实际系统用户ID。这用于存储目的。
创建文件结构
这些新增的设置引用了许多尚未存在的文件。我们将通过以下步骤创建它们。
如果您使用PostfixAdmin设置数据库,并且通过PostfixAdmin创建了数据库模式,您可以创建以下文件。不要忘记更改密码。
/etc/postfix/virtual_alias_maps.cf
user = postfix_user password = hunter2 hosts = localhost dbname = postfix_db table = alias select_field = goto where_field = address
/etc/postfix/virtual_mailbox_domains.cf
user = postfix_user password = hunter2 hosts = localhost dbname = postfix_db table = domain select_field = domain where_field = domain
/etc/postfix/virtual_mailbox_maps.cf
user = postfix_user password = hunter2 hosts = localhost dbname = postfix_db table = mailbox select_field = maildir where_field = username
为了支持别名域功能,请调整以下文件:
/etc/postfix/main.cf
virtual_alias_maps = proxy:mysql:/etc/postfix/virtual_alias_maps.cf,proxy:mysql:/etc/postfix/virtual_alias_domains_maps.cf virtual_alias_domains = proxy:mysql:/etc/postfix/virtual_alias_domains.cf
/etc/postfix/virtual_alias_domains_maps.cf
user = postfix_user
password = hunter2
hosts = localhost
dbname = postfix_db
query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('%u', '@', alias_domain.target_domain) AND alias.active = '1' AND alias_domain.active='1'
/etc/postfix/virtual_alias_domains.cf
user = postfix_user password = hunter2 hosts = localhost dbname = postfix_db query = SELECT alias_domain FROM alias_domain WHERE alias_domain='%s' AND active = '1'
/etc/postfix/virtual_alias_maps.cf
user = postfix_user password = hunter2 hosts = localhost dbname = postfix_db table = domains select_field = virtual where_field = domain
/etc/postfix/virtual_mailbox_domains.cf
user = postfix_user password = hunter2 hosts = localhost dbname = postfix_db table = forwardings select_field = destination where_field = source
/etc/postfix/virtual_mailbox_maps.cf
user = postfix_user password = hunter2 hosts = localhost dbname = postfix_db table = users select_field = concat(domain,'/',email,'/') where_field = email
在transport文件上运行postmap以生成其db。
# postmap /etc/postfix/transport
Dovecot
我们将创建自己的/etc/dovecot/dovecot.conf,而不是使用提供的Dovecot示例配置文件。请注意,这里的用户和组可能是vmail,而不是postfix!
/etc/dovecot/dovecot.conf
protocols = imap pop3
auth_mechanisms = plain
passdb {
driver = sql
args = /etc/dovecot/dovecot-sql.conf
}
userdb {
driver = sql
args = /etc/dovecot/dovecot-sql.conf
}
service auth {
unix_listener auth-client {
group = postfix
mode = 0660
user = postfix
}
user = root
}
mail_home = /home/vmail/%d/%n
mail_location = maildir:~
ssl_cert = </etc/ssl/private/vmail.crt
ssl_key = </etc/ssl/private/vmail.key
dovecot.conf.sample,请注意,默认配置文件会导入conf.d/*.conf的内容。这些文件会调用其他不存在于我们配置中的文件。现在我们创建/etc/dovecot/dovecot-sql.conf,我们在上面的配置中刚刚引用了它。使用以下内容,并检查一切是否已根据您的系统配置进行设置。
如果您使用了PostfixAdmin,请添加以下内容:
/etc/dovecot/dovecot-sql.conf
driver = mysql
connect = host=localhost dbname=postfix_db user=postfix_user password=hunter2
# It is highly recommended to not use deprecated MD5-CRYPT. Read more at http://wiki2.dovecot.org/Authentication/PasswordSchemes
default_pass_scheme = SHA512-CRYPT
# Get the mailbox
user_query = SELECT '/home/vmail/%d/%n' as home, 'maildir:/home/vmail/%d/%n' as mail, 5000 AS uid, 5000 AS gid, concat('dirsize:storage=', quota) AS quota FROM mailbox WHERE username = '%u' AND active = '1'
# Get the password
password_query = SELECT username as user, password, '/home/vmail/%d/%n' as userdb_home, 'maildir:/home/vmail/%d/%n' as userdb_mail, 5000 as userdb_uid, 5000 as userdb_gid FROM mailbox WHERE username = '%u' AND active = '1'
# If using client certificates for authentication, comment the above and uncomment the following
#password_query = SELECT null AS password, ‘%u’ AS user
如果您没有使用PostfixAdmin,您可以使用:
/etc/dovecot/dovecot-sql.conf
driver = mysql
connect = host=localhost dbname=postfix_db user=postfix_user password=hunter2
# It is highly recommended to not use deprecated MD5-CRYPT. Read more at http://wiki2.dovecot.org/Authentication/PasswordSchemes
default_pass_scheme = SHA512-CRYPT
# Get the mailbox
user_query = SELECT '/home/vmail/%d/%n' as home, 'maildir:/home/vmail/%d/%n' as mail, 5000 AS uid, 5000 AS gid, concat('dirsize:storage=', quota) AS quota FROM users WHERE email = '%u'
# Get the password
password_query = SELECT email as user, password, '/home/vmail/%d/%n' as userdb_home, 'maildir:/home/vmail/%d/%n' as userdb_mail, 5000 as userdb_uid, 5000 as userdb_gid FROM users WHERE email = '%u'
# If using client certificates for authentication, comment the above and uncomment the following
#password_query = SELECT null AS password, ‘%u’ AS user
DH参数
对于v2.3,您需要自己提供ssl_dh = /path/to/dh.pem。
要生成一个新的DH参数文件(这需要很长时间):
# openssl dhparam -out /etc/dovecot/dh.pem 4096
然后将该文件添加到/etc/dovecot/dovecot.conf中:
ssl_dh = </etc/dovecot/dh.pem
PostfixAdmin
请参阅 PostfixAdmin。
注意:为了与此文件中的配置匹配,config.inc.php应包含以下内容:
# /etc/webapps/postfixadmin/config.inc.php ... $CONF['domain_path'] = 'YES'; $CONF['domain_in_mailbox'] = 'NO'; ...
Roundcube
请参阅 Roundcube。
确保您的php.ini文件中的extension=pdo_mysql和extension=iconv都已取消注释。另外,检查.htaccess文件中的访问限制。假设localhost是您当前的host,请在浏览器中导航到https:///roundcube/installer/并按照说明进行操作。
Roundcube需要一个独立的数据库来工作。您不应将Roundcube和PostfixAdmin使用相同的数据库。创建一个第二个数据库roundcube_db和一个名为roundcube_user的新用户。
在运行安装程序时...
- 对于IMAP主机的地址,即
imap_host,请使用ssl:///或tls:///,而不是仅仅使用localhost。 - 使用端口
993。SMTP也是如此。 - 对于SMTP主机的地址,即
smtp_host,如果您使用了STARTTLS,请使用tls:///和端口587。如果您使用了SMTPS,请使用ssl:///和端口465。如果建立会话失败,请尝试使用tls://yourservername,并将yourservername替换为您的服务器名称。 - 有关此内容的解释,请参阅#Postfix。
- 确保生成的配置文件包含
$config['smtp_user'] = '%u';和$config['smtp_pass'] = '%p';行,否则您将无法发送邮件。
安装后的过程与任何其他Web应用程序(如PhpMyAdmin或PostfixAdmin)类似。配置文件位于/etc/webapps/roundcubemail/config/config.inc.php,它会覆盖defaults.inc.php。
Apache配置
如果您使用Apache,请将示例配置文件复制到您的Web服务器配置目录。
# cp /etc/webapps/roundcubemail/apache.conf /etc/httpd/conf/extra/httpd-roundcubemail.conf
在以下文件中添加此行:
/etc/httpd/conf/httpd.conf
Include conf/extra/httpd-roundcubemail.conf
Roundcube:修改密码插件
要允许用户从Roundcube内部更改密码,请执行以下操作:
通过将此行添加到以下文件中来启用密码插件:
/etc/webapps/roundcubemail/config/config.inc.php
$config['plugins'] = ['password'];
配置密码插件,并确保相应地修改设置。
/usr/share/webapps/roundcubemail/plugins/password/config.inc.php
<?php $config['password_driver'] = 'sql'; $config['password_db_dsn'] = 'mysql://<postfix_database_user>:<password>@localhost/<postfix_database_name>'; // If you are not using dovecot specify another algorithm explicitly e.g 'sha256-crypt' $config['password_algorithm'] = 'dovecot'; // For dovecot salted passwords only (above must be set to 'dovecot') // $config['password_algorithm_prefix'] = 'true'; // $config['password_dovecotpw'] = '/usr/bin/doveadm pw'; // $config['password_dovecotpw_method'] = 'SHA512-CRYPT'; // $config['password_dovecotpw_with_method'] = true; $config['password_query'] = 'UPDATE mailbox SET password=%P WHERE username=%u';
确保此文件只能由http用户和组读取,因为它包含敏感信息。
# chown http:http /usr/share/webapps/roundcubemail/plugins/password/config.inc.php # chmod o-r /usr/share/webapps/roundcubemail/plugins/password/config.inc.php
启动服务
应启动所有必要的守护进程以测试配置。请启动postfix和dovecot。
现在,为了测试目的,请在PostfixAdmin中创建一个域和邮件帐户。尝试使用Roundcube登录该帐户。然后给自己发送一封邮件。
测试
现在让我们看看Postfix是否会为我们的测试用户投递邮件。
nc servername 25 helo testmail.org mail from:<test@testmail.org> rcpt to:<cactus@virtualdomain.tld> data This is a test email. . quit
错误响应
451 4.3.0 <lisi@test.com>:Temporary lookup failure
您可能输入了错误的MySQL用户名/密码,或者MySQL套接字不在正确的位置。
如果您在启动postfix之前至少运行过一次newaliases,否则也会出现此错误。MySQL仅用于postfix的本地使用,不是必需的。
550 5.1.1 <email@spam.me>: Recipient address rejected: User unknown in virtual mailbox table.
仔细检查mysql_virtual_mailboxes.cf的内容,并检查main.cf中的mydestination设置。
确认收到一封邮件
现在输入$ find /home/vmail。
您应该会看到类似以下内容:
/home/vmailer/virtualdomain.tld/cactus@virtualdomain.tld /home/vmailer/virtualdomain.tld/cactus@virtualdomain.tld/tmp /home/vmailer/virtualdomain.tld/cactus@virtualdomain.tld/cur /home/vmailer/virtualdomain.tld/cactus@virtualdomain.tld/new /home/vmailer/virtualdomain.tld/cactus@virtualdomain.tld/new/1102974226.2704_0.bonk.testmail.org
关键是最后一个条目。这是一封实际的邮件,如果您看到了它,说明一切正常。
可选项目
尽管这些项目并非必需,但它们无疑使您的设置更加完善。
配额
要启用Dovecot的邮箱配额支持,请执行以下操作:
- 首先,在
/etc/dovecot/dovecot.conf中添加以下行:
dict {
quotadict = mysql:/etc/dovecot/dovecot-dict-sql.conf.ext
}
service dict {
unix_listener dict {
group = vmail
mode = 0660
user = vmail
}
user = root
}
service quota-warning {
executable = script /usr/local/bin/quota-warning.sh
user = vmail
unix_listener quota-warning {
group = vmail
mode = 0660
user = vmail
}
}
mail_plugins=quota
protocol pop3 {
mail_plugins = quota
pop3_client_workarounds = outlook-no-nuls oe-ns-eoh
pop3_uidl_format = %08Xu%08Xv
}
protocol lda {
mail_plugins = quota
postmaster_address = postmaster@yourdomain.com
}
protocol imap {
mail_plugins = $mail_plugins imap_quota
mail_plugin_dir = /usr/lib/dovecot/modules
}
plugin {
quota = dict:User quota::proxy::quotadict
quota_rule2 = Trash:storage=+10%%
quota_warning = storage=100%% quota-warning +100 %u
quota_warning2 = storage=95%% quota-warning +95 %u
quota_warning3 = storage=80%% quota-warning +80 %u
quota_warning4 = -storage=100%% quota-warning -100 %u # user is no longer over quota
}
- 创建一个新文件
/etc/dovecot/dovecot-sql.conf.ext,包含以下代码:
connect = host=localhost dbname=yourdb user=youruser password=yourpassword
map {
pattern = priv/quota/storage
table = quota2
username_field = username
value_field = bytes
}
map {
pattern = priv/quota/messages
table = quota2
username_field = username
value_field = messages
}
- 创建一个警告脚本
/usr/local/bin/quota-warning.sh,并确保其可执行。此警告脚本也适用于postfix的lmtp配置。
#!/bin/sh
BOUNDARY="$1"
USER="$2"
MSG=""
if [[ "$BOUNDARY" = "+100" ]]; then
MSG="Your mailbox is now overfull (>100%). In order for your account to continue functioning properly, you need to remove some emails NOW."
elif [[ "$BOUNDARY" = "+95" ]]; then
MSG="Your mailbox is now over 95% full. Please remove some emails ASAP."
elif [[ "$BOUNDARY" = "+80" ]]; then
MSG="Your mailbox is now over 80% full. Please consider removing some emails to save space."
elif [[ "$BOUNDARY" = "-100" ]]; then
MSG="Your mailbox is now back to normal (<100%)."
fi
cat << EOF | /usr/lib/dovecot/dovecot-lda -d $USER -o "plugin/quota=maildir:User quota:noenforcing"
From: postmaster@yourdomain.com
Subject: Email Account Quota Warning
Dear User,
$MSG
Best regards,
Your Mail System
EOF
- 编辑
user_query行,并在dovecot-sql.conf中添加iterat_query,如下所示:
user_query = SELECT '/home/vmail/%d/%n' as home, 'maildir:/home/vmail/%d/%n' as mail, 5000 AS uid, 5000 AS gid, concat('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%u' AND active = '1'
iterate_query = SELECT username AS user FROM mailbox
- 如上文“SpamAssassin”部分所述设置LDA。如果您不使用SpamAssassin,则在
/etc/postfix/master.cf中的管道应如下所示:
dovecot unix - n n - - pipe
flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/deliver -f ${sender} -d ${recipient}
如上所述,在Postfix的main.cf中启用它:
virtual_transport = dovecot
- 您可以在postfixadmin中为每个邮箱设置配额。确保config.inc.php中的相关行如下所示:
$CONF['quota'] = 'YES'; $CONF['quota_multiplier'] = '1024000';
重启postfix和dovecot服务。如果一切顺利,您应该能够通过以下命令列出所有用户的配额和使用情况:
doveadm quota get -A
您应该也能在roundcube中看到配额。
Dovecot自动创建和订阅文件夹
要自动创建“常规”邮件层级结构,请按以下方式修改您的/etc/dovecot/dovecot.conf,并根据您的具体需求进行编辑:
namespace inbox {
type = private
separator = /
prefix =
inbox = yes
}
namespace inbox {
mailbox Drafts {
auto = subscribe
special_use = \Drafts
}
mailbox Junk {
auto = subscribe
special_use = \Junk
}
mailbox Trash {
auto = subscribe
special_use = \Trash
}
mailbox Sent {
auto = subscribe
special_use = \Sent
}
}
Dovecot公共文件夹和全局ACL
在此部分,我们将启用IMAP命名空间公共文件夹,并结合全局和每文件夹ACL。
首先,在/etc/dovecot/dovecot.conf中添加以下行:
### ACLs
mail_plugins = acl
protocol imap {
mail_plugins = $mail_plugins imap_acl
}
plugin {
acl = vfile
# With global ACL files in /etc/dovecot/dovecot-acls file (v2.2.11+):
acl = vfile:/etc/dovecot/dovecot-acl
}
### Public Mailboxes
namespace {
type = public
separator = /
prefix = public/
location = maildir:/home/vmail/public:INDEXPVT=~/public
subscriptions = no
list = children
}
创建根目录/home/vmail/public以及您想要公开共享的文件夹,例如(句点是必需的!)/home/vmail/public/.example-1。
更改根目录中所有文件的所有权:
$ chown -R vmail:vmail /home/vmail/public
最后,创建并修改您的全局ACL文件,以允许用户访问这些文件夹:
/etc/dovecot/dovecot-acl
public/* user=admin@example.com lrwstipekxa
在上面的示例中,用户admin@example.com可以访问所有公共文件夹,并对它们执行任何操作。请根据您的具体需求进行编辑。
lrwstipekxa是授予的权限。请访问Dovecot Wiki了解更多详情。- 确保用户在其使用的客户端中订阅了这些文件夹。
反垃圾邮件
要使用SpamAssassin,您必须 将其与SQL数据库一起设置。否则,用户分数和过滤器数据将不会保存,因为用户是虚拟的,没有主目录来保存这些数据。
作为SpamAssassin的替代方案,可以考虑rspamd。开箱即用,它提供了惊人的垃圾邮件减少量、灰名单等功能,并且包含一个漂亮的Web界面。另请参阅[1]。
附注
备用的vmail文件夹结构
与其使用类似/home/vmail/example.com/user@example.com的目录结构,您可以通过替换select_field和where_field来拥有更整洁的子目录(不包含额外的域名):
query = SELECT CONCAT(SUBSTRING_INDEX(email,'@',-1),'/',SUBSTRING_INDEX(email,'@',1),'/') FROM users WHERE email='%s'
故障排除
IMAP/POP3客户端无法接收邮件
如果您遇到类似的错误,请查看/var/log/mail.log或以root身份运行journalctl -xn --unit postfix.service以了解更多信息。
您可能会发现,当至少有一封邮件等待处理时,Maildir/home/vmail/mail@domain.tld才会被创建。否则,在此之前就没有必要创建该目录了。
Roundcube无法删除邮件或查看任何“标准”文件夹
确保Roundcube的config.inc.php文件包含以下内容:
$config['default_imap_folders'] = ['INBOX', 'Drafts', 'Sent', 'Junk', 'Trash']; $config['create_default_folders'] = true; $config['protect_default_folders'] = true;
LMTP / Sieve
LMTP无法连接到sieve?确保您的服务器没有在本地路由消息。这可以在/etc/postfix/main.cf中设置。
mydestination =
发送给Gmail用户的邮件是否被归入垃圾邮件文件夹?
如果您没有实施SPF/DKIM/DMARC策略,Google Gmail(以及大多数其他大型电子邮件提供商)会将您的邮件直接发送到收件人的垃圾邮件文件夹。 (提示:Rspamd,通过上面的链接,会教您如何设置,并且会为您的邮件进行DKIM签名。)
发送和接收邮件中断,日志显示“transport_maps lookup failure”以及“unsupported dictionary type: hash”
Postfix 3.9.0已删除对“hash”传输映射类型的支持。要解决此问题,请停止postfix.service,编辑/etc/postfix/main.cf,将所有“hash:”实例替换为“lmdb:”。然后保存文件,删除/etc/postfix/transport.db,并使用postmap /etc/postfix/transport以新格式重新生成它。之后,您可以启动postfix.service。