Virtual Mail Server
How to establish a complete virtual user mail system
Postfix will be our MTA (mail transfer agent) to send and recieve encrypted mail. Virtual mail users will be managed with PostfixAdmin, a web interface for Postfix. An unlimited number of domains and domain (specific) user names may be managed and will be stored in a SQL database. Dovecot will be our MDA (mail delivery agent) to access email via secure IMAP from Roundcube, a web-based MUA (mail user agent) or an desktop MUA like Evolution. All encrypted mail communications will be secured with a TLS certificate. SPF (Sender Policy Framework) will ensure that the only verified servers/IP addresses may send mail from a given domain and DKIM (DomainKeys Identified Mail) will sign all outgoing messages with verification keys. This measures prevent our outgoing mail ending up in the junk box or our server being blacklisted for spam entirely. DMARC (Domain-based Message Authentication, Reporting and Conformance) ensures that both DKIM and SPF are properly enforced. Amavis, Spam-assassin will fiter messages for SPAM and ClamAV will be used for virus protection.
Postfix
│ └── PostfixAdmin
├── Dovecot
├──┬── SPF
│ ├── DKIM
│ └── DMARC
├── Amavis
│ ├── SpamAssassin
│ └── ClamAV
└── Roundcube
Postfix
Install Postfix and MariaDB packages.
pacman -Syu postfix mariadb postfix-mysql ca-certificates
Configure Postfix Uncomment and add or modify default Postfix settings.
/etc/postfix/main.cf
mail_owner = postfix
myhostname = mail.wildw1ng.com
mydomain = wildw1ng.com
myorigin = $mydomain
inet_interfaces = all
mydestination = $myhostname, localhost.$mydomain, localhost
mynetworks = 10.0.0.0/22, 127.0.0.0/8
relayhost =
alias_maps = hash:/etc/postfix/aliases
alias_database = $alias_maps
home_mailbox = Maildir/
smtpd_banner = $myhostname ESMTP $mail_name (Arch Linux)
inet_protocols = ipv4
append_dot_mydomain = no
mailbox_size_limit = 0
relay_domains = $mydestination
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
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
# Secure SMTP (receiving)
smtpd_tls_security_level = may
smtpd_use_tls = yes
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.wildw1ng.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mail.wildw1ng.com/privkey.pem
smtpd_tls_CApath = /etc/ssl/certs
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, check_policy_service unix:private/policy-spf
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
smtpd_tls_loglevel = 1
# Enable SASL authentication
smtp_sasl_auth_enable = yes
# Disallow any methods that do allow anonymous authentication
smtp_sasl_security_options = noanonymous
# Define the sasl_passwd file location
smtp_sasl_password_maps = hash:/etc/postfix/sasl/sasl_passwd
# Enable STARTTLS encryption
smtp_use_tls = yes
# Secure SMTP (sending)
smtp_tls_security_level = may
# smtp_tls_security_level = secure
# smtp_enforce_tls = yes
# Enable TLS logging
smtp_tls_loglevel = 1
# Discovering servers that support TLS
smtp_tls_note_starttls_offer = yes
non_smtpd_milters = unix:/run/opendkim/opendkim.sock, unix:/run/opendmarc/opendmarc.sock
smtpd_milters = unix:/run/opendkim/opendkim.sock, unix:/run/opendmarc/opendmarc.sock
policy-spf_time_limit = 3600s
# Disable VRFY (verify)
disable_vrfy_command = yes
# Block spam using DNS blacklists
smtpd_client_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_rbl_client bl.spamcop.net
# reject_rbl_client zen.spamhaus.org
smtpd_sender_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unknown_sender_domain, reject_unknown_reverse_client_hostname, reject_unknown_client_hostname
# Require the client to provide a HELO/EHLO hostname
smtpd_helo_required = yes
smtpd_helo_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_helo_hostname
smtp_helo_name = $mydomain
# Unsubscibe header
header_checks = regexp:/etc/postfix/list_unsub_header
# Protecting against forged sender addresses
smtpd_sender_login_maps=mysql:/etc/postfix/virtual_alias_maps.cf
# Hide the sender's IP and user agent in the Received header
smtp_header_checks = regexp:/etc/postfix/smtp_header_checks
/etc/postfix/master.cf
# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (no) (never) (100)
# ==========================================================================
smtp inet n - n - - smtpd
-o content_filter=amavisfeed:[127.0.0.1]:10024
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
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
-o syslog_name=postfix/submission
-o smtpd_tls_wrappermode=no
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o content_filter=amavisfeed:[127.0.0.1]:10024
-o smtpd_sender_restrictions=reject_sender_login_mismatch,permit_sasl_authenticated,reject
policy-spf unix - n n - 0 spawn
user=nobody argv=/usr/bin/policyd-spf
amavisfeed unix - - n - 2 smtp
-o smtp_data_done_timeout=1200
-o smtp_send_xforward_command=yes
-o disable_dns_lookups=yes
-o max_use=20
127.0.0.1:10025 inet n - y - - smtpd
-o content_filter=
-o smtpd_delay_reject=no
-o smtpd_client_restrictions=permit_mynetworks,reject
-o smtpd_helo_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o smtpd_data_restrictions=reject_unauth_pipelining
-o smtpd_end_of_data_restrictions=
-o mynetworks=127.0.0.0/8
-o smtpd_error_sleep_time=0
-o smtpd_soft_error_limit=1001
-o smtpd_hard_error_limit=1000
-o smtpd_client_connection_count_limit=0
-o smtpd_client_connection_rate_limit=0
-o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters
-o local_header_rewrite_clients=
Create unprivileged user
For security reasons, we create a new user vmail
to store the mails.
groupadd -g 5000 vmail
useradd -u 5000 -g vmail -s /usr/bin/nologin -d /home/vmail -m vmail
We use a gid and uid of 5000 in both cases so that we do not run into conflicts with regular users.
All our mail will be stored in /home/vmail.
MariaDB
We have to initialize the MariaDB data directory and create the system tables in the mysql database before starting the mariadb.service.
mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql
Enable and start mariadb.service.
systemctl enable mariadb
systemctl start mariadb
Improve the initial security of our MariaDB installation with recommended security measures,
such as removing anonymous accounts and removing the test database.
When prompted to “Switch to unix_socket authentication” enter n for No.
mysql_secure_installation
By default, MySQL will listen on the 0.0.0.0 address, which includes all network interfaces.
We have to restrict MySQL to listen only to the loopback address.
/etc/my.cnf.d/server.cnf
[mysqld]
#bind-address = localhost
bind-address = 127.0.0.1
Restart mariadb.service.
systemctl restart mariadb
Postfix database initialization
We have to create an empty database and give the corresponding user permission to use the database.
postfix_user
will have read/write access to the database postfix_db
using POSTFIXDBPASSWORD
as password.
mysql -u root -p
CREATE DATABASE postfix_db;
GRANT ALL ON postfix_db.* TO 'postfix_user'@'localhost' IDENTIFIED BY 'POSTFIXDBPASSWORD';
FLUSH PRIVILEGES;
QUIT;
We have to set up the necessary configurations for postfix to interact with the database for all its other transport needs.
/etc/postfix/virtual_alias_maps.cf
user = postfix_user
password = POSTFIXDBPASSWORD
hosts = localhost
dbname = postfix_db
table = alias
select_field = goto
where_field = address
/etc/postfix/virtual_mailbox_domains.cf
user = postfix_user
password = POSTFIXDBPASSWORD
hosts = localhost
dbname = postfix_db
table = domain
select_field = domain
where_field = domain
/etc/postfix/virtual_mailbox_maps.cf
user = postfix_user
password = POSTFIXDBPASSWORD
hosts = localhost
dbname = postfix_db
table = mailbox
select_field = maildir
where_field = username
/etc/postfix/virtual_alias_domains_maps.cf
user = postfix_user
password = POSTFIXDBPASSWORD
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 = POSTFIXDBPASSWORD
hosts = localhost
dbname = postfix_db
query = SELECT alias_domain FROM alias_domain WHERE alias_domain='%s' AND active = '1'
Only postfix should have access rights to these files, as they contain passwords.
chown root:postfix -R /etc/postfix/
chmod 640 /etc/postfix/virtual_*
We have to run postmap on transport to generate its database.
postmap /etc/postfix/transport
Dovecot
Install Dovecot package.
pacman -Syu dovecot
Create the dovecot configuration directory and configuration files.
mkdir /etc/dovecot
/etc/dovecot/dovecot.conf
protocols = imap
listen = *
auth_mechanisms = plain login
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_dh = </etc/dovecot/dh.pem
ssl_cert = </etc/letsencrypt/live/mail.wildw1ng.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.wildw1ng.com/privkey.pem
/etc/dovecot/dovecot-sql.conf
driver = mysql
connect = host=localhost dbname=postfix_db user=postfix_user password=POSTFIXDBPASSWORD
# 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
Set permissions.
chown dovecot:dovecot /etc/dovecot/*
Remove the old temporary SSL parameters file.
rm /var/lib/dovecot/ssl-parameters.dat
We are required to provide DH parameters. Generate a new DH parameters file (this might take a long time).
openssl dhparam -out /etc/dovecot/dh.pem 4096
Enable Dovecot debug logging.
/etc/dovecot/dovecot.conf
auth_verbose = yes
auth_verbose_passwords = no
auth_debug = yes
auth_debug_passwords = yes
mail_debug = yes
verbose_ssl = yes
Testing IMAP.
openssl s_client -connect 127.0.0.1:993
a login admin@wildw1ng.com PASSWORD
a examine inbox
a logout
PostfixAdmin
Web interface for Postfix used to manage mailboxes, virtual domains and aliases.
Install PostfixAdmin, Apache and PHP packages.
pacman -Syu postfixadmin apache php-fpm php-imap
Apache HTTP Server configuration
/etc/httpd/conf/httpd.conf
ServerName localhost
Listen 0.0.0.0:80
# php-fpm, an alternative PHP FastCGI implementation with some additional features (mostly) useful for heavy-loaded sites
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
# SSL
LoadModule ssl_module modules/mod_ssl.so
LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule rewrite_module modules/mod_rewrite.so
# Virtual hosts
Include conf/extra/httpd-vhosts.conf
# PostfixAdmin
Include /etc/httpd/conf/postfixadmin.conf
# php-fpm
Include conf/extra/php-fpm.conf
# Secure (SSL/TLS) connections
Include conf/extra/httpd-ssl.conf
<IfModule ssl_module>
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin
</IfModule>
<IfModule mod_ssl.c>
Listen 0.0.0.0:443
</IfModule>
Include /etc/httpd/conf/extra/httpd-vhosts-le-ssl.conf
php-fpm proxy configuration
/etc/httpd/conf/extra/php-fpm.conf
DirectoryIndex index.php index.html
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php-fpm/php-fpm.sock|fcgi://localhost/"
</FilesMatch>
chmod 644 /etc/httpd/conf/extra/php-fpm.conf
Configure Apache HTTP Server with php-fpm
/etc/httpd/conf/postfixadmin.conf
Alias /postfixadmin "/usr/share/webapps/postfixadmin/public"
<Directory "/usr/share/webapps/postfixadmin/public">
DirectoryIndex index.html index.php
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/postfixadmin/postfixadmin.sock|fcgi://localhost/"
</FilesMatch>
AllowOverride All
Options FollowSymlinks
Require all granted
SetEnv PHP_ADMIN_VALUE "open_basedir = /tmp/:/usr/share/webapps/postfixadmin:/etc/webapps/postfixadmin/:/var/cache/postfixadmin/templates_c"
</Directory>
/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'] = UTC
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
PHP configuration
/etc/php/php.ini
open_basedir = /var/cache/postfixadmin/:/etc/webapps/:/usr/share/webapps/:/tmp/:/var/cache/roundcubemail:/usr/share/webapps/roundcubemail:/etc/webapps/roundcubemail:/usr/share/pear/:/var/log/roundcubemail
date.timezone = "UTC"
extension=imap
extension=mysqli
extension=pdo_mysql
extension=iconv
extension=gd
extension=intl
extension=exif
extension=imagick
PostfixAdmin configuration
/etc/webapps/postfixadmin/config.local.php
<?php
$CONF['configured'] = true;
// correspond to dovecot maildir path /home/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'] = 'POSTFIXDBPASSWORD';
$CONF['database_name'] = 'postfix_db';
$CONF['default_aliases'] = array (
'abuse' => 'abuse@wildw1ng.com',
'hostmaster' => 'hostmaster@wildw1ng.com',
'postmaster' => 'postmaster@wildw1ng.com',
'webmaster' => 'webmaster@wildw1ng.com'
);
$CONF['vacation_domain'] = 'autoreply.wildw1ng.com';
$CONF['footer_text'] = 'Return to wildw1ng.com';
$CONF['footer_link'] = 'https://wildw1ng.com';
$CONF['encrypt'] = 'dovecot:SHA512-CRYPT';
$CONF['setup_password'] = 'HASHEDSETUPPASSWORD';
Enable and start Services.
systemctl enable httpd
systemctl enable php-fpm
systemctl enable postfix
systemctl enable dovecot
Generate hashes with non-default hash functions.
doveadm pw -s SHA512-CRYPT -p "DOVEADMPASSWORD"
Write the HASHEDSETUPPASSWORD to the configuration file.
Navigate to http://10.0.1.18/postfixadmin/setup.php
.
Now we can create a superadmin account.
Restrict access to setup.php after installation is finished.
chmod 600 /usr/share/webapps/postfixadmin/public/setup.php
Check the apache log for errors.
less /var/log/httpd/error_log
PostfixAdmin pacman hook
The database needs to be upgraded after a version bump.
We will see a message saying ‘The PostfixAdmin database layout is outdated’ on the login page.
Therefore we may set up a hook that runs the needed upgrade.php script automatically via a pacman hook.
/etc/pacman.d/hooks/postfixadmin.hook
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = postfixadmin
[Action]
Description = Run Postfixadmin upgrade.php to make sure database is up to date
When = PostTransaction
Exec = /usr/bin/runuser -u postfixadmin -- /usr/bin/php /usr/share/webapps/postfixadmin/public/upgrade.php
Roundcube
Full-featured, PHP web-based mail client.
Install Roundcube and PHP Plugin packages.
pacman -Syu roundcubemail php-gd php-intl php-imagick librsvg
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
.
Create an empty database and give the corresponding user permission to use the database.
mysql -u root -p
CREATE DATABASE `roundcube_db` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
CREATE USER `roundcube_user`@'localhost' IDENTIFIED BY 'ROUNDCUBEDBPASSWORD';
GRANT ALL PRIVILEGES ON `roundcube_db`.* TO `roundcube_user`@`localhost`;
FLUSH PRIVILEGES;
QUIT;
We need to initialize the roundcubemail database tables.
mysql -u root -p roundcube_db < /usr/share/webapps/roundcubemail/SQL/mysql.initial.sql
Copy the default configuration file and set permisions.
cd /etc/webapps/roundcubemail/config
cp config.inc.php.sample config.inc.php
chown http:http config.inc.php
chmod 640 config.inc.php
Set our mail server settings.
/etc/webapps/roundcubemail/config/config.inc.php
?php
$config = [];
// Database connection string (DSN) for read+write operations
// Format (compatible with PEAR MDB2): db_provider://user:password@host/database
// Currently supported db_providers: mysql, pgsql, sqlite, mssql, sqlsrv, oracle
// For examples see http://pear.php.net/manual/en/package.database.mdb2.intro-dsn.php
// NOTE: for SQLite use absolute path (Linux): 'sqlite:////full/path/to/sqlite.db?mode=0646'
// or (Windows): 'sqlite:///C:/full/path/to/sqlite.db'
$config['db_dsnw'] = 'mysql://roundcube_user:ROUNDCUBEDBPASSWORD@localhost/roundcube_db';
$config['imap_host'] = 'tls://mail.wildw1ng.com';
$config['smtp_host'] = 'tls://mail.wildw1ng.com';
$config['smtp_port'] = 587;
$config['imap_port'] = 993;
$config['mime_types'] = '/etc/webapps/roundcubemail/config/mime.types';
// IMAP host chosen to perform the log-in.
// See defaults.inc.php for the option description.
// $config['imap_host'] = 'localhost:143';
// SMTP server host (for sending mails).
// See defaults.inc.php for the option description.
// $config['smtp_host'] = 'localhost:587';
// SMTP username (if required) if you use %u as the username Roundcube
// will use the current username for login
$config['smtp_user'] = '%u';
// SMTP password (if required) if you use %p as the password Roundcube
// will use the current user's password for login
$config['smtp_pass'] = '%p';
// provide an URL where a user can get support for this Roundcube installation
// PLEASE DO NOT LINK TO THE ROUNDCUBE.NET WEBSITE HERE!
$config['support_url'] = 'https://wildw1ng.com';
// Name your service. This is displayed on the login screen and in the window title
$config['product_name'] = 'Roundcube Webmail';
// This key is used to encrypt the users imap password which is stored
// in the session record. For the default cipher method it must be
// exactly 24 characters long.
// YOUR KEY MUST BE DIFFERENT THAN THE SAMPLE VALUE FOR SECURITY REASONS
$config['des_key'] = 'LONGRANDOMSTRING';
// List of active plugins (in plugins/ directory)
$config['plugins'] = [
'archive',
'zipdownload',
'password',
];
// skin name: folder from skins/
$config['skin'] = 'elastic';
Set enable_installer
to enable the setup wizard
$config['enable_installer'] = true;
For Roundcube to be able to detect mime-types from filename extensions you need to point it to a mime.types file.
Apache usually comes with one.
cp /etc/httpd/conf/mime.types /etc/webapps/roundcubemail/config/mime.types
chown http:http /etc/webapps/roundcubemail/config/mime.types
chmod 640 /etc/webapps/roundcubemail/config/mime.types
/etc/webapps/roundcubemail/config/config.inc.php
$config['mime_types'] = '/etc/webapps/roundcubemail/config/mime.types';
If you have configured open_basedir in php.ini, make sure it includes /etc/webapps and /usr/share/webapps,
so PHP can open the required Roundcube files.
Enable the password plugin to let users change their passwords from within Roundcube.
/etc/webapps/roundcubemail/config/config.inc.php
$config['plugins'] = password;
Configure the password plugin and make sure you alter the settings accordingly.
/usr/share/webapps/roundcubemail/plugins/password/config.inc.php
<?php
$config['password_driver'] = 'sql';
$config['password_db_dsn'] = 'mysql://postfix_user:POSTFIXDBPASSWORD@localhost/postfix_db';
// 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'] = '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';
Now we finish the Roundcube installation with the wizard in our browser http://10.0.1.18/roundcube/installer
.
For security reasons, we have to disable the installer after finishing the wizard and remove the installer directory.
rm /usr/share/webapps/roundcubemail/installer
/etc/webapps/roundcubemail/config/config.inc.php
delete $config['enable_installer'] = true;
DNS Record
We need to set A and MX DNS records pointing our mail server.
A record pointing our system’s FQDN (hostname) to our mail server IPv4 address.
mail.wildw1ng.com 60 IN A 37.201.217.90
MX record specifies which mail server is responsible for accepting emails on behalf of a recipient’s domain.
All messages sent to @wildw1ng.com email addresses will be accepted by the mail.wildw1ng.com mail server.
wildw1ng.com 3600 IN MX 0 mail.wildw1ng.com
Open ports on mail server
Port | Service | Description |
---|---|---|
25 | SMTP | Transmission of email from email server to email server |
993 | IMAP | Secure session |
Check open ports on our machine.
ss -tapn
netstat -tlpn
Get SSL certificates with Certbot via Let’s Encrypt for Apache
/etc/httpd/conf/extra/httpd-vhosts.conf
<VirtualHost *:80>
ServerAdmin admin@wildw1ng.com
DocumentRoot "/usr/share/webapps/roundcubemail"
ServerName mail.wildw1ng.com
ServerAlias mail.wildw1ng.com
ErrorLog "/var/log/httpd/mail.wildw1ng.com-error.log"
CustomLog "/var/log/httpd/mail.wildw1ng.com-access.log" common
<Directory "/usr/share/webapps/roundcubemail">
AllowOverride All
Options FollowSymlinks
Require all granted
SetEnv PHP_ADMIN_VALUE "open_basedir /tmp/:/var/cache/roundcubemail:/usr/share/webapps/roundcubemail:/etc/webapps/roundcubemail:/usr/share/pear/:/var/log/roundcubemail"
</Directory>
RewriteEngine on
RewriteCond %{SERVER_NAME} =mail.wildw1ng.com
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
/etc/httpd/conf/extra/httpd-vhosts-le-ssl.conf
<IfModule mod_ssl.c>
SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000)
<VirtualHost *:443>
ServerAdmin admin@wildw1ng.com
DocumentRoot "/usr/share/webapps/roundcubemail"
ServerName mail.wildw1ng.com
ServerAlias mail.wildw1ng.com
ErrorLog "/var/log/httpd/mail.wildw1ng.com-error.log"
CustomLog "/var/log/httpd/mail.wildw1ng.com-access.log" common
<Directory "/usr/share/webapps/roundcubemail">
AllowOverride All
Options FollowSymlinks
Require all granted
SetEnv PHP_ADMIN_VALUE "open_basedir /tmp/:/var/cache/roundcubemail:/usr/share/webapps/roundcubemail:/etc/webapps/roundcubemail:/usr/share/pear/:/var/log/roundcubemail"
</Directory>
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/mail.wildw1ng.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/mail.wildw1ng.com/privkey.pem
SSLUseStapling on
</VirtualHost>
</IfModule>
Run Certbot to obtain a certificate.
certbot --apache
Certificate is saved at:
/etc/letsencrypt/live/mail.wildw1ng.com/fullchain.pem
Key is saved at:/etc/letsencrypt/live/mail.wildw1ng.com/privkey.pem
If we get errors, we have to ensure that SSL is not multiple defined.
grep -r "Listen 443" /etc/httpd
Sender Policy Framework
SPF is an email authentication protocol used to stop phishing attacks.
We can specify who is allowed to send email on behalf of our domain.
Install SPF package.
yay -Syu python-spf-engine
Modify Postfix configuration files to enable SPF.
/etc/postfix/main.cf
smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, check_policy_service unix:private/policy-spf
policy-spf_time_limit = 3600s
/etc/postfix/master.cf
policy-spf unix - n n - 0 spawn
user=nobody argv=/usr/bin/policyd-spf
SPF DNS Record
To allow other mail exchangers to validate mails apparently sent from our domain,
we need to set a DNS TXT record with v=spf1 mx ~all
.
We are approving the domain mail servers (mx) and if the SPF check fails, the result will be a soft failure (~all).
DomainKeys Identified Mail
DKIM is a sender authentication protocol that allows signing messages so mailbox providers can verify them.
This method is designed to detect email spoofing by identifying forged sender addresses in email.
Install OpenDKIM package.
pacman -Syu opendkim
Create a directory for dkim.
mkdir /var/db/dkim/
Generate a secret signing key.
opendkim-genkey -r -s default -d wildw1ng.com
chmod 400 /var/db/dkim/default.*
Copy the default configuration file.
cp /usr/share/doc/opendkim/opendkim.conf.sample /etc/opendkim/opendkim.conf
chmod 644 /etc/opendkim/opendkim.conf
Modify OpenDKIM configuration and create a Socket for DKIM.
/etc/opendkim/opendkim.conf
Domain wildw1ng.com
KeyFile /var/db/dkim/default.private
Selector default
Socket unix:/run/opendkim/opendkim.sock
TemporaryDirectory /run/opendkim
UMask 002
UserID opendkim
Canonicalization relaxed/simple
mkdir /run/opendkim
chown opendkim:postfix /run/opendkim
chmod 750 /run/opendkim
mkdir -p /etc/systemd/system/opendkim.service.d/
chmod 755 /etc/systemd/system/opendkim.service.d/
/etc/tmpfiles.d/opendkim.conf
D /run/opendkim 0750 opendkim postfix
chmod 644 /etc/tmpfiles.d/opendkim.conf
/etc/systemd/system/opendkim.service.d/override.conf
[Service]
User=
User=opendkim
Group=
Group=postfix
chmod 644 /etc/systemd/system/opendkim.service.d/override.conf
chown opendkim:postfix /var/db/dkim/
chown opendkim:postfix /var/db/dkim/default.private
Enable and start the opendkim.service.
systemctl enable opendkim
DKIM DNS Record
Add a DNS TXT record with the selector and public key.
less /var/db/dkim/default.txt
Copy everything in between (" “) without the brackets and quotes into a default._domainkey
TXT DNS Record.
v=DKIM1; k=rsa; s=email; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNXDCBiQKBgQDjk96JyEAU2QLfDjZYyHTHVWYP/effPipH3hpgfa+Nk Wg/WmfZXjI3CmDY+N3m+eRmZdIzYO9oPGi+r0h3ceSZe4Cj858/k/0D7aYdG18QQDLIY+x+dmp7MjRK1+/B1xWjWy/Sn4n F1zVmROVxuBraX2eL32deu+qrnZlsu2H9MwIDAQAB
Check the record.
host -t TXT default._domainkey.wildw1ng.com
Domain-based Message Authentication, Reporting and Conformance
DMARC is an email authentication protocol that provides domain-level protection, detecting and preventing email spoofing techniques used in phishing.
Install OpenDMARC package.
pacman -Syu opendmarc
Modify OpenDMARC configuration and create a Socket for DMARC.
/etc/opendmarc/opendmarc.conf
Socket unix:/run/opendmarc/opendmarc.sock
UMask 002
mkdir /run/opendmarc
chown opendmarc:postfix /run/opendmarc
chmod 750 /run/opendmarc
/etc/tmpfiles.d/opendmarc.conf
D /run/opendmarc 0750 opendmarc postfix
chmod 644 /etc/tmpfiles.d/opendmarc.conf
mkdir -p /etc/systemd/system/opendmarc.service.d/
chmod 755 /etc/systemd/system/opendmarc.service.d/
/etc/systemd/system/opendmarc.service.d/override.conf
[Service]
Group=
Group=postfix
chmod 644 /etc/systemd/system/opendmarc.service.d/override.conf
Add Mail Filter Sockets to our Postfix configuration and make sure that the DMARC milter is declared after the DKIM milter.
/etc/postfix/main.cf
non_smtpd_milters = unix:/run/opendkim/opendkim.sock, unix:/run/opendmarc/opendmarc.sock
smtpd_milters = unix:/run/opendkim/opendkim.sock, unix:/run/opendmarc/opendmarc.sock
Enable and start the opendmarc.service.
systemctl enable opendmarc
DMARC DNS Record
To enable DMARC for a domain, add a new TXT record to its DNS zone.
First testing, no harm as (sub)policy is “none”, but start to receive aggregated reports and failing reports (SPF and DKIM).
_dmarc.wildw1ng.com TXT v=DMARC1; rua=mailto:admin@wildw1ng.com; ruf=mailto:admin@wildw1ng.com; adkim=s; fo=1
After a certain time, after analyzing these reports enable the policy, for wildw1ng, for 10% of e-mail traffic.
_dmarc.wildw1ng.com TXT v=DMARC1; p=quarantine; rua=mailto:admin@wildw1ng.com; ruf=mailto:admin@wildw1ng.com; adkim=s; fo=1; pct=10
Then slowly raise the percentage and finalize with policy 100% enabled and only failing reports.
_dmarc.wildw1ng.com TXT v=DMARC1; p=quarantine; ruf=mailto:admin@wildw1ng.com; adkim=s; fo=1
Use DNS blacklists
/etc/postfix/main.cf
# Block spam using DNS blacklists
smtpd_client_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_rbl_client zen.spamhaus.org, reject_rbl_client bl.spamcop.net
List-Unsubscribe header
Set header checks.
/etc/postfix/main.cf
header_checks = regexp:/etc/postfix/list_unsub_header
Create a list_unsub_header
file.
/etc/postfix/list_unsub_header
/Content-Transfer-Encoding:/i PREPEND List-Unsubscribe: mailto:admin@wildw1ng.com?subject=unsubscribe
chmod 644 /etc/postfix/list_unsub_header
Amavis and ClamAV
Amavis is an interface between the MTA and content checkers, ClamAV virus scanner and SpamAssassin.
Install packages.
pacman -Syu amavisd-new clamav p7zip unrar arj lrzip lz4 lzo rpmextract
Disable anti-spam, enable logging.
/etc/amavisd/amavisd.conf
@bypass_virus_checks_maps = (1); # controls running of anti-virus code
@bypass_spam_checks_maps = (1); # controls running of anti-spam code
# $bypass_decode_parts = 1; # controls running of decoders&dearchivers
$mydomain = 'wildw1ng.com';
$myhostname = 'mail.wildw1ng.com';
$log_level = 5; # verbosity 0..5, -d
Enable ClamAV support and list the same clamd.sock as in /etc/clamav/clamd.conf
.
# http://www.clamav.net/
['ClamAV-clamd',
\&ask_daemon, ["CONTSCAN {}\n", "/run/clamav/clamd.ctl"],
qr/\bOK$/m, qr/\bFOUND$/m,
qr/^.*?: (?!Infected Archive)(.*) FOUND$/m ],
# # NOTE: run clamd under the same user as amavisd - or run it under its own
# # uid such as clamav, add user clamav to the amavis group, and then add
# # NOTE: match socket name (LocalSocket) in clamav.conf to the socket name in
# # this entry; when running chrooted one may prefer a socket under $MYHOME.
Add a comment to this line to enable anti-virus scan.
# @bypass_virus_check_maps = (1); # controls running of anti-virus code
After that, add clamav user to amavis group to avoid permission problems.
usermod -a -G amavis clamav
Updating ClamAV virus definition database
We need to run freshclam
before starting the service for the first time
or you will run into trouble/errors which will prevent ClamAV from starting correctly.
Start and enable clamav-freshclam.service so that the virus definitions are kept up to date.
systemctl enable clamav-freshclam.service
Start and enable Amavis and ClamAV services.
systemctl enable clamav-daemon.service
systemctl enable amavisd.service
Integration with Postfix
/etc/postfix/master.cf
#
# anti spam & anti virus section
#
amavisfeed unix - - n - 2 smtp
-o smtp_data_done_timeout=1200
-o smtp_send_xforward_command=yes
-o disable_dns_lookups=yes
-o max_use=20
127.0.0.1:10025 inet n - y - - smtpd
-o content_filter=
-o smtpd_delay_reject=no
-o smtpd_client_restrictions=permit_mynetworks,reject
-o smtpd_helo_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o smtpd_data_restrictions=reject_unauth_pipelining
-o smtpd_end_of_data_restrictions=
-o smtpd_restrictions_classes=
-o mynetworks=127.0.0.0/8
-o smtpd_error_sleep_time=0
-o smtpd_soft_error_limit=1001
-o smtpd_hard_error_limit=1000
-o smtpd_client_connection_count_limit=0
-o smtpd_client_connection_rate_limit=0
-o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters
-o local_header_rewrite_clients=
In this configuration we assume that postfix and Amavis are running on the same machine (i.e. 127.0.0.1).
If that is not the case edit /etc/amavisd/amavisd.conf
and the prevous Postfix entry accordingly.
Postfix will listen to port 10025 so that Amavis can send back checked emails to that port.
We also have to add a configuration in our smtp or submission sections.
-o content_filter=amavisfeed:[127.0.0.1]:10024
Using this options implies that Postfix will send emails to Amavis on port 10024, so that these can be checked.
If mail passes the control then these are sent to port 10025.
We can now restart postfix.service and amavisd.service.
SpamAssasin
Install package.
pacman -Syu spamassassin
Spamassassin is integrated in Amavis so we do not have to start spamassassin.service.
To enable support for Spamassassin comment the following line.
/etc/amavis/amavis.conf
# @bypass_spam_checks_maps = (1); # controls running of anti-spam code
Edit the SpamAssassin configuration.
$sa_tag_level_deflt = 1.0; # add spam info headers if at, or above that level
$sa_tag2_level_deflt = 1.0; # add 'spam detected' headers at that level
$sa_kill_level_deflt = 5.0; # triggers spam evasive actions (e.g. blocks mail)
$sa_dsn_cutoff_level = 8; # spam level beyond which a DSN is not sent
# $sa_quarantine_cutoff_level = 25; # spam level beyond which quarantine is off
$penpals_threshold_high = $sa_kill_level_deflt; # do not waste time on hi spam
$bounce_killer_score = 100; # spam score points to add for joe-jobbed bounces
Before we restart the amavisd service we have to run sa-update
.
mkdir /etc/mail/spamassassin/sa-update-keys
chown spamd:spamd /etc/mail/spamassassin/sa-update-keys
chmod 700 /etc/mail/spamassassin/sa-update-keys
cd /etc/mail/spamassassin
sudo -u spamd wget "http://spamassassin.apache.org/updates/GPG.KEY"
sudo -u spamd sa-update --import GPG.KEY
rm GPG.KEY
sudo -u spamd sa-update -D
sudo -u spamd sa-compile
Keep SpamAssassin up to date
Manual update.
sudo -u spamd sa-update --channel updates.spamassassin.org
sudo -u spamd sa-compile
Create service to automate the process.
/usr/lib/systemd/system/spamassassin-update.service
[Unit]
Description=SpamAssassin Update
After=network.target
[Service]
User=spamd
Group=spamd
Type=oneshot
# UMask=0022
ExecStart=/usr/bin/vendor_perl/sa-update --channel updates.spamassassin.org
SuccessExitStatus=1
ExecStart=/usr/bin/vendor_perl/sa-compile
# ExecStart=!/usr/bin/systemctl -q --no-block try-restart spamassassin.service
# uncomment the following ExecStart line to train SA's bayes filter
# and specify the path to the mailbox that contains spam email(s)
# ExecStart=/usr/bin/vendor_perl/sa-learn --spam <path_to_your_spam_mailbox>
/usr/lib/systemd/system/spamassassin-update.timer
[Unit]
Description=SpamAssassin Update Timer
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
Start and enable spamassassin-update.timer.
systemctl enable spamassassin-update.timer
Check permissions in /var/lib/spamassassin/
if you get errors.