Table of Contents
Primary MX
Oauth2?
Prerequisites
apt-get install amavisd-new dovecot-core dovecot-imapd dovecot-lmtpd dovecot-mysql dovecot-sieve haveged mailutils mariadb-server opendkim opendkim-tools p7zip postfix postfix-mysql postgrey spamassassin
Generate DH Params
openssl dhparam -out /etc/ssl/certs/dhparam.pem 3072
Create the user
groupadd -g 5000 vmail useradd -g vmail -u 5000 vmail -d /home/vmail mkdir -p /home/vmail chown -R vmail:vmail /home/vmail
Database
Secure your installation
mysql_secure_installation
Install the database
CREATE DATABASE `mail`; GRANT ALL ON mail.* TO 'mail_admin'@'localhost' IDENTIFIED BY '<changeme>'; FLUSH PRIVILEGES; USE mail; CREATE TABLE `domains` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `domain` VARCHAR(50) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `users` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `domain` INT(11) NOT NULL, `password` VARCHAR(106) NOT NULL, `email` VARCHAR(100) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `email` (`email`), FOREIGN KEY (DOMAIN) REFERENCES domains(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `aliases` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `domain` INT(11) NOT NULL, `source` VARCHAR(100) NOT NULL, `destination` VARCHAR(100) NOT NULL, PRIMARY KEY (`id`), FOREIGN KEY (DOMAIN) REFERENCES domains(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Postfix
SQL config files
In this setup, we'll prefer using unix sockets, since the base is local, but you could do it with a TCP socket as well.
mysql-postfix_alias.cf
user = mail_admin password = <redacted> dbname = mail query = SELECT destination FROM forwardings WHERE source='%s' hosts = unix:/var/run/mysqld/mysqld.sock
mysql-virtual_domains.cf
user = mail_admin password = <redacted> dbname = mail query = SELECT domain AS virtual FROM domains WHERE domain='%s' hosts = unix:/var/run/mysqld/mysqld.sock
mysql-virtual_email2email.cf
user = mail_admin password = <redacted> dbname = mail query = SELECT email FROM users WHERE email='%s' hosts = unix:/var/run/mysqld/mysqld.sock
mysql-virtual_forwardings.cf
user = mail_admin password = <redacted> dbname = mail query = SELECT destination FROM forwardings WHERE source='%s' hosts = unix:/var/run/mysqld/mysqld.sock
mysql-virtual_mailboxes.cf
user = mail_admin password = <redacted> dbname = mail query = SELECT CONCAT(SUBSTRING_INDEX(email,'@',-1),'/',SUBSTRING_INDEX(email,'@',1),'/') FROM users WHERE email='%s' hosts = unix:/var/run/mysqld/mysqld.sock
mysql-virtual_mailbox_limit_maps.cf
user = mail_admin password = <redacted> dbname = mail query = SELECT quota FROM users WHERE email='%s' hosts = unix:/var/run/mysqld/mysqld.sock
mysql-virtual_transports.cf
user = mail_admin password = <redacted> dbname = mail query = SELECT transport FROM transport WHERE domain='%s' hosts = unix:/var/run/mysqld/mysqld.sock
Configuration
main.cf
# See /usr/share/postfix/main.cf.dist for a commented, more complete version
# Debian specific: Specifying a file name will cause the first
# line of that file to be used as the name. The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname
smtpd_banner = $myhostname ESMTP $mail_name (Cray Linux Environment)
biff = no
# appending .domain is the MUA's job.
append_dot_mydomain = no
# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h
readme_directory = no
# Server TLS parameters
smtpd_tls_cert_file = /etc/dehydrated/certs/arwen.luthienstar.fr/fullchain.pem
smtpd_tls_key_file = /etc/dehydrated/certs/arwen.luthienstar.fr/privkey.pem
smtpd_use_tls = yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtpd_tls_dh1024_param_file = /etc/postfix/dh_3072.pem
# Send TLS parameters
smtp_tls_cert_file = $smtpd_tls_cert_file
smtp_tls_key_file = $smtpd_tls_key_file
smtp_use_tls = $smtpd_use_tls
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtp_tls_security_level = may
# Disable ssl compression (postfix >=2.11)
#tls_ssl_options = no_compression
# Secure cipherlist setup
tls_preempt_cipherlist = yes
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_protocols = $smtpd_tls_mandatory_protocols
smtp_tls_mandatory_protocols = $smtpd_tls_mandatory_protocols
smtp_tls_protocols = $smtpd_tls_mandatory_protocols
#smtpd_tls_eecdh_grade = auto
smtpd_tls_mandatory_ciphers = high
smtpd_tls_ciphers = $smtpd_tls_mandatory_ciphers
smtp_tls_mandatory_ciphers = $smtpd_tls_mandatory_ciphers
smtp_tls_ciphers = $smtpd_tls_mandatory_ciphers
smtpd_tls_mandatory_exclude_ciphers = aNULL, MD5, aEDH, RC4, eNULL, DES, 3DES, kRSA
smtpd_tls_exclude_ciphers = $smtpd_tls_mandatory_exclude_ciphers
smtp_tls_mandatory_exclude_ciphers = $smtpd_tls_mandatory_exclude_ciphers
smtp_tls_exclude_ciphers = $smtpd_tls_mandatory_exclude_ciphers
# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.
myhostname = arwen.luthienstar.fr
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
mydestination = arwen.luthienstar.fr, mx0.luthienstar.fr, localhost, localhost.localdomain
relayhost =
mynetworks = 127.0.0.0/8
mailbox_command = procmail -a "$EXTENSION"
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all
message_size_limit = 30720000
virtual_alias_maps = proxy:mysql:/etc/postfix/mysql-virtual_forwardings.cf, mysql:/etc/postfix/mysql-virtual_email2email.cf
virtual_mailbox_domains = proxy:mysql:/etc/postfix/mysql-virtual_domains.cf
virtual_mailbox_maps = proxy:mysql:/etc/postfix/mysql-virtual_mailboxes.cf
virtual_mailbox_base = /home/vmail
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000
# Security
#mtpd_delay_reject = yes
#mtpd_helo_required = yes
#mtpd_helo_restrictions = permit_mynetworks, reject_non_fqdn_hostname, reject_invalid_hostname, permit
smtpd_sasl_auth_enable = yes
broken_sasl_auth_clients = yes
smtpd_sasl_authenticated_header = yes
#smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, check_sender_access hash:/etc/postfix/sender_acl, check_client_access hash:/etc/postfix/rbl_override, reject_unauth_destination
smtpd_recipient_restrictions = reject_invalid_hostname, reject_unauth_pipelining, permit_sasl_authenticated, permit_mynetworks, check_sender_access hash:/etc/postfix/sender_acl, check_client_access hash:/etc/postfix/rbl_override, reject_unauth_destination
transport_maps = proxy:mysql:/etc/postfix/mysql-virtual_transports.cf
proxy_read_maps = $local_recipient_maps $mydestination $virtual_alias_maps $virtual_alias_domains $virtual_mailbox_maps $virtual_mailbox_domains $relay_recipient_maps $relay_domains $canonical_maps $sender_canonical_maps $recipient_canonical_maps $relocated_maps $transport_maps $mynetworks
#mime_header_checks = regexp:/etc/postfix/mime_header_checks
milter_default_action = accept
milter_protocol = 6
milter_command_timeout = 60s
#smtpd_milters = unix:/var/run/spf-milter-python/spfmilter.sock unix:/var/run/opendkim/opendkim.sock unix:/var/run/opendmarc/opendmarc.sock unix:/var/run/spamass/spamass.sock unix:/var/run/clamav/clamav-milter.sock
smtpd_milters = unix:/var/run/spf-milter-python/spfmilter.sock unix:/var/run/opendkim/opendkim.sock unix:/var/run/opendmarc/opendmarc.sock unix:/var/run/spamass/spamass.sock
non_smtpd_milters = unix:/var/run/opendkim/opendkim.sock
virtual_transport = maildrop
maildrop_destination_recipient_limit = 1
https://docs.j7k6.org/mailserver-postfix-dovecot-mysql-amavis-opendkim-postgrey-debian-buster-2019/
https://www.linux.com/news/child-safe-smtp-whitelist-postfix-and-mysql-0/
Dovecot + Sieve
https://www.linode.com/docs/guides/email-with-postfix-dovecot-and-mysql/
https://wiki1.dovecot.org/LDA/Sieve
https://blog.tinned-software.net/setup-sieve-mail-filter-with-postfix-and-dovecot/
pyspf-milter
If you have a start error with pyspf-milter, you can comment line 40 in /usr/lib/python3/dist-packages/spf_engine/milter_spf.py.
For confirmation, the line which needs to be commented is:
from spf_engine.util import own_socketfile
More details: https://bugs.launchpad.net/spf-engine/+bug/1856391