Slackware Mail Server with MariaDB, Postfix, and Dovecot
21 Jan 2019Introduction
Nearly two years after the release of Slackware Linux 14.2 and three years after my original blog post, I’m glad to finally provide an update. Sorry for the delay. The core packages that make up the mail server have received lots of updates. Besides the version and configuration updates I’ve also added some information for setting up Dovecot Pigeonhole and configuring email filtering.
This post contains some suggestions for a mildly secure mail server running on a Slackware Linux host. The guide assumes a default, fresh installation of Slackware64 14.2 that includes at least the A/
, AP/
, D/
, L/
, and N/
package series. By the end of the tutorial, you will have:
- Postfix for encrypted connections over SMTP
- Dovecot for local mail directories and encrypted connections over POP and IMAP
- MariaDB to store mailbox information
- Postgrey which will require unknown senders to resend their mail, eliminating most spam
- SpamAssassin for e-mail spam filtering based on content-matching rules
- ClamAV to detect trojans, viruses, malware and other malicious threats in your email
- amavisd-new to manage ClamAV and SpamAssasin
- OpenDKIM a DomainKeys Identified Mail (DKIM) milter to sign and/or verify mail
- nginx as a webserver (optional)
- Postfix Admin to manage mailboxes and domains using a TLS secured web user interface (optional)
- Roundcube with some plugins as a TLS secured webmail client (optional)
- Pigeonhole to add support for email filter rules (forwarding, placing in folders, etc)
Self-hosting Email
My previous post goes into more detail about why you would or wouldn’t want to host your own mail server. It also gives some advice about hosting providers and things to look out for.
Preparation
Hostname
Set up an FQDN as your hostname. We’ll use mail.example.org:
echo "mail.example.org" > /etc/HOSTNAME
hostname -F /etc/HOSTNAME
PHP + nginx
Install nginx
The nginx
webserver is at version 1.14.2
as of this writing, up from 1.8.0
used in my previous guide. HTTP/2 is now natively supported instead of being provided by the SPDY module. You can still hide the server headers in the source using the patches from the previous post. This is in addition to the server_tokens
configuration option. You can also compile nginx with the ngx_headers_more
module. Keep in mind there are still many ways to detect which web server you’re running and tools like nmap
are particularly good at doing it. It’s up to you to decide how much effort you want to put into this.
Create your user and use that to run the SlackBuild. For newer nginx
versions, simply updating the version number in the nginx.SlackBuild
file before running it usually does the trick.
useradd -r -M -U -c "User for nginx" -d /srv/httpd -s /bin/false nginx
NGINXUSER=nginx NGINXGROUP=nginx ./nginx.SlackBuild
Once again, if you want to enable syntax highlighlting for the nginx.conf
file in vim
, copy the contents of the contrib/vim
directory from the extracted nginx
source to ~/.vim
.
SSL Certificates
In the previous post I talked about some SSL certificate providers and curves. We use ECDSA certificates and the secp384r1
curve here.
Configure nginx
At minimum, your nginx.conf
file will need an events
section which can be left blank to activate the defaults or modified depending on the load you’re expecting on the server. Set worker_processes
equal to the number of real CPU cores on the machine.
user nginx;
worker_processes 4;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
map $sent_http_content_type $expires {
default off;
application/x-javascript 1d;
text/css 1d;
~image/ 1d;
}
expires $expires;
client_max_body_size 12m;
default_type application/octet-stream;
gzip on;
gzip_vary on;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_min_length 10240;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css text/xml text/javascript text/json application/json application/x-javascript application/xml application/xml+rss;
gzip_disable "MSIE [1-6]\.";
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
include /etc/nginx/mime.types;
upstream php_workers {
server unix:/var/run/php-fpm.sock;
}
sendfile on;
# Hide Nginx version number
server_tokens off;
types_hash_max_size 2048;
include /etc/nginx/conf.d/*.conf;
}
Create your /etc/nginx/conf.d/site.conf
# HTTP
server {
# Listen on ipv4
listen 80;
server_name _;
return 301 https://$host$request_uri;
}
# HTTPS
server {
listen 443;
server_name _;
root /var/www/html;
index index.php index.html;
# Allow access to '^/.well-known/'
location ~ ^/.well-known/ {
allow all;
access_log off;
log_not_found off;
autoindex off;
}
# Deny all attempts to access hidden files such as .htaccess.
location ~ /\. {
deny all;
}
# too many of these
location = /favicon.ico {
access_log off; log_not_found off;
}
location = /robots.txt {
access_log off; log_not_found off;
}
# global SSL options with Perfect Forward Secrecy (PFS) high strength ciphers
# first. PFS ciphers are those which start with ECDHE which means (EC)DHE
# which stands for (Elliptic Curve) Diffie-Hellman Ephemeral.
ssl on;
ssl_protocols TLSv1.2;
# RSA ciphers
# ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-RSA-RC4-SHA;
# ECDSA ssl ciphers; google chrome prefered order, 128bit most prefered
ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA;
ssl_ecdh_curve secp384r1; # 384 bit prime modulus curve efficiently supports ECDHE ssl_ciphers up to a SHA384 hash
ssl_prefer_server_ciphers on;
ssl_certificate /home/acme/.acme.sh/mail.example.org_ecc/fullchain.cer;
ssl_certificate_key /home/acme/.acme.sh/mail.example.org_ecc/mail.example.org.key;
#
# Running Roundcube as a subfolder on an existing virtual host
#
# Block access to default directories and files under these directories
location ~ /mail/(bin|config|installer|logs|SQL|temp|vendor)($|/.*) {
deny all;
}
# Block access to default files under top-directory and files start with same name.
location ~ /mail/(CHANGELOG|composer.json|INSTALL|jsdeps.json|LICENSE|README|UPGRADING)($|.*) {
deny all;
}
# Block plugin config files and sample config files.
location ~ /mail/plugins/.*/config.inc.php.* {
deny all;
}
# Block access to plugin data
location ~ /mail/plugins/enigma/home($|/.*) {
deny all;
}
# Redirect URI `/mail` to `/mail/`.
location = /mail {
return 301 /mail/;
}
location ~ ^/mail/(.*\.php)$ {
# Use HTTP Strict Transport Security to force client to use secure
# connections only. References:
#
# * RFC Document (6797): HTTP Strict Transport Security (HSTS)
# https://tools.ietf.org/html/rfc6797#section-6.1.2
#
# * Short tutorial from Mozilla:
# https://developer.mozilla.org/en-US/docs/Web/Security/HTTP_strict_transport_security
#
# WARNING: According to RFC document, HSTS will fail with self-signed SSL
# certificate.
# https://tools.ietf.org/html/rfc6797#page-27
#
# Syntax:
#
# Strict-Transport-Security: max-age=expireTime [; includeSubDomains] [; preload]
add_header Strict-Transport-Security "max-age=31536000";
#
# Template used to handle PHP fastcgi applications
#
# You still need to define `SCRIPT_FILENAME` for your PHP application, and
# probably `fastcgi_index` if your application use different index file.
#
include fastcgi_params;
# Directory index file
fastcgi_index index.php;
# Handle PHP files with upstream handler
fastcgi_pass php_workers;
# Fix the HTTPROXY issue.
# Reference: https://httpoxy.org/
fastcgi_param HTTP_PROXY '';
fastcgi_param SCRIPT_FILENAME /var/www/roundcubemail/$1;
}
location ~ ^/mail/(.*) {
alias /var/www/roundcubemail/$1;
index index.php;
}
#
# Running Postfixadmin as a subfolder on an existing virtual host
#
# Block access to default directories and files under these directories
location ~ /postfixadmin/(bin|config|installer|logs|SQL|temp|vendor)($|/.*) {
deny all;
}
# Block access to default files under top-directory and files start with same name.
location ~ /postfixadmin/(CHANGELOG.txt|GPL-LICENSE.TXT|INSTALL.TXT|LICENSE.TXT|README.md|composer.json|composer.lock)($|.*) {
deny all;
}
# Block plugin config files and sample config files.
location ~ /postfixadmin/plugins/.*/config.inc.php.* {
deny all;
}
# Block access to plugin data
location ~ /postfixadmin/plugins/enigma/home($|/.*) {
deny all;
}
# Redirect URI `/postfixadmin` to `/postfixadmin/`.
location = /postfixadmin {
return 301 /postfixadmin/;
}
location ~ ^/postfixadmin/(.*\.php)$ {
# Use HTTP Strict Transport Security to force client to use secure
# connections only. References:
#
# * RFC Document (6797): HTTP Strict Transport Security (HSTS)
# https://tools.ietf.org/html/rfc6797#section-6.1.2
#
# * Short tutorial from Mozilla:
# https://developer.mozilla.org/en-US/docs/Web/Security/HTTP_strict_transport_security
#
# WARNING: According to RFC document, HSTS will fail with self-signed SSL
# certificate.
# https://tools.ietf.org/html/rfc6797#page-27
#
# Syntax:
#
# Strict-Transport-Security: max-age=expireTime [; includeSubDomains] [; preload]
add_header Strict-Transport-Security "max-age=31536000";
#
# Template used to handle PHP fastcgi applications
#
# You still need to define `SCRIPT_FILENAME` for your PHP application, and
# probably `fastcgi_index` if your application use different index file.
#
include fastcgi_params;
# Directory index file
fastcgi_index index.php;
# Handle PHP files with upstream handler
fastcgi_pass php_workers;
# Fix the HTTPROXY issue.
# Reference: https://httpoxy.org/
fastcgi_param HTTP_PROXY '';
fastcgi_param SCRIPT_FILENAME /var/www/postfixadmin/public/$1;
}
location ~ ^/postfixadmin/(.*) {
alias /var/www/postfixadmin/public/$1;
index index.php;
}
# Normal PHP scripts
location ~ \.php$ {
#
# Template used to handle PHP fastcgi applications
#
# You still need to define `SCRIPT_FILENAME` for your PHP application, and
# probably `fastcgi_index` if your application use different index file.
#
include fastcgi_params;
# Directory index file
fastcgi_index index.php;
# Handle PHP files with upstream handler
fastcgi_pass php_workers;
# Fix the HTTPROXY issue.
# Reference: https://httpoxy.org/
fastcgi_param HTTP_PROXY '';
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
We’re going to skip setting up memcached
for now. The newer versions are for PHP 7 which will not be a Slackware default until version 15
is out or so. Rerfer to my old guide for help setting up memcached
.
Change the permissions of /var/lib/php
since we’re using nginx
instead of httpd
to run PHP:
chown root:nginx /var/lib/php/
If you’re getting any compilation errors for nginx
, PHP, or any package regarding graphics, you’ll need some X11 libraries. These are most likely not installed if you’re working on a headless server. Get the needed dependencies from a Slackware mirror:
slackpkg install libX11 libXpm libxcb libXau libXdmcp
PHP-FPM with FastCGI in nginx
We’ll set up PHP-FPM a bit different this time around by putting its configuration in a separate file. First you’ll need to make sure the startup script is executable. Start php-fpm
at least once to let it create its default configuration files:
chmod +x /etc/rc.d/rc.php-fpm
/etc/rc.d/rc.php-fpm start
/etc/rc.d/rc.php-fpm stop
We’ll add our own /etc/php-fpm.d/mailserver.conf
with the following content:
[mailserver]
user = nginx
group = nginx
listen = /var/run/php-fpm.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0666
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 5
request_terminate_timeout = 10s
request_slowlog_timeout = 10s
access.log = /var/log/php-fpm/php-fpm.log
slowlog = /var/log/php-fpm/slow.log
security.limit_extensions = .php .php3 .php4 .php5 .html .htm
Make sure this file is included in /etc/php-fpm.conf
. Edit the error_log
to log/php-fpm/php-fpm.log
to keep everything in place while you’re there.
Install the php-imagick
extension too. This will come in handy for Roundcube later.
Dovecot
Lots of development has been done on Dovecot since my last guide. Dovecot is now on version 2.3.4
as of this writing, up from version 2.2.16
from my past guide. We are still going to use Postfix Admin to store the user information in this setup instead of creating a Unix account for each mailbox. The email will be stored in /var/vmail
organized by domain and user, so the email for admin@example.org
would be stored in /var/vmail/example.org/admin
. We’ll create a single user to own the mailboxes on the system and let Dovecot manage them. Dovecot will also need its own user and group:
useradd -r -d /var/vmail -s /bin/false -u 150 -g 12 vmail
mkdir /var/vmail
chmod 770 /var/vmail
chown vmail:mail /var/vmail
groupadd -g 202 dovecot
useradd -d /dev/null -s /bin/false -u 202 -g 202 dovecot
groupadd -g 248 dovenull
useradd -d /dev/null -s /bin/false -u 248 -g 248 dovenull
You can use whatever names and IDs you want but make sure it doesn’t interfere with what Slackware already uses. Copy over the sample configuration files and replace some of them
cp -r /usr/doc/dovecot-2.3.4/example-config/* /etc/dovecot/
chown -R vmail:dovecot /etc/dovecot
chmod -R o-rw /etc/dovecot
We’ll set up /etc/dovecot/dovecot-sql.conf.ext
first with the database connection information
driver = mysql
connect = host=/var/run/mysql/mysql.sock dbname=mail user=mail password=password-here
default_pass_scheme = SHA512-CRYPT
Of course, set dbname
, user
, and password
appropriately. These are the same credentials Postfix Admin will use, so keep that in mind. We’re not creating the database or user yet. That will come later. Now let’s add the password_query
and user_query
to /etc/dovecot/dovecot-sql.conf.ext
:
password_query = \
SELECT username as user, password, '/var/vmail/%d/%n' as \
userdb_home, 'maildir:/var/vmail/%d/%n' as userdb_mail, \
150 as userdb_uid, 12 as userdb_gid \
FROM mailbox WHERE username = '%u' AND active = '1'
user_query = \
SELECT '/var/vmail/%d/%n' as home, 'maildir:/var/vmail/%d/%n' \
as mail, 150 AS uid, 12 AS gid, \
concat('dirsize:storage=', quota) AS quota \
FROM mailbox WHERE username = '%u' AND active = '1'
Edit /etc/dovecot/conf.d/10-auth.conf
and enable the SQL configuration file we just modified. You will need to disable plaintext authentication unless it’s already encrypted through TLS. We’ll also disable the auth-system.conf.ext
file that’s loaded by default. Don’t worry about authenticating with plain text; your connection will be secured with TLS so this is safe:
disable_plaintext_auth = yes
auth_mechanisms = plain login
#!include auth-system.conf.ext
!include auth-sql.conf.ext
Next, change the UIDs and mail location in /etc/dovecot/conf.d/10-mail.conf
to whatever you set up earlier:
mail_location = maildir:/var/vmail/%d/%n
mail_uid = vmail
mail_gid = mail
first_valid_uid = 150
last_valid_uid = 150
Dovecot has changed some of its TLS options. I use the secp384r1 curve for my ECDSA SSL certificates. My /etc/dovecot/conf.d/10-ssl.conf
file will looks like this:
ssl_cert = </home/acme/.acme.sh/mail.example.org_ecc/fullchain.cer
ssl_key = </home/acme/.acme.sh/mail.example.org_ecc/mail.example.org.key
ssl_min_protocol = TLSv1
ssl_cipher_list = ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA
ssl_curve_list = P-384
ssl_prefer_server_ciphers = yes
In order to have Dovecot authenticate, uncomment the user
, group
, and mode
lines in the unix_listener auth-userdb
section of the service auth
block in /etc/dovecot/conf.d/10-master.conf
. Additionally, set up a unix listener
for Postfix. Uncomment that section as well and add postfix
as the user
and group
. We’ll create those later when we’re setting up Postfix. Set up the stats-writer
permissions, too.
service auth {
unix_listener auth-userdb {
mode = 0666
user = vmail
group = mail
}
}
unix_listener /var/spool/postfix/private/auth {
mode = 0666
user = postfix
group = postfix
}
service stats {
unix_listener stats-reader {
user = vmail
group = mail
mode = 0660
}
unix_listener stats-writer {
user = vmail
group = mail
mode = 0660
}
}
Dovecot Pigeonhole
A new addition to this guide is Dovecot Pigeonhole. This adds support for the Sieve language (RFC 5228) and the ManageSieve protocol (RFC 5804) to Dovecot. Coupled with a Roundcube plugin, this will allow us to filter email based on any number of factors. For example, you can create an rule in Roundcube that will automatically place email coming from @bank.com
to a Bank Notifications
folder.
Grab the SlackBuild and run it. The package will install some configuration files in the Dovecot documentation directory. Copy these to /etc/dovecot/conf.d
:
/usr/doc/dovecot-2.3.4/example-config/conf.d/90-sieve.conf
/usr/doc/dovecot-2.3.4/example-config/conf.d/90-sieve-extprograms.conf
/usr/doc/dovecot-2.3.4/example-config/conf.d/20-managesieve.conf
Let’s make some changes to these files
First, edit /etc/dovecot/conf.d/20-lmtp.conf
, and add
protocol lmtp {
postmaster_address = postmaster@example.org
mail_plugins = $mail_plugins sieve quota
log_path = /var/log/dovecot-lmtp-errors.log
info_log_path = /var/log/dovecot-lmtp.log
}
For /etc/dovecot/conf.d/15-lda.conf
, add
protocol lda {
postmaster_address = postmaster@example.org
mail_plugins = $mail_plugins sieve quota
auth_socket_path = /var/run/dovecot/auth-master
log_path = /var/log/dovecot-lda-errors.log
info_log_path = /var/log/dovecot-lda.log
}
For /etc/dovecot/conf.d/10-mail.conf
, add
mail_home = /var/vmail/%d/%n/sieve
mail_location = maildir:/var/vmail/%d/%n
In /etc/dovecot/conf.d/20-managesieve.conf
, add
protocols = $protocols sieve
service managesieve-login {
inet_listener sieve {
port = 4190
}
service managesieve {
process_limit = 1024
}
protocol sieve {
log_path = /var/log/dovecot-sieve-errors.log
info_log_path = /var/log/dovecot-sieve.log
managesieve_max_line_length = 65536
managesieve_implementation_string = Dovecot Pigeonhole
}
Lastly, edit /etc/dovecot/conf.d/90-sieve.conf
, and add
plugin {
sieve = file:/var/vmail/%d/%n/sieve;active=/var/vmail/%d/%n/sieve/.dovecot.sieve
sieve_default = /etc/dovecot/sieve/default.sieve
sieve_global = /etc/dovecot/sieve/global/
}
lda_mailbox_autocreate = yes
lda_mailbox_autosubscribe = yes
Now we need to create some files that are needed for our configuration to work:
mkdir -p /etc/dovecot/sieve/global
chown -R vmail:mail /etc/dovecot/sieve/
touch /var/log/{dovecot-lda-errors.log,dovecot-lda.log}
touch /var/log/{dovecot-sieve-errors.log,dovecot-sieve.log}
touch /var/log/{dovecot-lmtp-errors.log,dovecot-lmtp.log}
chown vmail:dovecot /var/log/dovecot-*
Add postfix
to the dovecot
group. This is needed by amavisd-new
later.
usermod -G dovecot -a postfix
Postfix
Postfix is now at version 3.3.2
instead of 3.0.3
we used in the last guide. There haven’t been any major configuration changes so we’ll just proceed as normal. Create the required user and group before going any further:
groupadd -g 200 postfix
groupadd -g 201 postdrop
useradd -u 200 -d /dev/null -s /bin/false -g postfix postfix
Run the SlackBuild with support for MySQL and install it
DATABASE=mysql ./postfix.SlackBuild
Let’s create our MySQL map files now
/etc/postfix/mysql_virtual_alias_domainaliases_maps.cf
user = mail
password = mailpassword
hosts = localhost
dbname = mail
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
/etc/postfix/mysql_virtual_alias_maps.cf
user = mail
password = mailpassword
hosts = localhost
dbname = mail
table = alias
select_field = goto
where_field = address
additional_conditions = and active = '1'
/etc/postfix/mysql_virtual_domains_maps.cf
user = mail
password = mailpassword
hosts = localhost
dbname = mail
table = domain
select_field = domain
where_field = domain
additional_conditions = and backupmx = '0' and active = '1'
/etc/postfix/mysql_virtual_mailbox_domainaliases_maps.cf
user = mail
password = mailpassword
hosts = localhost
dbname = mail
query = SELECT maildir FROM mailbox, alias_domain
WHERE alias_domain.alias_domain = '%d'
AND mailbox.username=concat('%u', '@', alias_domain.target_domain )
AND mailbox.active = 1
/etc/postfix/mysql_virtual_mailbox_maps.cf
user = mail
password = mailpassword
hosts = localhost
dbname = mail
table = mailbox
select_field = CONCAT(domain, '/', local_part)
where_field = username
additional_conditions = and active = '1'
We’re going to strip the email client and IP address of each mail you send. Remember this is just security through obscurity and you’ll be violating RFC 2045 if you remove the MIME-Version string
. It’s still useful to prevent uninitiated recipients from figuring out where you are sending your mail from. Add these to the file /etc/postfix/header_checks
for now. We’ll enable it in the Postfix configuration later.
/^Received:/ IGNORE
/^User-Agent:/ IGNORE
/^X-Mailer:/ IGNORE
/^X-Enigmail:/ IGNORE
/^X-Originating-IP:/ IGNORE
/^x-cr-[a-z]*:/ IGNORE
/^Thread-Index:/ IGNORE
/^\s*Mime-Version: 1.0.*/ REPLACE Mime-Version: 1.0
Postfix Main Configuration
We only need to make some minor adjustments to /etc/postfix/main.cf
:
myhostname = mail.example.org
myorigin = $myhostname
inet_interfaces = all
mynetworks = 127.0.0.0/24 [::ffff:127.0.0.0]/104 [::1]/128
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
header_checks = regexp:/etc/postfix/header_checks
mime_header_checks = regexp:/etc/postfix/header_checks
smtpd_banner = $myhostname ESMTP $mail_name
biff = no
append_dot_mydomain = no
The Dovecot authentication section will remain the same as in my previous guide too
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
broken_sasl_auth_clients = no
smtpd_sasl_security_options = noanonymous
smtpd_sasl_local_domain =
smtpd_sasl_authenticated_header = yes
With newer versions of Postfix, you can now include both ECDSA and RSA certificates in your configuration. I’m disabling SSLv2, SSLv3, and TLSv1.0 in the configuration below. We’re excluding known insecure ciphers and setting the encryption level to may
. You can set this to encrypt
, if you want, but the Postfix documentation strongly advises against this for a public facing server.
lmtp_tls_ciphers = high
lmtp_tls_mandatory_ciphers = high
lmtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1
lmtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1
smtp_tls_ciphers = high
smtp_tls_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, 3DES, DES+MD5, LOW, DSS, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CDC3-SHA, KRB5-DE5, CBC3-SHA
smtp_tls_loglevel = 1
smtp_tls_mandatory_ciphers = high
smtp_tls_mandatory_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, 3DES, DES+MD5, LOW, DSS, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CDC3-SHA, KRB5-DE5, CBC3-SHA
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1
smtp_tls_note_starttls_offer = yes
smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1
smtp_tls_security_level = may
smtpd_reject_unlisted_recipient = yes
smtpd_reject_unlisted_sender = yes
smtpd_tls_auth_only = yes
smtpd_tls_cert_file= /home/acme/.acme.sh/mail.example.org/fullchain.cer
smtpd_tls_eccert_file = /home/acme/.acme.sh/mail.example.org_ecc/fullchain.cer
smtpd_tls_eckey_file = /home/acme/.acme.sh/mail.example.org_ecc/mail.example.org.key
smtpd_tls_eecdh_grade = ultra
smtpd_tls_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, 3DES, DES+MD5, LOW, DSS, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CDC3-SHA, KRB5-DE5, CBC3-SHA
smtpd_tls_key_file= /home/acme/.acme.sh/mail.example.org/mail.example.org.key
smtpd_tls_loglevel = 1
smtpd_tls_mandatory_ciphers = high
smtpd_tls_mandatory_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, 3DES, DES+MD5, LOW, DSS, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CDC3-SHA, KRB5-DE5, CBC3-SHA
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1
smtpd_tls_received_header = yes
smtpd_tls_security_level = may
smtpd_tls_session_cache_timeout = 3600s
tls_preempt_cipherlist = yes
tls_random_source = dev:/dev/urandom
Feel free to set these parameters to whatever fits your needs
delay_warning_time = 4h
maximal_queue_lifetime = 5d
minimal_backoff_time = 1000s
maximal_backoff_time = 8000s
message_size_limit = 20480000
smtp_helo_timeout = 60s
smtpd_recipient_limit = 16
smtpd_soft_error_limit = 3
smtpd_hard_error_limit = 12
Now we set up milters along with the Postgrey and OpenDKIM sockets
smtpd_helo_restrictions = permit_mynetworks, permit_sasl_authenticated, warn_if_reject reject_non_fqdn_hostname, reject_invalid_hostname, permit
smtpd_sender_restrictions = permit_sasl_authenticated, permit_mynetworks, warn_if_reject reject_non_fqdn_sender, reject_unknown_sender_domain, reject_unauth_pipelining, permit
smtpd_client_restrictions = reject_rbl_client sbl.spamhaus.org, reject_rbl_client blackholes.easynet.nl, reject_rbl_client b.barracudacentral.org
smtpd_recipient_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unauth_destination, check_policy_service unix:/var/run/postgrey/postgrey.sock, permit
smtpd_data_restrictions = reject_unauth_pipelining
smtpd_relay_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unauth_destination, check_policy_service unix:/var/run/postgrey/postgrey.sock, permit
smtpd_helo_required = yes
smtpd_delay_reject = yes
disable_vrfy_command = yes
mailbox_size_limit = 0
recipient_delimiter = +
non_smtpd_milters=unix:/var/run/opendkim/opendkim.sock
smtpd_milters=unix:/var/run/opendkim/opendkim.sock
We need to point Postfix to our database map files. Use the same UID
and GID
that was used for the Dovecot
virtual_mailbox_base = /var/vmail
virtual_mailbox_maps = mysql:/etc/postfix/mysql_virtual_mailbox_maps.cf, mysql:/etc/postfix/mysql_virtual_mailbox_domainaliases_maps.cf
virtual_uid_maps = static:150
virtual_gid_maps = static:12
virtual_alias_maps = mysql:/etc/postfix/mysql_virtual_alias_maps.cf, mysql:/etc/postfix/mysql_virtual_alias_domainaliases_maps.cf
virtual_mailbox_domains = mysql:/etc/postfix/mysql_virtual_domains_maps.cf
Last, set up Dovecot and Amavis
virtual_transport = dovecot
dovecot_destination_recipient_limit = 1
content_filter = amavis-forward:unix:amavis/amavisd.sock
Postfix Master Configuration
Moving on to /etc/postfix/master.cf
, we’ll set up SMTP with TLS on port 587
and SMTPS on port 465
. Most options can just be enabled by uncommenting them. Make sure you comment out the -o syslog_name=postfix/$service_name
option right under relay
.
submission inet n - - - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_enforce_tls=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject_unauth_destination,reject
-o smtpd_sasl_tls_security_options=noanonymous
-o cleanup_service_name=subcleanup
-o tls_preempt_cipherlist=yes
smtps inet n - - - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_tls_auth_only=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject_unauth_destination,reject
-o smtpd_sasl_security_options=noanonymous,noplaintext
-o smtpd_sasl_tls_security_options=noanonymous
subcleanup unix n - - - 0 cleanup
-o header_checks=regexp:/etc/postfix/submission_header_checks
relay unix - - n - - smtp
# -o syslog_name=postfix/$service_name
# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
maildrop unix - n n - - pipe
flags=DRhu user=vmail argv=/usr/local/bin/maildrop -d ${recipient}
dovecot unix - n n - - pipe
flags=DRhu user=vmail:mail argv=/usr/libexec/dovecot/dovecot-lda -f ${sender} -d $(recipient)
For Amavis, just make sure you set the max number of processes it’s allowed to run. In this example, it’s 4 (the same as the nginx
configuration).
amavis-forward unix - - - - 4 lmtp
-o lmtp_data_done_timeout=1200
-o lmtp_send_xforward_command=yes
-o disable_dns_lookups=yes
-o max_use=20
amavis/amavis-accept unix n - - - - smtpd
-o content_filter=
-o local_recipient_maps=
-o relay_recipient_maps=
-o cleanup_service_name=cleanup
-o smtpd_restriction_classes=
-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
Before you continue, make an /etc/aliases
file and generate the database:
touch /etc/aliases
newaliases
Amavis with ClamAV and SpamAssassin
Let’s set up virus and spam checking. We are using amavisd-new
here. It will be the interface bewtween Postfix, ClamAV and SpamAssassin.
We need a ton of things for Amavis and SpamAssassin. You can go the SlackBuilds route or just use CPAN. If you need to keep track of what you have installed with sbopkg
, slackpkg
or whatever Slackware “package manager” you use then go the SlackBuilds route. Before getting started you may want to install the re2c
, zeromq
, pyzor
, unrar
, arj
, cabextract
, lzop
, nomarch
, p7zip
, libmspack
, and GeoIP
packages to allow SpamAssassin and ClamAV to handle different compressed files and to satisfy some of their dependencies.
If you’re using CPAN, here’s what you need to install all the Perl modules:
cpan install CPAN
cpan install Log::Log4perl App::cpanminus
cpanm -f -v inc::latest Test::Pod Test::Pod::Coverage Encode::Detect Image::Info TimeDate Net::LibIDN Net::SSLeay Socket6 IO::Socket::IP IO::Socket::SSL IO::Socket::INET6 Crypt::OpenSSL::Bignum Crypt::OpenSSL::Random Crypt::OpenSSL::RSA Geography::Countries IP::Country Digest::SHA Digest::SHA1 Digest::HMAC HTML::Tagset HTML::Parser Test::LeakTrace Authen::NTLM Data::Dump LWP Net::CIDR::Lite PAR::Dist ExtUtils::MakeMaker ExtUtils::Install Net::HTTP WWW::RobotRules HTTP::Date File::Listing IO::HTML Encode::Locale LWP::Protocol::https LWP::MediaTypes HTTP::Message HTTP::Negotiate HTTP::Cookies HTTP::Daemon Bundle::LWP NetAddr::IP Net::Server Net::Ident MailTools Net::IP Net::DNS Net::DNS::Resolver::Programmable Mail::SPF Mail::DKIM Geo::IP Net::Patricia Convert::TNEF Convert::UUlib Convert::BinHex Archive::Zip IO::Stringy MIME::Tools Unix::Syslog BerkeleyDB IO::Multiplex Net::LibIDN File::LibMagic
SpamAssassin
Get the SpamAssassin SlackBuild and build it.
After SpamAssassin is installed, edit the /etc/spamassassin.conf
file and set the following options. You only really need ENABLED
but the rest are a good idea.
ENABLED=1
OPTIONS="--create-prefs --max-children 5 --helper-home-dir"
PIDFILE="/var/run/spamd.pid"
CRON=1
The SpamAssassin source no longer includes rules, so you’ll have to download them. Run sa-update
to do this.
ClamAV
You’ll need a user and group created first.
groupadd -g 210 clamav
useradd -u 210 -d /dev/null -s /bin/false -g clamav clamav
Run the SlackBuild with the COUNTRY
variable set to your country of choice.
Edit the /etc/clamd.conf
file and change the LocalSocket
and LocalSocketGroup
options
LocalSocket /var/run/clamav/clamd.sock
LocalSocketGroup amavis
Let’s install amavisd-new
before we update the ClamAV virus definitions.
amavisd-new
Create a user and group before you run the script.
groupadd -g 225 amavis
useradd -m -d /var/lib/amavis -s /bin/bash -u 225 -g 225 amavis
While we’re at it, go ahead and add the amavis
user to the clamav
group and vice versa.
usermod -G clamav -a amavis
usermod -G amavis -a clamav
Uncomment the lines @bypass_virus_checks_maps
and @bypass_spam_checks_maps
at the top of /etc/amavisd.conf
and add the following
@bypass_virus_checks_maps = (
%bypass_virus_checks, @bypass_virus_checks_acl, $bypass_virus_checks_re);
@bypass_spam_checks_maps = (
%bypass_spam_checks, @bypass_spam_checks_acl, $bypass_spam_checks_re);
Go down a bit further and uncomment @lookup_sql_dsn
, then modify it to connect to your database using the Unix socket and the proper credentials. Amavis uses the DBD::mysql
Perl module. The documentation states setting the host
value to localhost
will use the socket. This configuration will enable spam checking for the domains you’ve added to your database either manually or through Postfix Admin
@lookup_sql_dsn = (
['DBI:mysql:database=mail;host=localhost',
'mail',
'mailpassword']);
$sql_select_policy = 'SELECT domain from domain WHERE CONCAT("@",domain) IN (%k)';
There are a couple of other settings we can change. For instance, make sure you also set $max_servers
to the same number of processes you allowed Amavis to use in /etc/postfix/master.cf
. Setting the $sa_tag_level_deflt
opton to a large negative number will ensure that spam headers are added to every single email. Change the user and group to the ones you created earlier, set up a home directory for configuration files and quarantine emails, and set your domain name (not the same as your hostname). Make sure you also uncomment the amavis
section in @av_scanners
. We are also setting the Unix socket for Amavis here too.
$max_servers = 4;
$sa_tag_level_deflt = -9999;
$daemon_user = 'amavis';
$daemon_group = 'amavis';
$mydomain = 'example.org';
$myhostname = 'mail.example.org';
$MYHOME = '/var/lib/amavis';
$QUARANTINEDIR = "$MYHOME/virusmails";
$unix_socketname = "/var/spool/postfix/amavis/amavisd.sock";
$unix_socket_mode = 0660;
$interface_policy{'SOCK'} = 'mysock';
$policy_bank{'mysock'} = {
protocol => 'LMTP',
auth_required_release => 0,
}
Comment out $inet_socket_port
and $interface_policy('10026')
. There may already be an $interface_policy
set up for AM.PDP-SOCK
. Comment it out or modify it to what I have above. Notice we’re placing the socket in the Postfix queue directory, /var/spool/postfix
. We’ll need to create the directory to hold the socket and assign some permissions as well.
mkdir /var/spool/postfix/amavis/
chmod 770 /var/spool/postfix/amavis/
chown amavis:postfix /var/spool/postfix/amavis/
usermod -G amavis -a postfix
Use the following commands to let Postfix create the amavis-accept
PID:
cd /var/spool/postfix/public
ln -s ../amavis amavis
cd /var/spool/postfix/pid
mkdir unix.amavis
chown root:root unix.amavis
chmod 700 unix.amavis
In order for the above to work, we need to set a $forward_method
in /etc/amavisd.conf
. In my setup, amavisd-new
would ignore that configuration option and insist on using TCP port 10025. I could not for the life of me figure out how to get it to use a socket no matter what value I tried. I ended up having to modify the /usr/sbin/amavisd
Perl script directly. Look around line 926 or search for 10025. Comment out the existing $forward_method
and replace it with this:
$forward_method = $have_inet6 && !$have_inet4 ? 'smtp:[::1]:10025' : 'smtp:/var/spool/postfix/amavis/amavis-accept';
Go through the rest of /etc/amavisd.conf
file and modify any settings you might want changed.
Now go ahead and update your virus database by running freshclam
as root
. Don’t worry if you get a message from freshclam
saying clamd
was not updated. This is because we have not started clamd
yet. We also need to fix some permissions to get all three to play nicely.
chmod 775 /var/lib/spamassassin/
chown amavis:amavis /var/lib/spamassassin/
chown -R amavis:amavis /var/lib/spamassassin/
chown -R amavis:amavis /var/lib/amavis
chown -R clamav:amavis /var/lib/clamav
Postgrey
Set up the user and group first, then run the SlackBuild.
groupadd -g 301 postgrey
useradd -u 301 -d /var/lib/postgrey -s /bin/false -g postgrey postgrey
Set POSTGREYUSR
, POSTGREYGRP
, POSTGREYUID
and POSTGREYGID
in the SlackBuild to the values you set earlier when you created them. You can get an updated version of postgrey_whitelist_clients
from the Postgrey site and place it in /etc/postfix
, replacing the one included with the SlackBuild.
Edit the /etc/rc.d/rc.postgrey
script and set USER
, GROUP
, and HOST
to their proper values. Feel free to get rid of PORT
. Find the postgrey_start()
function and edit the postgrey
flags to make sure it uses a socket instead of TCP.
postgrey_start() {
echo "Starting postgrey milter: /usr/bin/postgrey -d --unix=/var/run/postgrey/postgrey.sock --pidfile=$PIDFILE --user=$USER --group=$GROUP --dbdir=/var/lib/postgrey --hostname=$HOST"
mkdir -p /var/run/postgrey
/usr/bin/postgrey -d \
--unix=/var/run/postgrey/postgrey.sock \
--pidfile=/var/run/postgrey/postgrey.pid \
--user=$USER --group=$GROUP \
--dbdir=/var/lib/postgrey \
--hostname=$HOST
}
The extracted source includes an init
script, too. It’s in contrib/postgrey.init
if you want to use it.
OpenDKIM, DNS and Building Trust
The setup up to this point should be pretty much complete and meet most people’s needs. Some mail servers are quite picky when it comes to receiving email. Gmail particularly doesn’t like when an email is not signed. The next section will walk you through signing your email with DomainKeys Identified Mail and setting up Sender Policy Framework. If you are using a hosting provider for your server, you will need to contact them and have them set up a PTR record for your IP address. This is also known as rDNS. Some mail servers will reject your email if the IP you are sending from does not point back to your domain name. In general, it should look something like this:
34.216.184.93.in-addr.arpa PTR 600 example.org
If you’re hosting at home, you can try asking your ISP to set this up for you but it is unlikely they’ll want to. They may be willing if you purchase a static IP. You’ll also need to add MX
records to your domain’s DNS records. You can add something like this
example.org MX 600 10 mail.example.org
That’s assuming mail.example.org points to your mail sever’s IP and you want a priority of 10. You can ask your DNS provider to add these for you. Check that the record has propagated with host
:
$ host -t MX example.org
example.org mail is handled by 10 mail.example.org.
OpenDKIM
Install (libbsd
), (opendbx
), then grab my SlackBuild and install OpenDKIM. Set up the user and group first:
groupadd -g 305 opendkim
useradd -r -u 305 -g opendkim -d /var/run/opendkim/ -s /sbin/nologin -c "OpenDKIM Milter" opendkim
We’ll need to create the run directory as well:
mkdir -p /var/run/opendkim
chown opendkim:opendkim /var/run/opendkim
We are using MariaDB here so set the USE_MYSQL
variable to yes
and run the SlackBuild. I used a modified version of CentOS’s init
script for rc.opendkim, but feel free to grab the one included in the source in the contrib/init/generic/
directory.
Once it’s installed, we’ll need to set up a basic configuration file. You can copy the sample one from opendkim/opendkim.conf.simple
in the extracted source to /etc/opendkim
and add the user and group we created earlier. Note that the SlackBuild already does this for you:
UserID opendkim:opendkim
KeyFile /etc/opendkim/keys/default.private
You’ll notice my init
script will automatically create some default keys for you in /etc/opendkim/keys
and create the directory if it doesn’t exist. We’re using Unix sockets in this guide, so let’s change a line in /etc/opendkim.conf
:
Socket local:/var/run/opendkim/opendkim.sock
We’ll generate a 2048 bit key with the opendkim-genkey
command. You can try something stronger like 4096 bits, but RFC 6376 suggests it might not fit in a 512 byte DNS UDP response. See section 3.3.3 Key Sizes for more information.
opendkim-genkey -b 2048 -D /etc/opendkim/keys -s mailsvr -d example.org
The selector is simply something to tell your key apart once you add it to your DNS records. You’ll end up with two files, mailsvr.private
, which is your key, and mailsvr.txt
which has a nicely formatted record you’ll need to add to your DNS zone. If you don’t manage your own DNS or have access to your zone file, simply copy the text starting with v=DKIM1
as a TXT
record in whatever control panel your DNS provider uses. For the example above, this is what I got (truncated for demonstration):
"v=DKIM1; k=rsa;
p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0m8F6p1AD"
You’ll need to wait a while before the DNS record propagates but once it does you can check it with dig
dig mailsvr._domainkey.example.org TXT
Sender Policy Framework
This basically consists of adding another TXT
record to your DNS zone. There used to be an SPF
type record but this was removed in RFC 7208. You’ll want to add something like this:
v=spf1 a mx ~all
MariaDB
We’ll set up MariaDB first (the scripts are still named rc.mysqld
):
chmod +x /etc/rc.d/rc.mysqld
/usr/bin/mysql_install_db --user=mysql
/etc/rc.d/rc.mysqld start
/usr/bin/mysql_secure_installation
Now create the user and database we’ll be using for Roundcube, Postfix Admin, and all other components.
CREATE DATABASE mail;
GRANT ALL PRIVILEGES ON mail.* TO "mail"@"localhost" \
IDENTIFIED BY "password-here";
FLUSH PRIVILEGES;
Start Up Email
Let’s start up the services we have so far in order to get all necessary files created properly. We’ll need the services running in order to set up Roundcube and Postfix Admin correctly.
You can place your startup commands in /etc/rc.d/rc.local
and the stop commands in /etc/rc.d/rc.local_shutdown
. Make sure both are executable and add this content
/etc/rc.d/rc.local
if [ -x /etc/rc.d/rc.php-fpm ]; then
/etc/rc.d/rc.php-fpm start
fi
if [ -x /etc/rc.d/rc.nginx ]; then
/etc/rc.d/rc.nginx start
fi
if [ -x /etc/rc.d/rc.postgrey ]; then
/etc/rc.d/rc.postgrey start
fi
if [ -x /etc/rc.d/rc.clamav ]; then
/etc/rc.d/rc.clamav start
fi
if [ -x /etc/rc.d/rc.spamd ]; then
/etc/rc.d/rc.spamd start
fi
if [ -x /etc/rc.d/rc.amavisd-new ]; then
/etc/rc.d/rc.amavisd-new start
fi
if [ -x /etc/rc.d/rc.postfix ]; then
/etc/rc.d/rc.postfix start
fi
if [ -x /etc/rc.d/rc.dovecot ]; then
/etc/rc.d/rc.dovecot start
fi
if [ -x /etc/rc.d/rc.opendkim ]; then
/etc/rc.d/rc.opendkim start
fi
/etc/rc.d/rc.local_shutdown
if [ -x /etc/rc.d/rc.nginx ]; then
/etc/rc.d/rc.nginx stop
fi
if [ -x /etc/rc.d/rc.php-fpm ]; then
/etc/rc.d/rc.php-fpm stop
fi
if [ -x /etc/rc.d/rc.opendkim ]; then
/etc/rc.d/rc.opendkim stop
fi
if [ -x /etc/rc.d/rc.postfix ]; then
/etc/rc.d/rc.postfix stop
fi
if [ -x /etc/rc.d/rc.dovecot ]; then
/etc/rc.d/rc.dovecot stop
fi
if [ -x /etc/rc.d/rc.postgrey ]; then
/etc/rc.d/rc.postgrey stop
fi
if [ -x /etc/rc.d/rc.amavisd-new ]; then
/etc/rc.d/rc.amavisd-new stop
fi
if [ -x /etc/rc.d/rc.clamav ]; then
/etc/rc.d/rc.clamav stop
fi
if [ -x /etc/rc.d/rc.spamd ]; then
/etc/rc.d/rc.spamd stop
fi
Roundcube and Postfix Admin
The same advice from my previous post regarding web based interfaces applies.
Postfix Admin
The most notable change between the 2.92
version used in my previous guide 3.2.1
as of this writing are the ability to reset passwords via email/SMS and the move of most of the files to the public/
directory.
cd /var/www/
wget https://github.com/postfixadmin/postfixadmin/archive/postfixadmin-3.2.1.tar.gz
tar -xzf postfixadmin-3.2.1.tar.gz
ln -s postfixadmin-postfixadmin-3.2.1 postfixadmin
cd postfixadmin
mkdir templates_c
chown nginx:root templates_c
Make a copy of config.inc.php
as config.local.php
and make your changes there. The ones we need for this guide are
<?php
$CONF['configured'] = true;
$CONF['database_type'] = 'mysql';
$CONF['database_user'] = 'mail';
$CONF['database_password'] = "password-here";
$CONF['database_name'] = 'mail';
$CONF['database_socket'] = '/var/run/mysql/mysql.sock';
$CONF['admin_email'] = 'admin@example.org';
$CONF['encrypt'] = 'dovecot:SHA512-CRYPT';
$CONF['dovecotpw'] = "/usr/bin/doveadm pw";
$CONF['default_aliases'] = array (
'abuse' => 'abuse@example.org',
'hostmaster' => 'hostmaster@example.org',
'postmaster' => 'postmaster@example.org',
'webmaster' => 'webmaster@example.org',
'virusalert' => 'virusalert@example.org',
'admin' => 'admin@example.org'
);
$CONF['domain_path'] = 'NO';
$CONF['domain_in_mailbox'] = 'YES';
$CONF['footer_text'] = 'Return to example.org';
$CONF['footer_link'] = 'https://example.org';
$CONF['emailcheck_resolve_domain']='NO';
$CONF['password_expiration'] = 'NO';
?>
This tells Postfix Admin to use Dovecot’s crypt()
scheme for passwords and to connect to MariaDB using Unix sockets. Next, visit https://mail.example.org/postfixadmin/setup.php and complete the setup. Make sure you add your $CONF['setup_password']
obtained from setup.php
to config.local.php
. Do this before clicking on Add Admin.
We will need to move all our Dovecot configuration to a single file in order to appease Postfix Admin. It seems it has trouble parsing the !include conf.d/*.conf
line in /etc/dovecot/dovecot.conf
cp /etc/dovecot/dovecot.conf{,.bk}
doveconf -n > /etc/dovecot/dovecot.conf
chmod 644 /etc/dovecot/dovecot.conf
usermod -G dovecot -a nginx
Add Email Domains and Mailboxes
Log in to https://mail.example.org/postfixadmin and head over to Domain List > New Domain. Fill in whatever works for you here to add a new domain, then head to Virtual List > Add Mailbox and create your first user. I set up example.org as an email domain and admin@example.org as my first user. Doing this will generate the needed database schema that Postfix will use. Go ahead and play around with this and make sure your aliases are set up
Roundcube
The latest version of Roundcube is now 1.3.8
, up from 1.1.1
used in my previous guide. We’ve already set up the nginx
configuration for Postfix Admin and Roundcube so now we just have to download their files and run the installers.
cd /var/www/
wget https://github.com/roundcube/roundcubemail/releases/download/1.3.8/roundcubemail-1.3.8-complete.tar.gz
tar -xvzf roundcubemail-1.3.8-complete.tar.gz
ln -s roundcubemail-1.3.8 roundcubemail
cd roundcubemail
We’ll need to create a database for Roundcube to use. Log in to MariaDB with mysql -u root -p
and create it. Make sure you use a strong password.
CREATE DATABASE roundcubemail /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
GRANT ALL PRIVILEGES ON roundcubemail.* TO "roundcube"@"localhost" \
IDENTIFIED BY "password-here";
FLUSH PRIVILEGES;
Roundcube includes an SQL file that can create the necessary database structure for you
mysql -u roundcube roundcubemail -p < SQL/mysql.initial.sql
I recommend you check out the INSTALL
file included in the source for a more complete guide on the installation. Now head over to https://mail.example.org/installer and make sure everything is OK in the Check environment section. You will probably need to temporarily comment out the location
blocks in /etc/nginx/conf.d/mail.example.org.conf
that block access to Roundcube URLs such as /installer
.
My installation complained about the date.timezone
setting in php.ini
so I had to set that. Get a list of supported time zones from here. You may need to restart nginx
and php-fpm
for the changes to take effect.
Click Next when you’re done to move on to the Create config section. You can leave most of these settings alone. If you want to know what a specific setting does, check out Roundcube’s wiki. Fill in the Database setup section with the user and database you created earlier.
Make sure you do not enable spellchecking support. If you do, Roundcube will connect to external services to check your spelling. Why would we go through all this trouble to have a third party see every word we type?
IMAP and SMTP Settings
We’re going to set default_host
to tls://mail.example.org
in the IMAP Settings section. In the SMTP Settings section, set smtp_server
to tls://mail.example.org
, smtp_port
to 587
and check the Use the current IMAP username and password for SMTP authentication option. Click on Create config.
After it’s done writing your configuraiton file, go through config/config.inc.php
and see if you’d like to change anything.
Plugins
You can enable whichever plugins best fit your needs. The only one we really need to set up the email filtering we discussed is managesieve
.
Finishing Up
Go ahead and create some filters in Roundcube and make sure emails are going to the right folders. Test sending email to and from your own domain as well as to external domains. Connect your email clients. They should automatically detect your sever’s open ports but you can use the following settings to set it up manually. You can substitute the hostname for example.org if that points to your server’s IP as well.
Username: testuser@example.org
Incoming (IMAP) server: mail.example.org
Port: 993
SSL/TLS Enabled
Authentication: Plain (Normal Password)
Username: testuser@example.org
Outgoing (SMTP) server: mail.example.org
Port: 587
STARTTLS Enabled
Authentication: Plain (Normal Password)
The first time you receive an email, it’ll be greylisted thanks to Postgrey. Check /var/log/maillog
:
Apr 24 22:38:56 mail postgrey[945]: action=greylist, reason=new, client_name=mail.example.net, client_address=61.4.1.30, sender=testuser@example.net, recipient=testuser@example.org
Apr 24 22:38:56 mail postfix/smtpd[19527]: NOQUEUE: reject: RCPT from mail.example.net[63.4.1.30]: 450 4.2.0 <testuser@example.org>: Recipient address rejected: Greylisted, see http://postgrey.schweikert.ch/help/example.org.html; from=<testuser@example.net> to=<testuser@example.org> proto=ESMTP helo=<mail.example.net>
Most mail servers will try once more after some time and will be allowed the second time they send. Spammers generally only try once so this should stop some most of the common spam you could receive. Once the mail passes through, Amavis will check for viruses and other malware.
Apr 24 22:48:23 mail amavis[1078]: (01078-02) Passed CLEAN {RelayedInbound}, [63.4.1.30]:43126 [63.4.1.30] <testuser@example.net> -> <testuser@example.org>
You can also test your anti-virus using the EICAR test file. Send yourself an email from another mail server with only the following string in the body:
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
It will be detected as a virus and you’ll see this in the log as well:
Apr 24 23:05:25 mail postfix/qmgr[1060]: BF9D558771: from=<virusalert@example.org>, size=2092, nrcpt=1 (queue active)
Apr 24 23:05:25 mail amavis[21120]: (21120-01) Blocked INFECTED (Eicar-Test-Signature) {DiscardedInbound,Quarantined}, [63.4.1.30]:36539 [63.4.1.30] <testuser@example.net> -> <testuser@example.org>, quarantine: /var/lib/amavis/virusmails
In a similar fashion, you can test your spam filter using the GTUBE. Send a message from another mail server to yourself with only the following string in the body:
XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
It will be detected as spam and you’ll see this in the log as well:
Apr 24 23:13:44 mail amavis[21119]: (21119-01) Passed SPAM {RelayedTaggedInternal,Quarantined}, [63.4.1.30]:36539 [63.4.1.30] <testuser@example.net> -> <testuser@example.org>, quarantine: /var/lib/amavis/virusmails
Other Considerations
You’ll notice in your /var/log/maillog
file that there will be a ton of bots and compromised third party servers trying to relay using your server, trying to log in using common usernames (admin, support, test, etc) and generally just trying to wreak havoc on your mail server. The setup suggestions I described in this article should prevent most of those attacks. You may consider trying something like fail2ban to automatically ban these IPs. Get yourself a nice firewall using AlienBob’s Easy Firewall Generator and block anything you don’t need.
I tested this set up by following it step by step on a fresh server and it worked for me. I may have missed some things. Contact me if you have any comments. Encrypted email is strongly encouraged and preferred.