Install services via apt
apt update && \
apt install -y postfix postfix-pgsql postfix-pcre dovecot-core dovecot-imapd dovecot-lmtpd dovecot-pgsql postgresql mailutils dovecot-sieve dovecot-managesieved rspamd redis-server clamav clamav-daemon swaks net-tools unbound
Enable Mailbox Location
Edit /etc/dovecot/conf.d/10-mail.conf
, look for mail_location, update it to the following
mail_location = maildir:~/Maildir
Enable LMTP
Update /etc/dovecot/conf.d/10-master.conf
, look for the service lmtp section and update it to the following
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0600
user = postfix
group = postfix
}
}
Update sieve to only listen to localhost
Update /etc/dovecot/conf.d/20-managesieve.conf
, change the contents to the following
protocol sieve {
}
service managesieve-login {
inet_listener sieve {
address = localhost
port = 4190
}
}
Update /etc/postfix/main.cf
, Add the following to the end of the file
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
broken_sasl_auth_clients = yes
smtpd_sasl_security_options = noanonymous
smtpd_sasl_tls_security_options = noanonymous, noplaintext
smtpd_sasl_authenticated_header = yes
virtual_transport = lmtp:unix:private/dovecot-lmtp
mailbox_transport = lmtp:unix:private/dovecot-lmtp
Update /etc/dovecot/conf.d/10-master.conf
, Update service auth
with
service auth {
unix_listener auth-userdb {
}
unix_listener /var/spool/postfix/private/auth {
mode = 0660
user = postfix
group = postfix
}
}
Setup PostgreSQL database
Update /etc/postgresql/12/main/pg_ident.conf
, Replace the file contents with the following
mailmap dovecot mailreader
mailmap postfix mailreader
mailmap root mailreader
Update /etc/postgresql/12/main/pg_hba.conf
, Replace the file contents with the following
local all postgres peer
local mail all peer map=mailmap
host roundcube roundcube 127.0.0.1/32 md5
Restart PostgreSQL
systemctl restart postgresql
Create the mail database with the following schema
# sudo -u postgres psql
CREATE USER mailreader;
CREATE DATABASE mail WITH OWNER mailreader;
# psql -U mailreader -d mail
CREATE TABLE aliases (
alias text NOT NULL,
email text NOT NULL
);
CREATE TABLE users (
email text NOT NULL,
password text NOT NULL,
maildir text NOT NULL,
created timestamp with time zone DEFAULT now()
);
ALTER TABLE aliases OWNER TO mailreader;
ALTER TABLE users OWNER TO mailreader;
Add virtual user
# doveadm pw -s BLF-CRYPT -r 15
Enter new password: ...
Retype new password: ...
{BLF-CRYPT}.............................................................==
# psql -U mailreader -d mail
INSERT INTO users (
email,
password,
maildir
) VALUES (
'foo@localhost',
'{BLF-CRYPT}.............................................................==',
'foo/'
);
Setup Dovecot with virtual users, storing them in PostgreSQL
Create user and directories
adduser --system --no-create-home --uid 500 --group --disabled-password --disabled-login --gecos 'dovecot virtual mail user' vmail
mkdir /home/mailboxes
chown vmail:vmail /home/mailboxes
chmod 700 /home/mailboxes
Update /etc/dovecot/dovecot-sql.conf.ext
, Replace the file contents with the following
driver = pgsql
connect = host=/var/run/postgresql/ dbname=mail user=mailreader
default_pass_scheme = BLF-CRYPT
password_query = SELECT email as user, password FROM users WHERE email = '%u'
user_query = SELECT email as user, 'maildir:/home/mailboxes/maildir/'||maildir as mail, '/home/mailboxes/home/'||maildir as home, 500 as uid, 500 as gid FROM users WHERE email = '%u'
Update /etc/dovecot/conf.d/10-auth.conf
, replace the file contents with the following
auth_mechanisms = plain login
!include auth-sql.conf.ext
Setup Postfix for virtual users in PostgreSQL
Create /etc/postfix/pgsql-aliases.cf
, and populate it with the following
user=mailreader
dbname=mail
table=aliases
select_field=alias
where_field=email
hosts=unix:/var/run/postgresql
Create /etc/postfix/pgsql-boxes.cf
, and populate it with the following
user=mailreader
dbname=mail
table=users
select_field=email
where_field=email
hosts=unix:/var/run/postgresql
Update /etc/postfix/main.cf
, add the following to the end of the file
alias_maps = hash:/etc/aliases proxy:pgsql:/etc/postfix/pgsql-aliases.cf
local_recipient_maps = proxy:pgsql:/etc/postfix/pgsql-boxes.cf $alias_maps
and removing the existing alias_maps
Setup mail folders
Edit /etc/dovecot/conf.d/10-mail.conf
, replace the inbox namespace with the following
namespace inbox {
inbox = yes
separator = /
mailbox "Drafts" {
auto = subscribe
special_use = \Drafts
}
mailbox "Sent" {
auto = subscribe
special_use = \Sent
}
mailbox "Trash" {
auto = subscribe
special_use = \Trash
}
mailbox "Junk" {
auto = subscribe
special_use = \Junk
}
mailbox "Archive" {
auto = subscribe
special_use = \Archive
}
}
Rspamd
Update Postfix
Update /etc/postfix/main.cf
, add the following to the end of the file
smtpd_milters = inet:localhost:11332
milter_default_action = accept
non_smtpd_milters = inet:localhost:11332
Update dovecot
Update /etc/dovecot/conf.d/20-lmtp.conf
, replace the file contents with the following
protocol lmtp {
postmaster_address = postmaster@localhost # replace localhost with your domain
mail_plugins = $mail_plugins sieve
}
Update /etc/dovecot/conf.d/20-imap.conf
, replace the file contents with the following
protocol imap {
mail_plugins = $mail_plugins imap_sieve
}
Update /etc/dovecot/conf.d/90-plugin.conf
, replace the file contents with the following
plugin {
sieve_plugins = sieve_imapsieve sieve_extprograms
sieve_implicit_extensions = +vnd.dovecot.report
sieve_before = /home/mailboxes/sieve/global-spam.sieve
# From elsewhere to Spam folder or flag changed in Spam folder
imapsieve_mailbox1_name = Junk
imapsieve_mailbox1_causes = COPY FLAG
imapsieve_mailbox1_before = file:/home/mailboxes/sieve/report-spam.sieve
# From Spam folder to elsewhere
imapsieve_mailbox2_name = *
imapsieve_mailbox2_from = Junk
imapsieve_mailbox2_causes = COPY
imapsieve_mailbox2_before = file:/home/mailboxes/sieve/report-ham.sieve
sieve_pipe_bin_dir = /home/mailboxes/sieve
sieve_global_extensions = +vnd.dovecot.pipe
}
Create the sieve directory and files
mkdir /home/mailboxes/sieve
Create /home/mailboxes/sieve/global-spam.sieve
, and populate it with the following
require ["fileinto", "imap4flags"];
if header :contains "X-Spam-Flag" "YES" {
addflag "\\Seen";
fileinto "Junk";
}
if header :is "X-Spam" "Yes" {
addflag "\\Seen";
fileinto "Junk";
}
Create /home/mailboxes/sieve/learn-ham.sh
, and populate it with the following
#!/bin/sh
exec /usr/bin/rspamc -h localhost:11334 learn_ham
Create /home/mailboxes/sieve/learn-spam.sh
, and populate it with the following
#!/bin/sh
exec /usr/bin/rspamc -h localhost:11334 learn_spam
Create /home/mailboxes/sieve/report-ham.sieve
, and populate it with the following
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables", "vnd.dovecot.report"];
if environment :matches "imap.mailbox" "*" {
set "mailbox" "${1}";
}
if string "${mailbox}" "Trash" {
stop;
}
if string "${mailbox}" "Junk" {
stop;
}
if environment :matches "imap.user" "*" {
set "username" "${1}";
}
pipe :copy "learn-ham.sh" [ "${username}" ];
Create /home/mailboxes/sieve/report-spam.sieve
, and populate it with the following
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables", "imap4flags"];
if environment :matches "imap.user" "*" {
set "username" "${1}";
}
setflag "\\Seen";
pipe :copy "learn-spam.sh" [ "${username}" ];
Update the permissions for the /home/mailboxes/sieve/
folder
chown -R vmail:vmail /home/mailboxes/sieve/ && \
chmod ug+x /home/mailboxes/sieve/learn-spam.sh && \
chmod ug+x /home/mailboxes/sieve/learn-ham.sh
Create files in /etc/rspamd/local.d/ to override configs
Update /etc/rspamd/options.inc
, add the following to the dns
section
nameserver = ["127.0.0.1:53:10"];
Create /etc/rspamd/local.d/actions.conf
, and populate it with the following
reject = 150;
add_header = 6;
greylist = 4;
Create /etc/rspamd/local.d/dmarc.conf
, and populate it with the following
servers = "127.0.0.1:6379";
actions = {
quarantine = "add_header";
reject = "reject";
}
reporting = true;
Create /etc/rspamd/local.d/spf.conf
, and populate it with the following
spf_cache_size = 1k;
spf_cache_expire = 1d;
max_dns_nesting = 10;
max_dns_requests = 30;
min_cache_ttl = 5m;
disable_ipv6 = false;
Create /etc/rspamd/local.d/classifier-bayes.conf
, and populate it with the following
servers = "127.0.0.1";
backend = "redis";
new_schema = true;
expire = 31536000;
Create /etc/rspamd/local.d/milter_headers.conf
, and populate it with the following
use = ["x-spamd-bar", "authentication-results"];
extended_spam_headers = true;
skip_local = false;
routines {
x-spamd-bar {
negative = "";
}
}
Install ClamAV
Rspamd
Create /etc/rspamd/local.d/antivirus.conf
, and populate it with the following
clamav {
attachments_only = false;
action = "reject";
symbol = "CLAM_VIRUS";
type = "clamav";
log_clean = true;
servers = "127.0.0.1:3310";
}
Update /etc/clamav/clamd.conf
, add the following to the end of the file
TCPSocket 3310
TCPAddr localhost
And remove the following configuration
LocalSocket
I’ve tried using the clamav socket but I think permissions issues is stopping it from running clamav and it’s failing silently so we are using the TCP socket for now, running netstat we can confirm the listening address is only localhost. I found restarting the server can break clamav’s unix socket. so I’ve removed it
Unofficial ClamAV signatures
The official signatures are a bit lacking so we download the unofficial ones
cd ~ && \
wget https://raw.githubusercontent.com/extremeshok/clamav-unofficial-sigs/master/clamav-unofficial-sigs.sh -O /usr/local/sbin/clamav-unofficial-sigs.sh && \
chmod 755 /usr/local/sbin/clamav-unofficial-sigs.sh && \
mkdir -p /etc/clamav-unofficial-sigs/ && \
wget https://raw.githubusercontent.com/extremeshok/clamav-unofficial-sigs/master/config/master.conf -O /etc/clamav-unofficial-sigs/master.conf && \
wget https://raw.githubusercontent.com/extremeshok/clamav-unofficial-sigs/master/config/user.conf -O /etc/clamav-unofficial-sigs/user.conf && \
os_conf="os.ubuntu.conf" && \
wget "https://raw.githubusercontent.com/extremeshok/clamav-unofficial-sigs/master/config/os/${os_conf}" -O /etc/clamav-unofficial-sigs/os.conf && \
/usr/local/sbin/clamav-unofficial-sigs.sh --force && \
/usr/local/sbin/clamav-unofficial-sigs.sh --install-logrotate && \
/usr/local/sbin/clamav-unofficial-sigs.sh --install-man && \
wget https://raw.githubusercontent.com/extremeshok/clamav-unofficial-sigs/master/systemd/clamav-unofficial-sigs.service -O /etc/systemd/system/clamav-unofficial-sigs.service && \
wget https://raw.githubusercontent.com/extremeshok/clamav-unofficial-sigs/master/systemd/clamav-unofficial-sigs.timer -O /etc/systemd/system/clamav-unofficial-sigs.timer && \
systemctl enable clamav-unofficial-sigs.service && \
systemctl enable clamav-unofficial-sigs.timer && \
systemctl start clamav-unofficial-sigs.timer
Install Roundcube
apt install -y roundcube php7.4-pgsql composer
Follow the prompts and choose pgsql(PostgreSQL) as the database server
Update /etc/roundcube/config.inc.php
by changing the value of $config['default_host']
to 'localhost'
Apache2 virtual host config
Create /etc/apache2/sites-available/mail.<domain>.conf
, and populate it with the following
<VirtualHost *:80>
ServerName mail.<domain>
DocumentRoot /usr/share/roundcube/
ServerAdmin postmaster@<domain>
ErrorLog ${APACHE_LOG_DIR}/roundcube-error.log
CustomLog ${APACHE_LOG_DIR}/roundcube-access.log combined
<Directory /usr/share/roundcube/>
Options -Indexes
AllowOverride All
Order allow,deny
allow from all
</Directory>
</VirtualHost>
Create the symlink
cd /etc/apache2/sites-available
cd ../sites-enabled/
ln -s ../sites-available/mail.<domain>.conf
Check the configuration
apachectl -S
Add Certbot
We open up port 80 for Certbot HTTP validation
ufw allow 80 && \
apt install -y certbot python3-certbot-apache && \
certbot --apache
Add Certificates to Postfix and Dovecot
Apache2 configs after running Certbot
SSLCertificateFile /etc/letsencrypt/live/mail.<domain>/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/mail.<domain>/privkey.pem
Postfix
Update /etc/postfix/main.cf
, update the following by replacing the lines
smtpd_tls_cert_file=/etc/letsencrypt/live/mail.<domain>/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/mail.<domain>/privkey.pem
Dovecot
Update /etc/dovecot/conf.d/10-ssl.conf
, update the following by replacing the lines
ssl_cert = </etc/letsencrypt/live/mail.<domain>/fullchain.pem # make sure to keep the <
ssl_key = </etc/letsencrypt/live/mail.<domain>/privkey.pem # make sure to keep the <
Getting Setup ready for opening up to the world
Rspamd DKIM
Run the following to create the DKIM
cd ~ && \
mkdir /var/lib/rspamd/dkim/ && \
rspamadm dkim_keygen -b 2048 -s mail -k /var/lib/rspamd/dkim/mail.key | tee -a /var/lib/rspamd/dkim/mail.pub && \
chown -R _rspamd: /var/lib/rspamd/dkim && \
chmod 440 /var/lib/rspamd/dkim/*
Create /etc/rspamd/local.d/dkim_signing.conf
, and populate it with the following
selector = "mail";
path = "/var/lib/rspamd/dkim/$selector.key";
allow_username_mismatch = true;
Copy the content for ARC
cp /etc/rspamd/local.d/dkim_signing.conf /etc/rspamd/local.d/arc.conf
Postfix
Update /etc/postfix/master.cf
, add the following to the end of the file
submission inet n - y - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_wrappermode=no
-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_relay_restrictions=permit_sasl_authenticated,reject
Update /etc/postfix/main.cf
by - changing the value of mydestination
to $myhostname, localhost.$mydomain, $mydomain, localhost
- add the following lines at the end of the file
masquerade_domains = $myorigin
IMAP
Update /etc/dovecot/conf.d/10-master.conf
, update the imap-login
to the below
service imap-login {
inet_listener imap {
address = 127.0.0.1
}
inet_listener imaps {
ssl = yes
}
service_count = 1024
}
Update /etc/dovecot/conf.d/10-ssl.conf
- update ssl
to required
- update #ssl_prefer_server_ciphers
to yes
and uncomment it
Firewall
ufw allow 443
ufw allow 25
ufw allow 587
ufw allow 993
Restart services
Restart postfix, rspamd and dovecot
systemctl restart apache2
systemctl restart clamav-daemon
systemctl restart postfix
systemctl restart rspamd
systemctl restart dovecot
DNS
DKIM & DMARC
Here is a good guide to setting up the DNS
https://linuxize.com/series/setting-up-and-configuring-a-mail-server/
SPF
v=spf1 mx ~all
Test settings
Double check services listening port
netstat -ltp
Spam mail test string
cd ~ && \
echo 'XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X' > body && \
swaks --to foo@localhost --body body
Virus mail test string
cd ~ && \
wget https://secure.eicar.org/eicar.com && \
swaks --to foo@localhost --attach - --server localhost < eicar.com
Ligitimate emails
swaks --to foo@localhost && \
cat /home/mailboxes/maildir/foo/new/*
Check logs files
Check that rspamd marks the files either contains spam or a virus
tail -F /var/log/rspamd/rspamd.log -n 25
References
- https://www.vultr.com/docs/how-to-install-postfix-dovecot-and-roundcube-on-ubuntu-20-04
- https://www.linuxbabe.com/mail-server/secure-email-server-ubuntu-postfix-dovecot
- https://www.digitalocean.com/community/tutorials/how-to-set-up-a-postfix-email-server-with-dovecot-dynamic-maildirs-and-lmtp
- https://github.com/alexandregz/twofactor_gauthenticator/tree/master
- https://serverfault.com/questions/277430/how-do-i-setup-a-sent-and-trash-folder-with-dovecot
- https://www.youtube.com/watch?v=6SfXXtb-nHM
- https://www.scaleway.com/en/docs/setup-a-mailserver-on-ubuntu-bionic-beaver-with-dovecot-postfix-rspamd/
- https://rspamd.com/doc/integration.html
- https://github.com/darix/dovecot-sieve-antispam-rspamd
- https://workaround.org/ispmail/stretch/filtering-viruses-malware/
- https://github.com/extremeshok/clamav-unofficial-sigs
- https://www.digitalocean.com/community/tutorials/how-to-install-your-own-webmail-client-with-roundcube-on-ubuntu-16-04
- https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-ubuntu-20-04
- https://rspamd.com/doc/modules/dkim_signing.html
- https://linuxize.com/post/install-and-integrate-rspamd/
- https://linuxize.com/series/setting-up-and-configuring-a-mail-server/
- https://www.digitalocean.com/community/tutorials/how-to-use-an-spf-record-to-prevent-spoofing-improve-e-mail-reliability
- https://www.sbarjatiya.com/notes_wiki/index.php/Postfix_SMTP_authentication_using_dovecot
- https://www.sparkpost.com/resources/email-explained/dmarc-explained/
- Postfix
- Dovecot
- Rspamd
- ClamAV
- Roundcube