19. Install the mail server

by Double Bastion - Updated October 1, 2025

A complete mail server is made up of 6 components:

1. The SMTP server: this is the server that handles the Simple Mail Transfer Protocol (SMTP) traffic. It is usually referred to as a Mail Transfer Agent (MTA) and its main function is to send emails to external mail servers and to receive emails from external mail servers. Accepted emails are included into the MTA’s queue. The best free and open source SMTP server, in the sense of the one that offers the most while asking for the least, is Postfix.

2. The IMAP and POP3 server: this is the server that allows email clients installed on the users’ computers (like Thunderbird), or web-based email clients (like Roundcube), to access the emails stored on the server and read them. The Internet Message Access Protocol (IMAP) is the protocol that allows the users to download the emails, while the original messages remain on the remote server, while the Post Office Protocol version 3 (POP3) is the protocol that typically downloads the emails to the clients’ computers and then deletes them from the remote server. Usually, the IMAP and POP3 server also acts as a Mail Delivery Agent (MDA) that is invoked by the MTA to fetch the incoming emails from the MTA and save them in the individual mailboxes. The best free and open source IMAP and POP3 server is Dovecot.

3. The antivirus application: the antivirus application is the program that scans all incoming and outgoing emails and their attachments (even compressed attachments) to find viruses and other types of malware. Once it detects a known virus/malware, it either moves the email to quarantine or deletes it. It has to be properly connected to the SMTP server, so that it can scan emails promptly, the very moment they arrive. Apart from real-time scanning of incoming and outgoing emails, the antivirus can be also used to scan periodically or on demand any sensitive directory on the server, such as the directory used to store emails. A virus that wasn’t detected when the email arrived can be detected during periodic scanning after an antivirus automatic update. The best free and open source antivirus application, both for mail servers and for general use on a server is ClamAV.

4. The spam filter application: the spam filter is the program that scans all incoming emails for spam characteristics. If it detects that an incoming email is spam, it marks it as such and the Mail Delivery Agent sends it to the Spam folder. It needs to be properly connected to the SMTP server, that will invoke it whenever an email arrives. The best free and open source spam filter is Apache SpamAssassin.

5. The web-based email accounts management application: this is the program that allows the administrator to add email domains, to add, modify or remove email accounts (mailboxes), to add email aliases, to implement quota limits for email accounts, etc., in an easy to use web interface. The best free and open source application for this purpose is Postfix Admin.

6. The web-based IMAP email client: This is the web application that allows the user to interact with the remote server to read/write/send emails. It is sometimes called a Mail User Agent (MUA). A good web-based IMAP client is easy to use, responsive, it allows editing emails in HTML or plain text format, adding attachments, changing passwords, configuring filters for incoming emails, configuring out-of-office replies, canned emails, etc. The best free and open source application that fulfills all these requirements is Roundcube.

It may appear that the 5th component is optional, because you can add email domains and create email accounts ‘by hand’ editing configuration files directly. However, if you try to do this for a large number of accounts, you will soon understand that the 5th component is actually needed in order to have a usable, complete mail server. The 6th component may also seem optional, since you can connect your locally installed Thunderbird to your remote mail server, and avoid using a web-based email client. Yet, in real life, a web-based email client like Roundcube, proves to be indispensable, since at least occasionally you will want to log in to your email account from different computers or devices that don’t have Thunderbird or an equivalent application installed and configured. So, for a complete mail server, the 6th component is also a must.

The Internet is full of incomplete, outdated and wrong information about installing a mail server. You will hardly find a single article that mentions all the 6 components listed above, let alone to explain how to install and configure them properly. This chapter was written as a reaction against the lack of complete and correct information on this subject.

To the 6 mandatory components listed above, we’ll also add Postgrey to implement greylisting, a method used to reduce spam. In separate chapters we’ll also explain how to install two email related applications: Mailman, used to manage classic mailing lists and phpList, for sending mass emails such as newsletters, marketing emails, announcements, etc.

19.1. IP reputation


When maintaining a mail server it is of utmost importance to have an IP with a good reputation at all times. This means an IP that is not included on any blacklist (or blocklist). Otherwise, many mail servers that will receive emails sent from your IP will reject them or will send them to the Spam/Junk folder.

Generally, when you rent a server, you will be assigned a ‘clean’ IP. However, it is possible that a previous user of that IP may have sent spam and thus caused that IP to be included on a public blacklist. For this reason, the first thing you should do after renting a server is to check its IP against the main public blacklists, by using this online tool: https://mxtoolbox.com/blacklists.aspx

If you find that the IP of your newly rented server has been included on a public blacklist, you can contact the maintainers of that blacklist (whose contact details you can find on the official web page of the blacklist) and ask that they delist your IP, since you are the new user of that IP and you are not responsible for what the previous users have done using that IP. If they refuse, the next step is to contact the hosting provider of your server, explain the situation and ask them to contact the blacklist maintainers and require that they delist the IP. This is almost guaranteed that it will work. If it doesn’t, the last method is to ask the hosting provider to assign you a different IP, since you want to install a mail server and you can’t risk having your emails rejected because the previous users of that IP sent spam and the IP was listed on a public blacklist.

You can also check if the IP has been reported for malicious activities, on https://www.abuseipdb.com/ . If you find that your IP has been reported, you can take the same steps mentioned above, to try to delist the IP.

There is also the possibility that after you install your mail server and start using it, some blacklist maintainers will include your IP on their public blacklist. If they do it because you sent real spam emails becuase you didn’t know what qualified as spam, you can contact them, explain the situation and ask that they delist your IP, since you now know what qulifies as spam and you changed your email sending policy. If they do it because your mail server has been used as a relay by real spammers, you have to investigate the breach, recheck all the mail server configurations, fix the problem, make sure that no unauthorized person can use your mail server again, then contact the blacklist maintainers and explain that the server has been reconfigured and the problem fixed. This is why you have to closely follow the instructions from this guide in order to configure a secure mail server and prevent any attackers to access it.

In fact, from all the components that one might install on a server, the mail server will receive the greatest number of attacks, mostly those recorded in logs as ‘postfix-sasl authentication failure’. This means that the attackers will try to log in to the Postfix server in order to send their spam.

It can also happen that the maintainers of a public blacklist include in their blacklist a whole range of IPs, just because they noticed that some spammers used the ‘snowshoe spamming’ method. You may find that your own IP has been included on the public blacklist just because it was part of the IP range considered suspicious by the blacklist maintainers. In this situation, to delist your IP, you should contact the company that hosts your server, explain the situation and ask them to contact the blacklist maintainers and request that they delist your IP.

The idea is to always keep an eye on the server’s IP reputation. If the IP is included on a blacklist, this will seriously impact your email deliverability.

19.1.1. Spam definition


The best definition of spam is probably the definition given by spamhaus.org here. We reproduce it below.

Spam is generally understood to be Unsolicited Bulk E-mail (UBE).

  • Unsolicited: the recipient has not granted verifiable permission for the message to be sent.
  • Bulk: the message is sent as part of a larger collection of messages with identical content.”

Therefore, to avoid sending spam, you should make sure that when you send mass emails (one email is sent to tens, hundreads, or thousands of recipients at once) you have the explicit approval of the recipients to receive emails from you. For this, it’s necessary to make each potential recipient explicitly opt-in to your newsletter/mass emailing service. You can also use a double opt-in method, by asking the users to enter their email address in a form or check the option to subscribe to your newsletter/emailing service, and then send them an email and ask them to reconfirm their subscription by clicking a link or a button included in the email. Also, each email that is sent to a list of subscribers has to contain the unsubscribe link at the bottom, so that every receiver can have their email address removed from the mailing list at any moment.

If you live in the US or the recipients of your emails live in the US, your emails must comply with the CAN-SPAM Act. For an introduction to CAN-SPAM, CASL (Canadian Anti-Spam Law) and the European Union’s GDPR (General Data Protection Regulation) see this article.

19.2. Mail server ports


To run a mail server, you will need to open the following ports in the firewall:

  • 25 (SMTP)
  • 587 (STARTTLS over SMTP)
  • 993 (IMAPS)
  • 995 (POP3S)

You can open the port for SMTP by running:

ufw allow 25

Open all the ports mentioned above in a similar manner.

In order to use the web-based email client, you will also need to have ports 80 and 443 open, but you already opened them when configuring Nginx.

Ports 25, 465 and 587 can be blocked by your hosting provider, to prevent spam. As we mentioned at the beginning of this guide, you should check if the hosting provider blocks these ports before renting the server. In general, even if they block these ports, the hosting providers are willing to unblock them after you open a support ticket and explain that you intend to host a mail server that needs those ports open and that you intend to send only legitimate emails that comply with all the anti-spam regulations. Here is an example of a hosting provider explaining what to do, to have those ports unblocked.

If you are not sure whether your hosting provider blocks port 25 for outgoing traffic, you can run:

telnet smtp.mail.com 25

If the port is open, the output will look like this:

Trying 74.208.5.15...
Connected to smtp.mail.com.
Escape character is '^]'.
220 mail.com (mrgmxus005) Nemesis ESMTP Service ready

If the port is blocked, the output will look like this:

Trying 74.208.5.15...
telnet: Unable to connect to remote host: Connection timed out

(To end the telnet session press Ctrl + ] and then type quit and press Enter.)

After making sure that all the necessary ports are open, the next step is to add the proper configurations in the /etc/hosts and the /etc/hostname files. We explained how to do this in the Configure the hosts and the hostname chapter. So, if you followed the guide, you have these configurations in place and it’s time to move to the next step.

19.4. Create a new database and user for the mail server


Log in to phpMyAdmin and create a new database called mail and a new user with the same name, on localhost. Enter a strong password for the user mail and copy it somewhere safe, to use it later. Then give user mail all the privileges over the mail database, except ‘grant’, which is not necessary.

19.5. Create a user to handle the mail directories


The emails will be stored in subdirectories of the /var/vmail directory, per domain and per email account. For example, admin@example.com will have the mail directory /var/vmail/example.com/admin. All of these mail directories will be owned by a single user named vmail, and the IMAP server, Dovecot, will use the vmail user to create and update the mail files.

First create the vmail directory:

mkdir /var/vmail

Then create the vmail user:

useradd -u 5000 -d /var/vmail -s /bin/false vmail

The meaning of the options used above is the following:

-u 5000: set 5000 as the user id (UID)

-d /var/vmail: set /var/vmail as home directory for the vmail user

-s /bin/false: add a user without shell access

When creating a new user without specifying the group name or the group id, by default, a group with the same name as the username is created, and the group id (GID) of the new group is the same as the user id (UID). Therefore, the command from above creates the vmail user with the UID 5000, and the vmail group with the GID 5000.

Change ownership for the /var/vmail directory:

chown vmail:vmail /var/vmail

19.6. Install Postfix


To install Postfix run:

apt-get install postfix postfix-mysql

When Postfix installation begins, you will be asked to choose a general type of mail configuration:

Select Internet site, then press Enter.

In the next screen you will be asked to enter the ‘system mail name’:

The ‘system mail name’ should be the domain appended to email addresses that don’t have a domain name specified. To allow other applications, like Mailman, to function properly, you should enter here the fully qualified domain name (FQDN) of your server (mail.example.com). So, the ‘system mail name’ will be mail.example.com, where example.com is the main domain hosted on your server. Enter mail.example.com and press Enter. You can change the ‘system mail name’ later by manually editing the /etc/mailname file.

The Postfix server will be installed and automatically started. To check the Postfix version run:

postconf mail_version

The output will look like this:

mail_version = 3.5.6

19.7. Configure Postfix


Navigate to /etc/postfix:

cd /etc/postfix

Create the following files with the following content in /etc/postfix (replace as6d516a51df6asdf with the actual password of the user mail, that you set up earlier):

nano mysql-sender-login-maps.cf
hosts = localhost
user = mail
password = as6d516a51df6asdf
dbname = mail
query = SELECT username AS allowedUser FROM mailbox WHERE username="%s" AND active = 1 UNION SELECT goto FROM alias WHERE address="%s" AND active = 1
nano mysql-virtual-alias-maps.cf
hosts = localhost
user = mail
password = as6d516a51df6asdf
dbname = mail
query = SELECT goto FROM alias WHERE address='%s' AND active = '1'
nano mysql-virtual-domains-maps.cf
hosts = localhost
user = mail
password = as6d516a51df6asdf
dbname = mail
query = SELECT domain FROM domain WHERE domain='%s' AND active = '1'
nano mysql-virtual-mailbox-maps.cf
hosts = localhost
user = mail
password = as6d516a51df6asdf
dbname = mail
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'

Change permissions as follows:

chmod 640 /etc/postfix/mysql-*

19.7.1. Edit main.cf


Make a copy of the original /etc/postfix/main.cf file:

cd /etc/postfix
cp main.cf main.cf_orig

Then delete all the content inside main.cf and open it for editing:

cat /dev/null > main.cf
nano main.cf

Add the following content replacing the values in red with your own values. Some parameters that are commented out for the moment will be enabled later on, when you’ll install additional applications that need those parameters:

# See /usr/share/postfix/main.cf.dist for a commented, more complete version

# Debian specific:  Specifying a file name will cause the first
# line of that file to be used as the name.  The Debian default
# is /etc/mailname.

compatibility_level = 3.6

inet_protocols = ipv4, ipv6
smtp_bind_address6 = 2b03:8df0:a24b:6eb::1
smtp_address_preference = ipv4

smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = no

# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h

readme_directory = no

# TLS parameters
smtpd_tls_key_file = /etc/letsencrypt/live/mail.example.com/privkey.pem
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.example.com/cert.pem
smtpd_tls_CAfile = /etc/letsencrypt/live/mail.example.com/fullchain.pem

smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_tls_security_level = may
smtp_tls_security_level = may
smtpd_tls_loglevel = 2

# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.

myhostname = mail.example.com
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
mydestination = localhost

mynetworks = 127.0.0.0/8 [::1]/128 [2b03:8df0:a24b:6eb::]/64
mailbox_size_limit = 0
message_size_limit = 62914560
recipient_delimiter = +
inet_interfaces = all

# a bit more spam protection
disable_vrfy_command = yes

smtpd_forbid_bare_newline = normalize
smtpd_forbid_bare_newline_exclusions = $mynetworks

# Auth
# Allow plaintext mechanisms, but only over a TLS-encrypted connection
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous

smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth_dovecot
smtpd_sasl_auth_enable = yes
smtpd_sasl_authenticated_header = yes
broken_sasl_auth_clients = yes

proxy_read_maps = $local_recipient_maps $mydestination $virtual_alias_maps $virtual_alias_domains $virtual_mailbox_maps $virtual_mailbox_domains $relay_recipient_maps $relay_domains $canonical_maps $sender_canonical_maps $recipient_canonical_maps $relocated_maps $transport_maps $mynetworks $smtpd_sender_login_maps

smtpd_sender_login_maps = proxy:mysql:/etc/postfix/mysql-sender-login-maps.cf

#mailman
#relay_domains = mailman.example.com
#relay_recipient_maps = hash:/var/lib/mailman/data/virtual-mailman
#transport_maps = hash:/etc/postfix/transport
#mailman_destination_recipient_limit = 1

# Virtual mailboxes
virtual_alias_maps = proxy:mysql:/etc/postfix/mysql-virtual-alias-maps.cf
#virtual_alias_maps = proxy:mysql:/etc/postfix/mysql-virtual-alias-maps.cf,
#                     hash:/var/lib/mailman/data/virtual-mailman
virtual_mailbox_base = /var/vmail/
virtual_mailbox_domains = proxy:mysql:/etc/postfix/mysql-virtual-domains-maps.cf
virtual_mailbox_limit = 0
virtual_mailbox_maps = proxy:mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
virtual_minimum_uid = 104
virtual_transport = lmtp:unix:private/dovecot-lmtp
local_transport = virtual
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000

milter_protocol = 2
milter_default_action = accept

#policyd-spf_time_limit = 3600

smtpd_helo_required = yes
strict_rfc821_envelopes = yes
invalid_hostname_reject_code = 554
multi_recipient_bounce_reject_code = 554
non_fqdn_reject_code = 554
relay_domains_reject_code = 554
unknown_address_reject_code = 554
unknown_client_reject_code = 554
unknown_hostname_reject_code = 554
unknown_local_recipient_reject_code = 554
unknown_relay_recipient_reject_code = 554
unknown_virtual_alias_reject_code = 554
unknown_virtual_mailbox_reject_code = 554
unverified_recipient_reject_code = 554
unverified_sender_reject_code = 554

#authorized_submit_users = !sshusername, !ftpusername, vmail, root, www-data, asterisk

smtpd_sender_restrictions =
   reject_sender_login_mismatch,
   permit_mynetworks,
   permit_sasl_authenticated,
   reject_unknown_sender_domain,
   reject_unlisted_sender,
   permit

smtpd_recipient_restrictions =
#   check_client_access hash:/etc/postfix/client_checks,
   permit_mynetworks,
   permit_sasl_authenticated,
   reject_invalid_hostname,
   reject_unknown_recipient_domain,
   reject_unauth_destination,
   reject_unauth_pipelining,
   reject_unknown_reverse_client_hostname,
   check_policy_service unix:private/policyd-spf,
#   check_policy_service inet:localhost:60000,
   reject_rbl_client zen.spamhaus.org,
   permit

smtpd_relay_restrictions =
   reject_invalid_hostname,
   reject_unknown_recipient_domain,
   reject_unauth_pipelining,
   permit_mynetworks,
   permit_sasl_authenticated,
   reject_unauth_destination,
   permit

header_checks = regexp:/etc/postfix/header_checks
mime_header_checks = regexp:/etc/postfix/header_checks

Replace example.com with the main domain hosted on your server. Replace sshusername with your SSH user and ftpusername with your FTP user. They won’t be allowed to send emails because they don’t need to. Also replace 2b03:8df0:a24b:6eb:: with the /64 subnet allocated to your server by your hosting provider (if your server hasn’t been allocated a /64 subnet from the start, you will need to contact technical support and ask that they allocate a /64 subnet to your machine, so that you can enable IPv6 connectivity for your mail server). Replace 2b03:8df0:a24b:6eb::1 with the specific IPv6 that you configured in /etc/network/interfaces (it should belong to the 2b03:8df0:a24b:6eb::/64 subnet mentioned above).

If your hosting provider allocated from the start a /128 subnet to your server, which means a single IPv6 address, and after you open a support ticket they refuse to change the IPv6 allocation from /128 to /64, it’s recommended to disable IPv6 connectivity for the mail server by replacing inet_protocols = ipv4, ipv6 with inet_protocols = ipv4 and restarting Postfix. spamhaus.org uses to blacklist an entire /64 subnet of addresses when one single IPv6 address in that range sends spam. This is why, your IPv6 address can get blacklisted just because neighboring IPs in the same range, which belong to other clients, send spam. In this way, if the receiving mail server is configured to reject emails from IPs blacklisted by spamhaus.org and your server sends an email using the IPv6 address, your email will be rejected. Therefore, in this case in which the server is allocated a /128 subnet and the hosting provider refuses to change the allocation type to a /64 subnet, to avoid the situation described above, it’s recommended to disable IPv6 connectivity for the mail server and use the IPv4 address exclussively, which is sufficient for effective email communication.

Remember that you have already obtained a Let’s Encrypt SSL certificate for mail.example.com and you have configured Nginx to use it, in the Install Nginx chapter. Therefore, now you only mention the location of the privkey.pem, cert.pem and fullchain.pem files, as shown above.

Please note that the message_size_limit = 62914560 directive sets a maximum size limit of 62914560 bytes (75MB) to any email (email text plus the attachments). This doesn’t mean that you can send emails up to 75MB in size to other email providers. Unfortunately, many mail servers have a maximum attachment size limit of around 20MB. Therefore, when the cumulative size of the files that you want to send as attachments is larger than 20MB, you can check if the receiving email service accepts attachments of the size you want to send, to avoid having your emails rejected because the size of your attachments exceeds their maximum limit.

Next, create the /etc/postfix/header_checks file, to remove some sensitive information from the header of outgoing emails:

nano /etc/postfix/header_checks

Add the following content inside this file:

/^X-Originating-IP:/        IGNORE
/^X-Mailer:/                IGNORE
/^User-Agent:/              IGNORE

Then use the postmap command to create the Postfix lookup table file for the header_checks file:

postmap header_checks

Then restart Postfix:

systemctl restart postfix

The authorized_submit_users = !sshusername, !ftpusername, vmail, root, www-data, asterisk directive allows the vmail, root, www-data and asterisk users to send emails by running PHP scripts containing the mail() function; it also allows them to send emails in command line by running commands like:

echo -e "Subject: Testing \n\n Hello! This is the body of the email." | /usr/sbin/sendmail contact@domain.com

It also denies the right to send emails for the sshusername and ftpusername users.

The phpList application, which we’ll describe later, won’t be configured to use the PHP mail() function. It will use the included PHPMailer library to connect on port 587 to the SMTP server which is Postfix. Therefore, it won’t need to send emails using the mail() function. Yet, many WordPress plugins, such as various contact forms, need to send emails with the mail() function and the user www-data. So, www-data has to be allowed to send emails by specifying it in the authorized_submit_users parameter.

If you have other users that you want to prevent from sending emails from the server in command line or using the mail() function, just add them like this:

authorized_submit_users = !sshusername, !ftpusername, !user1, !user2, vmail, root, www-data, asterisk

The user root is allowed to send emails because ‘System Health and Security Probe’ needs this to send periodic email reports to the system administrator, as we’ll show later. The vmail user is allowed to send emails because Roundcube’s auto-responder needs this to send ‘out of office’ replies. The user asterisk is allowed to send emails because Asterisk needs it in order to send voicemail messages.

19.7.2. Edit master.cf


Next, make a copy of the original /etc/postfix/master.cf file:

cp /etc/postfix/master.cf /etc/postfix/master.cf_orig

Delete all the content in /etc/postfix/master.cf:

cat /dev/null > /etc/postfix/master.cf

Open /etc/postfix/master.cf:

nano /etc/postfix/master.cf

Add the following content inside this file. Please note that some lines that have been commented out will be enabled later, when we’ll install additional software:

#
# Postfix master process configuration file.  For details on the format
# of the file, see the master(5) manual page (command: "man 5 master").
#
# Do not forget to execute "postfix reload" after editing this file.
#
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
smtp      inet  n       -       -       -       -       smtpd
#smtp      inet  n       -       -       -       1       postscreen
#smtpd     pass  -       -       -       -       -       smtpd
#dnsblog   unix  -       -       -       -       0       dnsblog
#tlsproxy  unix  -       -       -       -       0       tlsproxy
submission inet n       -       -       -       -       smtpd
# -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING
smtps     inet  n       -       -       -       -       smtpd
#  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
#  -o smtpd_sasl_auth_enable=yes
#  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING
#628       inet  n       -       -       -       -       qmqpd
pickup    fifo  n       -       -       60      1       pickup
cleanup   unix  n       -       -       -       0       cleanup
qmgr      fifo  n       -       n       300     1       qmgr
#qmgr     fifo  n       -       n       300     1       oqmgr
tlsmgr    unix  -       -       -       1000?   1       tlsmgr
rewrite   unix  -       -       -       -       -       trivial-rewrite
bounce    unix  -       -       -       -       0       bounce
defer     unix  -       -       -       -       0       bounce
trace     unix  -       -       -       -       0       bounce
verify    unix  -       -       -       -       1       verify
flush     unix  n       -       -       1000?   0       flush
proxymap  unix  -       -       n       -       -       proxymap
proxywrite unix -       -       n       -       1       proxymap
smtp      unix  -       -       -       -       -       smtp
relay     unix  -       -       -       -       -       smtp
#       -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
showq     unix  n       -       -       -       -       showq
error     unix  -       -       -       -       -       error
retry     unix  -       -       -       -       -       error
discard   unix  -       -       -       -       -       discard
local     unix  -       n       n       -       -       local
virtual   unix  -       n       n       -       -       virtual
lmtp      unix  -       -       -       -       -       lmtp
anvil     unix  -       -       -       -       1       anvil
scache    unix  -       -       -       -       1       scache
#
# ====================================================================
# Interfaces to non-Postfix software. Be sure to examine the manual
# pages of the non-Postfix software to find out what options it wants.
#
# Many of the following services use the Postfix pipe(8) delivery
# agent.  See the pipe(8) man page for information about ${recipient}
# and other message envelope options.
# ====================================================================
#
# maildrop. See the Postfix MAILDROP_README file for details.
# Also specify in main.cf: maildrop_destination_recipient_limit=1
#
maildrop  unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
#
# ====================================================================
#
# Recent Cyrus versions can use the existing "lmtp" master.cf entry.
#
# Specify in cyrus.conf:
#   lmtp    cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4
#
# Specify in main.cf one or more of the following:
#  mailbox_transport = lmtp:inet:localhost
#  virtual_transport = lmtp:inet:localhost
#
# ====================================================================
#
# Cyrus 2.1.5 (Amos Gouaux)
# Also specify in main.cf: cyrus_destination_recipient_limit=1
#
#cyrus     unix  -       n       n       -       -       pipe
#  user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
#
# ====================================================================
# Old example of delivery via Cyrus.
#
#old-cyrus unix  -       n       n       -       -       pipe
#  flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
#
# ====================================================================
#
# See the Postfix UUCP_README file for configuration details.
#
uucp      unix  -       n       n       -       -       pipe
  flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
#
# Other external delivery methods.
#
ifmail    unix  -       n       n       -       -       pipe
  flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp     unix  -       n       n       -       -       pipe
  flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
scalemail-backend unix  -       n       n       -       2       pipe
  flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}

#dovecot   unix  -       n       n       -       -       pipe
#  flags=DRhu user=vmail:vmail argv=/usr/bin/spamc -u debian-spamd -e /usr/lib/dovecot/deliver -d ${recipient}

#policyd-spf  unix  -       n       n       -       0       spawn
#    user=policyd-spf argv=/usr/bin/policyd-spf

#mailman unix  -       n       n       -       -       pipe
#  flags=FR user=list:list argv=/var/lib/mailman/bin/postfix-to-mailman.py ${nexthop} ${recipient}

Change ownership for all the files and directories inside the /etc/postfix directory like this:

chown -R root:postfix /etc/postfix/*

Check Postfix configuration:

postfix check

If you see this warning:

postfix/postfix-script: warning: symlink leaves directory: /etc/postfix/./makedefs.out

you can safely ignore it, because it is harmless. It just mentions that /etc/postfix/makedefs.out is a symlink that points to a directory outside of the /etc/postfix directory, namely /usr/share/postfix/makedefs.out

Please note that at this point, Postfix is not ready to send test emails.

The stages of the SMTP mail transaction process are described here. You can read more about the intricacies of the SMTP protocol here.

19.8. Configure Fail2ban to protect Postfix against brute-force attacks


To configure Fail2ban to protect Postfix against brute-force attacks open the /etc/fail2ban/jail.local file:

nano /etc/fail2ban/jail.local

Search for the [postfix] block, comment out all the lines in this block and add other lines, as follows:

[postfix]
# To use another modes set filter parameter "mode" in jail.local:
#mode    = more
#port    = smtp,465,submission
#logpath = %(postfix_log)s
#backend = %(postfix_backend)s

enabled  = true
port     = 25,465,submission
filter   = postfix
logpath  = /var/log/mail.log
findtime = 10800
maxretry  = 4
bantime = 2419200

Then search for the [postfix-sasl] block, comment out all the lines in this block and add other lines, as follows:

[postfix-sasl]
#filter   = postfix[mode=auth]
#port     = smtp,465,submission,imap,imaps,pop3,pop3s

#logpath  = %(postfix_log)s
#backend  = %(postfix_backend)s

enabled   = true
port      = 25,465,143,993,110,995,submission
filter    = postfix-sasl
logpath   = /var/log/mail.log
findtime = 10800
maxretry  = 2
bantime = 604800

Create the /etc/fail2ban/filter.d/postfix-sasl.conf file, so that Fail2ban can recognize all the attacks against the postfix-sasl jail:

nano /etc/fail2ban/filter.d/postfix-sasl.conf

Add the following content inside this file:

# Fail2Ban filter for postfix authentication failures
#

[INCLUDES]

before = common.conf

[Definition]

_daemon = postfix(-\w+)?/(?:submission/|smtps/)?smtp[ds]

failregex = warning: [-._\w]+\[<HOST>\]: SASL LOGIN authentication failed
            warning: unknown\[<HOST>\]: SASL PLAIN authentication failed
            RCPT from unknown\[<HOST>\]: .* Relay access denied
            ^%(__prefix_line)swarning: [-._\w]+\[<HOST>\]: SASL ((?i)LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed(:[ A-Za-z0-9+/:]*={0,2})?\s*$
            warning: hostname .* does not resolve to address\[<HOST>\]: Name or service not known
ignoreregex = authentication failed: Connection lost to authentication server$

[Init]

journalmatch = _SYSTEMD_UNIT=postfix.service

# Author: Yaroslav Halchenko

Reload Fail2ban:

systemctl reload fail2ban

19.9. Upgrading Postfix


Since Postfix has been installed from the official Debian repository, to upgrade it, all you need to do is to run apt-get update && apt-get dist-upgrade with a specific frequency, as described in the Maintenance steps chapter. This command will upgrade Postfix if there is a new version available. During these upgrades, the configuration changes implemented as described above, will be preserved.

19.10. Install Dovecot


Dovecot will be configured to use the Maildir mail store format to store email messages. This means that each message will be assigned a file with a unique name and will be stored in a directory on the server. As opposed to the mbox format, where each mailbox is a single file, with all email messages listed one after the other in that file, which makes it more difficult to delete or modify individual emails, using the Maildir format makes adding, deleting and modifying individual emails more efficient, without affecting the mailbox or other emails.

To install Dovecot run:

apt-get install dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-mysql

19.10.1. Configure Dovecot


Make a copy of the /etc/dovecot/dovecot.conf file:

cp /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf_orig

Empty dovecot.conf:

cat /dev/null > /etc/dovecot/dovecot.conf

Edit dovecot.conf:

nano /etc/dovecot/dovecot.conf

Add the following content inside this file:

# Enable installed protocols
!include_try /usr/share/dovecot/protocols.d/*.protocol

listen = *,[::]

log_path = /var/log/dovecot.log
auth_mechanisms = cram-md5
disable_plaintext_auth = yes

log_timestamp = "%Y-%m-%d %H:%M:%S "

passdb {
  args = /etc/dovecot/dovecot-mysql.conf
  driver = sql
}

protocols = imap pop3 lmtp sieve

service auth {
  unix_listener /var/spool/postfix/private/auth_dovecot {
    group = postfix
    mode = 0660
    user = postfix
  }
  unix_listener auth-master {
    mode = 0600
    user = vmail
  }
  user = root
}

ssl = required
ssl_min_protocol = TLSv1.2
ssl_client_ca_dir = /etc/ssl/certs
ssl_dh = </etc/dovecot/dh.pem

userdb {
  args = /etc/dovecot/dovecot-mysql.conf
  driver = sql
}

protocol pop3 {
  pop3_uidl_format = %08Xu%08Xv
  pop3_client_workarounds = oe-ns-eoh
}

protocol lda {
  postmaster_address = admin@example.com
  mail_plugins = $mail_plugins sieve
  mail_location = maildir:/var/vmail/%d/%n/
  mail_home = /var/vmail/%d/%n/
}

protocol lmtp {
  postmaster_address = admin@example.com
  mail_plugins = $mail_plugins sieve
  log_path = /var/log/dovecot-lmtp-errors.log
  mail_location = maildir:/var/vmail/%d/%n/
  mail_home = /var/vmail/%d/%n/
}

plugin {
  mail_home = /var/vmail/%d/%n/
  sieve = /var/vmail/%d/%n/dovecot.sieve
  sieve_dir = /var/vmail/%d/%n/
  sieve_after = /etc/dovecot/conf.d/custom-sieves/global_after.sieve
}

# Sieve configuration
protocol sieve {
  managesieve_implementation_string = dovecot
  log_path = /var/log/dovecot-sieve-errors.log
}

service managesieve-login {
  inet_listener sieve {
      port = 4190
  }
}

!include conf.d/*.conf
!include_try local.conf
!include_try ssl-keys.conf

Replace example.com with the main domain hosted on your server and admin@example.com with an email address that you want to use as postmaster address.

Create a separate file for the default SSL certificate and private key parameters:

nano /etc/dovecot/ssl-keys.conf

Enter the following content inside this file:

ssl_cert = </etc/letsencrypt/live/mail.example.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.example.com/privkey.pem

Replace example.com with the main domain hosted on your server. Change permissions for this file:

chown root:root /etc/dovecot/ssl-keys.conf
chmod 640 /etc/dovecot/ssl-keys.conf

Install the haveged package to increase the amount of available entropy of your server, which will be necessary when generating the Diffie-Hellman Parameters with openssl:

apt-get install haveged

Before generating the Diffie-Hellman Parameters, you can check the available entropy by running:

cat /proc/sys/kernel/random/entropy_avail

The entropy level should be above 2000 in order to allow rapid generation of random numbers. If it’s below 2000, you can perform various activities on the server, that would increase the entropy, like running the apt-get update, apt-get dist-upgrade or htop commands.

If the entropy level is above 2000, generate the Diffie-Hellman Parameters file by running:

openssl dhparam -out /etc/dovecot/dh.pem 4096

The process of generating the dh.pem file with the command from above can take even 45 minutes on a server with 1 CPU core, and 2 GB of RAM.

Since you included the ssl directives (ssl, ssl_min_protocol, ssl_client_ca_dir, ssl_dh, ssl_key, ssl_cert) in this main configuration file, there is no need to include them again in the /etc/dovecot/conf.d/10-ssl.conf file. So, first make a copy of this file:

cp /etc/dovecot/conf.d/10-ssl.conf /etc/dovecot/conf.d/10-ssl.conf_orig

Then open the file for editing:

nano /etc/dovecot/conf.d/10-ssl.conf

Comment out all the parameters that are not commented out in this file.

Since you specified auth_mechanisms = cram-md5 in the /etc/dovecot/dovecot.conf configuration file, there is no need to specify it in /etc/dovecot/conf.d/10-auth.conf, so, first make a copy of this file:

cp /etc/dovecot/conf.d/10-auth.conf /etc/dovecot/conf.d/10-auth.conf_orig

Open the file for editing:

nano /etc/dovecot/conf.d/10-auth.conf

Comment out the auth_mechanisms parameter, to make it look like this:

#auth_mechanisms = plain

The only uncommented line in this file should be:

!include auth-system.conf.ext

Since you mentioned mail_location = maildir:/var/vmail/%d/%n/ in the /etc/dovecot/dovecot.conf file, you need to comment out the mail_location parameter in the /etc/dovecot/conf.d/10-mail.conf file. First make a copy of the original file:

cp /etc/dovecot/conf.d/10-mail.conf /etc/dovecot/conf.d/10-mail.conf_orig

Then open the file for editing:

nano /etc/dovecot/conf.d/10-mail.conf

Comment out the mail_location line to make it look like this:

#mail_location = mbox:~/mail:INBOX=/var/mail/%u

Also, edit the mail_privileged_group parameter, to make it look like this:

mail_privileged_group = vmail

This file should also contain the following lines:

namespace inbox {
  inbox = yes
}

protocol !indexer-worker {
}

To have Dovecot create the default email directories (Sent, Drafts, Junk, Trash) automatically when new mailboxes (email accounts) are created with Postfix Admin, first make a copy of the /etc/dovecot/conf.d/15-mailboxes.conf file:

cp /etc/dovecot/conf.d/15-mailboxes.conf /etc/dovecot/conf.d/15-mailboxes.conf_orig

Open the file:

nano /etc/dovecot/conf.d/15-mailboxes.conf

Make the following blocks look like this:

  mailbox Drafts {
    auto = subscribe
    special_use = \Drafts
  }
  mailbox Junk {
    auto = subscribe
    special_use = \Junk
  }
  mailbox Trash {
    auto = subscribe
    special_use = \Trash
  }

  mailbox Sent {
    auto = subscribe
    special_use = \Sent
  }

Next, make a copy of the /etc/dovecot/conf.d/10-master.conf file:

cp /etc/dovecot/conf.d/10-master.conf /etc/dovecot/conf.d/10-master.conf_orig

Open the /etc/dovecot/conf.d/10-master.conf file:

nano /etc/dovecot/conf.d/10-master.conf

It should have the following content (pay attention to the lines in blue):

#default_process_limit = 100
#default_client_limit = 1000
# Default VSZ (virtual memory size) limit for service processes. This is mainly
# intended to catch and kill processes that leak memory before they eat up
# everything.
#default_vsz_limit = 256M
# Login user is internally used by login processes. This is the most untrusted
# user in Dovecot system. It shouldn't have access to anything at all.
#default_login_user = dovenull
# Internal user is used by unprivileged processes. It should be separate from
# login user, so that login processes can't disturb other processes.
#default_internal_user = dovecot

service imap-login {
  inet_listener imap {
    #port = 143
  }
  inet_listener imaps {
    port = 993
    ssl = yes
  }

  # Number of connections to handle before starting a new process. Typically
  # the only useful values are 0 (unlimited) or 1. 1 is more secure, but 0
  # is faster. <doc/wiki/LoginProcess.txt>
  #service_count = 1
  # Number of processes to always keep waiting for more connections.
  #process_min_avail = 0
  # If you set service_count=0, you probably need to grow this.
  #vsz_limit = $default_vsz_limit
}
service pop3-login {
  inet_listener pop3 {
    #port = 110
     port = 0
  }
  inet_listener pop3s {
    port = 995
    ssl = yes
  }
}

#service submission-login {
#  inet_listener submission {
    #port = 587
#  }
#}

service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    #mode = 0666
    user = postfix
    group = postfix
    mode = 0600
  }
  # Create inet listener only if you can't use the above UNIX socket
  #inet_listener lmtp {
    # Avoid making LMTP visible for the entire internet
    #address =
    #port =
  #}
}
service imap {
  # Most of the memory goes to mmap()ing files. You may need to increase this
  # limit if you have huge mailboxes.
  #vsz_limit = $default_vsz_limit

  # Max. number of IMAP processes (connections)
  #process_limit = 1024
}
service pop3 {
  # Max. number of POP3 processes (connections)
  #process_limit = 1024
}

#service submission {
  # Max. number of SMTP Submission processes (connections)
  #process_limit = 1024
#}

#service auth {
  # auth_socket_path points to this userdb socket by default. It's typically
  # used by dovecot-lda, doveadm, possibly imap process, etc. Users that have
  # full permissions to this socket are able to get a list of all usernames and
  # get the results of everyone's userdb lookups.
  #
  # The default 0666 mode allows anyone to connect to the socket, but the
  # userdb lookups will succeed only if the userdb returns an "uid" field that
  # matches the caller process's UID. Also if caller's uid or gid matches the
  # socket's uid or gid the lookup succeeds. Anything else causes a failure.
  #
  # To give the caller full permissions to lookup all users, set the mode to
  # something else than 0666 and Dovecot lets the kernel enforce the
  # permissions (e.g. 0777 allows everyone full permissions).
#  unix_listener auth-userdb {
#    mode = 0666
#    user = 
#    group = 
#  }


  # Postfix smtp-auth
  #unix_listener /var/spool/postfix/private/auth {
  #  mode = 0666
  #}

  # Auth process is run as this user.
  #user = $default_internal_user
#}

service auth-worker {
  # Auth worker process is run as root by default, so that it can access
  # /etc/shadow. If this isn't necessary, the user should be changed to
  # $default_internal_user.
  #user = root
  user = vmail
}

service dict {
  # If dict proxy is used, mail processes should have access to its socket.
  # For example: mode=0660, group=vmail and global mail_access_groups=vmail
  unix_listener dict {
    #mode = 0600
    #user =
    #group =
    user = vmail
  }
}

Next, navigate to /etc/dovecot:

cd /etc/dovecot

Create a file called dovecot-mysql.conf:

nano dovecot-mysql.conf

Add the following content inside this file:

driver = mysql
connect = host=localhost dbname=mail user=mail password=as6d516a51df6asdf
default_pass_scheme = CRAM-MD5
password_query = SELECT password FROM mailbox WHERE username = '%u'
user_query = SELECT CONCAT('maildir:/var/vmail/',maildir) AS mail, 5000 AS uid, 5000 AS gid FROM mailbox WHERE username = '%u'
iterate_query = SELECT username AS user FROM mailbox

Replace as6d516a51df6asdf with the actual password of the user mail, that you set up earlier.

Change ownership and permissions:

chown root:vmail dovecot-mysql.conf
chmod 640 dovecot-mysql.conf

You will also have to add the user www-data to the group dovecot, so that www-data has read and write access to the /run/dovecot/stats-writer file, which is necessary for Postfix Admin:

adduser www-data dovecot

19.10.2. Configure Fail2ban to protect Dovecot against brute-force attacks


To configure Fail2ban to protect Dovecot against brute-force attacks open the /etc/fail2ban/jail.local file:

nano /etc/fail2ban/jail.local

Search for the [dovecot] block, comment out all the lines in this block and add other lines, as follows:

[dovecot]
#port    = pop3,pop3s,imap,imaps,submission,465,sieve
#logpath = %(dovecot_log)s
#backend = %(dovecot_backend)s

enabled = true
port    = 465,143,993,110,995,submission,sieve
filter  = dovecot
logpath = /var/log/mail.log
maxretry = 5
bantime = 604800

Reload Fail2ban:

systemctl reload fail2ban

19.10.3. Configure logrotate to rotate Dovecot logs


Create a new file called dovecot inside the /etc/logrotate.d directory:

nano /etc/logrotate.d/dovecot

Add the following content inside this file:

/var/log/dovecot*.log {
  missingok
  rotate 4
  compress
  notifempty
  delaycompress
  size 2M
  sharedscripts
  postrotate
    /bin/kill -USR1 `cat /var/run/dovecot/master.pid 2>/dev/null` 2> /dev/null || true
  endscript
}

Before restarting Dovecot, you will have to install a few more packages, as described below.

19.10.4. Set up Sieve Mail Filtering


Install the dovecot-sieve and dovecot-managesieved packages:

apt-get install dovecot-sieve dovecot-managesieved

Since you entered the sieve configuration settings in the /etc/dovecot/dovecot.conf file, you have to comment out the corresponding configuration lines that exist by default in the /etc/dovecot/conf.d/90-sieve.conf file. First make a copy of the original file:

cp /etc/dovecot/conf.d/90-sieve.conf /etc/dovecot/conf.d/90-sieve.conf_orig

Then open the file:

nano /etc/dovecot/conf.d/90-sieve.conf

Comment out the following three lines, like this:

#plugin {
....

#  sieve = file:~/sieve;active=~/.dovecot.sieve
....

#}

All the lines in /etc/dovecot/conf.d/90-sieve.conf should be commented out.

Please note that installing dovecot-sieve without SpamAssassin is not enough to enable Dovecot spam filtering. Only after installing SpamAssassin, as we describe further down below, the spam filtering functionality will work.

The dovecot-sieve plugin has many other uses apart from spam filtering. It can be used to set up custom filters, so that incoming emails will go to certain folders, if they contain specific words in the Subject line, in the email body, etc.

19.10.4.1. Add global rules for sieve filtering


When you will install Roundcube, each user will be able to add their personal custom filter rules in the ‘Settings’ > ‘Filters’ section of their account. However, you will need to add global sieve filter rules that will apply to all mailboxes, in addition to the individual custom rules. We’ll describe below how to add a global rule for spam filtering, so that after you install SpamAssassin, all the incoming emails recognized as spam will be sent to the ‘Junk’ folder.

Create the global sieve rules directory and switch to it:

mkdir /etc/dovecot/conf.d/custom-sieves
cd /etc/dovecot/conf.d/custom-sieves

Create the global sieve rules file:

nano global_after.sieve

Add the following content inside this file:

require ["fileinto"];
if allof (header :contains "X-Spam-Flag" "YES")
{
        fileinto "Junk";
}

This rule sends spam emails to the ‘Junk’ folder automatically, when the X-Spam-Flag header contains the string YES . The X-Spam-Flag header will be added by SpamAssassin to each incoming email.

Compile sieve rules by running:

sievec global_after.sieve

This command will create the compiled file global_after.svbin.

Verify again that in the /etc/dovecot/dovecot.conf file, in the plugin block, you have included the sieve_after parameter, as follows:

plugin {
  mail_home = /var/vmail/%d/%n/
  sieve = /var/vmail/%d/%n/dovecot.sieve
  sieve_dir = /var/vmail/%d/%n/
  sieve_after = /etc/dovecot/conf.d/custom-sieves/global_after.sieve
}

To be able to create custom filters using the web interface, you will need to install Roundcube and the managesieve Roundcube plugin, as we describe further down below.

Finally, you can restart Dovecot for all the configuration changes to take effect:

systemctl restart dovecot

You can test if Dovecot sieve is working by running:

telnet localhost 4190

The output should be similar to this:

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
"IMPLEMENTATION" "dovecot"
"SIEVE" "fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext"
"NOTIFY" "mailto"
"SASL" "CRAM-MD5"
"STARTTLS"
"VERSION" "1.0"
OK "Dovecot (Debian) ready."

To end the telnet session press Ctrl + ] and then type quit and press Enter.

19.10.5. Use Dovecot to delete old emails


To avoid having to delete old emails manually when they become too old, are no longer of interest and take up valuable storage space, you can use the doveadm command and a cron job as described below.

It may not be a good idea to automatically delete emails that are just a few months old or even a few years old, but in general, when they are 10 years old or older, you can safely delete them. Even more so if you use Backup Manager as described here to backup the entire /var/vmail directory with a specific frequency, since all the emails are stored in /var/vmail. As long as you keep these backups, you can retrieve old messages any time from the /var/vmail archives, if you need to, even if they have been automatically deleted from the mail server.

The commands to delete all the emails older than 10 years from the Inbox, Sent and Junk directories of the bob_smith@example.com account are:

doveadm expunge -u 'bob_smith@example.com' mailbox Inbox sentbefore 3650d
doveadm expunge -u 'bob_smith@example.com' mailbox Sent sentbefore 3650d
doveadm expunge -u 'bob_smith@example.com' mailbox Junk sentbefore 3650d

3650d means 3650 days. You can specify any number of days smaller than 49711. Instead of Inbox, Sent, Junk, you can specify Trash, Drafts, etc.

To delete the emails older than 10 years from all the email directories of bob_smith@example.com, you can run:

doveadm expunge -u 'bob_smith@example.com' mailbox '*' sentbefore 3650d

To delete the emails older than 10 years from the Junk folders of all the email accounts, you can run:

doveadm expunge -A mailbox Junk sentbefore 3650d

To delete the emails older than 10 years from all the email directories and from all the email accounts on the server, you can run:

doveadm expunge -A mailbox '*' sentbefore 3650d

Before running the commands from above it’s recommended to first search for the messages that will be deleted, without deleting them, by using the search argument and the -v (verbose) or -vD (verbose and debug) arguments, like this:

doveadm -v search -u 'bob_smith@example.com' mailbox Trash sentbefore 3650d

If the search command returns a list of strings like the following:

50cdf911bb5d665fdf350000170dfdba 4
50cdf911bb5d665fdf350000170dfdba 5
50cdf911bb5d665fdf350000170dfdba 6
50cdf911bb5d665fdf350000170dfdba 7
50cdf911bb5d665fdf350000170dfdba 8
50cdf911bb5d665fdf350000170dfdba 9
50cdf911bb5d665fdf350000170dfdba 10
50cdf911bb5d665fdf350000170dfdba 11
50cdf911bb5d665fdf350000170dfdba 12

it means that the messages older than the specified number of days were found successfully and you can run the corresponding expunge command:

doveadm expunge -u 'bob_smith@example.com' mailbox Trash sentbefore 3650d

Otherwise, if the search command doesn’t output anything, it means that the search has been successful but no messages older than the specified number of days could be found. If the command returns errors, it means that the corresponding expunge command will return similar errors and will probably fail.

To delete the emails older than 10 years from all the email directories and from all the email accounts automatically, once every 3 months, create a cron job:

crontab -e

Add the following lines inside this file:

# Delete all the emails that are 10 years old or older from all the email accounts, in the 5th day of every third month, at 2:30 AM
30 2 5 Jan,Apr,Jul,Oct * doveadm expunge -A mailbox '*' sentbefore 3650d

The command specified in this cron job may return some errors similar to the following:

doveadm(nobody): Error: Namespace '': Mail storage autodetection failed with home=/nonexistent
doveadm(systemd-coredump): Error: Namespace '': Mail storage autodetection failed with home=/

These errors are caused by doveadm iterating through some system users in addition to the correct email users. These errors are harmless and can be ignored.

19.10.6. Upgrading Dovecot


Since Dovecot has been installed from the official Debian repository, to upgrade it, all you need to do is to run apt-get update && apt-get dist-upgrade with a specific frequency, as described in the Maintenance steps chapter. This command will upgrade Dovecot if there is a new version available. During these upgrades, the configuration changes implemented as described above, will be preserved.

19.11. Install Postfix Admin


As mentioned above, Postfix Admin is needed to manage email domains, email addresses, aliases, user quota etc., in a time-effective manner. You can find the latest release of Postfix Admin on the official GitHub Releases page. If you right-click on the zip archive of the latest version and choose ‘Copy Link Location’, you can copy the URL of the archive to clipboard.

First download the archive of the latest version to the /tmp directory, uncompress it, move it into a subdirectory of the /var/www directory and change ownership and permissions for all its files and folders, as follows:

cd /tmp
wget https://github.com/postfixadmin/postfixadmin/archive/postfixadmin-3.2.4.zip

unzip postfixadmin-3.2.4.zip
rm postfixadmin-3.2.4.zip
mv postfixadmin-postfixadmin-3.2.4 /var/www/net-pstfxdmn

Replace 3.2.4 with your version number. Replace net-pstfxdmn with a custom name, that will be also the name of the Postfix Admin login page. Choose a name that is easy to remember for you, but difficult to guess for an attacker. Definitely something different than postfixadmin, or postfixadmin-login, etc. Change ownership and permissions for Postfix Admin files and directories:

chown -R www-data:www-data /var/www/net-pstfxdmn
find /var/www/net-pstfxdmn -type d -exec chmod 750 {} +
find /var/www/net-pstfxdmn -type f -exec chmod 640 {} +

Next create the directory templates_c inside /var/www/net-pstfxdmn and set the right permissions and ownership, otherwise accessing the setup page will generate an error:

cd /var/www/net-pstfxdmn
mkdir templates_c
chown -R www-data:www-data templates_c
chmod 750 templates_c

Then copy the config.inc.php file to config.local.php and change ownership and permissions:

cp config.inc.php config.local.php
chown www-data:www-data config.local.php
chmod 640 config.local.php

Open the config.local.php file for editing:

nano config.local.php

Search for the following line:

// error message from $PALANG (see languages/*) will be displayed.

Remove the * sign after languages/, because it will cause a big portion of the lines that follow, to be considered as PHP comments, which is not the case. Then, set the following parameters as follows:

$CONF['configured'] = true;
$CONF['database_type'] = 'mysqli';
$CONF['database_host'] = 'localhost';
$CONF['database_user'] = 'mail';
$CONF['database_password'] = 'as6d516a51df6asdf';
$CONF['database_name'] = 'mail';
$CONF['admin_email'] = 'admin@example.com';
$CONF['smtp_server'] = 'mail.example.com';
$CONF['smtp_client'] = 'mail.example.com';
$CONF['encrypt'] = 'dovecot:CRAM-MD5';
$CONF['dovecotpw'] = "/usr/bin/doveadm pw";

Replace example.com with the main domain hosted on your server, as6d516a51df6asdf with the actual password of the user mail that you set up earlier, and admin@example.com with the email address that you want to use for superadmin.

Comment out the following lines, to make them look like this:

//if(@file_exists('/usr/bin/doveadm')) { // @ to silence openbase_dir stuff; see https://github.com/postfixadmin/postfixadmin/issues/171
//    $CONF['dovecotpw'] = "/usr/bin/doveadm pw"; # debian
//}

Also, set up the following parameters like this:

$CONF['default_aliases'] = array (
    'abuse' => 'admin@example.com',
    'hostmaster' => 'admin@example.com',
    'postmaster' => 'admin@example.com',
    'webmaster' => 'admin@example.com'
);

$CONF['domain_path'] = 'YES';
$CONF['domain_in_mailbox'] = 'NO';
$CONF['maildir_name_hook'] = 'NO';
$CONF['footer_text'] = 'Return to mail.example.com';
$CONF['footer_link'] = 'https://mail.example.com';

19.11.1. Configure Nginx for Postfix Admin


Open the /etc/nginx/sites-enabled/0-conf file for editing:

nano /etc/nginx/sites-enabled/0-conf

Add the following block inside the server block for mail.example.com, right below the block for phpMyAdmin (location /net-mrdblog { }):

    location /net-pstfxdmn {
        root /var/www;
        index index.php setup.php;
        auth_basic 'Restricted';
        auth_basic_user_file /etc/nginx/htpass/postfixadmin;
        location ~ ^/net-pstfxdmn/(.+.php)$ {
                       fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
                       fastcgi_param HTTPS on;
                       fastcgi_index index.php;
                       fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                       include /etc/nginx/fastcgi_params;
        }
    }

Replace net-pstfxdmn with your custom name for the Postfix Admin login page. Don’t forget to create the password file for HTTP authentication in order to restrict access to the login page. If you want to allow the user verner to access the Postfix Admin login page, run:

htpasswd -c /etc/nginx/htpass/postfixadmin verner

Change ownership and permissions for the new password file:

chown www-data:root /etc/nginx/htpass/postfixadmin
chmod 400 /etc/nginx/htpass/postfixadmin

Reload Nginx:

systemctl reload nginx

Next open up a web browser and access:

https://mail.example.com/net-pstfxdmn/public/setup.php

Replace example.com with your main domain and net-pstfxdmn with your custom Postfix Admin login page.

You will see a screen similar to this:

In the ‘Setup password’ field enter a setup password, enter it again in the ‘Setup password (again)’ field, copy it somewhere safe to use it later (you will also need it when upgrading Postfix Admin), then click on ‘Generate setup_password hash’.

A hash will be generated and displayed below the ‘Generate setup_password hash’ button, like this:

Copy the long string between quotes to the clipboard.

Open the config.local.php file:

nano /var/www/net-pstfxdmn/config.local.php

Search for $CONF['setup_password'] and add the hash that you copied earlier to it like this:

$CONF['setup_password'] = '$2y$10$Fafxog0Lt7KAIdd0H.BhiOeNFjBQq7uaPFc1xwyRtkj1zpJeKCmZa';

Return to the setup page and refresh the page. The setup script will perform some checks and will update the database, as seen below:

In the ‘Setup password’ field enter the setup password that you used earlier, in the ‘Admin’ field enter the superadmin’s email address, which can be admin@example.com, administrator@example.com, etc., in the ‘Password’ and ‘Password (again)’ fields enter a password for the superadmin, then click ‘Add Admin’.

From now on, you can log in to Postfix Admin with the superadmin email address and password that you have just set up, at:

https://mail.example.com/net-pstfxdmn/public

After logging in, you will see the following screen:

You can use Postfix Admin to:

add new email domains: ‘Domain List’ > ‘Add Domain’ > in the ‘Domain’ field enter the new domain, (Eg.: example.com), you can set ‘Aliases’, ‘Mailboxes’ and ‘Pass expires’ to 0 for unlimited aliases and mailboxes and no password expiration, you can leave everything else as it is, then click ‘Add Domain’.

add mailboxes to each domain: ‘Virtual List’ > ‘Add Mailbox’ > in the ‘Username’ field enter the part before @ of the new mailbox/email address, below the ‘Username’ field select the domain to which the new mailbox/email address will belong, in the ‘Password’ and ‘Password (again)’ fields enter the new password, you can leave the ‘Name’ field empty, you can also leave the ‘Quota’ field empty to assign unlimited storage space to the new mailbox, then click ‘Add Mailbox’.

add aliases for any email address: ‘Domain List’ > click on a domain > click on the ‘Add Alias’ button > in the ‘Alias’ field enter the part before @ of the new alias, in the ‘To’ field enter the complete email address to which the chosen alias is assigned (you can enter multiple email addresses, one per line), leave ‘Active’ checked, then click ‘Add Alias’. For example, if you have configured info@example.com as alias of office@example.com, all the emails sent to info@example.com will go to office@example.com.

After you first log in to Postfix Admin, add the mailbox of the superadmin: admin@example.com or administrator@example.com, etc., as a new mailbox for the example.com domain. Add other necessary mailboxes for your domain(s). For example contact@example.com, info@example.com, sales@example.com, support@example.com, etc.

Also, add root@mail.example.com as alias of the superadmin’s email address, admin@example.com for example, so that when an application installed on the server will send a notification email to the default root@mail.example.com, the email will reach the superadmin’s mailbox. (To add root@mail.example.com as an alias of admin@example.com you should first add mail.example.com as a new domain, in ‘Domain List’ > ‘New Domain’.)

After the installation, it’s a good idea to delete the setup.php file, although in the new Postfix Admin versions it’s not really necessary:

cd /var/www/net-pstfxdmn/public
rm setup.php

19.11.2. Move the configuration file outside the web root


To increase security, copy the config.local.php file to the /srv/scripts directory:

cp /var/www/net-pstfxdmn/config.local.php /srv/scripts/postfixadmin.php

Replace the content of the /var/www/net-pstfxdmn/config.local.php file like this:

cd /var/www/net-pstfxdmn
cat /dev/null > config.local.php
nano config.local.php

Add the following line inside this file:

<?php include(‘/srv/scripts/postfixadmin.php’); ?>

Change ownership and permissions for the /srv/scripts/postfixadmin.php file:

cd /srv/scripts
chown www-data:root postfixadmin.php
chmod 400 postfixadmin.php

19.11.3. Configure Fail2ban to protect Postfix Admin against brute-force attacks


Navigate to the /etc/fail2ban/filter.d directory:

cd /etc/fail2ban/filter.d

Create a new filter file for Postfix Admin:

nano postfixadmin.conf

Add the following content in this file:

[Definition]

failregex =  ^<HOST> .* \"POST /net-pstfxdmn/login.php HTTP/2.0\" 200 15.*$
             ^<HOST> .* \"GET /net-pstfxdmn HTTP/2.0\" 401 195 .*$
ignoreregex =

Replace net-pstfxdmn with your custom name of the Postfix Admin login page. The second line identifies the failed log in attempts against the HTTP authentication window, which is displayed right before loading the Postfix Admin login page, and can be also subject to brute-force attacks. Next, open the /etc/fail2ban/jail.local file:

nano /etc/fail2ban/jail.local

Add the following block above the [phpmyadmin] block:

[postfixadmin]
enabled  = true
filter   = postfixadmin
logpath  = /var/log/sites/mail.example.com/access.log           
port     = 80,443
findtime = 3600
maxretry = 4
bantime = 604800

Replace example.com with your main domain.

Reload Fail2ban:

systemctl reload fail2ban

19.11.4. Upgrading Postfix Admin


Before upgrading Postfix Admin to a new version, it’s recommended to verify if the new version has been tested and confirmed to function well within the suite of applications described in this guide. Once we test an application and confirm that it works well, we include it on this page.

If you check the official GitHub Releases page of Postfix Admin, and you notice that a new version has been released, you can upgrade Postfix Admin by following the steps from below.

First create the backups. Use phpMyAdmin to export the mail database and change the name of the resulting sql file, to make it include the date of the backup (Eg.: mail-2021-02-15.sql). Then create an archive of the Postfix Admin directory (/var/www/net-pstfxdmn in our example), including the date in the name of the archive, like this:

cd /var/bm_archives
tar czf postfixadmin-2021-02-15.tar.gz /var/www/net-pstfxdmn

Replace net-pstfxdmn with the custom name of the Postfix Admin directory, which is also the name of the login page.

Use a FTP client like FileZilla to upload the sql file to the remote server and place it in /var/bm_archives, and to download the tar.gz archive to your local computer, so as to have the two backup files in two separate locations. You can also copy them to a third location on an external storage device, etc.

Next, download the latest version of Postfix Admin from the official GitHub releases page and extract the archive:

cd /tmp
wget https://github.com/postfixadmin/postfixadmin/archive/postfixadmin-3.2.5.zip

unzip postfixadmin-3.2.5.zip
rm postfixadmin-3.2.5.zip

Replace 3.2.5 with your version.

Rename the Postfix Admin directory, /var/www/net-pstfxdmn to /var/www/net-pstfxdmn_old:

Move the new Postfix Admin uncompressed directory to /var/www, changing its name to /var/www/net-pstfxdmn:

mv /tmp/postfixadmin-postfixadmin-3.2.5 /var/www/net-pstfxdmn

Create an empty config.local.php file in the /var/www/net-pstfxdmn directory:

cd /var/www/net-pstfxdmn
nano config.local.php

Add the following line inside this file:

<?php include('/srv/scripts/postfixadmin.php'); ?>

Next create the directory templates_c in /var/www/net-pstfxdmn:

mkdir templates_c

Change ownership and permissions for the /var/www/net-pstfxdmn directory:

chown -R www-data:www-data /var/www/net-pstfxdmn
find /var/www/net-pstfxdmn -type d -exec chmod 750 {} +
find /var/www/net-pstfxdmn -type f -exec chmod 640 {} +

Then access the setup page by going to:

https://mail.example.com/net-pstfxdmn/public/setup.php

Replace example.com with your main domain, and net-pstfxdmn with the custom name of the Postfix Admin login page. You will see the following screen:

Enter the setup password that you saved when you installed Postfix Admin in the ‘Setup password’ field, then click on the ‘Login with setup_password’ button. The setup script will check the dependencies and also update the database. If everything is OK, the next screen will look like this:

Ignore the two warnings.

If you want to add a new superadmin account, you can do it by filling the text boxes under ‘Add Superadmin Account’. If you don’t want a new superadmin account, because you intend to use the old superadmin account(s), just leave the text boxes under ‘Add Superadmin Account’ empty. The upgrade is finished. You can access the login page at:

https://mail.example.com/net-pstfxdmn/public

In case the database upgrade fails, you can access https://mail.example.com/net-pstfxdmn/public/setup.php?debug=1 to see the last executed query.

After the installation finishes, it’s a good idea to delete the setup.php file:

cd /var/www/net-pstfxdmn/public
rm setup.php

After you log in to Postfix Admin and confirm that everything is working, you can delete the /var/www/net-pstfxdmn_old directory:

cd /var/www
rm -r net-pstfxdmn_old

19.12. Set up MX records for your domains

After installing the components described up to this point, you must add the specific DNS records necessary to send/receive emails (the MX records), and the records needed to legitimate your emails, so that they won’t be considered spam or forged emails (the SPF, DKIM, DMARC, ARC and DANE/MTA-STS records).

If you use BIND, the MX record for the example.com domain, as it should appear in the BIND configuration file for example.com (in the /etc/bind/db.example.com file) will look like this:

@      IN      MX     10    mail.example.com.

where @ is the placeholder for example.com , and 10 is the priority indicator (10 is the default priority). Please note that there is a period at the end of the mail.example.com domain name. The period “.” at the end of a domain name indicates that it is fully qualified.

You will need to add an MX record like the one for example.com from above, for any other domain that you host on your server: secondsite.net, thirdsite.info, fourthsite.org, etc. If you use BIND, the respective MX records will be added in the corresponding files: /etc/bind/db.secondsite.net, /etc/bind/db.thirdsite.info, /etc/bind/db.fourthsite.org, etc.

However, for your main domain (example.com), you will also have to add an additional MX record for the mail subdomain. This will be necessary for sending DMARC reports, etc. The record should look like this:

mail      IN      MX     10    mail.example.com.

Any subdomain of the example.com domain, (like mailman, friendica, etc.) should have a MX record similar to the one from above if you want to send emails from the corresponding domain names (mailman.example.com, friendica.example.com, etc.).

If you use Hurricane Electric’s DNS services, to add an MX record for the example.com domain, first click on ‘New MX’, then fill the fields as follows:

19.13. Set up SPF (Sender Policy Framework) for your domains


SPF (Sender Policy Framework) is an email authentication method that specifies what IPs are authorized to send emails for a given domain, to make it harder for fraudsters to forge emails. Setting up SPF records for domains used to send emails is very important for email deliverability.

The SPF record for the example.com domain, as it should appear in the BIND configuration file for example.com (/etc/bind/db.example.com) will look like this:

@     IN     TXT    "v=spf1 a mx ip4:123.123.123.123 ip6:2b03:8df0:a24b:6eb::1 -all"

where @ is the placeholder for example.com, 123.123.123.123 is the public IPv4 address of your server and 2b03:8df0:a24b:6eb::1 is the IPv6 address used by your mail server (it’s recommended to set here an IPv6 address from the 2b03:8df0:a24b:6eb::/64 subnet allocated to your machine by your hosting provider. If your provider didn’t allocate a /64 subnet to your server and they refuse to do so after you open a support ticket, it’s recommended to disable IPv6 connectivity for your mail server and to remove the ip6:2b03:8df0:a24b:6eb::1 part from the record presented above. Otherwise, your emails may be rejected just because other IPs in the same /64 range sent spam and as a result, the whole /64 range has been blacklisted).

Below is a description of the options used:

v=spf1 specifies the SPF version, is required and has to be the first tag.

a authorizes the hosts identified in the domain’s A records to send e-mail.

mx is a shorthand for all the hosts listed in the MX records for the example.com domain.

ip4 and ip6 specify the IP addresses that are allowed to send emails from the example.com domain.

123.123.123.123 is the IPv4 address of your server.

2b03:8df0:a24b:6eb::1 is the IPv6 address of your mail server.

-all indicates that mail from your domain should only come from servers identified in the SPF record. Anything coming from any other source is trying to impersonate your mail server. An alternative is ~all, which shows the same thing but also that mail servers should accept the message and flag it as forged, instead of simply rejecting it. -all makes it more difficult for spammers to send emails that impersonate your domain.

A SPF record like the one from above has to be added for any domain hosted on your server, if you intend to use email addresses with that domain to send emails, which is usually the case.

However, for your main domain (example.com), you will also have to add an additional SPF record for the mail subdomain. This will be necessary for sending DMARC reports, etc. The record should look like this:

mail     IN     TXT    "v=spf1 a mx ip4:123.123.123.123 ip6: 2b03:8df0:a24b:6eb::1 -all"

Any subdomain of the example.com domain, (like mailman, friendica, etc.) should have a SPF record similar to the one from above if you want to send emails from the corresponding domain names (mailman.example.com, friendica.example.com, etc.).

To verify the SPF records of incoming mail and thus avoid accepting forged emails, you have to enable two lines that are commented out in the /etc/postfix/master.cf file. Open the file for editing:

nano /etc/postfix/master.cf

Scroll down to the two lines mentioning policyd-spf and remove the comment sign (#), to make them look like this:

policyd-spf  unix  -       n       n       -       0       spawn
    user=policyd-spf argv=/usr/bin/policyd-spf

Also, open the /etc/postfix/main.cf file:

nano /etc/postfix/main.cf

Uncomment the policyd-spf_time_limit line (by removing the # sign), to increase the Postfix policy agent timeout, which will prevent Postfix from aborting the agent if transactions run a bit slowly. Make that line look like this:

policyd-spf_time_limit = 3600

In the same file, check if you have the following two lines in the smtpd_recipient_restrictions entry:

smtpd_recipient_restrictions =
    ...
    reject_unauth_destination,
    check_policy_service unix:private/policyd-spf,
    ...

Without these settings, SPF verification won’t function correctly.

Restart Postfix:

systemctl restart postfix

From now on, if you look at the raw source code of incoming emails (in Thunderbird click ‘More’ on the horizontal bar above the message, then click ‘View Source’), you will see the Received-SPF header followed by Pass or Fail. The domain of the Return-Path email address (also known as the ‘MAIL FROM’, ‘envelope from’, ‘reverse path’ or ‘bounce’ address) is the one used by the receiving mail server to get the SPF record. After it retrieves the record, it looks at the list of IP addresses that are allowed to send emails on behalf of the domain used in the Return-Path address, and then it compares the allowed IP addresses with the IP address that sent the email. If the sender’s IP is among the allowed IPs, the email passes SPF authentication, otherwise it doesn’t.

If you use Hurricane Electric’s DNS services, to add a SPF record for example.com click on ‘New TXT’ and fill the fields as follows:

Replace example.com with your own domain, replace 123.123.123.123 with the IPv4 address of your server and 2b03:8df0:a24b:6eb::1 with the IPv6 address of your mail server.

19.14. Set up DKIM (DomainKeys Identified Mail) for your domains


DKIM (DomainKeys Identified Mail) is an email authentication method that uses cryptographic signatures to verify that the content of emails hasn’t been altered in transit and that the email sender’s domain hasn’t been forged.

DKIM relies on asymmetric encryption: first you generate a private/public key pair, then enter the public key as a TXT record of the domain used to send emails. When your server sends an email, the email content (the ‘From’ field, subject, message body, different email headers, etc.) is hashed and the hash is encrypted with the private key, resulting a signature that is added to the email in the ‘DKIM-Signature’ header. The receiving mail server searches for the corresponding public key in your domain’s DNS records and uses it to decrypt the encrypted hash from the ‘DKIM-Signature’ header. Next, it generates its own hash from the email content and compares it with the decrypted hash from the ‘DKIM-Signature’ header. If the hashes match, it indicates that the email was signed by the indicated domain and that it hasn’t been tampered with in transit and thus the email passes DKIM verification.

Like SPF records, DKIM records help reduce the probability that emails from your server will be considered spam by other mail servers.

To implement DKIM for your domains, first you have to install some new packages:

apt-get install opendkim opendkim-tools postfix-policyd-spf-python postfix-pcre

Then create the directories necessary to store the new files:

mkdir -p /etc/opendkim/keys

Make a copy of the /etc/opendkim.conf file:

cp /etc/opendkim.conf /etc/opendkim.conf_orig

Emtpy the /etc/opendkim.conf file:

cat /dev/null > /etc/opendkim.conf

Open the file for editing:

nano /etc/opendkim.conf

Add the following content inside this file:

Mode  sv
Syslog  yes
UserID  opendkim:postfix
UMask  007
Socket  local:/run/opendkim/opendkim.sock
PidFile  /run/opendkim/opendkim.pid
Selector  default
SendReports  no
SoftwareHeader  yes
Canonicalization  relaxed/simple
SubDomains  no
SignatureAlgorithm  rsa-sha256
OversignHeaders  From
AlwaysAddARHeader  yes
KeyTable  refile:/etc/opendkim/KeyTable
SigningTable  refile:/etc/opendkim/SigningTable
ExternalIgnoreList  refile:/etc/opendkim/TrustedHosts
InternalHosts  refile:/etc/opendkim/TrustedHosts

Mode specifies the operating mode: s for singing, v for verifying and sv for both signing and verifying.

Syslog enables detailed logging using syslog.

UserID mentions the user and group under which the opendkim process runs.

UMask defines the permissions mask for file creation for the user and group defined by UserID. This will only apply to the creation of the socket file, when Socket specifies a UNIX domain socket, and of the PID file.

Socket specifies the socket which the milter will listen on. Posfix will send messages to OpenDKIM for DKIM signing and verification through this socket.

PidFile mentions the path to the file containing OpenDKIM’s process identification number (PID).

Selector defines the name of the selector that will be used when signing messages. Even if it is used only when signing with a single key and is ignored when a KeyTable is defined, you can still use this parameter to specify a default DKIM selector.

SendReports specifies whether or not the OpenDKIM filter should generate a structured report and sent it to the email sender when the signature verification fails, the signature includes a reporting request (“r=y”) and the signing domain specifies a reporting address in a reporting record in the DNS.

SoftwareHeader causes OpenDKIM to add a “DKIM-Filter” header field to sent and received emails. The OpenDKIM’s version and the job ID are included in this header.

Canonicalization defines the canonicalization methods used when signing messages. The simple method allows almost no modification while the relaxed method tolerates minor changes such as whitespace replacement. relaxed/simple indicates that the relaxed method will be applied to the message header, while the simple method will be applied to the message body.

SubDomains specifies whether OpenDKIM will sign the subdomains of the domains listed in the Domain parameter, as well as the domains. The Domain parameter is ignored if a KeyTable is defined. Also, even if this is set to no, subdomains can still be signed, like any domain, as explained below.

SignatureAlgorithm mentions the signature algorithm to use when generating signatures. Must be either “rsa-sha256”, or, if it’s not available, “rsa-sha1”.

OversignHeaders specifies the headers that should be included in all signature header lists once more than the number of times they were actually present in the signed message. The purpose of this is to prevent the ill-intentioned addition of the mentioned fields between the signer and the verifier. Since the verifier would include those headers when performing the verification, if they had been added by an intermediary, the hash of the original message and the hash calculated by the receiving server would differ and the DKIM verification would fail.

AlwaysAddARHeader causes OpenDKIM to add an “Authentication-Results:” header even to unsigned messages from domains that don’t have a “signs all” policy. In such cases, the DKIM result will be none.

KeyTable specifies the location of a file mapping key names to signing keys. This table will be queried to convert key names to sets of data of the form: signing_domain:signing_selector:private_key. If present, it overrides any KeyFile directive. It requires a SigningTable.

SigningTable defines a file that lists the signatures to apply to a message based on the address found in the From header. The lookup will return the name of a key found in the KeyTable, the key being used to sign the message.

ExternalIgnoreList specifies a file that lists the “external” hosts that can send emails through the server as one of the signing domains without credentials.

InternalHosts defines a file with a list of internal hosts whose emails should be signed but not verified. In general it includes 127.0.0.1, ::1, localhost and the domains (with subdomains) hosted on the server.

Also, in the /etc/opendkim directory, you have to create three files. First create the /etc/opendkim/TrustedHosts file:

nano /etc/opendkim/TrustedHosts

Add the following content inside this file:

127.0.0.1
::1
localhost
*.example.com
*.secondsite.net
*.thirdsite.info
*.fourthsite.org
*.fifthsite.us

Replace example.com with the main domain hosted on your server and secondsite.net, thirdsite.info, fourthsite.org, fifthsite.us, with the other domains hosted on the server.

Then create the /etc/opendkim/SigningTable file:

nano /etc/opendkim/SigningTable

Add the following content inside this file:

*@example.com  default._domainkey.example.com
*@secondsite.net  default._domainkey.secondsite.net
*@thirdsite.info  default._domainkey.thirdsite.info
*@fourthsite.org  default._domainkey.fourthsite.org
*@fifthsite.us  default._domainkey.fifthsite.us

Replace example.com with the main domain hosted on your server and secondsite.net, thirdsite.info, fourthsite.org, fifthsite.us, with the other domains hosted on the server.

You should also configure subdomains in a similar manner. For example, you will want to configure the mail.example.com subdomain for DKIM signing, to send DMARC reports, explained in the next chapter. Thus, the SigningTable described above should contain a line for mail.example.com, like this:

*@example.com  default._domainkey.example.com
*@mail.example.com  default._domainkey.mail.example.com
*@secondsite.net  default._domainkey.secondsite.net
*@thirdsite.info  default._domainkey.thirdsite.info
*@fourthsite.org  default._domainkey.fourthsite.org
*@fifthsite.us  default._domainkey.fifthsite.us

Any other subdomain that you will use to send emails, should have its own line in the SigningTable, similar to the one for mail.example.com shown above.

Next, create the /etc/opendkim/KeyTable file:

nano /etc/opendkim/KeyTable

Add the following content inside this file:

default._domainkey.example.com  example.com:default:/etc/opendkim/keys/example.com.private
default._domainkey.mail.example.com  mail.example.com:default:/etc/opendkim/keys/mail.example.com.private
default._domainkey.secondsite.net  secondsite.net:default:/etc/opendkim/keys/secondsite.net.private
default._domainkey.thirdsite.info  thirdsite.info:default:/etc/opendkim/keys/thirdsite.info.private
default._domainkey.fourthsite.org  fourthsite.org:default:/etc/opendkim/keys/fourthsite.org.private
default._domainkey.fifthsite.us  fifthsite.us:default:/etc/opendkim/keys/fifthsite.us.private

Replace example.com with the main domain hosted on your server and secondsite.net, thirdsite.info, fourthsite.org, fifthsite.us, with the other domains hosted on the server.

Navigate to the /etc/opendkim/keys directory:

cd /etc/opendkim/keys

Generate the keys for example.com by running:

opendkim-genkey -b 2048 -h rsa-sha256 -r -s default -d example.com

This command will create the files default.private and default.txt. Change the name of these two files as follows:

mv default.private example.com.private
mv default.txt example.com.txt

Repeat the keys generation and files renaming described above, for mail.example.com (the corresponding files will be mail.example.com.private and mail.example.com.txt) and for each domain that you use to send emails (secondsite.net, thirdsite.info, fourthsite.org, fifthsite.us, etc.)

Change ownership for the /etc/opendkim directory and its content and permissions for the /etc/opendkim/keys directory:

chown -R opendkim:opendkim /etc/opendkim
chmod 700 /etc/opendkim/keys

To allow the user postfix to have proper access to the /run/opendkim/opendkim.sock file, you have to add it to the opendkim group by running:

adduser postfix opendkim

In this way, the /run/opendkim directory, which is owned by the opendkim user and the opendkim group, with 750 permissions, will allow the postfix user to access the /run/opendkim/opendkim.sock file.

Also edit the /etc/postfix/main.cf file:

nano /etc/postfix/main.cf

Add the following directives right below the milter_default_action = accept line:

smtpd_milters = local:/run/opendkim/opendkim.sock
non_smtpd_milters = local:/run/opendkim/opendkim.sock

Restart opendkim and Postfix:

systemctl restart opendkim
systemctl restart postfix

19.14.1. Create the DKIM TXT records


If you look into the /etc/opendkim/keys/example.com.txt file that you generated earlier for the example.com domain with the opendkim-genkey command, you will see a content similar to this:

default._domainkey	IN	TXT	( "v=DKIM1; h=rsa-sha256; k=rsa; s=email; "
	  "p=MIIBIdCBEhdqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp562Yiy2bRa4xUJgpJlPbk69tWp5aYiZkOYJWIACpcK6TgTS197HavECcZVTu4ZijcmyZqecfytqvjo23mW+7lBc4DMlynYzcNtC2e/i29mKeMT4KPzfz8czBdByjZ76ZKclUkgtVhtsaF2KPUsw6vgn2dYFjMSNQWRtM3LyiWymkwOeKOkbHrQsgb4vxReiq6M8jPd4r0Ad5"
	  "k2FlgfDFqvRgYy3GxQUtAIYdT1G7T7Jc+Q433DG4RPq4WGA6YufkskPbmyP5yquOiJuPNSxzo6srC3zhGzzL6MTMk145iof85keUVgTi3XrlWKPwd8/VOgos2nyVLK5ewd5jnWawJGASZB" )  ; ----- DKIM key default for example.com

Please note that the long string listed after p= is split into two sections. You have to copy this long string in a TXT record added to the example.com domain, in your DNS settings, preserving the two sections of the string.

Therefore, the DKIM record for the example.com domain, as it should appear in the BIND configuration file for example.com (/etc/bind/db.example.com) will look like this:

default._domainkey    IN     TXT    "v=DKIM1; h=sha256; k=rsa; s=email;"  "p=MIIBIdCBEhdqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp562Yiy2bRa4xUJgpJlPbk69tWp5aYiZkOYJWIACpcK6TgTS197HavECcZVTu4ZijcmyZqecfytqvjo23mW+7lBc4DMlynYzcNtC2e/i29mKeMT4KPzfz8czBdByjZ76ZKclUkgtVhtsaF2KPUsw6vgn2dYFjMSNQWRtM3LyiWymkwOeKOkbHrQsgb4vxReiq6M8jPd4r0Ad5"  "k2FlgfDFqvRgYy3GxQUtAIYdT1G7T7Jc+Q433DG4RPq4WGA6YufkskPbmyP5yquOiJuPNSxzo6srC3zhGzzL6MTMk145iof85keUVgTi3XrlWKPwd8/VOgos2nyVLK5ewd5jnWawJGASZB"

The two parts of the string that appears after p= are separated only by two spaces. The selector, default in this case, can be replaced by a selector of your choice, made up of letters, numbers, dates, etc., but in that case you should specify the same selector on the corresponding row in the /etc/opendkim/SigningTable file and in the /etc/opendkim/KeyTable file.

For the mail.example.com subdomain, the DKIM record will look like this:

default._domainkey.mail    IN     TXT    "v=DKIM1; h=sha256; k=rsa; s=email;"  "p=BDICIkCDhGdqhkiGw70DAQEFAAOCAQ8AMIIBCgKCAQEAp562Yiy2bRa4xUJgpJlPbk69tWp5aYiZkOYJWIACpcK6TgTS197HavEDcZVTu4ZijcmyZqecfytqvjo23mW+7lBc4DMlynYzcNtC2e/i29mKeMT8KPzfz8czBdByjZ56ZKclUkgtVhtsaF2KPUsw6vgn2dYFjMSNQWRtM3LyiWymkwOeKOkbHrQsgb4vxReiq6M8jPd4k5DE4"  "g7DlafDFqvRgYy3GxQUtAIYdT1G7T9Jc+Q433DG4RPq4WGA6YufkskPbmyP5yquOiJuPNSxzo6srC3zhGzzL6MTMk145iof85keUVgTi3XrlWK5wd8/VOgos2nyVLK5ewd5jnWawDF7W5C"

Please note that BIND adds .example.com. after default._domainkey in the first example, and after default._domainkey.mail in the second example, automatically. This is why you don’t need to mention it explicitly.

Add similar TXT records in the DNS settings for all your other domains and subdomains that you use to send emails.

To test the OpenDKIM configuration for example.com run:

opendkim-testkey -d example.com -s default -vvv

The last message should be “key OK”. If you see a “key not secure” message above the “key OK” line, it doesn’t represent an error, it just means that DNSSEC is not in use for opendkim and you can ignore it.

If you look at the raw source code of incoming emails, you should see headers similar to the following:

DKIM-Filter: OpenDKIM Filter v2.11.0 mail.example.com 7536DF093
Authentication-Results: mail.example.com;
        dkim=pass (2048-bit key; unprotected) header.d=otherdomain.com header.i=@otherdomain.com header.a=rsa-sha256 header.s=default header.b=b6e0/eVf;
        dkim-atps=neutral

If you use Hurricane Electric’s DNS services, to add a DKIM record for example.com, click on ‘New TXT’, then fill the fields as follows:

19.14.2. Renewing the DKIM keys


As mentioned in the Maintenance Steps chapter, once every two years, after upgrading the operating system of your server (or more frequently if you prefer, like once a year), you should re-generate the DKIM private and public key and update the corresponding DNS record, for each domain and subdomain that you use to send emails. To achieve this, first navigate to the /etc/opendkim/keys directory:

cd /etc/opendkim/keys

Generate the keys for example.com by running:

opendkim-genkey -b 2048 -h rsa-sha256 -r -s default -d example.com

This command will create the files default.private and default.txt. Change the name of these two files as follows:

mv default.private example.com.private
mv default.txt example.com.txt

Repeat the keys generation and files renaming described above, for mail.example.com (the corresponding files will be mail.example.com.private and mail.example.com.txt), for each of the other subdomains that you use to send emails (like mailman.example.com and friendica.example.com) and for each domain that you use to send emails (secondsite.net, thirdsite.info, fourthsite.org, fifthsite.us, etc.)

Set the correct ownership for the /etc/opendkim directory and its content and permissions for the /etc/opendkim/keys directory:

chown -R opendkim:opendkim /etc/opendkim
chmod 700 /etc/opendkim/keys

Next, update the corresponding DKIM records for all the domains and subdomains whose keys you have just renewed.

The content of the /etc/opendkim/keys/example.com.txt file that you have generated for the example.com domain with the opendkim-genkey command, is similar to this:

default._domainkey	IN	TXT	( "v=DKIM1; h=rsa-sha256; k=rsa; s=email; "
	  "p=MIIBIdCBEhdqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp562Yiy2bRa4xUJgpJlPbk69tWp5aYiZkOYJWIACpcK6TgTS197HavECcZVTu4ZijcmyZqecfytqvjo23mW+7lBc4DMlynYzcNtC2e/i29mKeMT4KPzfz8czBdByjZ76ZKclUkgtVhtsaF2KPUsw6vgn2dYFjMSNQWRtM3LyiWymkwOeKOkbHrQsgb4vxReiq6M8jPd4r0Ad5"
	  "k2FlgfDFqvRgYy3GxQUtAIYdT1G7T7Jc+Q433DG4RPq4WGA6YufkskPbmyP5yquOiJuPNSxzo6srC3zhGzzL6MTMk145iof85keUVgTi3XrlWKPwd8/VOgos2nyVLK5ewd5jnWawJGASZB" )  ; ----- DKIM key default for example.com

Please note that the long string listed after p= is split into two sections. You have to copy this long string preserving the two sections, and use it to replace the old corresponding string from the DKIM record for example.com in your DNS settings.

Therefore, the DKIM record for the example.com domain, as it should appear in the BIND configuration file for example.com (/etc/bind/db.example.com) will look like this:

default._domainkey    IN     TXT    "v=DKIM1; h=sha256; k=rsa; s=email;"  "p=MIIBIdCBEhdqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp562Yiy2bRa4xUJgpJlPbk69tWp5aYiZkOYJWIACpcK6TgTS197HavECcZVTu4ZijcmyZqecfytqvjo23mW+7lBc4DMlynYzcNtC2e/i29mKeMT4KPzfz8czBdByjZ76ZKclUkgtVhtsaF2KPUsw6vgn2dYFjMSNQWRtM3LyiWymkwOeKOkbHrQsgb4vxReiq6M8jPd4r0Ad5"  "k2FlgfDFqvRgYy3GxQUtAIYdT1G7T7Jc+Q433DG4RPq4WGA6YufkskPbmyP5yquOiJuPNSxzo6srC3zhGzzL6MTMk145iof85keUVgTi3XrlWKPwd8/VOgos2nyVLK5ewd5jnWawJGASZB"

The two parts of the string that appears after p= are separated only by two spaces. Remember that the selector, default in this case, can be replaced with any selector of your choice, made up of letters, numbers, dates, etc., but in that case you should specify the same selector on the corresponding row in the /etc/opendkim/SigningTable file and in the /etc/opendkim/KeyTable file.

19.15. Set up DMARC (Domain-based Message Authentication, Reporting and Conformance) for your domains


DMARC (Domain-based Message Authentication, Reporting and Conformance) is an email authentication method that verifies that the domain in the Return-Path address (also known as the ‘MAIL FROM’ address or ‘envelope from’ address) matches the domain in the From field and that the domain in the DKIM signature matches the domain in the From field of incoming emails, to prevent domain spoofing (forging). A DMARC record is also used to advise mail servers what they should do with emails claiming to be from your domain but failing SPF and/or DKIM validation. DMARC also allows requesting reports from other mail servers. DMARC should be configured only after setting up SPF and DKIM records, for each domain.

The DMARC record for the example.com domain, as it should appear in the BIND configuration file for example.com (/etc/bind/db.example.com) will look like this:

_dmarc     IN     TXT    "v=DMARC1; p=reject; sp=reject; adkim=s; aspf=s; rua=mailto:admin@example.com"

where admin@example.com is the address where you want to receive aggregate reports from other mail servers, for emails coming from your mail server.

Below is a description of the options used:

v specifies the protocol version, in this case DMARC1.

p specifies the policy to be used for emails coming from the example.com domain. The available options are:

  • quarantine specifies that if an email fails validation, the receiving mail server must set it aside for processing. When Postfix is used, by default, this results in the received email being put in the Postifx hold queue for an indefinite period of time.
  • reject specifies that the receiving mail server must reject the emails that fail validation.
  • none specifies that the receiving mail server must take no action if emails don’t pass validation.

sp specifies the policy for subdomains, such as subdomain.example.com. It accepts the same arguments as the p tag.

adkim specifies the alignment mode for DKIM, which determines how strictly DKIM records are validated. The available options are:

  • r relaxed alignment mode: the domain in the DKIM signature must be equal to or a subdomain of the domain in the From field (example.com and forum.example.com will pass validation).
  • s strict alignment mode: the domain in the DKIM signature must be equal to the domain in the From field.

aspf specifies the alignment mode for SPF verification. The available options are:

  • r relaxed alignment mode: the domain in the Return-Path address must be equal to or a subdomain of the domain in the From field (example.com and forum.example.com will pass validation).
  • s strict alignment mode: the domain in the Return-Path address must be equal to the domain in the From field.

rua specifies the email address that will receive the aggregate reports. This option accepts multiple addresses in the following format: rua=mailto:tom@domain1.com,mailto:jerry@domain2.com,mailto:oscar@domain3.com; . Once you receive reports and notice that everything works well, you can disable this feature by removing the rua=mailto:admin@example.com parameter from the DMARC record, because in time, these reports can become annoying.

There is a second type of DMARC reports, called failure reports or forensic reports, that can be asked for, using the ruf option (ruf=mailto:admin@example.com). However, since failure reports are sent for each failed email and they contain a copy of the original failed email, which can contain personal identifiable information, most email providers no longer send failure reports due to privacy related concerns. Therefore, it’s not recommended to include the ruf option in the DMARC records. There are 2 other options associated with the ruf option, which shouldn’t be included in the DMARC records, if the ruf option is not included. These 2 options are:

fo specifies which authentication failures will be reported in failure (forensic) reports. The available options are:

  • 0 requests a report if all authentication methods fail. For example, if an SPF check failed but the DKIM authentication was successful, a report would not be sent.
  • 1 requests a report if any authentication check fails.
  • d requests a report if a DKIM check fails.
  • s requests a report if an SPF check fails.

rf specifies the format used for failure (forensic) reports. The available options are:

  • afrf uses the ‘Authentication Failure Reporting Format’ as defined by RFC 6591.
  • iodef uses the ‘Incident Object Description Exchange Format’ as defined by RFC 5070 & RFC 6685.

Please note that you will need to add a DMARC record like the one for example.com, for any other domain that you host on your server: secondsite.net, thirdsite.info, fourthsite.org, etc.

Regarding subdomains, it should be noted that by default they inherit the DMARC policy of the parent domain. Therefore, if the parent domain has the DMARC policy p=reject and no subdomains policy (sp=) is mentioned, and the subdomains don’t have their own DMARC records, the subdomains will inherit the p=reject policy of the parent domain. If the parent domain has both a p tag and an sp tag specified, the sp tag can be different from the p tag (like p=quarantine; sp=reject;), and the sp tag will apply to all the subdomains that don’t have their own DMARC record. If you have a subdomain, like forum.example.com, for which you want a different DMARC policy than that of the parent domain, or different from the one specified by the sp tag of the parent domain, you can add a separate DMARC record for that subdomain, with its own p tag. That p tag will override the sp tag and the p tag of the parent domain and will look like this:

_dmarc.forum     IN     TXT    "v=DMARC1; p=reject; adkim=s; aspf=s; rua=mailto:forum_admin@forum.example.com"

Please note that there is no need to specify the parent domain part after the subdomain part (like this: _dmarc.forum.example.com.) because BIND will add the parent domain automatically. Therefore, _dmarc stands for _dmarc.example.com. and _dmarc.forum stands for _dmarc.forum.example.com.

Also, the DMARC records for subdomains shouldn’t contain any sp tags, because they will be ignored. If you need a specific DMARC policy for the subdomain of a subdomain, like marketing.forum.example.com, you should add a separate DMARC record with its own p tag for that subdomain of a subdomain.

If you use Hurricane Electric’s DNS services, to add a DMARC record for example.com, click on ‘New TXT’, then fill the fields as follows:

19.15.1. Install OpenDMARC


Once you have set up correct DMARC records for your domains and subdomains, the mail servers that will receive emails from your server will know how to treat them. However, to be able to evaluate the SPF and DKIM records associated with the emails that your mail server receives, and take action accordingly, and to be able to send DMARC reports to whomever requires them, you will need to install and configure OpenDMARC, a free and open source software implementation of the DMARC specification. To install OpenDMARC run:

apt-get install opendmarc

During installation you will be asked if you want to configure the database backend for OpenDMARC:

You will configure it later manually, using the configuration file stored in /etc/dbconfig-common, so, choose ‘No’ using the ‘Tab’ key, then press ‘Enter’ to continue the installation.

After it gets installed, you can check that it’s running with:

systemctl status opendmarc

Next, enable the opendmarc service, so that it starts at system startup:

systemctl enable opendmarc

Make a copy of the original configuration file:

cp /etc/opendmarc.conf /etc/opendmarc.conf_orig

Open the configuation file for editing:

nano /etc/opendmarc.conf

Add/edit the following parameters in this file, to make them look like this:

AuthservID OpenDMARC-mail.example.com
PidFile /run/opendmarc/opendmarc.pid
PublicSuffixList /usr/share/publicsuffix/public_suffix_list.dat
RejectFailures true
IgnoreAuthenticatedClients true
SPFIgnoreResults true
SPFSelfValidate true
IgnoreHosts /etc/opendmarc/ignore.hosts
HistoryFile /run/opendmarc/opendmarc.dat
DomainWhitelistFile /etc/opendmarc/whitelist.domains
Socket local:/run/opendmarc/opendmarc.sock
Syslog true
TrustedAuthservIDs mail.example.com
UMask 0002
UserID opendmarc:opendmarc

Replace mail.example.com with your hostname. The /run/opendmarc/opendmarc.dat file will be the file containing the data used to generate the aggregate reports. All the other parameters in this file should be commented out.

Next, create the /etc/opendmarc directory:

mkdir /etc/opendmarc

Create the /etc/opendmarc/ignore.hosts file that will contain a list of hosts specified by their IP address, IP address range or domain, that will be ignored for purposes of DMARC validation. It will contain at least the server’s own IP addresses:

nano /etc/opendmarc/ignore.hosts

Add the following lines inside this file:

127.0.0.1
::1
123.123.123.123
2b03:8df0:f401:b5e2::/64
2b03:8df0:708::a2b4:ef4d

Replace 123.123.123.123 with the IPv4 address of your server, replace 2b03:8df0:f401:b5e2::/64 with the IPv6 subnet allocated by your hosting provider to your server, if such a subnet has been allocated, and replace 2b03:8df0:708::a2b4:ef4d with your primary IPv6 address.

Then, create the /etc/opendmarc/whitelist.domains file:

nano /etc/opendmarc/whitelist.domains

This will be the file containing the domains for which ARC signature headers will be trusted, if they also pass ARC chain verification, as described in the next chapter. Therefore, add the following domains in this file:

google.com
gmail.com
googlegroups.com
messagingengine.com
pobox.com
topicbox.com
umich.edu
fastmail.com
fastmail.fm
one.com
securemx.jp
zone.eu

Set the right ownership for the /etc/opendmarc directory and its content:

chown -R opendmarc:opendmarc /etc/opendmarc

Add the user postfix to the opendmarc group, so that Postfix can access the OpenDMARC’s socket:

adduser postfix opendmarc

Restart the opendmarc service, to apply the changes:

systemctl restart opendmarc

Next, to configure Postfix to connect to OpenDMARC, open the /etc/postfix/main.cf file:

nano /etc/postfix/main.cf

Edit the smtpd_milters and the non_smtpd_milters parameter, to make them look like this:

smtpd_milters = unix:/run/opendkim/opendkim.sock unix:/run/opendmarc/opendmarc.sock
non_smtpd_milters = unix:/run/opendkim/opendkim.sock unix:/run/opendmarc/opendmarc.sock

This means that you specify the OpenDMARC socket path right after the OpenDKIM socket path. Restart Postfix, to apply the changes:

systemctl restart postfix

At this moment you can test your DMARC configuration by sending an email from one of your other email accounts to an email account on the server that you have just configured. When you’ll look at the raw source code of the received email (in Thunderbird you can do this by clicking ‘More’ on the bar above the message, then clicking ‘View Source’), you should see the following header:

Authentication-Results: OpenDMARC-mail.example.com; dmarc=pass (p=reject dis=none) header.from=somedomain.org

The next step is to configure the database backend used by OpenDMARC to store the data needed to send aggregate reports:

nano /etc/dbconfig-common/opendmarc.conf

Edit the following parameters to make them look like this:

dbc_install='true'
dbc_upgrade='true'
dbc_remove=''
dbc_dbtype='mysql'
dbc_dbuser='opendmarc'
dbc_dbpass='strongpassword'
dbc_dballow='localhost'
dbc_dbserver='localhost'
dbc_dbport=''
dbc_dbname='opendmarc'
dbc_dbadmin='phpmyadminuser'
dbc_basepath=''
dbc_ssl=''
dbc_authmethod_admin=''
dbc_authmethod_user=''

Replace strongpassword with a strong password of your choice and replace phpmyadminuser with the user that you use to log in to phpMyAdmin. This is different from the user root, which shouldn’t be allowed to log in to phpMyAdmin, as explained earlier.

Once you set the parameters mentioned above in the /etc/dbconfig-common/opendmarc.conf file, when running dpkg-reconfigure opendmarc, the dbconfig-common tool will use the phpmyadminuser user to create the opendmarc database and the opendmarc user with privileges over the opendmarc database. However, it will look for phpmyadminuser‘s password in the /etc/mysql/debian.cnf file. If it won’t find it there, the database creation will fail. Therefore, open the /etc/mysql/debian.cnf file:

nano /etc/mysql/debian.cnf

Comment out the user line in the [client] block, to make it look like this:

[client]
host     = localhost
# user     = root

Then add the following 2 lines, right below # user = root:

user = phpmyadminuser
password = phpmdmnusrpassword

Replace phpmyadminuser with the user that you use to log in to phpMyAdmin and replace phpmdmnusrpassword with his password.

Next, run:

dpkg-reconfigure opendmarc

You will be asked a number of questions. Answer as follows (after selecting the correct option with the arrow keys or with ‘Tab’, press ‘Enter’ to choose the option):


“Reinstall the database for opendmarc?”
Yes

“Connection method for MySQL database of opendmarc:”
Unix socket

“Authentication plugin for MySQL database:”
default

“MySQL database name for opendmarc:”
opendmarc

“MySQL username for opendmarc:”
opendmarc@localhost

“MySQL application password for opendmarc:”
here enter the password specified by the dbc_dbpass parameter, in the /etc/dbconfig-common/opendmarc.conf file (you can copy and paste it using your mouse)

“Password confirmation:”
here paste again the password from above

“Name of the database’s administrative user:”
here you should have the name of the user that you use to log in to phpMyAdmin (it should be different from root)

“opendmarc.conf: A new version (/tmp/dbconfig-package-config.94jo4n) of configuration file /etc/dbconfig-common/opendmarc.conf is available, but the version installed currently has been locally modified.

What do you want to do about modified configuration file opendmarc.conf?”
keep the local version currently installed

“Password of the database’s administrative user:”
here enter the password of the user that you use to log in to phpMyAdmin


The database configuration process will then write a few lines about the steps taken and at the end it should inform you that the opendmarc database has been created successfully:

creating database opendmarc: success.
verifying database opendmarc exists: success.
populating database via sql...  done.
dbconfig-common: flushing administrative password

Since you no longer need the changes made to the /etc/mysql/debian.cnf file, open it:

nano /etc/mysql/debian.cnf

Remove the changes made earlier, to make the [client] section look like this:

[client]
host     = localhost
user     = root

Also, since the database and the associated user have been created, there is no need to store sensitive data, like the password of the opendmarc user, in the dbc_dbpass parameter, in the /etc/dbconfig-common/opendmarc.conf file. So, open the file:

nano /etc/dbconfig-common/opendmarc.conf

Change the dbc_install parameter to false, since you no longer need to configure the database with dbconfig-common, copy the password from the dbc_dbpass parameter to a safe location, since you will need it later, then remove it, and remove also the name of the administrative user from the dbc_dbadmin parameter:

dbc_install='false'

dbc_dbpass=''

dbc_dbadmin=''

Make the same changes in the /etc/dbconfig-common/opendmarc.conf.ucf-dist file.

Next, you will need a shell script to import the data from the /run/opendmarc/opendmarc.dat file to the newly created database and to send the aggregate reports. First create the /etc/opendmarc/reports directory:

mkdir /etc/opendmarc/reports

Create the dmarc_reports script inside the /etc/opendmarc/reports directory:

nano /etc/opendmarc/reports/dmarc_reports

Add the following content inside this file:

#!/bin/bash
# To use this script first enter the password for the database user 'opendmarc' in a file called 'pwdin.txt', then run the 'enc.sh' script to write the encrypted password in the 'tbout.txt' file and then delete the 'pwdin.txt' file by running: shred -u pwdin.txt

DB_SERVER='localhost'
DB_USER='opendmarc'
TFRFL=$(</etc/opendmarc/reports/nbkin.txt)
TBOUT=$(</etc/opendmarc/reports/tbout.txt)
DB_PASS=$(echo "${TBOUT}" | openssl enc -d -des3 -base64 -pbkdf2 -pass pass:${TFRFL})
DB_NAME='opendmarc'
WORK_DIR='/run/opendmarc'
REPORT_EMAIL='dmarc-reporting@mail.example.com'
REPORT_ORG='mail.example.com'

mv -f ${WORK_DIR}/opendmarc.dat ${WORK_DIR}/opendmarc_import.dat
cat /dev/null > ${WORK_DIR}/opendmarc.dat
chown opendmarc:opendmarc ${WORK_DIR}/opendmarc.dat
chown opendmarc:opendmarc ${WORK_DIR}/opendmarc_import.dat

/usr/sbin/opendmarc-import --dbhost=${DB_SERVER} --dbuser=${DB_USER} --dbpasswd=${DB_PASS} --dbname=${DB_NAME} --verbose < ${WORK_DIR}/opendmarc_import.dat
/usr/sbin/opendmarc-reports-mod --dbhost=${DB_SERVER} --dbuser=${DB_USER} --dbpasswd=${DB_PASS} --dbname=${DB_NAME} --verbose --interval=86400 --report-email $REPORT_EMAIL --report-org $REPORT_ORG
/usr/sbin/opendmarc-expire --dbhost=${DB_SERVER} --dbuser=${DB_USER} --dbpasswd=${DB_PASS} --dbname=${DB_NAME} --verbose

Replace dmarc-reporting@mail.example.com with a real email address on your server, created using Postfix Admin. OpenDMARC will mention it in the From field of the aggregate reports and the receivers of your reports will be able to send replies if they ever want to. Replace mail.example.com with the hostname of your server.

To avoid storing the password of the opendmarc user as plaintext in the newly created script, you can encrypt it using openssl as explained below. Indeed, this implies storing in a file the password used by openssl to encrypt the opendmarc user’s password, but it’s still better than entering the latter as plaintext in the dmarc_reports script.

First navigate to the /etc/opendmarc/reports directory:

cd /etc/opendmarc/reports

Here create a simple script to encrypt the opendmarc user’s password:

nano enc.sh

Add the following content inside this file:

#!/bin/bash

OGTXT=$(</etc/opendmarc/reports/pwdin.txt)
TFRFL=$(</etc/opendmarc/reports/nbkin.txt)
PROCPD=$(echo ${OGTXT} | openssl enc -e -des3 -base64 -pbkdf2 -pass pass:${TFRFL})
echo ${PROCPD} > tbout.txt
chmod 700 tbout.txt
chown opendmarc:opendmarc tbout.txt

Next, create the nbkin.txt file that will contain a password used by openssl to encrypt the opendmarc user’s password:

nano nbkin.txt

Enter a strong password inside this file, similar to the following:

RVoqYK17g05KupEYBUzfE4Sow5UocaTP58LHKJoydYUPsK9SL9N

Then create the pwdin.txt file, that will be used to store the opendmarc user’s password temporarily:

nano pwdin.txt

Add the real password of the opendmarc user in this file (this is the password that you entered earlier in the /etc/dbconfig-common/opendmarc.conf file, in the dbc_dbpass parameter).

Next, set the appropriate ownership and permissions for the newly created files:

cd /etc/opendmarc
chown -R opendmarc:opendmarc reports
chmod 755 reports
chmod 700 reports/*

Navigate to the /etc/opendmarc/reports directory:

cd /etc/opendmarc/reports

Run the enc.sh script to encrypt the opendmarc user’s password taken from the pwdin.txt file:

./enc.sh

This script will create the tbout.txt file, containing the encrypted password that will be used by the dmarc_reports script.

Since now you have the encrypted password in the tbout.txt file, you no longer need the plaintext password in the pwdin.txt file, so, shred the file by running:

shred -u pwdin.txt

Please note that the /usr/sbin/opendmarc-reports Perl script that is part of the default OpenDMARC installation is the component that is supposed to send the aggregate report emails by connecting to a SMTP server that runs on localhost. The problem is that it tries to send the emails without authenticating with the SMTP server and a well-configured SMTP server won’t allow this. If you invoke this script in the /etc/opendmarc/reports/dmarc_reports script described above, the sending of aggregate reports will fail. To overcome this problem, you should use a slightly modified version of the default /usr/sbin/opendmarc-reports script. First make a copy of the original file:

cp /usr/sbin/opendmarc-reports /usr/sbin/opendmarc-reports-mod

Open the new file for editing:

nano /usr/sbin/opendmarc-reports-mod

Search for the line if (!$smtp->mail($repemail) || and comment out the lines that send the email, adding below them the lines that use sendmail for the same purpose, like this:

#              if (!$smtp->mail($repemail) ||
#                  !$smtp->to($repdest) ||
#                  !$smtp->data() ||
#                  !$smtp->datasend($mailout) ||
#                  !$smtp->dataend())
#              {
#                            $smtpfail = 1;
#                            $smtpstatus = "failed to send";
#              }

               # use sendmail instead of Net::SMTP to send the report email
               open(MAIL, "|/usr/sbin/sendmail -t -f " . $repemail . "");
               if (!(print MAIL $mailout))
               {
                             $smtpfail = 1;
                             $smtpstatus = "failed to send";
               }
               close(MAIL);

The new script will use the /usr/sbin/sendmail utility instead of the Net::SMTP Perl module to send the emails. Since the /etc/opendmarc/reports/dmarc_reports script which will invoke the modified script, will be run by root, which is mentioned in /etc/postfix/main.cf in the authorized_submit_users list, the emails will be sent without requiring authentication. Also, if OpenDMARC is upgraded, it won’t affect the script that you have just modified, since it has a different name than the default opendmarc-reports.

By default, the aggregate reports sent by OpenDMARC will be sent as zip archives attached to messages similar to the following:

This is a DMARC aggregate report for example.com
generated at Wed Oct 23 05:25:02 2024

It’s recommended to make the content of these emails more explicit, to inform the receivers why they receive such messages, since some of them may not know and they may think such messages are spam. To replace the content from above with a more explicit content, while you have the /usr/sbin/opendmarc-reports-mod file open, search for these lines:

                        $mailout .= "This is a DMARC aggregate report for $domain\n";
                        $mailout .= "generated at " . localtime() . "\n";

and replace them with these lines:

                        $mailout .= "This is an aggregate DMARC report from " . $repdom . " for your domain " . $domain . " generated " . $datestr . ".\n";
                        $mailout .= "\n";
                        $mailout .= "Important information:\n";
                        $mailout .= "* DMARC (Domain-based Message Authentication, Reporting & Conformance) is a protocol used to protect your domain from unauthorized use, such as email spoofing and phishing. It is set up in the DNS records of your domain.\n";
                        $mailout .= "* You are receiving this report because your email address is included in the 'rua' tag of the DMARC record for the domain " . $domain . ". This tag mentions where aggregate reports should be sent.\n";
                        $mailout .= "* If you no longer wish to receive these reports, or if you would like them sent to a different email address, please update the 'rua' tag in the DMARC record of your domain " . $domain . ".\n";
                        $mailout .= "\n";
                        $mailout .= "For more information about DMARC, you can visit DMARC.org.\n";
                        $mailout .= "\n";
                        $mailout .= "For any feedback or questions regarding our service, you can reach us at " . $repemail . "\n";
                        $mailout .= "\n\n";
                        $mailout .= "Thank you,\n";
                        $mailout .= "" . $repdom . " Mail Security\n";

To test if the dmarc_reports script is set up correctly, run:

/etc/opendmarc/reports/dmarc_reports

If the output doesn’t show any errors or failures, it means that the script will function correctly.

The last step is to set up a cron job to run the script periodically:

crontab -e

Enter the following 2 lines at the end of the file, to run the script every day at 5:25 AM:

# Import DMARC data into the 'opendmarc' database and send DMARC reports (if needed) every day at 5:25 AM
25 5 * * * /etc/opendmarc/reports/dmarc_reports > /dev/null 2>&1

If you receive DMARC aggregate reports from different email providers and find it difficult to read the long XML files, you can use gratis online DMARC report analyzers, like this one. These analyzers parse the DMARC reports’ XML data into easy-to-read reports.

Please note that you may receive DMARC reports from google.com mentioning that the SPF or DKIM verification for some emails sent from your server to some gmail.com addresses failed, although your SPF and DKIM settings are not to be blamed. In these situations, if you know that you have configured SPF and DKIM correctly, as explained above, you should ignore such reported data because it can happen that some of your gmail.com recipients had their email accounts configured to forward incoming emails to different gmail.com accounts. While forwarding the emails, the sending IP or other elements of the original messsages were changed by Google’s servers to their own IP, domain, etc., which naturally caused the SPF and/or DKIM verification to fail.

19.16. Set up ARC (Authenticated Received Chain) for your domains


ARC (Authenticated Received Chain) is an email authentication method that allows mail servers to accept incoming emails even if they fail DKIM or SPF validation, in situations in which they have been modified in transit by legitimate intermediate servers that have forwarded them to their destination.

The problem with DMARC verification is that it assumes that emails are never changed on their way from sender to receiver. In reality, some intermediate mail servers may legitimately change messages in transit, such as when forwarding them (the intermediate server may change certain email headers), or when a mailing list server adds the list’s name to the Subject field, adds an ‘Unsubscribe’ link or a disclaimer in the message footer, etc. As a result, these legitimate emails appear to have been altered in transit and fail DKIM verification. Similarly, since relay servers or mailing list servers send emails from IPs different from the original IPs, these messages can fail SPF verification. If messages fail both DKIM and SPF, they fail DMARC and as a result they are either rejected or treated with suspicion.

To solve this problem of authentication failures caused by legitimate intermediaries, the ARC authentication method was introduced. This method gives intermediate servers a way to sign the original message’s validation results. In this way, when the receiving mail server finds that the incoming email fails SPF or DKIM validation but the original message passed the SPF and DKIM checks at the first intermediate mail server, and the only modifications were made by intermediaries trusted by the receiving mail server, the receiving server can choose to accept the email.

ARC uses three email headers:

ARC-Authentication-Results – specifies an instance number (eg: i=1 or i=2, etc.) and the results of SPF, DKIM and DMARC validation (eg: spf=pass, dkim=pass, dmarc=pass).

ARC-Seal – specifies an instance number (eg: i=1 or i=2, etc.), a DKIM-like signature of the previous ARC-Seal headers (eg: b=vfGrdNkAnfFQ5DKjC8wpNZOZVeW3q4gv2eCb5B7IB...), and the validity of the previous ARC entries (eg: cv=pass or cv=fail or cv=none).

ARC-Message-Signature – specifies an instance number (eg: i=1 or i=2, etc.) and a DKIM-like signature of the entire message content with the exception of the ARC-Seal headers (eg: b=d5Q0ev9yY7JPkp9vuI2P3giB7g6v5zmELvb9s2xZOEs9wyDqT2y...).

When an intermediate server modifies a message, to sign the modification, it follows these steps:

  1. Copies the content of the original Authentication-Results header into a new ARC-Authentication-Results header with the proper instance number and prepends it to the message.
  2. Generates the ARC-Message-Signature, including the instance number, and prepends it to the message.
  3. Calculates the ARC-Seal for the previous ARC-Seal headers, which validates the authenticity of the intermediate servers’ contribution to the ARC chain, and prepends it to the message.

When the recipient mail server receives an ARC-signed message, to perform the ARC validation, it follows these steps:

  1. Validates the chain of ARC-Seal headers (checks if there are any missing entries, that all ARC-Seal headers state that the previous ARC entries are valid, etc.)
  2. Validates the latest ARC-Message-Signature (found based on the instance number).

If the two validations are successful, the email passes ARC validation. If ARC validation is implemented using OpenARC together with OpenDMARC and Postfix as described in this guide, if the receiving server trusts all the intermediate servers in the ARC chain (the domains of the intermediate servers are listed in the /etc/opendmarc/whitelist.domains file, mentioned in the /etc/opendmarc.conf file, as shown in the previous chapter), the server accepts the email even if it has failed DMARC.

To implement ARC signing for outgoing emails and ARC validation for incoming emails, you need to install OpenARC. For the time being, there is only an experimental OpenARC package in the Debian repositories and this package can’t be installed on Debian 12 stable because of unmet dependencies. Therefore, the best way to install OpenARC on Debian 12 is to compile, install and configure it as explained below.

First create the openarc directory inside /srv/scripts and switch to it:

mkdir -p /srv/scripts/openarc
cd /srv/scripts/openarc

Update the package lists:

apt-get update

Next, run commands like:

dpkg --get-selections | grep packagename

to verify that the following packages are installed: debhelper, libbsd-dev, libjansson-dev, libmilter-dev, libssl-dev, libidn2-dev and pkgconf. If they are not, install them with:

apt-get install packagename

While in the /srv/scripts/openarc directory, download OpenARC from the flowerysong repository, which contains many improvements that haven’t been included in the official OpenARC repository. To do so, navigate to the Releases page of the repository, in the latest version section right-click on the tar.gz archive and choose ‘Copy Link’ to copy the link to your clipboard. Then, use the copied link to download the package like this:

wget https://github.com/flowerysong/OpenARC/releases/download/v1.1.0/openarc-1.1.0.tar.gz

Extract the archive, remove it, rename the directory, set ownership for it and swith to it:

tar xf openarc-1.1.0.tar.gz
rm openarc-1.1.0.tar.gz
mv openarc-1.1.0 OpenARC
chown -R root:root OpenARC
cd OpenARC

Next, run the following commands:

autoreconf -fvi
./configure

If the ./configure command doesn’t output any errors, start the compilation process by running:

make

If the process ends without errors it means that the compilation has been successful and you can install the program with:

make install

After the installation you’ll have to add a new location to the /etc/ld.so.conf.d/x86_64-linux-gnu.conf file and then update the shared libraries cache by running the following commands:

echo "/usr/local/lib" >> /etc/ld.so.conf.d/x86_64-linux-gnu.conf
ldconfig

At this moment, if you run:

openarc -V

the output should be similar to the following:

openarc: OpenARC Filter v1.1.0
	Compiled with OpenSSL 3.0.14 4 Jun 2024
	SMFI_VERSION 0x1000001
	libmilter version 1.0.1
	libopenarc 1.1.0:

Next, create the openarc system group and the openarc system user without home directory and without shell access, like this:

addgroup --system openarc
adduser --system --no-create-home --ingroup openarc --shell /bin/false openarc

Copy the openarc binary to the /usr/sbin directory:

cp /srv/scripts/openarc/OpenARC/openarc/.libs/openarc /usr/sbin

Create the /run/openarc directory for the socket file and PID file, and set the proper ownership and permissions:

mkdir /run/openarc
chown openarc:openarc /run/openarc
chmod 750 /run/openarc

Create the /etc/openarc directory and set the correct ownership for it:

mkdir /etc/openarc
chown openarc:openarc /etc/openarc

Copy the /etc/opendkim/TrustedHosts file to /etc/openarc and set the correct ownership :

cp /etc/opendkim/TrustedHosts /etc/openarc
chown openarc:openarc /etc/openarc/TrustedHosts

Copy the /etc/opendkim/keys directory to /etc/openarc and set the right ownership for it:

cp -r /etc/opendkim/keys /etc/openarc
chown -R openarc:openarc /etc/openarc/keys

Copy the sample configuration file to /etc:

cp /srv/scripts/openarc/OpenARC/openarc/openarc.conf.sample /etc/openarc.conf

Open the configuration file for editing:

nano /etc/openarc.conf

Edit the following parameters, to make them look as follows:

AuthservID   example.com
Canonicalization   relaxed/simple
Domain   example.com
FinalReceiver   no
InternalHosts   /etc/openarc/TrustedHosts
KeyFile   /etc/openarc/keys/example.com.private
Mode   sv
OversignHeaders   From
PidFile   /run/openarc/openarc.pid
Selector   open-arc
SignatureAlgorithm   rsa-sha256
Socket   local:/run/openarc/openarc.sock
Syslog   Yes
UMask   0002
UserID   openarc:openarc

Replace example.com with the main domain hosted on your server. Replace open-arc with a selector of your choice. It can contain letters, numbers, dates, etc.

Please note that the KeyFile parameter has to be included in all situations, even if the explanatory note above it states: “Ignored if SigningTable and KeyTable are used.” Even if OpenDKIM uses a SigningTable and a KeyTable, the KeyFile parameter has to be specified as shown above, with its value set to /etc/openarc/keys/example.com.private, even if OpenARC will ARC-sign other domains existing on the server (secondsite.net, thirdsite.info, etc.). Otherwise, the openarc service will not start. Also, the Domain parameter specifies only the main domain, example.com, although OpenARC will sign many other domains hosted on the server.

Next, create the systemd service:

nano /etc/systemd/system/openarc.service

Add the following lines inside this file:

[Unit]
Description=OpenARC Authenticated Received Chain (ARC) Milter
Documentation=man:openarc(8) man:openarc.conf(5) https://openarc.org/
After=network-online.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/run/openarc/openarc.pid
ExecStart=/usr/sbin/openarc -c /etc/openarc.conf
ExecReload=/bin/kill -USR1 $MAINPID
Restart=on-failure
User=openarc
Group=openarc

[Install]
WantedBy=multi-user.target

Apply the changes, enable and start the openarc service and check if it’s running:

systemctl daemon-reload
systemctl enable openarc
systemctl start openarc
systemctl status openarc

To connect OpenARC to Postfix, open the /etc/postfix/main.cf file:

nano /etc/postfix/main.cf

Edit the smtpd_milters and the non_smtpd_milters parameter, to make them look like this:

smtpd_milters = unix:/run/opendkim/opendkim.sock unix:/run/openarc/openarc.sock unix:/run/opendmarc/opendmarc.sock
non_smtpd_milters = unix:/run/opendkim/opendkim.sock unix:/run/openarc/openarc.sock unix:/run/opendmarc/opendmarc.sock

Add the user postfix to the openarc group:

adduser postfix openarc

Restart Postfix:

systemctl restart postfix

Add a TXT record to the DNS settings for example.com. If you use BIND, the ARC record for the example.com domain, as it will appear in the BIND configuration file for example.com (/etc/bind/db.example.com) should look like this:

open-arc._domainkey   IN     TXT    "k=rsa;"  "p=DFICIjANBgkqhkiG9w05AQEFAAOCAQ8AMIIBCgKCAQEApTr64ZHLPFN5BNFXHK0RnaR5RPlGLl4DwPtuFfPEN+bJeHP4NTGHoQeR+Alxc038KWCeGD8zR9YNEJNn1bFK2odxwoKKqdXWshCBur2tqU4Vy+d8Wczt8CMxBOfimqC6w/LnC4gQ1ztlwknQy4MyyWcIlRkun4Ow0iRwrMJo6W1Tel1PWG1N+kmQDrTyQYO/EY2cFf0VBV5le"  "BF5Crp0bEg9sTXm5BxS0X/vsuinhgkK5G+6JJ1RPHw4nzqF5RXOIJNYziHd6P/IKy381KC0u9MIPtia5LN4y5XykzX5akBE02wbZMaN6/OrT5T4LF5gHmLvFNc7CWF9w1HSG6/3QIDAQAB"

Replace open-arc with your selector and replace the two long strings (separated by two empty spaces) specified after p=, with the corresponding strings from the DKIM record for example.com which looks like this:

default._domainkey    IN     TXT    "v=DKIM1; h=sha256; k=rsa; s=email;"  "p=DFICIjANBgkqhkiG9w05AQEFAAOCAQ8AMIIBCgKCAQEApTr64ZHLPFN5BNFXHK0RnaR5RPlGLl4DwPtuFfPEN+bJeHP4NTGHoQeR+Alxc038KWCeGD8zR9YNEJNn1bFK2odxwoKKqdXWshCBur2tqU4Vy+d8Wczt8CMxBOfimqC6w/LnC4gQ1ztlwknQy4MyyWcIlRkun4Ow0iRwrMJo6W1Tel1PWG1N+kmQDrTyQYO/EY2cFf0VBV5le"  "BF5Crp0bEg9sTXm5BxS0X/vsuinhgkK5G+6JJ1RPHw4nzqF5RXOIJNYziHd6P/IKy381KC0u9MIPtia5LN4y5XykzX5akBE02wbZMaN6/OrT5T4LF5gHmLvFNc7CWF9w1HSG6/3QIDAQAB"

This means that you will use the same cryptographic key for DKIM and ARC for each domain, which is allowed by the official ARC specifications.

Add a TXT record similar to the one for example.com for every other domain that you will use to send emails: secondsite.net, thirdsite.info, etc. If you are using BIND, the ARC record for secondsite.net will be added to the /etc/bind/db.secondsite.net file, for thirdsite.info to the /etc/bind/db.thirdsite.info file, etc.

Regarding subdomains, you should add an ARC record for each subdomain that you will use to send emails. For example, if you are using BIND, the ARC record for the subdomain mail.example.com will be similar to the one for example.com except that it will start with open-arc._domainkey.mail instead of open-arc._domainkey (taking into account that BIND adds the domain part (example.com) after the subdomain part (mail) automatically, like this: open-arc._domainkey.mail.example.com.). Also, the two long strings after p= will be identical with the ones from the DKIM record for mail.example.com.

Once you’ve configured ARC as explained above, you can test it by sending a message with a content of your choice to echo@openarc.org. After a few minutes you will receive an automated response that should contain the following headers:

ARC-Seal: i=2; a=rsa-sha256; d=dahlem.somaf.de; s=arc1; t=1730654876;
	cv=pass; b=cfdfGrdNuAnfFQoDKjC88pKZOZVeW39gv2ePb35BIXkMYdINBT+syw/AqeLhPZHSXHOy+HFkAVao9e3/E0sSV1qod6egZov2S6DCLSipxYBR54pZtrwTGCo/BbQvsrRrcuaD0BSAA4c8mQ+n52UzlhejYjVSc+8axROuWScaj48Z2uuK6QlOVpKoOky7qWOlggVwBJF3HVb0njLmBday8pI/bNIT4SHDJfb/E1ZZgDyTLcMwKqFiu8AL3OgW7d0ysD6ysfMKHDuPICAGMbSC0rjc7UCAntsLZkuAFdc2r//NGqDklZw4eQeWxJ68mGEgN7MqFn8bLm+m/1H3CopjTBW==
ARC-Message-Signature: i=2; a=rsa-sha256; d=dahlem.somaf.de; s=arc1;
	t=1730654876; c=relaxed/relaxed;
	bh=5T2IqY3jOLpZqwUImaczqT9Q67IkWWZrOh/V4C8b5Rc=;
	h=X-purgate-ID:X-purgate-size:X-purgate:X-purgate:X-purgate-Ad:
	 X-purgate-type:ARC-Message-Signature:ARC-Authentication-Results:
	 DKIM-Filter:DKIM-Signature:Received:Content-Type:Message-ID:Date:
	 MIME-Version:Content-Language:To:From:Subject:Autocrypt; b=jK7CvyY7JjPp9vuI2P3giB36vmnELvb9s2xZOFs99yDqTd9/qAGeRRbAy8hgNUO8NPABoZ5VmvuReT+uBHjl+KsGANN7ecOjMK5Qi5sbUMZq0cDdg8DhkIkBhxGAMf6uJ7PB4sZ+wyVbblRBE6NUuF1SaYfhCfP4grlDlls/VApLfCzJIZI0jOsWyliAc+sOF4Hks3cdVAbwaY1Rq3ZXVlUh5/xqgRW0Z0Wm/kTg2/8EGpjXuSQV5HI7GPvq/5xrZqt3sH7M1b+uT50CiGz2YLvICcfXrGIlIYB9xcuXyqxg1lY81PIM4BTShIY1DZ5Bq19aQGl3tl9l213saAw/RB==
ARC-Authentication-Results: i=2; dahlem.somaf.de; dmarc=pass (p=reject dis=none) header.from=example.com; 
	spf=pass smtp.mailfrom=example.com; 
	arc=pass smtp.remote-ip=123.123.123.123; 
	dkim=pass (2048-bit key; secure) header.d=example.com header.i=@example.com header.a=rsa-sha256 header.s=default header.b=V0+MZKRI

Since you have installed OpenARC ‘manually’, if you ever want to uninstall it completely, run the following commands:

systemctl stop openarc
systemctl disable openarc
cd /srv/scripts/openarc/OpenARC
make uninstall

Afterwards, check if any of the following directories and files that were created by OpenARC or by you while installing it still exist, and if they exist, delete them:

/usr/local/lib/libopenarc.a
/usr/local/lib/libopenarc.la
/usr/local/lib/libopenarc.so
/usr/local/lib/libopenarc.so.0
/usr/local/lib/libopenarc.so.0.0.0
/usr/local/lib/pkgconfig/openarc.pc
/usr/local/share/doc/openarc
/etc/openarc.conf
/etc/openarc
/run/openarc
/usr/sbin/openarc
/etc/systemd/system/openarc.service

Then open the /etc/postfix/main.cf file and remove the socket file path (unix:/run/openarc/openarc.sock) from both smtpd_milters and non_smtpd_milters.

Then run:

systemctl daemon-reload
systemctl restart postfix

19.17. Set up DANE (DNS-based Authentication of Named Entities) for your domains

DANE (DNS-based Authentication of Named Entities) is a protocol that allows TLS certificates to be bound to domain names that use DNSSEC (Domain Name System Security Extensions). Organizations can publish a hash of their legitimate TLS certificates in their DNS records, allowing connecting servers to verify that the certificate information transmitted over SMTP or HTTPS matches what is published in their DNS records. Although DANE has various possible applications, it is mainly used to secure data transfer between SMTP servers.

STARTTLS allows SMTP servers to notify connecting servers that they support TLS encrypted communication, thus offering other servers the opportunity to upgrade their connections to TLS encrypted sessions. However, upgrading to TLS encrypted communication is dependent on the connecting server that can decide to use this option or not. If the connecting server decides not to use TLS encryption, the data transfer is done in plain text. This is why STARTTLS is often called “opportunistic TLS”.

Since the STARTTLS negotiation happens in plain text, an attacker can remove STARTTLS commands, so that the encryption negotiation data sent from one server never reaches the other. In this situation both servers take the invalid or unexpected responses as indication that the other party doesn’t support STARTTLS, and they default to plain text mail transfer. This is a type of “man-in-the-middle (MITM) attack” that represents a real vulnerability for mail servers that rely on STARTTLS for encrypted mail transfer.

To solve this issue, DANE for SMTP establishes a way to verify the identity of the recipient SMTP server before starting to transfer an email over a STARTTLS encrypted layer. To achieve this, it uses information retrieved from the recipients’ DNS zone, which is DNSSEC-signed. The DNS records used for DANE are called TLSA resource records. They signal TLS support and offer the means by which sending SMTP servers can authenticate legitimate recipient SMTP servers. More specifically, the sending server compares the hash of the recipient server’s TLS certificate with the hash retrieved from the TLSA record. If the two hashes match, the connection is considered trustworthy.

This means that a sending SMTP server that supports DNSSEC, STARTTLS and DANE, is ‘required’ to encrypt its connections with a receiving server that has DNSSEC-signed TLSA records for the receiving domain. However, if any of those elements is missing, the two servers fall back to TLS without DANE or even to plain text transport. The system works this way to ensure that message delivery can proceed even in the worst case scenario.

DANE is strictly dependent on DNSSEC. If you don’t have DNSSEC enabled for your DNS servers, you cannot use DANE. However, if you cannot enable DNSSEC for your DNS servers, you can use an alternative method to secure your SMTP server-to-SMTP server connections by using the MTA-STS standard, as explained in the next chapter. The MTA-STS method plays a similar role to DANE for SMTP, although it’s less secure, as explained here.

mail.example.com is the subdomain whose TLS certificates (specified in /etc/postfix/main.cf) are used by your SMTP server to encrypt data transfer to and from other SMTP servers. To implement DANE, first you have to change the way Certbot renews the Let’s Encrypt TLS certificates for mail.example.com. Let’s Encrypt certificates are automatically renewed every 90 days. The standard renewal process involves generating a new private key with every renewal and thus a new public key. This would result in a new key hash that would appear in the TLSA record, which means you would have to update your TLSA record on each renewal, otherwise the key hash specified there would be wrong and your mail server will not be trusted by any other mail server. This is why you should configure Certbot to reuse your private key so that the public key hash value doesn’t change and your TLSA record remains valid across automated certificate renewals.

The idea that reusing the same private key during automated TLS certificate renewals may be unsafe can be ignored because you will renew your private key manually once every two years or once a year as explained below. It’s just that between two manual renewals of the TLS certificate for mail.example.com (using a new private key and updating the TLSA record), you will not have to worry that the automated Let’s Encrypt renewals every 90 days will render your TLSA record invalid and break DANE verification.

The most practical and error proof method to use Let’s Encrypt certificates with DANE is to configure Let’s Encrypt automated renewals for mail.example.com so that they use the same private key. Then, once every two years or once a year, you manually force a Let’s Encrypt certificate renewal with a new private key and update your TLSA record accordingly.

To force Certbot to use the same private key during automated renewals of TLS certificates for mail.example.com, open the renewal configuration file for mail.example.com:

nano /etc/letsencrypt/renewal/mail.example.com.conf

Right below [renewalparams] add the following line:

reuse_key = True

Then, to test if Certbot will use the same private key when renewing the certificate for mail.example.com, run:

certbot renew --cert-name mail.example.com -v --dry-run

Since you added the --dry-run option, the command won’t actually renew the certificate. However, you should see the following line in the output:

Reusing existing private key from /etc/letsencrypt/live/mail.example.com/privkey.pem.

Next, navigate to /srv/scripts:

cd /srv/scripts

Generate the hash from the public key of the current TLS certificate of mail.example.com:

openssl x509 -in /etc/letsencrypt/live/mail.example.com/cert.pem -noout -pubkey | openssl pkey -pubin -outform DER | openssl sha256 > initial_cert_hash

Then renew the Let’s Encrypt certificate for mail.example.com even if it hasn’t expired:

certbot renew --cert-name mail.example.com --force-renewal

Next, generate the hash from the public key of the new certificate:

openssl x509 -in /etc/letsencrypt/live/mail.example.com/cert.pem -noout -pubkey | openssl pkey -pubin -outform DER | openssl sha256 > renewed_cert_hash

Both files, initial_cert_hash and renewed_cert_hash, will contain one line similar to this:

SHA2-256(stdin)= f2751f2251291a5cdf75ae7e17d723fd191be519414fbe92fd6a5e3a275d6a2d

Compare the two hashes with:

diff initial_cert_hash renewed_cert_hash && echo OK || echo error

If the output is OK, it means that the two hashes are identical and this proves that during the renewal process Certbot will use the same private key and thus the hash obtained from the public key will remain the same. This means that the TLSA record used for DANE verification will remain valid accross automated certificate renewals.

The next step is to add the TLSA records in your DNS settings for example.com. If you are using BIND, the TLSA records specified in the /etc/bind/db.example.com file will look like this:

_25._tcp   IN   TLSA   3 1 1   f2751f2251291a5cdf75ae7e17d723fd191be519414fbe92fd6a5e3a275d6a2d
_25._tcp.mail   IN   TLSA   3 1 1   f2751f2251291a5cdf75ae7e17d723fd191be519414fbe92fd6a5e3a275d6a2d

Replace f2751f2251291a5cdf75ae7e17d723fd191be519414fbe92fd6a5e3a275d6a2d with the alphanumeric string that comes after the equal sign (=) in the /srv/scripts/renewed_cert_hash file that you have just created. BIND adds .example.com. automatically after _25._tcp and after _25._tcp.mail , so, there is no need to write _25._tcp.example.com. and _25._tcp.mail.example.com. at the beginning of the two records respectively, although that would be equally correct.

Each TLSA record shown above begins with the port number (25, because SMTP connections start on port 25), then specifies the protocol associated with the open port (tcp), then the domain name (mail.example.com), then, the data portion contains three numbers representing three fields: the usage field (3 = DANE-EE: Only match against the destination server’s certificate), the selector field (1 = SPKI: Use the certificate’s Subject Public Key Info (SPKI) field) and the matching type field (1 = SHA-256: The data in the TSLA record is an SHA-256 hash of the SPKI) and then the hash obtained from the public key of the TLS certificate (more specifically the SHA-256 hash of the Subject Public Key Info (SPKI) field of the certificate). The three mentioned fields can have other values, as explained in RFC6698.

Add TLSA records similar to the ones shown above, with the same hash, for each domain or subdomain that you use to send emails. If you use BIND, the TLSA record for secondsite.net will be added to the /etc/bind/db.secondsite.net file, for thirdsite.info the TLSA record will be added to the /etc/bind/db.thirdsite.info file, etc.

You can also add TXT records for SMTP TLS Reporting (shortened to TLS-RPT), to specify the email address where you want to receive periodic aggregate reports about TLS connectivity issues, problems related to expired TLS certificates, etc. The record for example.com, as it should appear in the BIND configuration file /etc/bind/db.example.com, will look like this:

_smtp._tls    IN    TXT    "v=TLSRPTv1; rua=mailto:smtptlsreports@example.com"

Replace smtptlsreports@example.com with the email address where you want to receive the periodic TLS reports. You can specify multiple email addresses separated by commas, like this: rua=mailto:tom@domain1.com,mailto:jerry@domain2.com,mailto:oscar@domain3.com; If you want to disable receiving TLS reports, remove the entire TXT record.

To configure Postfix for DANE open the /etc/postfix/main.cf file:

nano /etc/postfix/main.cf

Right below the smtpd_tls_security_level = may line edit or add the following three parameters:

smtp_dns_support_level = dnssec
smtp_tls_security_level = dane
smtp_host_lookup = dns

Restart Postfix:

systemctl restart postfix

To check your DANE implementation you can use an online DANE validation service (enter the following data: Host / Domain: example.com, Service: SMTP, Port: 25, Protocol: tcp).

19.17.1. Renewing the private key used for generating Let’s Encrypt certificates for mail.example.com


As mentioned in the Maintenance Steps chapter, once every two years, after upgrading the operating system (or more frequently if you prefer, like once a year), change the private key used for generating Let’s Encrypt certificates for mail.example.com, knowing that these certificates are used during DANE verification. First open the /etc/letsencrypt/renewal/mail.example.com.conf file:

nano /etc/letsencrypt/renewal/mail.example.com.conf

Replace reuse_key = True with reuse_key = False .

Then get a new certificate for mail.example.com, even if the old certificate is still valid:

certbot renew --cert-name mail.example.com --force-renewal

Next, navigate to /srv/scripts:

cd /srv/scripts

Generate the hash from the public key of the new certificate:

openssl x509 -in /etc/letsencrypt/live/mail.example.com/cert.pem -noout -pubkey | openssl pkey -pubin -outform DER | openssl sha256 > renewed_cert_hash

Then take the new hash, specified after the equal sign in the renewed_cert_hash file, and use it to replace the old hash in the TLSA records of example.com and mail.example.com and of all the other domains and subdomains that you use to send emails, in your DNS settings.

Open again the /etc/letsencrypt/renewal/mail.example.com.conf file:

nano /etc/letsencrypt/renewal/mail.example.com.conf

Replace again reuse_key = False with reuse_key = True , to ensure that the same private key will be used during automated certificate renewals in the future, so that the hash in the TLSA records will remain valid.

19.18. MTA-STS (Mail Transfer Agent – Strict Transport Security)


MTA-STS (Mail Transfer Agent – Strict Transport Security) is a standard for secure email delivery that allows a receiving server to inform a sending server that it accepts secure email delivery using SMTP over TLS, and that emails should not be delivered over insecure SMTP connections, to mitigate SMTP downgrade attacks. It also allows a MTA-STS capable sending server to verify that the domain name on the TLS certificate of the receiving server matches the domain in the MTA-STS policy file that is served over HTTPS by the receiving party, in order to hinder DNS spoofing attacks.

To enable MTA-STS, the receiving servers must announce that they support MTA-STS in their DNS records and publish a MTA-STS policy in a file served over HTTPS by a web server. This will allow the sending mail servers that support MTA-STS to know that they should only use TLS (1.2 or higher) connections when sending emails to the receiving mail servers. MTA-STS requires the use of STARTTLS for emails to be successfully sent and received. If a sending mail server doesn’t perform MTA-STS validations, emails will still be delivered, and TLS will be used if the sending server chooses to use it and the receiving server supports it.

Set up MTA-STS for your domains only if you don’t have access to DNS services with DNSSEC enabled and as a result you cannot set up DANE (DNS-based Authentication of Named Entities) for your domains. Although in principle DANE and MTA-STS can be both enabled for a mail server, in practice, for a sending mail server, when using Postfix and postfix-mta-sts-resolver (a daemon that allows Postfix to perform MTA-STS verification for outgoing connections), it turns out that MTA-STS overrides DANE, which is against the RFC8461, 2 specification. A Postfix developer advices that when enabling both DANE and MTA-STS for a server, the few domains that are known to support both MTA-STS & DANE should be routed to a dedicated SMTP transport that uses postfix-mta-sts-resolver, while DANE should be used for the other domains. Taking into account that DANE is superior to MTA-STS as explained here, that MTA-STS has some weaknesses documented in Chapter 10 of its own specification description, to avoid unnecessary complications that can generate issues in time, it’s recommended to enable either DANE or MTA-STS for your mail server: implement DANE if DNSSEC is available (as explained in the previous chapter) or implement MTA-STS (as explained in this chapter) if DNSSEC is not available and as a result, DANE can’t be used.

To implement MTA-STS, first add a TXT record starting with _mta-sts to the DNS records for the main domain hosted on your server, example.com. If you are using BIND, the record for example.com, as it should appear in the BIND configuration file /etc/bind/db.example.com, will look like this:

_mta-sts    IN    TXT   "v=STSv1; id=1731721171422488821;"

Replace 1731721171422488821 with an alphanumeric string of your choice with a maximum length of 32 characters. When the MTA-STS policy is changed, the value of the id in this TXT record must be updated to a value that is new and unique, signaling to the SMTP servers that a new policy has been set. It is common to use a timestamp for the id value, such as the timestamp given by the command: date +%s%N (the number of nanoseconds since the beginning of the UNIX epoch (January 1, 1970)). Please note that the first part of the TXT record from above, _mta-sts , is equivalent to _mta-sts.example.com. , since BIND adds the domain automatically after the keyword _mta-sts .

You can also add a second TXT record for SMTP TLS Reporting (shortened to TLS-RPT), to specify the email address where you will receive periodic aggregate reports about TLS connectivity issues, violations of your MTA-STS policy, problems related to expired TLS certificates, etc. The record for example.com, as it should appear in the BIND configuration file /etc/bind/db.example.com, will look like this:

_smtp._tls    IN    TXT    "v=TLSRPTv1; rua=mailto:smtptlsreports@example.com"

Replace smtptlsreports@example.com with the email address where you want to receive the periodic TLS reports. You can specify multiple email addresses separated by commas, like this: rua=mailto:tom@domain1.com,mailto:jerry@domain2.com,mailto:oscar@domain3.com; If you want to disable receiving TLS reports, remove the entire record.

Add a pair of TXT records similar to the records for example.com shown above for every other domain that you will use to send emails: secondsite.net, thirdsite.info, etc. If you are using BIND, the MTA-STS records for secondsite.net will be added to the /etc/bind/db.secondsite.net file, for thirdsite.info to the /etc/bind/db.thirdsite.info file, etc.

Regarding subdomains, you should add a pair of MTA-STS records for each subdomain that you use to send emails. For example, you will want to add MTA-STS records for the mail.example.com subdomain . If you are using BIND, the MTA-STS records for the subdomain mail.example.com will look like this:

_mta-sts.mail    IN    TXT   "v=STSv1; id=11731727864047013003;"
_smtp._tls.mail    IN    TXT    "v=TLSRPTv1; rua=mailto:tlsrpt@mail.example.com"

Next, write the MTA-STS policy for the domain example.com. It should be similar to the following:

version: STSv1
mode: enforce
mx: mail.example.com
max_age: 604800

Parameters description follows:

version – mentions the MTA-STS standard version. It must contain the value STSv1.

mode – specifies the TLS requirement policy. Besides enforce, it can also have the value none, which means that MTA-STS is disabled for this domain, and the value testing which means that MTA-STS is used, but the sending SMTP server is allowed to fall back to plain text communication in case of TLS failure.

mx – specifies the MX host for this domain. It has to match the MX record from the DNS settings for this domain. Multiple hosts can be mentioned, one per line. They can be specified either as FQDN (mx: mail.example.com), or using a wildcard (mx: *.example.com). The wildcard character can replace only the leftmost label.

max_age – represents the time in seconds that a SMTP server is allowed to cache the policy. It can be set to a value even higher than 604800 (one week) but it’s not recommended to be set to a lower value.

Then replace every newline character from the MTA-STS policy shown above, with \r\n, to make it look like this:

version: STSv1\r\nmode: enforce\r\nmx: mail.example.com\r\nmax_age: 604800\r\n

Next, create the A and AAAA DNS records for the subdomain mta-sts.example.com . They should be identical with the A and AAAA records for mail.example.com, with the difference that instead of mail you will enter mta-sts .

Then open the /etc/nginx/sites-enabled/0-conf configuration file:

nano /etc/nginx/sites-enabled/0-conf

Add the following temporary block at the bottom of the file:

server {
    listen 80;
    listen [::]:80;
    server_name mta-sts.example.com;

    location /.well-known/acme-challenge {
        root /var/www;
    }
}

Replace example.com with your own domain. Reload Nginx:

systemctl reload nginx

Get the Let’s Encrypt TLS certificate for the mta-sts.example.com subdomain:

certbot certonly --agree-tos --webroot -w /var/www/ -d mta-sts.example.com

Open again the /etc/nginx/sites-enabled/0-conf configuration file:

nano /etc/nginx/sites-enabled/0-conf

Replace the temporary block mentioned above with the following blocks:

server {
    listen  80;
    listen [::]:80;
    server_name mta-sts.example.com;
    return  301 https://mta-sts.example.com$request_uri;
}

server {
    listen      443 ssl http2;
    listen [::]:443 ssl http2;
    server_name mta-sts.example.com;

    ssl_certificate /etc/letsencrypt/live/mta-sts.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mta-sts.example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/mta-sts.example.com/chain.pem;
    ssl_dhparam /etc/nginx/ssl/dhparam.pem;
    ssl_session_timeout 4h;
    ssl_session_cache shared:SSL:40m;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_stapling on;
    ssl_stapling_verify on;

    add_header Strict-Transport-Security "max-age=63072000" always;
    add_header X-Frame-Options SAMEORIGIN;
    add_header X-Content-Type-Options nosniff;
    add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive";

    location = /.well-known/mta-sts.txt {
       default_type text/plain;
       return 200 "version: STSv1\r\nmode: enforce\r\nmx: mail.example.com\r\nmax_age: 604800\r\n";
    }
}

Replace example.com with your domain. Please note that instead of creating a real mta-sts.txt policy file, saving it to a specific location and then specifying that location in the Nginx server block, you configured Nginx so that when a request is made for https://mta-sts.example.com/.well-known/mta-sts.txt the web server just serves the plain text content that would have been included in that file if it had existed.

Reload Nginx:

systemctl reload nginx

Regarding subdomains, you should write a MTA-STS policy identical with the one for example.com shown above for every subdomain that you will use to send emails. For example, you will want to write a MTA-STS policy for the mail.example.com subdomain. It will be:

version: STSv1
mode: enforce
mx: mail.example.com
max_age: 604800

Then, you will have to add A and AAAA records for the mta-sts.mail.example.com subdomain, then obtain a Let’s Encrypt TLS certificate for that subdomain, and then configure the two Nginx server blocks described above for mta-sts.mail.example.com, by replacing mta-sts.example.com from the two server blocks shown above with mta-sts.mail.example.com .

You can check your MTA-STS record and policy by entering your domain in the domain field on this page and clicking the ‘Check MTA-STS’ button. Under ‘Record Status’ you should see ‘Valid’, under ‘Policy Validation’ you should also see ‘Valid’ and under ‘Policy Mode’ you should see ‘Enforce’.

Up to this point you’ve configured MTA-STS for incoming connections. To enable it for outgoing connections you’ll have to install the postfix-mta-sts-resolver package. Run:

apt-get install postfix-mta-sts-resolver

The package will be installed together with all the required dependencies and the /lib/systemd/system/postfix-mta-sts-resolver.service systemd service will be enabled and started automatically.

To configure Postfix to use the resolver open the /etc/postfix/main.cf file:

nano /etc/postfix/main.cf

Add the following two lines right below the line that starts with smtpd_tls_CAfile = :

smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
smtp_tls_policy_maps = socketmap:inet:127.0.0.1:8461:postfix

Please note that smtpd_tls_CAfile and smtp_tls_CAfile are two different parameters. Restart Postfix:

systemctl restart postfix

Then, if you run:

postmap -q example.com socketmap:inet:127.0.0.1:8461:postfix

you should see:

secure match=mail.example.com

19.19. BIMI (Brand Indicators for Message Identification)


BIMI (Brand Indicators for Message Identification) is a specification that allows displaying brand logos next to authenticated emails. BIMI requires a valid DMARC record with a policy of either ‘reject’ or ‘quarantine’, a square logo in SVG Tiny P/S format, and a DNS TXT record for the domain used to send emails, indicating the location of the SVG file. The location URI of the logo must use the HTTPS transport.

The format of the BIMI record is the following:

default._bimi   IN   TXT   "v=BIMI1; l=https://www.example.com/path/to/logo.svg; a=https://www.example.com/path/to/certificate.pem"

where https://www.example.com/path/to/logo.svg indicates the location of the SVG file and https://www.example.com/path/to/certificate.pem indicates the location of a BIMI Evidence Document, which can be either a Verified Mark Certificate (VMC) or a Common Mark Certificate (CMC). VMCs and CMCs are cryptographic certificates issued by Mark Verifying Authorities (MVAs) such as DigiCert and Entrust at prices starting from $1188 / year after conducting a multi-stage verification of the company requesting the certificate, which includes verifying the physical address of the company, the existence of a publicly listed organization phone number, the authority and identity of the person requesting the certificate via a video call meeting, etc., as mentioned here and here. Unlike Verified Mark Certificates which require prior registration of the logo as a trademark (which entail additional costs, documents submitted to the registration authority and waiting time), the Common Mark Certificates require only the proof that the requestor has used the logo for at least 12 months. Obtaining a CMC instead of a VMC however is still an elaborate process, since the verification steps mentioned above are still required and since they involve human interaction, the process cannot be automated and will never be free of charge.

Indeed, the a= tag is optional, as mentioned in the BIMI draft specification. If the a= tag is not specified, it is assumed to have an empty value and if it has an empty value, the BIMI record is considered to be self-asserted. However, the whole point of BIMI is to offer email receivers a way to quickly verify that the emails they receive are coming from the legitimate senders. This implies that the sender’s logo should appear next to the received email only if the mailbox provider has automatically verified the BIMI record and found that the logo belonged indeed to the email sender, since the Mark Verifying Authority (DigiCert, etc.) certified that ownership. If there is no Verified Mark Certificate or Common Mark Certificate specified by the a= tag, to certify that the logo belongs to the sender, there is no guarantee that the sender didn’t use the logo of another company. Therefore, even if the BIMI specification allows an empty a= tag, a BIMI record with an empty a= tag goes against the rationale of BIMI.

Even if Yahoo Mail allows BIMI records with an empty a= tag, it will not show the sender’s logo, unless the sender domain has high reputation and is known to send bulk emails.

Another problem is that even after registering their logo as a trademark, buying a Verified Mark Certificate and adding a correct BIMI DNS record for their sending domain, a company can still have problems with having their logo displayed in the inbox of some email service providers like Gmail. This happens because these providers determine BIMI validity based not only on the correctness of the BIMI record and the existence of a VMC but also on spam sending history and reputation of the sending domain.

Therefore, even if BIMI seems an attractive standard that promotes the use of DMARC (and hence of SPF and DKIM on which DMARC is based), even if it promises to build trust in sending brands and increase email open rates, in reality BIMI fails its mission because it can be used either without a VMC/CMC, in which case it doesn’t prove anything and it can be easily abused, or with a VMC/CMC, in which case the sending company must go through an elaborate and costly process of verification which will not even guarantee that their logo will appear in their receivers’ inboxes and even when their logo will appear, it won’t guarantee an increased open rate. BIMI also fails its mission because the companies that invented it (which formed the so-called BIMI Group) didn’t want to understand that email receivers will always open emails without logos just as often as they will open emails with logos, knowing instinctively that the absence of a logo doesn’t guarantee that emails are fraudulent or unimportant. Like Organization Validation (OV) and Extended Validation (EV) TLS certificates, which are not worth buying unless you are a state institution, a bank or a very large retailer, since almost no one does the extra clicks to look at the type of TLS certificate a website uses, the Verified Mark Certificates and the Common Mark Certificates are also not worth buying, since, as mentioned, even when the BIMI logo is shown next to the email, this doesn’t really increase the receivers’ trust in the sender or email open rates. On the contrary, the BIMI logo feature can be abused and in such cases, quite ironically, it deceives email receivers and helps impersonation and phishing attacks. This has already happened to Gmail in 2023.

In conclusion, we advice against implementing BIMI. It has a very low adoption rate, since many companies have the intuition that it’s a defective standard. Any security standard that you implement on your server must be based on sound judgement, taking into account real facts, not simple hypotheses and knowing that new technical standards can be ill-conceived, as becomes evident when they are later abandoned. Also, if a few giant tech companies, whose desire to increase their influence over our digital life is well-known, propose a new standard that requires email senders to buy expensive certificates and go through a time-consuming verification process to make their emails seem more legitimate, this in itself is highly suspicious and seems to hide commercial interests.


All the standards mentioned above that are used to authenticate and secure email communication (SPF, DKIM, DMARC, ARC, DANE, MTA-STS, BIMI) cannot offer an absolute guarantee that the content of emails will be totally inaccessible to attackers. Since emails are by default stored as plain text on receiving and sending mail servers, any attacker that finds a way to access the file system of the mail servers can read the received or sent emails. This is why, if you want to send highly sensitive information by email, you should use email encryption, as described in the Configure Thunderbird for email encryption subchapter. In that situation, as long as the private key is not compromised, even if an attacker gets hold of an email message while in transit or from the storage of a mail server, he won’t be able to decrypt it and read its content.

19.20. Install Roundcube


Log in to phpMyAdmin and create a database called roundcubemail and a user called roundcubeuser, on localhost, with a strong password. Save the password somewhere safe, to use it later. Then give user roundcubeuser all the privileges over the roundcubemail database, except the GRANT privilege, which is not necessary.

Then use a browser to access Roundcube’s official download page. Right-click on the ‘Download’ button of the latest ‘Stable Version’ ‘Complete’ of Roundcube, then choose ‘Copy Link Location’ from the context menu, to copy the download link to the clipboard, so that you can use it later, with the wget command.

On your remote server, navigate to /tmp:

cd /tmp

Download the latest version of Roundcube:

wget https://github.com/roundcube/roundcubemail/releases/download/1.4.8/roundcubemail-1.4.8-complete.tar.gz

Replace 1.4.8 with your version number. Uncompress the archive:

tar xf roundcubemail-1.4.8-complete.tar.gz

Remember that you already created the /var/www/mail.example.com directory when installing phpMyAdmin, so now you only need to copy the uncompressed folders and files of Roundcube to /var/www/mail.example.com:

cp -r /tmp/roundcubemail-1.4.8/* /var/www/mail.example.com

Remove the directory and archive from /tmp, because you no longer need them:

rm -r /tmp/roundcubemail-1.4.8 /tmp/roundcubemail-1.4.8-complete.tar.gz

Change ownership and permissions for the /var/www/mail.example.com directory and its content:

chown -R www-data:www-data /var/www/mail.example.com
find /var/www/mail.example.com -type d -exec chmod 750 {} +
find /var/www/mail.example.com -type f -exec chmod 640 {} +

Use a browser to access the Roundcube installer at:

https://mail.example.com/installer/

The first screen will look like this:

The installer checks if all the needed software packages are installed. All the checks should display OK, with the exception of PostgreSQL, and the other DBMS, which are not necessary, since you have MariaDB installed (identified by the installer as MySQL). Click ‘Next’.

In the next screen, leave all the fields and checkboxes as they are, with the exception of the fields/checkbloxes listed below, which you should fill in as follows:

General configuration

Under enable_spellcheck, uncheck Make use of the spell checker.

Logging & Debugging

log_driver file

log_dir /var/log/sites/mail.example.com/

Database setup

Database server localhost

Database name roundcubemail

Database user name roundcubeuser

Database password strongpassword (enter the password of roundcubeuser set up earlier)

IMAP Settings

default_host mail.example.com

SMTP Settings

smtp_server
tls://mail.example.com

Make sure that Use the current IMAP username and password for SMTP authentication is checked.

Display settings & user prefs

In the language* field enter the RFC1766 formatted language name (en_US, de_DE, de_CH, fr_FR, pt_BR, etc.), like:

language* en_US

skin* elastic

prefer_html* check Prefer displaying HTML messages and for Compose HTML formatted messages choose ‘always’

Plugins

Check the following plugins:

additional_message_headers
jqueryui
managesieve
markasjunk
password
show_additional_headers

As mentioned, leave all the other fields/checkboxes as they are, then click ‘CREATE CONFIG’.

The installer will display the following message at the top of the page:

The config file was saved successfully into /var/www/mail.example.com/config directory of your Roundcube installation.

Please note that there is a ‘Continue’ button below the message. Before clicking on that button, leave the browser open and edit the /var/www/mail.example.com/config/config.inc.php file:

nano /var/www/mail.example.com/config/config.inc.php

Add the following line at the top of the file:

$config['enable_installer'] = true;

Also, since you specified that /var/log/sites/mail.example.com should be the log directory, you have to give www-data write access to this directory:

chown www-data:root /var/log/sites/mail.example.com

Next, come back to the installation page opened in your browser and click on the ‘Continue’ button mentioned above.

In the next screen you will see the message ‘DB Schema: NOT OK (Database not initialized)’. Click on the ‘Initialize database’ button below this message. After the initialization, all the checks should display OK.

Next, open the /var/www/mail.example.com/config/config.inc.php file:

nano /var/www/mail.example.com/config/config.inc.php

Below the $config['db_dsnw'] line, add the following line:

$config['log_driver'] = 'file';

Below $config['smtp_server'] = 'tls://mail.example.com'; add the following lines:

$config['smtp_port'] = '587';
$config['smtp_user'] = '%u';
$config['smtp_pass'] = '%p';
$config['auto_create_user'] = true;

Tell Roundcube to use the browser’s timezone when displaying the time when emails were received/sent. Add these lines at the end of the file:

// Use this timezone to display date/time.
// Valid timezone identifiers are listed here: php.net/manual/en/timezones.php
// 'auto' will use the browser's timezone settings.
$config['timezone'] = 'auto';

Also, add the following lines, to set a session expiration time different from the default of 10 minutes, so as to avoid being logged out unexpectedly:

// Session lifetime in minutes
$config['session_lifetime'] = 60;

To disable the installer which is no longer needed, remove the following line:

$config['enable_installer'] = true;

Now, that Roundcube is installed, for security reasons, delete the installer directory:

cd /var/www/mail.example.com
rm -r installer

19.20.1. Move the configuration file outside the web root


To increase security, copy the config.inc.php file to a directory outside /var/www, like this:

cp /var/www/mail.example.com/config/config.inc.php /srv/scripts/roundcube.php

Empty the configuration file:

cd /var/www/mail.example.com/config
cat /dev/null > config.inc.php

Open the configuration file:

nano config.inc.php

And add the following line inside this file:

<?php include('/srv/scripts/roundcube.php'); ?>

Change the ownership and permissions for the /srv/scripts/roundcube.php file:

cd /srv/scripts
chown www-data:root roundcube.php
chmod 400 roundcube.php

Reload Nginx:

systemctl reload nginx

19.20.2. Enabling debug mode


If you want to enable debug mode to troubleshoot various problems, add the following lines to the configuration file (/srv/scripts/roundcube.php):

// Debug level (from 1 to 4)
$config['debug_level'] = 4;

// Log SQL queries
$config['sql_debug'] = true;

// Log IMAP conversation
$config['imap_debug'] = true;

// Log LDAP conversation
$config['ldap_debug'] = true;

// Log SMTP conversation
$config['smtp_debug'] = true;

Remember to comment these lines out when you don’t need debug mode enabled.

19.20.3. Restrict access to Roundcube’s login page


At this point, you can log in to Roundcube using any of the email addresses and their respective passwords that you have set up previously in Postfix Admin. The login page is:

https://mail.example.com

When you access this page, you will be asked for your HTTP authentication credentials. You have to enter the username and password set up earlier (in the Install phpMyAdmin chapter, in the /etc/nginx/htpass/mail.example.com password file).

Please note that you will have to add to the /etc/nginx/htpass/mail.example.com file a username and password for all the users that you want to allow access to Roundcube’s login page. At this point, the password file should contain the name and the hashed password of only one user (verner in our example). If you want to allow the user albert to access the login page, you should add his name and a password to the password file like this:

htpasswd /etc/nginx/htpass/mail.example.com albert

Repeat this command for all the users that you want to allow access to Roundcube’s login page.

After entering your HTTP authentication credentials, you will see the login page:

Now you can use the email addresses and their respective passwords that you have set up in Postfix Admin, to log in to Roundcube.

Before you can send emails to external domains, make sure that you have properly configured a MX record, a SPF record, an Opendkim record and a DMARC record (apart from the A and AAAA records), as described in the 19.12., 19.13., 19.14. and 19.15. chapters, respectively.

19.20.4. Configure logrotate to rotate the Roundcube errors log


Roundcube will write access data to the access.log and errors to the errors.log. Both logs are located in the directory that you have set up earlier as log_dir, namely /var/log/sites/mail.example.com. You have to configure logrotate to rotate these logs.

Open the /etc/logrotate.d/nginx file:

nano /etc/logrotate.d/nginx

Add the following block at the end of this file:

/var/log/sites/mail.example.com/access.log  /var/log/sites/mail.example.com/errors.log {
        missingok
        rotate 10
        compress
        delaycompress
        notifempty
        create 0640 www-data adm
        size 2M
        sharedscripts
        prerotate
                if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
                        run-parts /etc/logrotate.d/httpd-prerotate; \
                fi; \
        endscript
        postrotate
                [ ! -f /var/run/nginx.pid ] || kill -USR1 `cat /var/run/nginx.pid`
        endscript
}

19.20.5. Configure Fail2ban to protect Roundcube against brute-force attacks


Navigate to the /etc/fail2ban/filter.d directory:

cd /etc/fail2ban/filter.d

Ignore the roundcube-auth.conf file which is already there, and create a new file called roundcube.conf:

nano roundcube.conf

Add the following content in this file:

[Definition]

failregex = IMAP Error: Login failed for .* from <HOST>
            ^<HOST> .* \"GET / HTTP/2.0\" 401 195 .*$
ignoreregex =

The second line identifies the failed log in attempts against the HTTP authentication window, which is displayed right before loading the Roundcube login page and can be also subject to brute-force attacks. Now open the /etc/fail2ban/jail.local file:

nano /etc/fail2ban/jail.local

Add the following lines in the 'Webmail and groupware servers' section:

[roundcube]
enabled  = true
filter   = roundcube
logpath  = /var/log/sites/mail.example.com/errors.log
           /var/log/sites/mail.example.com/access.log
port     = 80,443
maxretry = 4
bantime = 259200

Replace example.com with the main domain hosted on your server. Reload Fail2ban:

systemctl reload fail2ban

19.20.6. Enable the ‘managesieve’ plugin in Roundcube


When configuring the Roundcube installation, you have checked managesieve in the list of plugins that will be installed. So, the managesieve plugin is already installed. Yet, to be able to use it and create custom filters from Roundcube ‘Settings’ > ‘Filters’, you will have to enable it. Navigate to the managesieve directory of Roundcube:

cd /var/www/mail.example.com/plugins/managesieve

Copy config.inc.php.dist to config.inc.php:

cp config.inc.php.dist config.inc.php

Open the config.inc.php file:

nano config.inc.php

Change $config['managesieve_port'] = null; to:

$config['managesieve_port'] = 4190;

Change $config['managesieve_default'] = '/etc/dovecot/sieve/global'; to:

$config['managesieve_default'] = '/etc/dovecot/conf.d/custom-sieves/global_after.sieve';

Also change $config['managesieve_vacation'] = 0; to:

$config['managesieve_vacation'] = 1;

Restart Dovecot:

systemctl restart dovecot

19.20.7. Enable the ‘password’ plugin in Roundcube


When installing Roundcube, you have checked ‘password’ in the list of plugins that will be installed. Therefore, the ‘password’ plugin is already installed. Yet, to be able to use it, you have to enable it.

Execute the following SQL commands in order to give user roundcubeuser SELECT privileges on the username and password columns and UPDATE privileges on the password column of the mailbox table from the mail database, on localhost:

mysql -u root -p
MariaDB [(none)]> GRANT SELECT (username, password), UPDATE (password) ON mail.mailbox TO 'roundcubeuser'@'localhost';
MariaDB [(none)]> FLUSH PRIVILEGES;
MariaDB [(none)]> exit
Bye

Whithout these privileges, the users won’t be able to change their passwords from inside Roundcube.

Navigate to the plugin’s directory:

cd /var/www/mail.example.com/plugins/password

Copy the config.inc.php.dist file to config.inc.php:

cp config.inc.php.dist config.inc.php

Edit config.inc.php:

nano config.inc.php

Edit only the lines listed below. They should look like this:

$config['password_driver'] = 'sql';
$config['password_confirm_current'] = true;
$config['password_minimum_length'] = 8;
$config['password_log'] = false;
$config['password_login_exceptions'] = null;
$config['password_hosts'] = array('mail.example.com');
$config['password_force_save'] = true;
$config['password_algorithm'] = 'dovecot';
//$config['password_dovecotpw'] = '/usr/local/sbin/doveadm pw'; // for dovecot-2.x
//$config['password_dovecotpw'] = '/usr/local/sbin/dovecotpw'; // for dovecot-1.x
$config['password_dovecotpw'] = '/usr/bin/doveadm pw';
$config['password_dovecotpw_method'] = 'CRAM-MD5';
$config['password_dovecotpw_with_method'] = true;

// SQL Driver options.

$config['password_db_dsn'] = 'mysql://mail:gdf6g19VBg45sDfg14@localhost/mail';

$config['password_query'] = 'UPDATE mail.mailbox SET password=%P WHERE username=%u AND password=%O LIMIT 1';

$config['password_crypt_hash'] = 'md5';
$config['password_hash_algorithm'] = 'sha1';

where gdf6g19VBg45sDfg14 is the password of the user mail, that you set up when you created the mail database.

Now, every user can change her/his password when logged in to Roundcube from ‘Settings’ > ‘Password’.

19.20.8. Enable the ‘autologon’ and ‘autologout’ plugins


If you want to integrate Roundcube with Roundpin, to be able to open a new email window with one click, to easily send emails to your Roundpin contacts, and to be able to check incoming emails from inside Roundpin, you will need to enable the ‘autologon’ and ‘autologout’ Roundcube plugins. To do so, edit the main configuration file (remember that you have copied the main configuration file, /var/www/mail.example.com/config/config.inc.php, as /srv/scripts/roundcube.php, for security reasons):

nano /srv/scripts/roundcube.php

Add ‘autologon’ and ‘autologout’ to the list of enabled plugins, by modifying the $config['plugins'] array, as follows:

// List of active plugins (in plugins/ directory)
$config['plugins'] = array('additional_message_headers', 'jqueryui', 'managesieve', 'markasjunk', 'password', 'show_additional_headers', 'autologon', 'autologout');

The ‘autologon’ plugin is already present in the /var/www/mail.example.com/plugins directory because it’s part of the default Roundcube installation, therefore, you don’t have to download it and place it in that directory. However, you have to change it slightly in order to use it to integrate Roundcube with Roundpin. Open the /var/www/mail.example.com/plugins/autologon/autologon.php file for editing:

nano /var/www/mail.example.com/plugins/autologon/autologon.php

Search for the following lines, and comment them out, to make them look like this:

//        if (!empty($_GET['_autologin']) && $this->is_localhost()) {
//            $args['user']        = 'me';
//            $args['pass']        = '******';
//            $args['host']        = 'localhost';
//            $args['cookiecheck'] = false;
//            $args['valid']       = true;
//        }

Then add the following lines right below them:

        if (!empty($_POST['_autologin']) && $this->is_localhost())
        {
            $args['user'] = $_POST['_user'];
            $args['pass'] = $_POST['_pass'];
            $args['host'] = '127.0.0.1';
            $args['cookiecheck'] = false;
            $args['valid'] = true;
        }

Then, search for the following line and comment it out, to make it look like this:

//        return $_SERVER['REMOTE_ADDR'] == '::1' || $_SERVER['REMOTE_ADDR'] == '127.0.0.1';

Next, add the following line right below it:

        return true;

The ‘autologout’ plugin has to be first downloaded from here, then uncompressed and saved in the /var/www/mail.example.com/plugins directory under the name autologout. You should also set the right ownership and permissions for the new plugin:

cd /var/www/mail.example.com/plugins
chown -R www-data:www-data autologout
chmod 750 autologout
chmod 640 autologout/*

This concludes enabling the two plugins.

19.20.9. Creating canned emails


If you frequently send emails with similar content, you can create a template, or a ‘canned’ email, that you can then select from a drop-down list, modify a bit if necessary and send. This will spare you the time to write the same content over and over again.

To create a ‘canned’ email go to ‘Settings’ > ‘Responses’ > click on the ‘Create’ button on the upper bar. You will see the following screen:

In the ‘Name’ field enter a name for that ‘canned’ email, in the ‘Response Text’ text area enter the content of the email, then click ‘Save’. You can create as many ‘canned’ emails as you need. Then, when you want to send a new email or to reply to a received email, you will see the ‘Responses’ button on the upper bar of the email editing window. When you click on ‘Responses’, you will see a drop-down list with all the ‘canned’ emails that you have saved. When you choose an email from the drop-down list, its content will be automatically inserted in the text area of the new message or reply. You can then modify the text where you find necessary and send the email to the recipient.

19.20.10. Creating filters


If you want to filter incoming messages according to certain rules, to separate them from regular emails and send them to a specific folder, flag them, redirect them, discard them, etc. you can create an email filter. To create a new filter go to ‘Settings’ > ‘Filters’ > click on the ‘Create’ button on the upper bar. You will see the following screen:

In the ‘Filter name’ field enter a name for the filter, in the ‘Scope’ field choose from the drop-down list one of the three options: ‘matching all of the following rules’, ‘matching any of the following rules’ or ‘all messages’. In the ‘Rules’ section add any rules you need. For example:

Subject

From

contains

is equal to

check out our catalog

contact@domain.com

You can add new rules by clicking on the ‘+’ sign.

In the ‘Actions’ section add any action you need. For example:

Move message to

Junk

You can add multiple actions, such as:

Redirect message to

Send message copy to

info@somedomain.com

office@otherdomain.com

Click ‘Save’.

If you want to separate a specific type of incoming emails from the regular emails and send them to a separate folder, you can create a new folder in ‘Settings’ > ‘Folders’ > ‘Create’, then add a filter to send all incoming emails falling under a specific rule, to the newly created folder.

19.20.11. Creating ‘out of office’ replies


When you know you will be out of office for a period of time because of a vacation, a voyage, etc., and you want to inform your contacts that you are out of office if they send you emails during that period, you can create an ‘out of office’ reply. Go to ‘Settings’ > ‘Out of Office’. You will see the following screen:

In the ‘Subject’ field enter a subject for the reply, such as ‘I’m on vacation’. In the ‘Body’ field enter the content of the message, for example:

Hello,

I’m on vacation until July 31. For urgent matters you can contact my assistant at office@domain.com.

As soon as I come back I’ll send you my response.

Thank you for your understanding!

Sincerely,

John Doe

In the ‘Start time’ and ‘End time’ fields enter the days from the beginning and the end of the time period in which you want the automatic ‘out of office’ reply to be sent. In the ‘Status’ field choose ‘On’. Under ‘Advanced settings’, in the ‘My e-mail address’ enter the email address of the Roundcube account. In the ‘Reply interval’ enter 1. This means that the mail server will send the ‘out of office’ reply only once per day if a contact sends multiple emails to your email address in a day. In the ‘Incoming message action’ field choose ‘Keep’ if you want the incoming messages to be kept in the Inbox after they arrive. You can also choose to discard the messages that arrive during the vacation, to redirect them, or to send copies of the incomng messages to a different email address. Click ‘Save’.

Please note that after you create the ‘out of office’ reply, it will be listed in the ‘Filters’ section. This is because the ‘out of office’ reply is nothing else than a type of filter: it filters all the incoming emails and if it finds that some emails have arrived in the specified period of time, it replies with the predefined text of the ‘out of office’ response. Thus, you can create multiple ‘out of office’ replies as regular filters, one for each period of vacation during the year, and you can enable and disable them as needed. If in addition to an ‘out of office’ reply you add other type of filters, in the ‘Out of Office’ section you will find a new field called ‘Put the out-of-office rule after’, which will allow you to choose the filter after which the ‘out of office’ filter will be applied to incoming emails.

19.20.12. Changing themes


You can change Roundcube’s themes, called ‘skins’ by clicking on ‘Settings’ on the left bar > ‘Preferences’ > ‘User Interface’ > ‘Interface Skin’. You can choose between ‘Classic’, ‘Elastic’ and ‘Larry’. However, only ‘Elastic’ is responsive.

In the ‘Preferences’ section you can also change other settings, such as those related to ‘Composing Messages’.

19.20.13. Upgrading Roundcube


Before upgrading Roundcube to a new version, it’s recommended to verify if the new version has been tested and confirmed to function well within the suite of applications described in this guide. Once we test an application and confirm that it works well, we include it on this page.

You can check whether a new version of Roundcube has been released on the official github.com ‘Releases’ page: https://github.com/roundcube/roundcubemail/releases . When a new version is avaialble, you can upgrade your existing installation by following the steps detailed below.

First make a backup of your Roundcube database by logging in to phpMyAdmin, clicking on the Roundcube database (roundcubemail) on the left panel, then going to ‘Export’, then ‘Go’, then ‘Save File’, then ‘OK’. Don’t forget to include the date of the backup in the name of the exported file (Eg.: roundcubemail-2021-9-25.sql).

Then make a backup copy of the whole Roundcube directory:

cd /var/bm_archives
tar czf var-www-mail.example.com-2021-09-25.tar.gz /var/www/mail.example.com

Replace example.com-2021-09-25 with your domain and date of backup. You can use a FTP client like FileZilla to upload the database backup from your local computer (roundcubemail-2021-9-25.sql) to the backups directory on the remote server (/var/bm_archives). Also, you can use FTP to download the backup of the Roundcube files (var-www-mail.example.com-2021-09-25.tar.gz) to your local computer. In this way you will have the backups both on the remote server and your local computer.

Please note that you shouldn’t overwrite the Roundcube installation with the new version. First download the latest version of Roundcube from their official website to the /tmp directory. Navigate to https://roundcube.net/download/, look for the latest ‘Complete’ stable version, right-click on the ‘Download’ button and choose ‘Copy Link’ to copy the URL to the clipboard. Then run:

cd /tmp
wget https://github.com/roundcube/roundcubemail/releases/download/1.4.8/roundcubemail-1.4.8-complete.tar.gz

Replace 1.4.8 with your version number. Extract the archive, then go to the /bin directory:

tar xf roundcubemail-1.4.8-complete.tar.gz 
cd roundcubemail-1.4.8/bin

Next use the installto.sh script to upgrade Roundcube. Run:

php installto.sh /var/www/mail.example.com

Replace example.com with your domain. You’ll be asked if you want to update. Type y and press Enter. The update script will then copy all the new files to the target location and check and update the database schema.

Next, set the right ownership and permissions for the /var/www/mail.example.com directory:

chown -R www-data:www-data /var/www/mail.example.com
find /var/www/mail.example.com -type d -exec chmod 750 {} +
find /var/www/mail.example.com -type f -exec chmod 640 {} +

The next step is to update Roundcube dependencies with Composer. First check if you have it installed by running:

composer.phar

If the output shows Composer’s version and a list of options and commands, it means it’s installed. If the output is:

-bash: composer.phar: command not found

it means that Composer is not installed. To install Composer system-wide to manage dependencies when updating php applications, run:

cd /usr/local/bin
curl -sS https://getcomposer.org/installer | php

This will create a composer.phar file in the /usr/local/bin directory and you will be able to run it without specifying its full path, like this:

composer.phar

When you want to update Composer, run:

composer.phar self-update

Now update the Roundcube dependencies with Composer by running:

cd /var/www/mail.example.com
composer.phar update --no-dev 

Then, because you are using the build-in addressbook, you have to also run the indexing script (/var/www/mail.example.com/bin/indexcontacts.sh). First change its permissions:

chmod 740 /var/www/mail.example.com/bin/indexcontacts.sh

Then run it specifying its full path, like this:

/var/www/mail.example.com/bin/indexcontacts.sh

Then the archive and the directory of the new Roundcube version from the /tmp directory can be removed:

cd /tmp
rm -r roundcubemail-1.4.8-complete.tar.gz roundcubemail-1.4.8

If during the upgrade process you are asked if you want to let the upgrade script to edit the configuration file to remove obsolete settings, etc., you should answer yes. In this case, the /var/www/mail.example.com/config/config.inc.php file will be edited by the script and for security reasons you’ll have to take its content outside the web root, as you did when installing Roundcube. So, copy the content of the config.inc.php file to /srv/scripts like this:

cp /var/www/mail.example.com/config/config.inc.php /srv/scripts/roundcube.php

Next, empty the /var/www/mail.example.com/config/config.inc.php file:

cat /dev/null > /var/www/mail.example.com/config/config.inc.php

Edit the file:

nano /var/www/mail.example.com/config/config.inc.php

Place the following line inside this file:

<?php include('/srv/scripts/roundcube.php'); ?>

Set the proper permissions for the /srv/scripts/roundcube.php file:

cd /srv/scripts
chown www-data:root roundcube.php
chmod 400 roundcube.php

Since you have modified the ‘autologon’ plugin in order to integrate Roundcube with Roundpin as explained earlier in the Enable the ‘autologon’ and ‘autologout’ plugins chapter, you will need to make the changes again after upgrading Roundcube. Therefore, open the /var/www/mail.example.com/plugins/autologon/autologon.php file for editing:

nano /var/www/mail.example.com/plugins/autologon/autologon.php

Search for the following lines, and comment them out, to make them look like this:

//        if (!empty($_GET['_autologin']) && $this->is_localhost()) {
//            $args['user']        = 'me';
//            $args['pass']        = '******';
//            $args['host']        = 'localhost';
//            $args['cookiecheck'] = false;
//            $args['valid']       = true;
//        }

Then add the following lines right below them:

        if (!empty($_POST['_autologin']) && $this->is_localhost())
        {
            $args['user'] = $_POST['_user'];
            $args['pass'] = $_POST['_pass'];
            $args['host'] = '127.0.0.1';
            $args['cookiecheck'] = false;
            $args['valid'] = true;
        }

Then, search for the following line and comment it out, to make it look like this:

//        return $_SERVER['REMOTE_ADDR'] == '::1' || $_SERVER['REMOTE_ADDR'] == '127.0.0.1';

Next, add the following line right below it:

        return true;

19.21. Install and integrate SpamAssassin


The best free and open source spam filter is SpamAssassin, officially called “Apache SpamAssassin”. Indeed, Rspamd seems to offer a bit faster spam filtering, but at the cost of using large amounts of RAM memory, which makes it inferior to SpamAssassin. To install SpamAssassin run:

apt-get install spamassassin spamc dovecot-antispam spamass-milter

Make a copy of the /etc/default/spamd file:

cp /etc/default/spamd /etc/default/spamd_orig

Open the /etc/default/spamd file:

nano /etc/default/spamd

Change the OPTIONS parameter, to make it look like this:

OPTIONS="--create-prefs --max-children 5 --helper-home-dir /var/lib/spamassassin -u debian-spamd -g debian-spamd --syslog=/var/log/spamassassin/spamd.log"

Create the log directory and set the right ownership for it:

mkdir /var/log/spamassassin
chown -R debian-spamd:debian-spamd /var/log/spamassassin

Configure logrotate to rotate the new logs:

nano /etc/logrotate.d/spamassassin

Add the following content inside the /etc/logrotate.d/spamassassin file:

/var/log/spamassassin/spamd.log {
      copytruncate
      rotate 12
      weekly
      compress
      missingok
      postrotate
         /bin/systemctl restart spamd.service > /dev/null
      endscript
}

The debian-spamd user and group and the spamass-milter user and group are created automatically when installing SpamAssassin with the command mentioned above. Add the spamass-milter user to the debian-spamd group:

adduser spamass-milter debian-spamd

Make a copy of the /etc/default/spamass-milter file:

cp /etc/default/spamass-milter /etc/default/spamass-milter_orig

Open the /etc/default/spamass-milter file:

nano /etc/default/spamass-milter

Change the OPTIONS parameter, to make it look like this:

OPTIONS="-u spamass-milter -i 127.0.0.1 -m -I"

Enable spamass-milter by running:

systemctl enable spamass-milter

Please note that spamd is the daemonized version of the spamassassin executable and the spamd service is enabled and started by default, therefore, you don’t need to enable and start it. Also, the spamassassin service, doesn’t need to be enabled and started. The only services that need to be running in order to have your emails filtered in real time by SpamAssassin is spamd and spamass-milter.

Make a copy of the /etc/spamassassin/local.cf file:

cp /etc/spamassassin/local.cf /etc/spamassassin/local.cf_orig

Open the /etc/spamassassin/local.cf file:

nano /etc/spamassassin/local.cf

Change/add the following parameters to make them look like this:

report_safe 0
required_score 5.0

use_bayes               1
use_bayes_rules         1
bayes_auto_learn        1

skip_rbl_checks         1
skip_uribl_checks       1

# Enable Pyzor                
use_pyzor               1
pyzor_path /usr/bin/pyzor
pyzor_options --homedir /var/lib/spamassassin/.pyzor

# Disable Razor2
use_razor2              0

#   Set headers which may provide inappropriate cues to the Bayesian
#   classifier
bayes_ignore_header X-Bogosity
bayes_ignore_header X-Spam-Flag
bayes_ignore_header X-Spam-Status

ifplugin Mail::SpamAssassin::Plugin::Shortcircuit
endif # Mail::SpamAssassin::Plugin::Shortcircuit

To integrate SpamAssassin with Postfix first open the /etc/postfix/master.cf file:

nano /etc/postfix/master.cf

Uncomment the following two lines located near the end of the file, above the policyd-spf line. The two lines should look like this:

dovecot   unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail:vmail argv=/usr/bin/spamc -u debian-spamd -e /usr/lib/dovecot/deliver -d ${recipient}

Next, open the /etc/postfix/main.cf file:

nano /etc/postfix/main.cf

Edit the smtpd_milters and non_smtpd_milters lines, to make them look like this :

smtpd_milters = unix:/var/spool/postfix/spamass/spamass.sock unix:/run/opendkim/opendkim.sock unix:/run/opendmarc/opendmarc.sock
non_smtpd_milters = unix:/var/spool/postfix/spamass/spamass.sock unix:/run/opendkim/opendkim.sock unix:/run/opendmarc/opendmarc.sock

SpamAssassin uses 3 main mechanisms to classify emails as spam/non-spam:

1. built-in rules, like sets of regular expressions, that can be updated regularly with the sa-update tool. These rules are stored by default in the /var/lib/spamassassin/<spamassassin version number> directory.

2. the built-in Naive Bayes classifier which correlates the use of specific words with spam and non-spam emails and then, using Bayes’ theorem, calculates the probability that the emails are spam or not.

3. plugins like Pyzor or Razor2, which check the checksum of email messages against databases stored on central servers in real time, to see if those messages have been reported as spam or whitelisted as non-spam in the past, in order to decide their spam score.

Although the built-in rules can be updated daily, it’s enough to update them once every 3 days by setting up a cron job:

crontab -e

Add the following lines inside the file:

# Update SpamAssassin's built-in rules once every 3 days at 3:15 AM
15 3 */3 * * /usr/bin/sa-update && systemctl reload spamd

The built-in Naive Bayes classifier can be ‘trained’ automatically, by adding bayes_auto_learn 1 inside /etc/spamassassin/local.cf, or manually, by feeding large collections of known spam and non-spam (ham) emails to SpamAssassin, using the sa-learn tool, like this:

sudo -u debian-spamd sa-learn --spam /path/to/spam/folder
sudo -u debian-spamd sa-learn --ham /path/to/ham/folder

Although you can ‘train’ SpamAssassin manually by using your own collections of spam and non-spam emails, or you can download archives of sample spam and non-spam emails from different sources, in which case you won’t know how accurately those emails have been classified, it’s recommended to use the automatic ‘training’, which you have already enabled, by adding bayes_auto_learn 1 to /etc/spamassassin/local.cf. In this way, once SpamAssassin clearly identifies an email as spam or non-spam, it ‘autolearns’ the pattern, so that future similar emails will be classified accordingly. If an email allows SpamAssassin to ‘autolearn’ something, in the ‘X-Spam-Status’ header of that email (in Thunderbird you can inspect the headers of an email by clicking ‘More’ on the bar above the message, then clicking ‘View Source’) you will see the ‘autolearn’ parameter set to autolearn=spam or autolearn=ham. In case it didn’t learn anything from that email, which can happen even if autolearning is turned on, you will see autolearn=no.

Please note that by default, the Bayes classifier won’t be used in deciding if emails are spam or ham until 200 of spam emails and 200 of ham emails have been learned, either by autolearning or by manual training.

To see the number of spam and ham emails that have been added to the database up until the present, run:

sudo -u debian-spamd sa-learn --dump magic

The result will look similar to this:

0.000          0          3          0  non-token data: bayes db version
0.000          0        216          0  non-token data: nspam
0.000          0        320          0  non-token data: nham
0.000          0      15419          0  non-token data: ntokens
0.000          0 1540463637          0  non-token data: oldest atime
0.000          0 1822479226          0  non-token data: newest atime
0.000          0          0          0  non-token data: last journal sync atime
0.000          0          0          0  non-token data: last expiry atime
0.000          0          0          0  non-token data: last expire atime delta
0.000          0          0          0  non-token data: last expire reduction count

This means that 216 emails have been identified as spam and 320 as ham (non-spam).

If you want to see detailed debug information related to spam/ham learning, you can run the command from above with the -D option:

sudo -u debian-spamd sa-learn --dump magic -D

Since Pyzor and Razor2 are similar, it’s enough to install and enable only Pyzor to help SpamAssassin to better distinguish between spam and non-spam emails. You have already installed Pyzor by running the first command in this chapter and you have enabled it by adding the Pyzor related parameters in the /etc/spamassassin/local.cf file, as instructed above. You only need to open port 24441, which is used by Pyzor to communicate with public.pyzor.org:

ufw allow 24441

To apply all the changes restart the following services:

systemctl restart spamd
systemctl restart spamass-milter
systemctl restart postfix
systemctl restart dovecot

You can check if SpamAssassin is configured correctly by running:

sudo -u debian-spamd spamassassin --lint

If that command doesn’t output anything, it means there are no configuration issues.

To check if Pyzor is working you can run:

echo "test" | spamassassin -D pyzor 2>&1 | less

The result should look like this:

Jul 30 18:39:02.181 [2072037] dbg: pyzor: network tests on, attempting Pyzor
Jul 30 18:39:02.945 [2072037] dbg: pyzor: adjusting rule PYZOR_CHECK priority to -100
Jul 30 18:39:03.812 [2072037] dbg: pyzor: pyzor is available: /usr/bin/pyzor
Jul 30 18:39:03.820 [2072053] dbg: pyzor: child process 2072053 forked
Jul 30 18:39:03.822 [2072053] dbg: pyzor: opening pipe: /usr/bin/pyzor --homedir /var/lib/spamassassin/.pyzor check </tmp/.spamassassin3052036BFCdhKtmp
Jul 30 18:39:04.443 [2072053] dbg: pyzor: [2072055] finished: exit 1
Jul 30 18:39:04.508 [2072037] dbg: pyzor: child process 2072053 finished, reading results
Jul 30 18:39:04.508 [2072037] dbg: pyzor: got response: public.pyzor.org:24441\t(200, 'OK')\t29765682\t407051

To check if SpamAssassin works appropriately, send an email from one of your other email addresses hosted on other servers to an email address on the mail server that you are configuring now (for example to admin@example.com). On the ‘Subject’ line enter ‘Test’ and in the email body enter the following line, with an empty line above it and an empty line below it:

XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X

Send the email. If SpamAssassin is functioning correctly, the message from above will be sent to the ‘Junk’ folder of your email account.

To see the way SpamAssassin marks an email as spam, you can look at the source of an email identified as such. You will see the headers added by SpamAssassin, which will look similar to this:

X-Spam-Flag: YES
X-Spam-Status: Yes, score=1001.6 required=5.0 tests=DKIM_SIGNED,DKIM_VALID,
        FREEMAIL_FROM,GTUBE,SUBJ_ALL_CAPS,UNPARSEABLE_RELAY autolearn=no
        autolearn_force=no version=3.4.2
X-Spam-Report: 
        * 1000 GTUBE BODY: Generic Test for Unsolicited Bulk Email
        *  0.0 FREEMAIL_FROM Sender email is commonly abused enduser mail
        *      provider (...)
        *  1.6 SUBJ_ALL_CAPS Subject is all capitals
        * -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature
        *  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily
        *       valid
        *  0.0 UNPARSEABLE_RELAY Informational: message has unparseable relay
        *      lines
X-Spam-Level: **************************************************
X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on
        mail.example.com

After you install SpamAssassin you may notice in your /var/log/mail.log file a warning like this:

spamass-milter[12892]: Could not retrieve sendmail macro "b"!.  Please add it to confMILTER_MACROS_ENVFROM for better spamassassin results

This is a harmless warning that will be removed in the future versions of SpamAssassin and you can ignore it.

19.21.1. Upgrading SpamAssassin


Since SpamAssassin has been installed from the official Debian repository, to upgrade it, all you need to do is to run apt-get update && apt-get dist-upgrade with a specific frequency, as described in the Maintenance steps chapter. This command will upgrade SpamAssassin if there is a new version available. Also, during these upgrades, the configuration changes implemented as described above, will be preserved.

19.22. Integrate ClamAV using clamav-milter


A mail server should scan all incoming and outgoing emails, including their compressed attachments, with an antivirus. ClamAV is well-suited for this purpose. If you already installed it as described in the Install ClamAV chapter, you will only need to integrate it with your mail server, as described below.

First install the clamsmtp and clamav-milter packages:

apt-get install clamsmtp clamav-milter

Make a copy of the /etc/clamav/clamav-milter.conf file:

cp /etc/clamav/clamav-milter.conf /etc/clamav/clamav-milter.conf_orig

Open the /etc/clamav/clamav-milter.conf file:

nano /etc/clamav/clamav-milter.conf

Change the content of this file to make it look like this:

#Automatically Generated by clamav-milter postinst
#To reconfigure clamav-milter run #dpkg-reconfigure clamav-milter
#Please read /usr/share/doc/clamav-base/README.Debian.gz for details

MilterSocket /run/clamav/clamav-milter.ctl
FixStaleSocket true
User clamav
ReadTimeout 120
Foreground false
PidFile /run/clamav/clamav-milter.pid
ClamdSocket unix:/run/clamav/clamd.ctl
OnClean Accept
OnInfected Reject
RejectMsg "The message has been rejected because the Antivirus has detected the following malware/virus: %v"
OnFail Defer
AddHeader Replace
LogSyslog false
LogFacility LOG_LOCAL6
LogVerbose false
LogInfected Off
LogClean Off
LogRotate true
MaxFileSize 600M
SupportMultipleRecipients false
TemporaryDirectory /tmp
LogFile /var/log/clamav/clamav-milter.log
LogTime true
LogFileUnlock false
LogFileMaxSize 1M
MilterSocketGroup postfix
MilterSocketMode 664

To avoid having to restart clamav-milter manually if it enters failed state after an automatic ClamAV update, first run:

systemctl edit clamav-milter.service

This will create and open the /etc/systemd/system/clamav-milter.service.d/override.conf file. When the file is opened, right below these lines:

### Editing /etc/systemd/system/clamav-milter.service.d/override.conf
### Anything between here and the comment below will become the new contents of the file

add these lines:

[Service]
Restart=on-failure

To apply the changes, run:

systemctl daemon-reload
systemctl restart clamav-milter

Next, make a copy of the /etc/clamsmtpd.conf file:

cp /etc/clamsmtpd.conf /etc/clamsmtpd.conf_orig

Open the /etc/clamsmtpd.conf file:

nano /etc/clamsmtpd.conf

Change the following directives, to make them look like this:

OutAddress: 10026

Listen: 127.0.0.1:10025

ClamAddress: /run/clamav/clamd.ctl

PidFile: /run/clamsmtp/clamsmtpd.pid

Create the socket directory and set the proper ownership for it:

mkdir /var/spool/postfix/clamav
chown -R clamav:postfix /var/spool/postfix/clamav

Next, open the /etc/postfix/main.cf file:

nano /etc/postfix/main.cf

Edit the smtpd_milters and non_smtpd_milters lines, to make them look like this:

smtpd_milters = unix:/var/spool/postfix/spamass/spamass.sock unix:/run/clamav/clamav-milter.ctl unix:/run/opendkim/opendkim.sock unix:/run/opendmarc/opendmarc.sock
non_smtpd_milters = unix:/var/spool/postfix/spamass/spamass.sock unix:/run/clamav/clamav-milter.ctl unix:/run/opendkim/opendkim.sock unix:/run/opendmarc/opendmarc.sock

Restart services and update the virus signatures:

systemctl restart clamav-daemon
systemctl restart clamav-milter
freshclam
systemctl restart postfix

19.22.1. Test email virus detection


You can check if ClamAV is scanning incoming and outgoing emails by sending an email from an email address on a different host to an email address on the server that you are configuring, and vice versa. If you check the source of such an email (in Thunderbird, in the opened message window, go to ‘More’ > ‘View Source’), you will see two lines like this:

X-Virus-Scanned: clamav-milter 0.102.4 at mail.example.com
X-Virus-Status: Clean

To check if the antivirus detects viruses included in the email attachment or in the email body, do the following experiment:

Create the EICAR Anti-Virus Test File by saving the following string in a text file called testing:

X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*

The EICAR test file simulates a real virus, without causing any harm. Attach the testing file to an email and send that email to an email address hosted on the server that you are configuring. Be aware that some email providers, such as mail.com will refuse to send an email with an attachedment that contains a test virus. When trying to send such a message, you will receive a ‘Mail delivery failed: returning message to sender’ message. On the other hand, other email providers, such as proton.com will let you send the email.

If the real-time email virus detection works as expected, you shouldn’t receive the email with the ‘infected’ attachment in the mailbox located on the server that you are configuring. Instead, in the /var/log/clamav/clamav.log file, you will see a line this this:

Sat Mar 05 11:10:38 2021 -> fd[10]: Eicar-Signature(69630e4574ec6798239b091cda43dca0:69) FOUND

You will see a similar line if you try to send an ‘infected’ email from the server you are configuring, to an email account on an external host. In this situation, if you check the /var/log/mail.log file, you will also see a line like the one shown below, which will indicate that the antivirus has prevented the ‘infected’ email from being sent, although the message will appear as successfully sent in Roundcube:

milter-hold: END-OF-MESSAGE from mail.example.com[123.123.123.123]: milter triggers HOLD action; from=<admin@example.com> to=<username@recipientdomain.com> proto=ESMTP helo=<mail.example.com>

Therefore, ClamAV deletes immediately any infected incoming or outgoing email. As mentioned, an infected outgoing email will have a copy saved in the Sent folder, although it won’t be actually sent.

You can repeat the experiment from above, by archiving the testing file in different formats (zip, tar.gz, tar.xz, tar.bz2, etc.) and attaching the archive to the test email, to test if ClamAV can detect viruses hidden inside archives, or by including the EICAR string in the body of the email. The EICAR string wasn’t created to be detected when surrounded by other texts, so, if you include it in the body of an email, make sure to include it alone, without any other text lines above or below it.

19.22.2. Remove the X-Virus-Scanned and the X-Virus-Status headers from all emails


After you run the tests described above and verify that the antivirus works as expected, you should remove the X-Virus-Scanned and the X-Virus-Status headers from outgoing and incoming emails, because they can give potential attackers information about clamav-milter‘s existence and version. To remove this line from the headers of all the emails, edit the /etc/clamav/clamav-milter.conf file:

nano /etc/clamav/clamav-milter.conf

Comment out the AddHeader Replace parameter like this:

#AddHeader Replace

Then restart clamav-milter:

systemctl restart clamav-milter

19.23. Install and integrate Postgrey


Postgrey is an application that implements ‘greylisting’ to fight spam. ‘Greylisting’ means that when the mail server receives a request for email delivery from an external SMTP server whose IP address is seen for the first time, it neither blacklists it, nor whitelists it. It just responds with a temporary SMTP error code, to test if the external server will try again after a few minutes, as it is required by the official RFC rules and as all the legitimate SMTP servers will do. If the external server tries again after a specified time, the email is accepted. This approach drastically reduces spam, because in general spammers use compromised mail servers to send as many spam emails as possible in a short period of time, since their behavior can be noticed, the compromised system can be patched, or their IP can be included on public blacklists. Thus, the typical spam sending SMTP server will try to deliver an email only once.

To install Postgrey run:

apt-get install postgrey

Edit the /etc/postfix/main.cf file:

nano /etc/postfix/main.cf

Uncomment the following line located at the end of the smtpd_recipient_restrictions block, before reject_rbl_client zen.spamhaus.org, to make it look like this:

smtpd_recipient_restrictions =
            ...
            check_policy_service inet:localhost:60000,
            reject_rbl_client zen.spamhaus.org,
            permit

Restart Postfix:

systemctl restart postfix

Make a copy of the /etc/default/postgrey file:

cp /etc/default/postgrey /etc/default/postgrey_orig

Edit the /etc/default/postgrey file:

nano /etc/default/postgrey

Edit the POSTGREY_OPTS and the POSTGREY_TEXT directives. Make them look like this:

POSTGREY_OPTS="--inet=60000 --delay=199 --max-age=35 --auto-whitelist-clients=5"

POSTGREY_TEXT="Please come back later."

The options used in the POSTGREY_OPTS parameter are explained below:

--inet=60000 make Postgrey listen on port 60000 on localhost, both on IPv4 and IPv6;

--delay=199 reject the emails from new IPs for a minimum time span of 199 seconds;

--max-age=35 delete recorded IPs older than 35 days since the last time that they have been seen

--auto-whitelist-clients=5 whitelist a client only after the 5th successful email delivery

The text included in the POSTGREY_TEXT parameter is the customized rejection text.

Postgrey comes with two important configuration files: /etc/postgrey/whitelist_recipients and /etc/postgrey/whitelist_clients.

The /etc/postgrey/whitelist_recipients file offers a quick way to bypass all greylisting mechanisms. In this file you can include all the email addresses hosted on the server for which you want to receive emails from any external server, including potential spammers. This can be useful for public addresses that should receive emails instantly, such as postmaster@ or abuse@ (these two addresses have been already included in this file by default).

The /etc/postgrey/whitelist_clients file lets you control the whitelisting of various mail hosts. In this file you can include domains, IPs or regular expressions, to identify the mail servers that will be allowed to send emails without passing through the greylisting mechanism implemented by Postgrey, so that you can be sure that all emails coming from them will reach your mailboxes. By default, this file already contains a long list of mail servers that are known to have problems with resending emails, although they are legitimate mail servers.

You can add other domains to /etc/postgrey/whitelist_clients, such as domains that you know to be legitimate email senders, like the following:

mout.gmx.com
mail.com
aol.com
hotmail.com

Restart Postgrey:

systemctl restart postgrey

To see if Postgrey is listening to port 60000, run:

lsof -i :60000

The result should look like this:

COMMAND     PID     USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
postgrey  24520 postgrey    5u  IPv6 473078      0t0  TCP localhost:60000 (LISTEN)
postgrey  24520 postgrey    6u  IPv4 473079      0t0  TCP localhost:60000 (LISTEN)

To check if Postgrey is working as expected, you can send an email from an email account hosted on an external server, to an email account hosted on the server you are configuring. If the IP of the sending server is new, you will see a line similar to the one from below, in the /var/log/mail.log file:

mail postgrey[25613]: action=greylist, reason=new, client_name=sender.com, client_address=124.124.124.124/32, sender=usernameg@senderdomain.com, recipient=admin@example.com

Also, you will receive the email after a delay of 199 seconds (3.3 minutes) or more. In the header of the received email you can notice the X-Greylist header, that will look like this:

X-Greylist: delayed 303 seconds by postgrey-1.36 at mail.example.com;

19.23.1. Upgrading Postgrey


Since Postgrey has been installed from the official Debian repository, to upgrade it, all you need to do is to run apt-get update && apt-get dist-upgrade with a specific frequency, as described in the Maintenance steps chapter. This command will upgrade Postgrey if there is a new version available. Also, during these upgrades, the configuration changes implemented as described above, will be preserved.

19.24. Special Postfix settings

19.24.1. Blacklisting and whitelisting IPs and domains with Postfix


If you want to blacklist IPs, IP ranges, domains and subdomains, so that all the emails coming from them will be rejected, you need to edit the /etc/postfix/main.cf file:

nano /etc/postfix/main.cf

Add check_client_access hash:/etc/postfix/client_checks, right below smtpd_recipient_restrictions = like this:

smtpd_recipient_restrictions =
            check_client_access hash:/etc/postfix/client_checks,
            reject_sender_login_mismatch,
            permit_mynetworks,
            permit_sasl_authenticated,
            reject_invalid_hostname,
            ...

Next create a file called client_checks in the /etc/postfix directory:

nano  client_checks

Here, first list all the IPs, IP ranges or domains that you want rejected, and then all the IPs, IP ranges or domains that you want to whitelist, so that all the emails coming from them will be accepted, like the following:

example.com              REJECT 
123.123.123.123          REJECT
222.222.222.0/24         REJECT
2002:acf4::1             REJECT
2001:db8:1234::/48       REJECT
example2.org             OK
example3.net             OK
111.111.111.111          OK
232.232.232.0/24         OK
2002:be21::2             OK
2001:cf9:45::/48         OK

You can add an optional message to be sent by Postfix when rejecting an email like so:

example.com               REJECT     Your domain is blacklisted

Please note that when listing a domain, like example.com, with the REJECT directive, the emails from all its subdomains will also be rejected (the emails from sub1.example.com or sub2.example.com will also be rejected). In other words, listing a domain with REJECT will also blacklist its subdomains.

Next, create the lookup table file by running:

postmap client_checks

Run the postmap command again each time you modify the /etc/postfix/client_checks file.

In the situation in which you want to reject or accept the emails coming from specific email addresses, in spite of the Postfix policy for their domains or IPs, you can do so by using the check_sender_access directive. Open the /etc/postfix/main.cf file and add check_sender_access hash:/etc/postfix/sender_checks, right above check_client_access hash:/etc/postfix/client_checks, like this:

smtpd_recipient_restrictions =
       check_sender_access hash:/etc/postfix/sender_checks,
       check_client_access hash:/etc/postfix/client_checks,
       reject_sender_login_mismatch,
       permit_mynetworks,
       permit_sasl_authenticated,
       reject_invalid_hostname,
       ...

Then create the sender_checks file in the /etc/postfix directory:

nano sender_checks

Add the email addresses that you want to blacklist/whitelist like this:

contact@example3.com    REJECT
office@example4.net     OK

This will ensure that all the emails coming from contact@example3.com will be rejected, even if the example3.com domain is whitelisted in the /etc/postfix/client_checks file. It will also ensure that all the emails coming from office@example4.net will be accepted, even if the example4.net domain is blacklisted in the /etc/postfix/client_checks file or if the IP of the example4.net domain is included in a public blacklist like zen.spamhaus.org . Then run the postmap command:

postmap  sender_checks

Run the postmap command again each time you modify the /etc/postfix/sender_checks file.

For all the changes to take effect restart Postfix:

systemctl restart postfix

Please note that for the above settings to work correctly, the check_sender_access directive has to be placed before the check_client_access directive, and both of them must be placed before all the other directives in the smtpd_recipient_restrictions block.

19.24.2. Configuring alias email addresses


If you want all the emails coming to one email address to be automatically transferred to a different email address, you can do this by configuring an alias email address. For example you want all the emails received by support@somedomain.com to be sent automatically to office@anotherdomain.net. All you have to do is to use Postfix Admin to add support@somedomain.com as the alias of office@anotherdomain.net . So, log in to Postfix Admin at:

https://mail.example.com/net-pstfxdmn/public

Replace example.com with the main domain hosted on your server and net-pstfxdmn with the custom name of the Postfix Admin login page. Once logged in, go to ‘Domain List’, click on the domain of the email address that you want to set as alias, here it’s somedomain.com, next click on ‘Add Alias’, then, in the ‘Alias’ text box enter the first part of the email address that you want to set as alias, here support, then, in the ‘To’ text area enter one per line all the email addresses to which you want all the emails to be transferred; here we want all the emails to go to office@anotherdomain.net, so we’ll enter only office@anotherdomain.net . Then click on ‘Add Alias’. From now on, all the emails received by support@somedomain.com will be automatically transferred to office@anotherdomain.net.

19.24.3. The Return-Path header


If you look at the raw source code of a received email (in Thunderbird click ‘More’ on the bar above the message, then click ‘View Source’), you will notice a header called Return-Path. This header indicates the email address where the non-delivery receipts or bounce messages are to be sent. If you installed the mail server according to the instructions from above, the Return-Path email address of your outgoing emails will be the same as the ‘From’ email address. This is a good default behavior because when you send a message to an email address that is misspelled or doesn’t exist, you want to receive the ‘Undelivered Mail Returned to Sender’ message immediately in the same email account (mailbox) that you used to send the email, so as to know that you have to check the spelling and try again, search for the correct email address, etc. However, if you send an email to thousands of people, like when you run an email campaign using a mass-mailing program like phpList, it becomes important to set a Return-Path email address different from the ‘From’ address. This will ensure that you have all the bounce receipts in one place and you can analyze them later to decide if the respective recipients’ addresses need to be spelled differently, removed from the mailing list, etc. In the Install phpList chapter we describe how to create a special mailbox for bounce receipts (bounces@example.com) and how to configure phpList to use it as the Return-Path address. Since in that context the bounce receipts are received separately, they can be easily processed automatically by phpList with a specific freqency.

The Return-Path email address is often called the ‘return path’, ‘envelope from’, ‘reverse path’, ‘MAIL FROM’ or ‘bounce’ address. The email headers that specify it can be the Return-Path, envelope-from, Reverse-Path or X-Mailfrom headers. Sometimes only one or two of these headers appear in the raw source code of emails.

It is worth noting that some mailing list software incorporate identifiers into the ‘return path’ address to facilitate automatic handling of bounced emails. This technique is called Variable Envelope Return Path (VERP). For example, the entire recipient address can be included in the ‘return path’ address, having ‘@’ replaced with ‘=’, since a ‘return path’ with two ‘at’ signs would be invalid.

The ‘return path’ address should not be confused with the ‘reply to’ address, which is specified by the optional ‘Reply-To’ email header that can be added to emails by email clients and other mail sending software. The ‘reply to’ address is the email address that will receive the replies to the sent message.

19.24.4. Changing the From, Return-Path and X-Sender email addresses of outgoing emails


A very important directive in Postfix settings is the reject_sender_login_mismatch directive located in the smtpd_recipient_restrictions block, in the /etc/postfix/main.cf file. This directive prevents logged in email clients (like Roundcube), to send emails with a From address different from the email address that they are logged in with. Without this directive in place, any user that gets logged in to Postfix with a web application like Roundcube, can change the From address when sending an email, to literally anything that they want, even to nonexistent addresses with nonexistent domains. In this way they can send emails impersonating existing users on the same mail server or other users on other mail servers or nonexistent users. Thus, the reject_sender_login_mismatch has a very important role, by forcing users that log in with email clients like Roundcube, to use their real email address in the From field. They can still change the From address in their email client, but when they try to send the email, they will receive an error telling them that the server rejected the message.

However, there can be rare cases in which you want to replace the real From email address, as well as the Return-Path and X-Sender addresses, with different email addresses that exist on the same server. This is called ‘address rewriting’. For example, you want all the emails sent from your server from the address info@domain.com to appear as being sent from office@otherdomain.net. In other words, you want Postfix to rewrite the From, Return-Path and X-sender addresses for all the emails sent from info@domain.com, so that they appear as being sent from office@otherdomain.net. You can also add office@otherdomain.net as the Reply-To address to all the emails sent from info@domain.com. To be noted that the From, Return-Path and X-sender addresses are hosted on your server and the emails are sent to external email addresses, not to local ones.

To rewrite the From and X-sender address and to add a Reply-To address, first make sure that the following line is present at the bottom of the /etc/postfix/main.cf file:

header_checks = regexp:/etc/postfix/header_checks

Then edit the header_checks file:

nano /etc/postfix/header_checks

Enter the following lines at the bottom of this file:

/^From:.*info@domain.com/ REPLACE From: office@otherdomain.net
/^X-Sender:.*info@domain.com/ REPLACE X-Sender: office@otherdomain.net
/^From:.*info@domain.com/ PREPEND Reply-To: office@otherdomain.net

You can add multiple similar lines for different email addresses.

Then run the postmap command:

postmap /etc/postfix/header_checks

With these configurations you replaced the From and X-Sender address and added a Reply-To address to all the emails sent from info@domain.com. However, the Return-Path address, that can be seen by any recipient in the message’s source, remained info@domain.com . To change the Return-Path address to office@otherdomain.net , first edit the /etc/postfix/main.cf file:

nano /etc/postfix/main.cf

Add smtp_generic_maps = hash:/etc/postfix/generic-maps right below smtpd_sender_login_maps = proxy:mysql:/etc/postfix/mysql-sender-login-maps.cf .

Then create the generic-maps file like this:

nano /etc/postfix/generic-maps

Enter the following content in this file:

info@domain.com  office@otherdomain.net

You can enter multiple similar lines for other email addresses. If you had wanted all the emails coming from the domain.com domain to have office@otherdomain.net as Return-Path address, you would have entered:

@domain.com  office@otherdomain.net

Run the postmap command:

postmap /etc/postfix/generic-maps

Change ownership for the new files:

cd /etc/postfix
chown root:postfix generic-maps generic-maps.db

Restart Postfix:

systemctl restart postfix

Please keep in mind that the address rewriting configured as described above works only for emails sent to external email addresses. It won’t work for emails sent to email addresses hosted on the same server. Those emails will still appear as being sent from their real sending address.

19.25. S/MIME certificates for digital signing of emails and email encryption


“S/MIME” stands for “Secure/Multipurpose Internet Mail Extensions” and is a standard for public-key encryption and signing of MIME data. S/MIME certificates are different from the SSL/TLS certificates, and can be used to digitally sign emails and/or encrypt them. Companies like DigiCert, GlobalSign, Sectigo, etc. offer S/MIME certificates at various prices.

If you think of using S/MIME certificates to sign and encrypt your emails, to guarantee privacy, integrity and increase the receivers’ trust in your messages, you’ll have to take into account that many mail clients need to be manually configured to make them trust the Certificate Authority (CA) that issued the S/MIME certificate, otherwise they can display errors like The digital signature on this message isn’t valid or trusted, which instead of increasing trust will actually create distrust and suspicion.

For example, the Office 365 environment does not trust any publicly trusted or privately trusted Certificate Authority under which the S/MIME certificates have been issued and as a result, digital signing and email encryption based on S/MIME certificates has to be enabled manually in Office 365. It has to be manually enabled also in mobile apps such as ‘Microsoft Outlook for Android’, ‘Microsoft Outlook for iOS’ and in other mail clients. Since as described here, the manual configuration is a laborious process, it is likely that a substantial number of your email interlocutors will not have their software properly configured to trust your S/MIME certificates and consequently, they will have issues reading or trusting your messages.

In conclusion, at the moment, the best method to guarantee privacy, integrity and authenticity in email communication is to use OpenPGP key pairs in Thunderbird to sign and encrypt emails, as explained here.

If you don’t need to encrypt an email and you just want to send a formal request/proposal to a person and you feel it’s important to reassure the receiver that you are the real sender of the email, you can always write the message in a pdf document, sign it with your qualified digital certificate, then attach the pdf document to the email and send it. This implies that you have a ‘qualified digital certificate’ in your name. Many companies offer such certificates at reasonable prices. Since digital signatures created using a ‘qualified digital certificate’ are even more secure and trustworthy than wet ink signatures, the receiver can be confident that the pdf document comes from you and it hasn’t been modified in transit.

19.26. Undeserved email deliverability issues


After you install the mail server as described above, you will want to send test emails to email addresses offered by different email providers, to see if they reach their target and are not considered spam. This is a way to test if the SPF, DKIM and DMARC records are effective in proving to external mail servers that the emails coming from your mail server are legitimate.

You can expect that all the big email providers will accept your emails in the ‘Inbox’ (where they belong), with the exception of Microsoft and Yahoo. Even if your mail server is perfectly configured and your server’s IP is not included on any public blacklist (or blocklist), it can still be included on private blacklists, without your fault. Big companies like Microsoft or Yahoo have their own private blacklists in which they include IPs and even entire IP ranges, based on criteria that they keep for themselves. Your IP can be included on such a private blacklist because in the past other clients used that IP to send spam, or just because it is part of a range of IPs that were identified as spam-sending IPs, or even because your IP is part of a range of IPs that have been known to be dynamically allocated and not static IPs. There may be also other reasons. So, even if you made absolutely no mistake and your mail server is perfectly configured, your emails can be automatically filtered as spam by Yahoo, or they can be even rejected by Microsoft, with an ‘Undelivered Mail Returned to Sender’ message similar to this:

hotmail-com.olc.protection.outlook.com[104.47.58.33] said: 550 5.7.1
    Unfortunately, messages from [123.123.123.123] weren't sent. Please contact
    your Internet service provider since part of their network is on our block
    list (S3150). You can also refer your provider to
    http://mail.live.com/mail/troubleshooting.aspx#errors.

where 123.123.123.123 is the IP of your server.

The first thing to do in this situation, is to try to delist your IP from the private blacklists of the two providers.

If your emails are rejected by ‘office.com’ email accounts, you can try to delist your IP on the delisting portal available at: https://sender.office.com . However, if your emails are rejected by ‘hotmail.com’/’live.com’/’outlook.com’ email accounts, or by ‘yahoo.com’ emails accounts, you should send a support request by filling a form, as shown below:

For Microsoft, the support form is:

https://support.microsoft.com/en-us/getsupport?oaspworkflow=start_1.0.0.0&wfname=capsub&productkey=edfsmsbl3&locale=en-us&ccsid=635900597399910848

For Yahoo, the support form is:

https://io.help.yahoo.com/contact/index?page=contactform&locale=en_US&token=Zh/BBVqXzLHlIbokbUqVWTUbuuQeXGkGnZzhKR2JQ4O6mMQdy9JSWdtWFXvjthcYCRj9bUIFfycOfG+4GOHPHoOGa8HwDO2+0kYRtTcdR8O13Mvs9cOruJ0TlC3hh4bCEtPlZ0yk7fvp1MFjGnAOWw==&selectedChannel=email-icon&guccounter=1

In these forms you should explain shortly that you rented the server recently, that you didn’t send any bulk emails, that you just sent test emails and that they should remove your IP from their blacklist, because all your emails are rejected / filtered as spam by their mail servers.

You will receive an automated answer after about one day. If they refuse to delist your IP you should reply and insist. If they suggest that you should join their Junk Email Reporting program (JMRP) and Smart Network Data Services program (SNDS), it’s recommended not to join. It won’t make any difference if you do. They can ask you to prove that you acquired the IP recently by attaching the first invoice from your hosting provider and the email that the hosting provider sent you when you signed up with them, confirming the opening of your account (you can copy this email in a PDF file). You can send them these documents after you remove your personal data from them. If they refuse to delist your IP, you can insist, stressing that you rented the server with that IP only a short time before and that you didn’t send any bulk emails.

If even after your explanations, they refuse to delist your IP, the next step is to open a support ticket with your hosting provider. You should explain the same things to them, insisting that you only sent test emails, that the server is perfectly configured and that other major email providers accept your emails in the Inbox. You should urge them to contact Microsoft/Yahoo support and ask that they delist your IP or the IP range to which your IP belongs, because this situation was not caused by any mistakes on your part and it impairs your email communication.

Depending on your experience with Microsoft and Yahoo support, you can even display a message similar to the following, on your newsletter subscription page (if you have one):

“We recommend that you sign up with an email address with a different domain than office.com, outlook.com, live.com, hotmail.com or yahoo.com. Apart from the privacy concerns that they arise, these types of email accounts are known for rejecting perfectly legitimate emails or tagging them as spam.”

It’s possible that even aol.com accounts can filter the emails coming from your server as spam, but after the user marks an email as ‘Not spam’ (by clicking on the OK sign on the upper bar), all the emails that will follow will land in the ‘Inbox’.

19.26.1. UCEPROTECTL3 blacklist


If you find that your server’s IP has been included on the UCEPROTECTL3 blacklist (http://www.uceprotect.net/en/rblcheck.php), you should know that this is not a serious blacklist and that it is not used for mail filtering by many system administrators. However, it seems that it is used by Microsoft’s mail servers, which makes it problematic. If your IP is only included on the UCEPROTECT Level 3 blacklist and not on the Level 2 or Level 1 blacklists, it means that you didn’t send any spam but other IPs in the same range as your IP sent spam. By including your IP on the Level 3 blacklist, they try to make you complain to your hosting provider, and in this way, your hosting provider might feel pressured to take action against the spam-sending IPs. They also ask for money for every delisting from the UCEPROTECTL3 blacklist, in spite of the fact that the IP user didn’t do anything wrong. Also, users who payed for delisting confessed that their IPs were delisted, but that didn’t help, since their emails were still rejected by Microsoft. This way of asking for money in exchange for delisting IPs, which none of the reputable public blacklists practice, is suspect and unethical. Under no circumstance should you pay to have your IP delisted from a blacklist like UCEPROTECTL3.

The solution to this problem is to contact your hosting provider and describe the situation, pointing out that according to UCEPROTECT’s own words, you didn’t send any spam, but other IPs from the same range, did. Your hosting provider may stop the real spammers and this will cause the delisting of your IP, or the delisting may happen automatically, if no IPs in the same range as yours will send spam within a certain period of time.

You can send your questions and comments to: