Virtual user mail system with Postfix, Dovecot and Roundcube (简体中文)
这篇文章主要介绍怎么配置一个使用虚拟用户的邮件系统。例如:些邮件系统的发信人、收信人并不是linux的系统用户(不存在于/etc/passwd
中)。
概略来讲,本文使用Postfix提供邮件发送服务, 使用Dovecot提供IMAP接收服务, 使用Roundcube作为邮件的web前端, 使用PostfixAdmin作为管理界面来管控这一整套系统。
本文提供的解决方案将允许您使用当前最好的安全机制,您将能够使用SMTP和SMTPS发送邮件,并使用POP3,POP3S,IMAP和IMAPS接收邮件。 此外,由于使用了PostfixAdmin,配置将很容易,用户将能够使用Roundcube登录。
安装
开始这一步之前, 你需要按照链接中的页面安装好Mysql和Postfix。
安装 这两个软件包: dovecot 和 roundcubemail 。
配置
用户
出于安全原因,应创建一个新用户来存储邮件:
# groupadd -g 5000 vmail # useradd -u 5000 -g vmail -s /usr/bin/nologin -d /home/vmail -m vmail
gid 和 uid 都使用 5000 ,这样可以避免和普通用户的冲突。所有你的邮件都会存储在 /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证书来加密邮件通信(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
Before you copy & paste the configuration below, check if relay_domains
has already been already set. If you leave more than one active, you will receive warnings during runtime.
relay_domains
can be dangerous. You usually do not want Postfix to forward mail of strangers. $mydestination
is a sane default value. Double check its value before running postfix! See http://www.postfix.org/BASIC_CONFIGURATION_README.html#relay_to
Also follow Postfix#Secure SMTP (receiving) pointing to the files you created in #通信加密证书.
Setting up Postfix
To /etc/postfix/main.cf
append:
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 = hash:/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 broken_sasl_auth_clients = yes smtpd_tls_loglevel = 1 smtp_tls_security_level = may smtp_tls_loglevel = 1
- In the configuration above
virtual_mailbox_domains
is a list of the domains that you want to receive mail for. This CANNOT contain the domain that is set inmydestination
. That is why we leftmydestination
to be localhost only.
-
virtual_mailbox_maps
will contain the information of virtual users and their mailbox locations. We are using a hash file to store the more permanent maps, and these will then override the forwards in the MySQL database.
-
virtual_mailbox_base
is the base directory where the virtual mailboxes will be stored.
The virtual_uid_maps
and virtual_gid_maps
are the real system user IDs that the virtual mails will be owned by. This is for storage purposes.
Create the file structure
Those new additional settings reference a lot of files that do not even exist yet. We will create them with the following steps.
If you were setting up your database with PostfixAdmin and created the database schema through PostfixAdmin, you can create the following files. Do not forget to change the password:
/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
For alias domains functionality adjust the following files:
/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
Run postmap on transport to generate its db:
# postmap /etc/postfix/transport
Dovecot
Instead of using the provided Dovecot example config file, we will create our own /etc/dovecot/dovecot.conf
. Please note that the user and group here might be vmail instead of 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
, beware that the default configuration file imports the content of conf.d/*.conf
. Those files call other files that are not present in our configuration.Now we create /etc/dovecot/dovecot-sql.conf
, which we just referenced in the config above. Use the following contents and check if everything is set accordingly to your system's configuration.
If you used PostfixAdmin, then you add the following:
/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
Without having used PostfixAdmin you can use:
/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 parameters
With v2.3 you are required to provide ssl_dh = </path/to/dh.pem
yourself.
To generate a new DH parameters file (this will take very long):
# openssl dhparam -out /etc/dovecot/dh.pem 4096
then add the file to /etc/dovecot/dovecot.conf
ssl_dh = </etc/dovecot/dh.pem
PostfixAdmin
See PostfixAdmin.
Note: To match the configuration in this file, config.inc.php should contain the following.
# /etc/webapps/postfixadmin/config.inc.php ... $CONF['domain_path'] = 'YES'; $CONF['domain_in_mailbox'] = 'NO'; ...
Roundcube
Make sure that both extension=pdo_mysql
and extension=iconv
are uncommented in your php.ini
file. Also check the .htaccess
for access restrictions. Assuming that localhost is your current host, navigate a browser to http://localhost/roundcube/installer/
and follow the instructions.
Roundcube needs a separate database to work. You should not use the same database for Roundcube and PostfixAdmin. Create a second database roundcube_db
and a new user named roundcube_user
.
While running the installer ...
- For the address of the IMAP host, use
ssl://localhost/
ortls://localhost/
and not justlocalhost
. - Use port
993
. Likewise with SMTP. - For the address of the SMTP host, use
tls://localhost/
and port587
if you used the proper TLS mode.
- (use
ssl://localhost/
with port465
if you used the wrapper mode)
- See #Postfix for an explanation on that.
The post install process is similar to any other webapp like PhpMyAdmin or PostFixAdmin. The configuration file is in /etc/webapps/roundcubemail/config/config.inc.php
which works as an override over default.inc.php
.
Apache configuration
If you are using Apache, copy the example configuration file to your webserver configuration directory.
# cp /etc/webapps/roundcubemail/apache.conf /etc/httpd/conf/extra/httpd-roundcubemail.conf
Add the following line in
/etc/httpd/conf/httpd.conf
Include conf/extra/httpd-roundcubemail.conf
Roundcube: Change Password Plugin
To let users change their passwords from within Roundcube, do the following:
Enable the password plugin by adding this line to
/etc/webapps/roundcubemail/config/config.inc.php
$config['plugins'] = array('password');
Configure the password plugin and make sure you alter the settings accordingly:
/usr/share/webapps/roundcubemail/plugins/password/config.inc.php
$config['password_driver'] = 'sql'; $config['password_db_dsn'] = 'mysql://<postfix_database_user>:<password>@localhost/<postfix_database_name>'; // for dovecot salted passwords only // $config['password_dovecotpw'] = 'doveadm pw'; // $config['password_dovecotpw_method'] = 'SHA512-CRYPT'; // $config['password_dovecotpw_with_method'] = true; $config['password_query'] = 'UPDATE mailbox SET password=%c WHERE username=%u';
Fire it up
All necessary daemons should be started in order to test the configuration. Start both postfix
and dovecot
.
Now for testing purposes, create a domain and mail account in PostfixAdmin. Try to login to this account using Roundcube. Now send yourself a mail.
Testing
Now lets see if Postfix is going to deliver mail for our test user.
nc servername 25 helo testmail.org mail from:<test@testmail.org> rcpt to:<cactus@virtualdomain.tld> data This is a test email. . quit
Error response
451 4.3.0 <lisi@test.com>:Temporary lookup failure
Maybe you have entered the wrong user/password for MySQL or the MySQL socket is not in the right place.
This error will also occur if you neglect to run newaliases at least once before starting postfix. MySQL is not required for local only usage of postfix.
550 5.1.1 <email@spam.me>: Recipient address rejected: User unknown in virtual mailbox table.
Double check content of mysql_virtual_mailboxes.cf and check the main.cf for mydestination
See that you have received a email
Now type $ find /home/vmailer
.
You should see something like the following:
/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
The key is the last entry. This is an actual email, if you see that, it is working.
Optional Items
Although these items are not required, they definitely add more completeness to your setup
Quota
To enable mailbox quota support by dovecot, do the following:
- First add the following lines to /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 }
- Create a new file /etc/dovecot/dovecot-dict-sql.conf.ext with the following code:
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 }
- Create a warning script /usr/local/bin/quota-warning.sh and make sure it is executable. This warning script works with postfix lmtp configuration as well.
#!/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
- Edit the user_query line and add iterat_query in dovecot-sql.conf as following:
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
- Set up LDA as described above under SpamAssassin. If you are not using SpamAssassin, the pipe should look like this in /etc/postfix/master.cf :
dovecot unix - n n - - pipe flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/deliver -f ${sender} -d ${recipient}
As above activate it in Postfix main.cf
virtual_transport = dovecot
- You can set up quota per each mailbox in postfixadmin. Make sure the relevant lines in config.inc.php look like this:
$CONF['quota'] = 'YES'; $CONF['quota_multiplier'] = '1024000';
Restart postfix and dovecot services. If things go well, you should be able to list all users' quota and usage by the this command:
doveadm quota get -A
You should be able to see the quota in roundcube too.
Autocreate and autosubscribe folders in Dovecot
To automatically create the "usual" mail hierarchy, modify your /etc/dovecot/dovecot.conf
as follows, editing to your specific needs.
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 public folder and global ACLs
In this section we enable IMAP namespace public folders combined with global and per-folder ACLs.
First, add the following lines to /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 }
Create the root directory /home/vmail/public
and the folders you want to publicly share, for example (the period is required!) /home/vmail/public/.example-1
.
Change the ownership of all files in the root directory:
$ chown -R vmail:vmail /home/vmail/public
Finally, create and modify your global ACL file to allow users access to these folders:
/etc/dovecot/dovecot-acl
public/* user=admin@example.com lrwstipekxa
In the above example, user admin@example.com
has access to, and can do anything to, all the public folders. Edit to fit your specific needs.
-
lrwstipekxa
are the permissions being granted. Visit the Dovecot wiki for further details. - Make sure the user subscribes to the folders in the client they are using.
Fighting Spam
As an alternative to SpamAssassin, consider rspamd. Out of the box, it delivers an amazing amount of spam reduction, greylisting, etc and includes a nifty webui. See also [1].
Sidenotes
Alternative vmail folder structure
Instead of having a directory structure like /home/vmail/example.com/user@example.com
you can have cleaner subdirectories (without the additional domain name) by replacing select_field
and where_field
with:
query = SELECT CONCAT(SUBSTRING_INDEX(email,'@',-1),'/',SUBSTRING_INDEX(email,'@',1),'/') FROM users WHERE email='%s'
Troubleshooting
IMAP/POP3 client failing to receive mails
If you get similar errors, take a look into /var/log/mail.log
or use journalctl -xn --unit postfix.service
to find out more.
It may turn out that the Maildir /home/vmail/mail@domain.tld
is just being created if there is at least one email waiting. Otherwise there would not be any need for the directory creation before.
Roundcube not able to delete emails or view any 'standard' folders
Ensure that the Roundcube config.inc.php file contains the following:
$rcmail_config['default_imap_folders'] = array('INBOX', 'Drafts', 'Sent', 'Junk', 'Trash'); $rcmail_config['create_default_folders'] = true; $rcmail_config['protect_default_folders'] = true;
LMTP / Sieve
Is LMTP not connecting to sieve? Ensure that your server is not routing the messages locally. This can be set in /etc/postfix/main.cf
:
mydestination =
Are your emails sent to gmail users ending up in their junk/spam folders?
Google gmail (and most other large email providers) will send your emails straight into your recipients junk / spam folder if you have not implemented SPF / DKIM / DMARC policies. (Hint: Rspamd, via the link above, shows you how to set this up, and will DKIM sign your emails.)