archlinux部署mail系统

这篇文章主要介绍怎么配置一个使用虚拟用户的邮件系统。例如:些邮件系统的发信人、收信人并不是linux的系统用户(不存在于/etc/passwd中)。

简单来讲,本文使用Postfix提供邮件发送服务, 使用Dovecot提供IMAP接收服务, 使用Roundcube作为邮件的web前端, 使用PostfixAdmin作为管理界面来管控这一整套系统, 目前适配PHP8

本文提供的解决方案将允许您使用当前最好的安全机制,您将能够使用SMTP和SMTPS发送邮件,并使用POP3,POP3S,IMAP和IMAPS接收邮件。 此外,由于使用了PostfixAdmin,配置将很容易,用户将能够使用Roundcube登录。

特别说明:邮件系统需要稳定运行,至少需要1c1g内存的机器,否则会导致OOM

安装

开始这一步之前, 你需要按照链接中的页面安装好mysql和postfix

[root@mail ~]# pacman -S mysql postfix postfix-mysql postfix-lmdb dovecot

安装认证的软件包和webmail

[root@mail ~]# pacman -S php php-fpm nginx nginx-mod-cache_purge
[root@mail ~]# pacman -S postfixadmin roundcubemail

配置

Postfix

您需要将发往“root”的所有邮件映射到另一个帐户vmail
[root@mail ~]# nano -w /etc/postfix/aliases

root:           vmail 

检查配置 Check configuration

运行postfix check 命令来完成配置检查。它会输出所有你在配置文件中可能写错的东西。

运行postconf命令可以查看所有的配置。运行postconf -n命令可以查看与默认配置的区别。

加密SMTP发送

nano -w /etc/postfix/main.cf
# 发送加密
smtp_tls_security_level = may
smtp_tls_loglevel = 1

加密SMTP接收

nano -w /etc/postfix/main.cf
# 接收加密
smtpd_tls_security_level = may
smtpd_tls_cert_file = /etc/ssl/private/vmail.crt
smtpd_tls_key_file = /etc/ssl/private/vmail.key
smtpd_tls_loglevel = 1

设置SMTP服务

nano -w /etc/postfix/master.cf
# Choose one: enable submission for loopback clients only, or for any client.
#127.0.0.1:submission 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
  -o smtpd_tls_auth_only=yes
  -o smtpd_reject_unlisted_recipient=no
#     Instead of specifying complex smtpd_<xxx>_restrictions here,
#     specify "smtpd_<xxx>_restrictions=$mua_<xxx>_restrictions"
#     here, and specify mua_<xxx>_restrictions in main.cf (where
#     "<xxx>" is "client", "helo", "sender", "relay", or "recipient").
#  -o smtpd_client_restrictions=
#  -o smtpd_helo_restrictions=
#  -o smtpd_sender_restrictions=
  -o smtpd_relay_restrictions=
  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING
# Choose one: enable submissions for loopback clients only, or for any client.
#127.0.0.1:submissions inet n  -       n       -       -       smtpd
submissions     inet  n       -       n       -       -       smtpd
  -o syslog_name=postfix/submissions
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_reject_unlisted_recipient=no
#     Instead of specifying complex smtpd_<xxx>_restrictions here,
#     specify "smtpd_<xxx>_restrictions=$mua_<xxx>_restrictions"
#     here, and specify mua_<xxx>_restrictions in main.cf (where
#     "<xxx>" is "client", "helo", "sender", "relay", or "recipient").
#  -o smtpd_client_restrictions=
#  -o smtpd_helo_restrictions=
#  -o smtpd_sender_restrictions=
  -o smtpd_relay_restrictions=
  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

smtpd_*_restrictions 相关选项没有取消注释,是因为$mua_*_restrictions 并没有在main.cf 中定义.

检查服务定义

#nano -w /etc/services:

#smtps 465/tcp # Secure SMTP
#smtps 465/udp # Secure SMTP

如果没有服务定义,postfix将无法启动

postfix/master[5309]: fatal: 0.0.0.0:smtps: Servname not supported for ai_socktype

nano -w /etc/postfix/main.cf

myhostname = mail.6us.cn
mydomain = 6us.cn
myorigin = $mydomain 
inet_interfaces = all 
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain, mail.$mydomain

用户

出于安全原因,应创建一个新用户来存储邮件:

[root@mail ~]# groupadd -g 5000 vmail
[root@mail ~]# useradd -u 5000 -g vmail -s /usr/bin/nologin -d /srv/vmail -m vmail
# 否则无法调用doveadm pw
[root@mail ~]# usermod -aG dovecot postfixadmin

gid 和 uid 都使用 5000 ,这样可以避免和普通用户的冲突。所有你的邮件都会存储在 /srv/vmail中。也可以将家目录更改到像是 /home/vmail/mail 这样的你自已定义的目录,需要注意的是下面的所有配置中都要对应修改。

数据库

新安装的数据库需要初始化

[root@mail etc]# mariadb-install-db --user=mysql --basedir=/usr --datadir=/srv/mysql

nano -w /etc/my.cnf.d/server.cnf

[mysqld]
datadir=/srv/mysql

你需要建立一个空数据库和相应的用户。 在这篇文章中,用户:postfix_user 有 读/写 这个数据库: postfix_db 的权限 我们将这个用户的密码设为: 12345678 。 你需要去创建数据库和用户,并给它们合适的权限。下面列出了操作命令:

$ mysql -u root
mysql> CREATE DATABASE postfix_db;
mysql> CREATE USER 'postfix_user'@'localhost' IDENTIFIED BY '12345678';
mysql> GRANT ALL PRIVILEGES ON postfix_db.* TO 'postfix_user'@'localhost' WITH GRANT OPTION;
mysql> flush privileges;

新的数据库没有管理员,按照下面的方法创建

CREATE USER 'root'@'%' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
flush privileges;
ALTER USER 'root'@'localhost' IDENTIFIED BY 'password';

注意: mysql可以只能使用mariadb

通信加密证书

您将需要一个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

使用 PostfixAdmin

nano -w /etc/webapps/postfixadmin/config.local.php

<?php
$CONF['configured'] = true;
// correspond to dovecot maildir path /srv/vmail/%d/%u
$CONF['domain_path'] = 'YES';
$CONF['domain_in_mailbox'] = 'NO';
$CONF['database_type'] = 'mysqli';
$CONF['database_host'] = 'localhost';
$CONF['database_user'] = 'postfix_user';
$CONF['database_password'] = '12345678';
$CONF['database_name'] = 'postfix_db';

// globally change all instances of ''change-this-to-your.domain.tld''
// to an appropriate value
$CONF['default_aliases'] = array (
    'abuse' => 'abuse@6us.cn',
    'hostmaster' => 'hostmaster@6us.cn',
    'postmaster' => 'postmaster@6us.cn',
    'webmaster' => 'webmaster@6us.cn'
);

$CONF['vacation_domain'] = 'autoreply.6us.cn';

$CONF['footer_text'] = 'Return to 6us.cn';
$CONF['footer_link'] = 'http://6us.cn';

$CONF['encrypt'] = 'dovecot:SHA512-CRYPT';
//
$CONF['dovecotpw'] = "/usr/sbin/doveadm pw";
// 安装密码
//$CONF['setup_password'] = 'yourhashhere';

使用 Nginx

安装相关软件

[root@mail etc]# pacman -S php php-fpm php-gd php-geoip php-imap php-intl php-tidy php-xsl 

nano -w /etc/php/php-fpm.d/postfixadmin.conf

[postfixadmin]
user = postfixadmin
group = postfixadmin
listen = /run/postfixadmin/postfixadmin.sock
listen.owner = root
listen.group = http
listen.mode = 0660
pm = ondemand
pm.max_children = 4
php_admin_value['date.timezone'] = Shanghai/Asia
php_admin_value['session.save_path'] = /tmp
php_admin_value['open_basedir'] = /tmp/:/usr/share/webapps/postfixadmin/:/etc/webapps/postfixadmin/:/usr/bin/doveadm:/var/cache/postfixadmin

nano -w /etc/php/php.ini

extension=iconv
;extension=imap
extension=intl
extension=mysqli 
extension=pdo_mysql 

date.timezone = "UTC"

open_basedir = /srv/http/:/etc/webapps/postfixadmin:/var/cache/postfixadmin:/usr/share/webapps/postfixadmin/:/etc/webapps/roundcubemail:/var/cache/roundcubemail:/usr/share/webapps/roundcubemail:/srv/roundcubemail:/var/log/roundcubemail:/home/:/tmp/:/dev/urandom

nano -w /etc/nginx/vhost/postfixadmin.conf

server {
      listen 8081;
      server_name 6us.cn;
      root /usr/share/webapps/postfixadmin/public/;
      index index.php;
      charset utf-8;

      access_log /var/log/nginx/6us.cn-access.log;
      error_log /var/log/nginx/6us.cn-error.log;

      location / {
        try_files $uri $uri/ index.php;
      }

      location ~* \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        include fastcgi_params;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/postfixadmin/postfixadmin.sock;
      }
}    

现在您可以转到PostfixAdmin的设置页面,让PostfixAdmin创建所需的表并在那里创建用户。

Postfix

nano -w /etc/postfix/main.cf

relay_domains = $mydestination
#virtual_alias_maps = proxy:mysql:/etc/postfix/virtual_alias_maps.cf
virtual_alias_maps = proxy:mysql:/etc/postfix/virtual_alias_maps.cf,proxy:mysql:/etc/postfix/virtual_alias_domains_maps.cf,proxy:mysql:/etc/postfix/virtual_alias_domains_catchall_maps.cf
virtual_mailbox_domains = proxy:mysql:/etc/postfix/virtual_mailbox_domains.cf
virtual_alias_domains = proxy:mysql:/etc/postfix/virtual_alias_domains.cf
virtual_mailbox_maps = proxy:mysql:/etc/postfix/virtual_mailbox_maps.cf
virtual_mailbox_base = /srv/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_auth_only = yes
smtpd_tls_received_header = yes
smtpd_sasl_local_domain = $mydomain
broken_sasl_auth_clients = yes

# 发送加密
smtp_tls_security_level = may
smtp_tls_loglevel = 1

# 接收加密
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_tls_loglevel = 1

注意

  • 上面的 virtual_mailbox_domains是你接收邮件的域名列表,并且不能包含你设置在 mydestination 中的域名. 所以我们把 mydestination 保留为 localhost.
  • virtual_mailbox_maps 包含所有的虚拟账户以及他的邮件路径.
  • virtual_mailbox_base 是邮件基础目录.

nano -w /etc/postfix/virtual_alias_maps.cf

user = postfix_user
password = 12345678
hosts = localhost
dbname = postfix_db
table = alias
select_field = goto
where_field = address

nano -w /etc/postfix/virtual_mailbox_domains.cf

user = postfix_user
password = 123456
hosts = localhost
dbname = postfix_db
table = domain
select_field = domain
where_field = domain

nano -w /etc/postfix/virtual_mailbox_maps.cf

user = postfix_user
password = 12345678
hosts = localhost
dbname = postfix_db
table = mailbox
select_field = maildir
where_field = username

如果要修改映射关系
nano -w /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

nano -w /etc/postfix/virtual_alias_domains_maps.cf

user = postfix_user
password = 12345678
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'

nano -w /etc/postfix/virtual_alias_domains.cf

user = postfix_user
password = 12345678
hosts = localhost
dbname = postfix_db
query = SELECT alias_domain FROM alias_domain WHERE alias_domain='%s' AND active = '1'

nano -w /etc/postfix/virtual_alias_domains_catchall_maps.cf
user = postfix_user
password = 12345678
hosts = localhost
dbname = postfix_db
query  = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1'

更新对应关系

postmap /etc/postfix/transport

Dovecot

nano -w /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 = 0666
        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
ssl_dh = </etc/dovecot/dh.pem

nano -w /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
openssl dhparam -out /etc/dovecot/dh.pem 4096

不用与postfix公用数据库。 在这篇文章中,用户:roundcubemail 有 读/写 这个数据库: roundcubemail 的权限 我们将这个用户的密码设为: 12345678 。 你需要去创建数据库和用户,并给它们合适的权限。下面列出了操作命令:

$ mysql -u root
mysql> CREATE DATABASE roundcubemail;
mysql> CREATE USER 'roundcube'@'localhost' IDENTIFIED BY '12345678';
mysql> GRANT ALL PRIVILEGES ON roundcubemail.* TO 'roundcube'@'localhost' WITH GRANT OPTION;
mysql> flush privileges;

nano -w /etc/nginx/vhost/roundcubemail.conf

server {
    listen 80;
    server_name 6us.cn;
    root         /usr/share/webapps/roundcubemail;
    index        index.php

    access_log /var/log/nginx/roundcubemail-access.log;
    error_log /var/log/nginx/roundcubemail-error.log;


    location ^~ /.well-known/acme-challenge/ {
        default_type "text/plain";
        root /srv/http/letsencrypt;
    }

    location / {
        index        index.php index.html index.htm;
        try_files    $uri $uri/ /index.php?$args;
    }
 
    # 静态文件直接发送过期提示,并且关闭日志.
    location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
           access_log off; 
           log_not_found off; 
           expires max;
    }
 
    # 转发所有的PHP请求.
    location ~ \.php$ {
        # 防御零日攻击,如果是通过socket转发到其他服务器的,请关闭
        try_files $uri = 404;

        #注意: 修改php.ini中的设置 "cgi.fix_pathinfo = 0;"
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
 
        include fastcgi_params;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
#       fastcgi_intercept_errors on;
        fastcgi_pass php-handler;
    }
 
    #error_page  404              /404.html;
 
    # redirect server error pages to the static page /50x.html 
    # 
    error_page   500 502 503 504  /50x.html; 
    location = /50x.html { 
        root   html; 
    }
 
    # deny access to .htaccess files, if Apache's document root 
    # concurs with nginx's one 
    # 
    location ~ /\.ht { 
        deny  all; 
    }

    location ^~ /backup {
        deny  all;
    }


    location ~ ^/(README|INSTALL|LICENSE|CHANGELOG|UPGRADING)$ {
        deny all;
    }

    location ~ ^/(bin|SQL|config|temp|logs)/ {
        deny all;
    }
}    

nano -w /etc/webapps/roundcubemail/config/defaults.inc.php

$config['db_dsnw'] = 'mysql://roundcube:password@localhost/roundcubemail';
$config['plugins'] = array('password');

mkdir /srv/roundcubemail
chown -R http.http /srv/roundcubemail

防火墙需要开放下列端口

POP3:          110
POP3S:         995
SMTP:           25
SMTPS:         465
STARTTTLSSMTP: 587
IMAP:          143
IMAPS:         993

出现错误

[24-Oct-2020 17:14:44 UTC] PHP Warning:  stream_socket_enable_crypto(): SSL operation failed with code 1. OpenSSL Error messages:
error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed in /usr/share/webapps/roundcubemail/program/lib/Roundcube/rcube_imap_generic.php on line 1087

一般是ssl 的证书过期了

/etc/ssl/private/

出现错误

[13-Jul-2022 09:00:26 UTC] PHP Fatal error:  Uncaught Error: Class "Spoofchecker" not found in /usr/share/webapps/roundcubemail/program/lib/Roundcube/rcube_spoofchecker.php:50

出现错误

warning: lmdb:/etc/postfix/transport is unavailable. open database /etc/postfix/transport.db: No such file or directory
postmap /etc/postfix/transport 

要么php-intl没有安装,要么不兼容,最好的做法就是跳过

        return false;
        // Spoofchecker is part of ext-intl (requires ICU >= 4.2)
        $checker = new Spoofchecker();

匿名邮件服务器

所谓匿名邮件服务器,就是只能接受邮件,不能发送邮件,同时把系统中所有的没有接收人的邮件都汇总过来,然后显示出来

1.上传程序

2.设置catch-all email account

创建最后的匹配文件

nano -w proxy:mysql:/etc/postfix/virtual_alias_domains_catchall_maps.cf

参考上面的增加
在数据库中创建一个特殊的用户, 请设置好你的domain

INSERT INTO `postfix_db`.`alias` (`address`, `goto`, `domain`, `created`, `modified`) VALUES ('@$domain', 'unknown@$domain', '$domain', '2000-01-01 00:00:00', '2000-01-01 00:00:00')

创建对应domain的用户:unknown@$domain

这样所有的不在列表的邮件全部会发送给对应的邮箱

关于Zeno Chen

本人涉及的领域较多,杂而不精 程序设计语言: Perl, Java, PHP, Python; 数据库系统: MySQL,Oracle; 偶尔做做电路板的开发,主攻STM32单片机
此条目发表在Linux分类目录。将固定链接加入收藏夹。