Mail server HOWTO

Jump to: navigation, search

The task of setting up a mail server can be seen as complicated because there are many different options and configurations available. Many times there are numerous ways to achieve the same thing. As this page describes many different options, depending on your needs you may decide to use some parts and not use others.

Script

This HOWTO has been implemented in a bash script which can be downloaded using Git. If git is not installed, install it as root:

zypper in git-core

Then clone the repository with your regular user:

git clone https://github.com/freekdk/GenPDSDM.git

GenPDSDM stands for Generation of Postfix, Dovecot with SPL, DKIM and DMARC. It creates a folder, named GenPDSDM, with the script and documentation.

Server

The standard email application is Postfix. It is installed and enabled in a default installation of openSUSE. This means it should already be active after the system has been started for the first time. This also means that the setup application /usr/sbin/config.postfix has been run. This application may initialize quite a few parameters in the files /etc/postfix/main.cf and /etc/postfix/master.cf. Among others the name of your system and the parameter mynetworks. After the initial initialization a parameter is set to prevent further running this application again. It is also run when using the mail module of YaST, which sets parameters in /etc/sysconfig/postfix.

However the assumption of this HOWTO is that only the initialization, right after booting the system for the first time, has been done.

Inbound

The main types of connections for inbound mail are:

  • POP or IMAP - these are client protocols and mostly used by user mail clients, but a mail server can also retrieve mail using these protocols.
  • SMTP - Simple Mail Transfer Protocol is the main protocol used by mail server to, well, transfer mail.

POP & IMAP

If you want to fetch mail from more than one mailbox, fetchmail is the tool to use. It will get mail from various servers with various protocols and various people.

First see that fetchmail is installed. Next you need to configure /etc/fetchmailrc. Open it with your favourite editor as root. Each mailbox needs to be configured separately. Things you need to know is:

  • local user id
  • remote server
  • remote user id
  • remote password

Now for each remote mailbox write:

poll remote.example.com
     proto auto
     user "remote_userid"
     pass "remote_password"
     is local_userid

This will deliver the mail from the mailbox remote_userid@remote.example.com via the SMTP server (MTA) on localhost in the mailbox for user local_userid, wherever that mailbox is configured in the MTA. Do this for any and all remote mailboxes. See that /etc/fetchmailrc is chmod 700.

Read man fetchmail for more info. Also there is a program fetchmailconf which could be used.

Now you want to do this automatically. As root you type

# systemctl enable fetchmail.service

This will automatically start fetchmail when booting the machine. To start it immediately type

# systemctl start fetchmail.service

This will get the mail every 10 minutes. You can change this by changing FETCHMAIL_POLLING_INTERVAL=600 to any other time interval.

Do NOT set this lower than 600 seconds (10 Minutes), as it will load the provider's mail server, unecessarily, and may even be against their Terms of Service

FETCHMAIL_POLLING_INTERVAL and other parameters can be changed either by editing /etc/sysconfig/fetchmail or by using YaST's sysconfig Editor (System -> /etc/sysconfig Editor): choose Network -> Mail -> Fetchmail.

Configure your client to get mail via mbox in case your MTA uses the simplest method of delivery.

Alternative for fetchmail

The recommended way is to configure POP and/or IMAP accounts in the email client of the users of these accounts.

SMTP

You can get mail directly sent to your server. For this you need several things:

  • Domain name
  • Fixed IP address
  • Correct MX records

If you have a Dynamic IP address, seriously reconsider to apply for a fixed address. You are on your own.

You should have entered the name of your system in /etc/hostname; in this article we use the name 'ehost' and the domain name is 'example.com' and the IP address is 192.168.1.25. This means that you should have entered in /etc/hosts an entry

192.168.1.25 ehost.example.com ehost

First see that a fresh installed postfix is running otherwise install postfix and start and enable it:

zypper in --no-recommends postfix telnet
systemctl enable postfix.service
systemctl start postfix.service
systemctl status postfix.service

Next check that it is properly running with a telnet session:

# telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 ehost.example.com ESMTP Postfix

Use quit to exit.

  • Next see that the outside world is able to connect to port 25.
  • At this moment postfix is only listening to port 25 on localhost, so you need to make it listen to outside systems by giving the next command and restarting postfix by using the recommended way of changing parameters in /etc/postfix/main.cf, using postconf:
postconf -e "inet_interfaces = all"
  • Restart postfix by using:
systemctl restart postfix.service
  • You also need to open your firewall with YaST or with, assuming your interface is eth0 and you are using a fresh installed firewalld:
systemctl enable firewalld.service
systemctl start firewalld.service
firewall-cmd --zone=public --add-interface=eth0
firewall-cmd --zone=public --add-service=smtp
  • Also forward port 25 or for testing another port in your router to this port 25 in your system. Test it with This site.
The standard configuration of postfix does protect you already from being a spam relay.
  • The next step is to customize postfix with your identity (do this by replacing example.com by your domain name and hostname by smtp.example.com; this is a common name for a receiving email server). The name of your host in /etc/hostname can be different from this name, but should also be mentioned in the list of domains to be considered as email domains on your system. You also want email send by accounts from this domain to have addresses like account@example.com . So, four things should be added and/or changed:
postconf -e "myhostname = smtp.example.com"
postconf -e "mydomain = example.com"
postconf -e "mydestination = \$myhostname, \$mydomain, localhost, localhost.\$mydomain, $(cat /etc/hostname).\$mydomain"
postconf -e "myorigin = \$mydomain"
The backslash in front of the $ is needed to get the dollar in this parameter.
  • Restart postfix. You will now be able to receive mail for login accounts (e.g. root) on example.com or the other domains mentioned in mydestination, assuming that these domains have associated IP addresses in the DNS or in /etc/hosts of the sending system.
  • Install packet mailx on your system, and use the command:
mailx -s test root@example.com < test.txt

to send the content of test.txt (do 'echo "test line" > test.txt' to create it) to the root account on your system. After the @ you can test the other domains as well. The message should arrive in /var/spool/mail/root. The sender in the message should be root@example.com.

Aliases

Postfix in the above status will accept email addresses that have names before the @ that correspond with usernames on the system and with names in the first row of the file /etc/aliases. The names in the first row end with a colon(:), behind the colon another name should be present, which should be the name of an account on the system or another name in this file. Following the recursion it should end in a valid destination, otherwise the sender will receive an Undelivered message.

Mail will be put in /var/spool/mail/user_login. Most of the time you might want to receive mail under something else then your login. If your login is user, you probably would like an email address like firstname.lastname@example.com

You then link an alias to user with the name firstname.lastname . This can be done by editing /etc/aliases. Add the following at the end of the file:

firstname.lastname:    user

You will see some there already. If you want a generic address like sales@example.com and that to be received by several people, add the following line:

sales:                 user_1, \user_2, \user_3

After additions, you need to run newaliases. To make it work you need the following command:

postconf "alias_maps = lmdb:/etc/aliases"

You may also want the reverse, login_user should have a sender email address like firstname.lastname. This is done in the file /etc/postfix/sender_canonical in an entry like:

login_user firstname.lastname

or even:

login_user some_name@some_domain

After changing this file, don't forget to run the command:

postmap /etc/postfix/sender_canonical

It also requires in /etc/postfix/main.cf an entry for sender_canonical_maps with the following script:

db_type=$(postconf default_database_type)
db_type=${db_type##* }
postconf "sender_canonical_maps = ${db_type}:/etc/postfix/sender_canonical"

Enhancing protection of incoming mail on port smtp(25)

To enhance protection on incoming mail, mainly against spam, we need to take a few more measures. The recommended commands are:

postconf "smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks,\
 reject_invalid_helo_hostname, reject_non_fqdn_sender, reject_unknown_sender_domain,\
 reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unauth_destination,\
 reject_rbl_client zen.spamhaus.org, permit"
postconf -M smtp/type='smtp    inet    n    -    n    -    -    smtpd -o smtpd_helo_required=yes'

The first command adds a number of reject conditions on which reject_rbl_client zen.spamhaus.org. This one uses a list of black listed IP addresses from which you reject any message. Your ISP may have added your IP address to this list. The value reject_unauth_destination rejects all messages with a destination not in mydestination, relaydomains and a few others (see the postfix manual). It is the main condition to prevent your server from being a general mail relay.

Note that the first command is entered in main.cf which works on all incoming mail entries of postfix. So these other mail entries may need another set or an empty set of parameters for smtpd_recipient_restrictions.

Allowing incoming email to outside addresses

In the current state of postfix email from IP addresses mentioned in mynetworks are allowed to be send on to outside addresses. Messages coming from other IP addresses are only allowed to local users who have an account on the system, so also the ones which are mentioned in /etc/aliases. However the messages to outside users are send in the clear. See the chapter Outgoing to send these on an encrypted connection.

However we want authenticated users to be able to send email to outside addresses from a network address outside mynetworks. Authentication involves sending username and password on the connection to our server, so we want this connection to be secured. There are several ways for postfix to authenticate users. We will do this using dovecot, the POP3 and IMAP server, which we restrict to use the standard login accounts of the system. A side effect of using a secured connection is that also other incoming messages are able to come in over a secured connection. A sending server will check if a secured connection is possible and, if so, will use that.

Dovecot works with maildir as opposed to mbox, which means that we will not use /var/spool/mail/login_user/ as the destination of email messages, but a folder in the home folder of a user, indicated as ~/Maildir/ . For this we need to configure postfix with:

postconf -e "home_mailbox = Maildir/"

Configuring dovecot

First we install dovecot:

zypper in --no-recommends dovecot
We use --no-recommends to prevent lots of unneeded packages to be installed.

In the file /etc/dovecot/dovecot.conf we only add one line to alter the default. After line with: "#protocols = imap pop3 lmtp submission" enter:

protocols = imap

This restricts dovecot to serve only the imap protocol. Also note that the default line with listen means that dovecot can be accessed from all IP addresses. The imap port, 143, will be opened when dovecot is activated. The connection to this port is not secured, so username and password are send in the clear.

In /etc/dovecot/conf.d/10-ssl.conf you can remove the comment character # in front of:

ssl = yes
ssl_cert = </etc/ssl/private/dovecot.crt
ssl_key = </etc/ssl/private/dovecot.pem

and

ssl_dh = </etc/dovecot/dh.pem

This means that the server is also accessible via imaps, port 993. However you need to populate the three mentioned files. We come to that later.

For postfix we need to change file /etc/dovecot/conf.d/10-master.conf. Find the line # Postfix smtp-auth and remove the three comment characters # and insert two lines so it reads:

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

The last changes are in /etc/dovecot/conf.d/10-mail.conf; find the line with #mail_location and add after that line:

mail_location = maildir:~/Maildir

Find after the line namespace inbox { the line #prefix and add after that line the line:

prefix = INBOX.

With the current setting in the firewall both ports, 143 (imap) and 993 (imaps), are only accessible via localhost. The recommendation is that imap is only accessible from your local network and imaps from all IP-address. So the commands for the firewall, assuming zone public contains the network interface, are:

firewall-cmd --zone=internal --add-source=192.168.0.0/16 # your local network
firewall-cmd --zone=internal --add-service=imap
firewall-cmd --zone=public --add-service=imaps

The last issue is to populate the certificate files. The dovecot package offers a script in /usr/share/doc/packages/dovecot/mkcert.sh. Before you run that script you must edit the file dovecot-openssl.cnf and enter values for C, ST, L, O, CN(=imap.example.com) and emailAddress(=postmaster@example.com). The certificate you generate will have a live time of 365 days, so you may change the value of 365 at line 37 in 3650 (10 years). After editing these two files to suite your need, do the following:

cd /usr/share/doc/packages/dovecot/
. ./mkcert.sh

Dovecot also requires a file /etc/dovecot/dh.pem, which can be generated with:

openssl dhparam -out /etc/dovecot/dh.pem 4096
Icon-warning.png
Warning: Generation of this file takes quite some time; on a Raspberry Pi 4B (1.5 GHz) 160 minutes

After this you should be enable and start dovecot with:

systemctl enable dovecot.service
systemctl start dovecot.service
The generated certificate is a so-called self-signed certificate. This means that connecting the first time using imaps will raise a question in the email client if this certificate should be accepted or not. The assumption here is that only a few users will ever use this connection legitimate, so this is acceptable. The client will store the public certificate, so the next time the client will immediately accept the connection.

Using Let's Encrypt to generate and maintain certificates

In case you want properly signed certificates, so your users do not need to accept the self-signed certificates, you can use certificates signed by ["Let's Encrypt"]. See this wiki page on how to generate and maintain these (you need to renew these each 3 month).

It is assumed that you have been able to generate a Let's Encrypt certificate for either example.com and *.example.com or imap.example.com. Such a certificate is available in respectively /etc/letsencrypt/live/example.com/ or /etc/letsencrypt/live/imap.example.com/. The files fulchain.pem and privkey.pem are needed to replace respectively /etc/ssl/private/dovecot.crt and /etc/ssl/private/dovecot.pem. Note that the last one should have been protected by:

chmod 600 /etc/ssl/private/dovecot.pem

Later you will see how to add these to postfix.

Configuring postfix to allow validated users to send to outside addresses

These type of clients will use another port to deliver their messages, most likely to be send outside. This port is called the submission port which has the value 587. This means that we have to add support for this port in /etc/postfix/master.cf. The command is:

postconf -M submission/type='submission inet n       -       n       -       -       smtpd\
  -o syslog_name=postfix/submission\
  -o smtpd_tls_security_level=encrypt\
  -o content_filter=\
  -o smtpd_sasl_auth_enable=yes\
  -o smtpd_tls_auth_only=yes\
  -o smtpd_reject_unlisted_recipient=no\
  -o smtpd_recipient_restrictions=\
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject\
  -o milter_macro_daemon_name=ORIGINATING\
  -o disable_vrfy_command=yes'

For a detailed explanation on these parameters, you are referred to the documentation on http://www.postfix.org In short, you need to use an encrypted connection, and to authenticate for mail presentation via this port. Apart from this entry in the master file, you also need to define parameters in /etc/postfix/main.cf. These are:

postconf "smtpd_sasl_path = private/auth"
postconf "smtpd_sasl_type = dovecot"
postconf "smtpd_tls_auth_only = yes"
postconf "smtpd_tls_CAfile = /etc/postfix/cacert.pem"
postconf "smtpd_tls_cert_file = /etc/postfix/newcert.pem"
postconf "smtpd_tls_key_file = /etc/postfix/newkey.pem"
db_type=$(postconf default_database_type)
db_type=${db_type##* }
postconf "smtpd_tls_session_cache_database = ${db_type}:/var/lib/postfix/smtpd_tls_session_cache"
postconf "smtpd_helo_required = yes"
postconf "smtpd_tls_loglevel = 1"

As you may have noticed we need a few files with certificates to encrypt the connection to port 587. As with the connection to dovecot, we can use a self-signed certificate, again because we assume only a few users will be using that port to send email. The host name used to make a connection to this port is often named smtp.example.com or mail.example.com (remember to enter these names in your DNS for example.com; maybe by using a CNAME to example.com). Below are the commands to generate the certificates. The first one is to generate a signing key:

/usr/share/ssl/misc/CA.pl -newca

You will be asked to enter a pass phase twice and values for Country Code, State or Province Name, Locality Name, Organization Name, Organizational Unit Name, Common Name, and Email Address of the Certificate Authority. Remember the password you entered! At the end press the Enter key when asked for A challenge password and An optional company name. For Common Name entering "Certificate Authority" is recommended and for Email Address "ca@example.com". Except for the Country Code, Common Name and Email Address, you may leave the values empty. The keys are present in a newly created folder ./demoCA/. The generated certificate has a lifetime of 1095 days, which you may want to change in 10 years or 3653 days, with:

openssl x509 -in demoCA/cacert.pem -days 3653 -out ./cacert.pem -signkey demoCA/private/cakey.pem

This is meant to replace the original one by:

mv ./cacert.pem demoCA/

To display this certificate you may use:

openssl x509 -in demoCA/cacert.pem -text

Next we generate a private key for host example.com and create an unsigned public key certificate. You may leave out -subj and the part between quotes, but than you will be asked to enter these values, which should be the same you entered earlier.

openssl req -new -nodes -subj '/CN=smtp.example.com/O=Your organisation name/C=XY/ST=Your State or Province/L=Your City Name/emailAddress=postmaster@example.com' -keyout newkey.pem -out newreq.pem

Next you need to sign this certificate with the private key of the Certificate Authority.

openssl ca -days 3653 -out newcert.pem -infiles newreq.pem

The first question asked is for the above used pass phase and the final question is weather to sign the certificate or not; obviously Yes. To display this certificate you may use:

openssl x509 -in newcert.pem -text

The last action is to move the generated files to there final destination; see above where these are expected to be located.

cp demoCA/cacert.pem newkey.pem newcert.pem /etc/postfix/
chmod 644 /etc/postfix/cacert.pem /etc/postfix/newcert.pem

In case you want to use Let's Encrypt certificates you either have the certificate for the name smtp.example.com or for example.com and *.example.com in either /etc/letsencrypt/live/smtp.example.com/ or /etc/letsencrypt/live/example.com/. fullchain.pem should be copied to both /etc/postfix/cacert.pem and /etc/postfix/newcert.pem and privkey.pem to /etc/postfix/newkey.pem.

Encrypted access to the general entry point of postfix

On port 25 all email with a destination for your domain will enter. We discussed already some of the security issues associated with this entry point. At least we want to make it possible for senders to send a message via an encrypted connection. This means that the server should provide the STARTTLS service. For this postfix needs a number of parameters active on the service listening on port 25. These parameters are:

postconf "smtpd_tls_security_level = may"
postconf "smtpd_sasl_local_domain = \$myhostname"
postconf "smtpd_tls_received_header = yes"
postconf "smtpd_noop_commands = etrn"

The process tlsmgr needs to be activated by:

postconf -M tlsmgr/type='tlsmgr    unix  -       -       n       1000?   1       tlsmgr'

Restart postfix with:

systemctl restart postfix.service

You can test the secured connection with:

openssl s_client -connect localhost:25 -starttls smtp

This should give a lot of output. You can do an smtp session and finally or immediately enter: quit to exit the STARTTLS session.

Outgoing, preferably over an encrypted connection

This will be about all the mail that goes from you or your co-users to the Internet.

Please note that this section of the guide assumes that you have followed the steps above already, especially the sections about certification. Simply following what's described below will leave you with some troubleshooting to do.

In the current state of your server postfix will look at the destination address of the message. If its destination is outside your system, it will lookup the MX record of the domain part, the part after the @, or, if that record does not exist, the A and AAAA records (IPv4 and IPv6 addresses). It will also look up the A and AAAA records of the name(s) in the MX record. So it will have one or more IP addresses to send the message to. It will try to connect to port 25 of these IP addresses and, if the connection succeeds, will present HELO/EHLO, MAIL FROM and RCPT TO commands. There are many reasons why these destination servers will deny access when these commands are presented. One of these reasons is that the connection is not encrypted. So to enhance acceptance of your messages, we will configure postfix to try to use an encrypted (TLS) connection when possible. The destination server indicates this by offering a STARTTLS parameter. For this we need to add a number of lines to /etc/postfix/main.cf:

postconf "smtp_tls_security_level = may"
postconf "smtp_tls_ciphers = medium"

This is the so-called Opportunistic TLS. It allows access to sites with low and no security settings. When using an encrypted wrapper this will most probably generate some errors. Use:

postconf "smtp_tls_wrappermode = yes"
postconf "smtp_tls_security_level = encrypt"

Some protocols are insecure and should not be used. For postfix < 3.6 (Leap < 15) use:

postconf "smtp_tls_protocols = !SSLv2, !SSLv3"

otherwise (Tumbleweed, Leap >= 15.x):

postconf "smtp_tls_protocols = >=TLSv1, <=TLSv1.3"

The next parameter logs the connections made.

postconf "smtp_tls_note_starttls_offer = yes"

The next parameter logs even more. When all is well, set it to 0.

postconf "smtp_tls_loglevel = 1"

The next parameter defines the location of cached information for later reuse.

db_type=$(postconf default_database_type)
db_type=${db_type##* }
postconf "smtp_tls_session_cache_database = ${db_type}:/var/lib/postfix/smtp_tls_session_cache"

Obviously you need to restart/reload postfix.

Pointing your Email program

The simplest way to send your email to others from your client, is to hand over your email to a server at your provider. Most of the time this will be an SMTP server. Most likely you need a login name and password. Read the documentation of your email-client on how to configure this.

In case you or co-users of your computer need to send email from an application or script it will be more easy to send it to the MTA (in this article postfix) of your machine.

Sending email via the local server to/via the provider for all

The standard for postfix is that it will send email to an external address directly to the server of that address. So if you point your email clients to send email to this postfix server, postfix will send this on. However most receiving email servers have restrictions on accepting messages from somewhere, so it is likely that your email is not accepted. A safe method is to send all your outgoing email to the server of your provider. Postfix uses relayhost for that purpose. Most likely your ISP requires a username and password and often not the standard access port (25) is used, but the submission port (587). So if your ISP does not use access control and the standard port, use:

postconf "relayhost = smtp.example.net"

this means send it to the MX record(s) of smtp.example.net, else use:

postconf "relayhost = [smtp.example.net]:587"
db_type=$(postconf default_database_type)
db_type=${db_type##* }
postconf "smtp_sasl_password_maps = ${db_type}:/etc/postfix/sasl_passwd"
postconf "smtp_sasl_auth_enable = yes"
postconf "smtp_sasl_tls_security_options = noanonymous"
postconf "smtp_sasl_security_options = noanonymous"
postconf "smtp_use_tls = yes"

As you can see this requires the file /etc/postfix/sasl_passwd, in which you enter:

echo "[smtp.example.net]:587 username:password" >> /etc/postfix/sasl_passwd

After that you give the command:

postmap /etc/postfix/sasl_passwd

Both files should be properly protected; owned by root:root and "chmod 0600". Restart postfix with systemctl restart postfix.service and configure your email client to point to the SMTP server at localhost.

The square brackets around smtp.example.net indicate that the system should not use MX records of that name to send the email to, but directly to the A or AAAA records of that name. If these brackets are not used messages are send to the servers mentioned in the MX records of that name.

Filtering incoming email

Filtering on origin (o.a. using SPF) and destination of messages

There are two entry points for email, smtp (port 25) and submission (port 587). Another entry point submissions (465) is discouraged. Both use different checks, The one for submission is in the file /etc/postfix/master.cf. See the postconf -M submission command above. More specific the parameter smtpd_recipient_restrictions, which needs less checks, because all users need to be authenticated. The main entry point is smtp, which can be accessed from anywhere. The most important feature is access checks. This means that any email with a destination not explicitly configured here should be denied. This is done with the parameter smtpd_recipient_restrictions. The value of this parameter, we use here, is configured with:

postconf smtpd_recipient_restrictions = permit_sasl_authenticated,\
permit_mynetworks, reject_invalid_helo_hostname, reject_non_fqdn_sender,\
reject_unknown_sender_domain, reject_non_fqdn_recipient,\
reject_unknown_recipient_domain, reject_unauth_destination,\
reject_rbl_client zen.spamhaus.org,\
check_policy_service unix:private/policyd-spf"

These checks are performed at the very begin of the communication with your server. This is even before any content of the message has been transferred. It means that the sending server gets denied access to your server and this sending server is responsible to inform the sender if that is a legitimate sender.

Note this is used only on incoming messages on port smtp(25); on the other port submission(587) in master.cf an explicit value for this parameter is present, which disables the value in main.cf.

It means: an incoming messages is tested according to the sequence of the elements in this parameter.

  • When a user sends credentials that are properly authenticated, permit_sasl_authenticed allows access.
  • When a user sends a message from an IP address in mynetworks access is allowed.
  • When improper values are presented in HELO/EHLO, MAIL FROM and RCPT TO commands.
  • When the IP address of the sending host is mentioned in the DNS of zen.spamhaus.org the message will be denied access.
  • When a message fulfills the requirements of the mentioned policy_service, unix:private/policyd-spf, it is allowed access. This service does the so-called SPF check. This check looks at the domain of the sender address (in the MAIL FROM command), tries to get the SPF record of that domain, which specifies which IP addresses are allowed to send messages for that domain, and compares these addresses with the address of the sending IP address. If the check fails, the message is rejected. If there is no SPF information for the sender domain or the check is positive, the message is allowed.

To make the check on SPF we need a policy service in master.cf:

postconf -M policyd-spf/type='policyd-spf    unix    -    n    n    -    0     spawn\
  user=policyd-spf argv=/usr/lib/policyd-spf-perl'

This service needs its own user, so we add:

 useradd -c "SPF Policy Server for Postfix" -d /etc/policyd-spf -s "/sbin/nologin" -r policyd-spf

Also add:

postconf "policyd-spf_time_limit = 3600"

The package can be added with the following commands:

zypper ar https://download.opensuse.org/repositories/devel:/languages:/perl/15.4/ postfix-policyd-spf-perl
zypper in postfix-policyd-spf-perl

The package is noarch, so it can be installed on any architecture, however a dependency packet, perl-NetAddr-IP, is architecture dependent, but will be installed from the main repository. We only need to apply the SPF policy on incoming messages on port 25. Because such an entry "smtp inet" already exist we use sed to add the option:

sed -i '/^smtp[ \t]*inet/a\    -o smtpd_relay_restrictions=check_policy_service,unix:private/policyd-spf,permit'

Content filtering

Especially email delivered on the local system should be filtered for unwanted content; spam and viruses. This is done by giving the command:

postconf "content_filter = amavis:[127.0.0.1]:10024"
Note that this parameter has been disabled in submission by giving it a null value

This means that when a message is allowed access, it will be send on to amavis on port 10024 on localhost. For this we need to define an entry in /etc/postfix/master.cf as:

postconf -M amavis/type='amavis    unix  -       -       n       -       4       smtp -o smtp_data_done_timeout=1200 -o smtp_send_xforward_command=yes -o disable_dns_lookups=yes -o max_use=20'

Amavis and related packages

The service on this port is provided by the packet amavisd-new, and the systemd service amavis.service, which requires also the package spamassassin. Also a number of decoders are needed to unpack attachments in an email message. So installation is done using:

zypper in amavisd-new spamassassin arc arj lzop clzip rzip melt cabextract lz4 p7zip-full rzsz tnef zoo clamav

Configuration is in /etc/amavisd.conf. The configuration file /etc/amavisd.conf may need a few changes.

  • $max_servers is standard 2, maybe you want only 1
  • $mydomain has the value 'example.com', so you need to change that in your domain name
  • either you include an alias virusalert in /etc/aliases or you replace virusalert in postmaster, for which there is already an entry in /etc/aliases.
  • $myhostname has the value 'host.example.com' and is comment, so you need to remove the # and change that in your host name.

Amavis uses clamav for scanning on virusses. This packet provides two services freshclam and clamd both use configuration files /etc/freshclam.conf and clamd.conf, but these do not necessary need changes. The logging for all these service goes to syslog. The first time spamassassin needs initialization, so the following commands are needed to enable and start the services with:

systemctl enable freshclam.service
systemctl start freshclam.service
systemctl enable clamd.service
sleep 10 #freshclam needs the first time some time to settle before clamd can be activated
systemctl start clamd.service
sa-update
systemctl enable amavis.service
systemctl start amavis.service

Return from amavis to postfix

The amavis service returns the message back to postfix via port 10025 on localhost, which needs to be enabled using:

postconf -M localhost:10025/type='localhost:10025 inet   n       -       n       -       -       smtpd\
 -o content_filter=\
 -o smtpd_delay_reject=no\
 -o smtpd_client_restrictions=permit_mynetworks,reject\
 -o smtpd_helo_restrictions=\
 -o smtpd_sender_restrictions=\
 -o smtpd_recipient_restrictions=permit_mynetworks,reject\
 -o smtpd_data_restrictions=reject_unauth_pipelining\
 -o smtpd_end_of_data_restrictions=\
 -o smtpd_restriction_classes=\
 -o mynetworks=127.0.0.0/8\
 -o smtpd_error_sleep_time=0\
 -o smtpd_soft_error_limit=1001\
 -o smtpd_hard_error_limit=1000\
 -o smtpd_client_connection_count_limit=0\
 -o smtpd_client_connection_rate_limit=0\
 -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_address_mappings\
 -o local_header_rewrite_clients=\
 -o local_recipient_maps=\
 -o relay_recipient_maps='

Using SPF on outgoing e-mail

In case you want to protect others from using email addresses of your domain, you are depending on mailers in the rest of the world. These mailers need to use SPF validation and you only need to have a SPF record in the DNS of your domain. Remember this check is done using the domain part of the address in the MAIL FROM and the IP-address that makes the connection. Such a record is a TXT record which contains the following:

@ IN TXT "v=spf1 mx a ip4:192.0.7.25 ip6:2001:1234:4567::32 a:example.com ~all"
  • @ means it is a record in your top domain: example.com
  • v=spf1 indicates the TXT record is an SPF record
  • mx means that the IP address associated with your MX server is allowed to send email with your domain addresses
  • a means that IP-addresses in your A and AAAA record are allowed to send email with your email addresses
  • ip4:192.0.7.25 means that a system with this IP address is allowed ...
  • ipv6:2001:1234:4567::32 means that this IPv6 address is allowed ...
  • a:example.com means that IP-addresses mentioned in A and AAAA records of example.com are allowed ...
  • ~all should always be the last element in your record, where ~ means: when there is no IP address allowed it is a soft failure, which adds up to the spam count. When this character is -, it means deny access.

When MAIL FROM contains an empty address, in case of a.o. a non-delivery message, there is no domain to check for an SPF record. In that case the receiving server will use the name in the HELO/EHLO command. So if your server uses smtp.example.com as the name in this command, you should also have the following record in your DNS:

smtp IN TXT "v=spf1 a -all"

Obviously you need an A and/or AAAA record for smtp.example.com and any server trying to mimic your server using this name in the HELO/EHLO command will be denied access.

More information about SPF records can be found in RFC7208.

Using DKIM

DKIM, short for DomainKeys Identified Mail, gives the body and header of an outgoing email a digital signature. The public key is published via DNS, so a receiving server can verify the digital signature of incoming messages.

The package amavisd-new contains the necessary support for both validating incoming messages and providing the signatures for header and body of outgoing messages. There is also the package opendkim, which is meant for large servers, but this is left out of this article.

Note that there might not be a relation between the domain of the sender in a message and the domain which provides the signature. It is up to the receiving server what to do with the result of a positive result. The hope is that this server is trusting the message more than non-signed messages.

Messages coming in via port 25 (smtp inet... in master.cf) use the option content_filter=smtp:[127.0.0.1]:10024, which means the message is send to port 10024. The amavis daemon is listening on this port and processes the message. The amasvisd configuration file already contains $enable_dkim_verification = 1, so when the message contains a DKIM signature, amavisd will check the signature and will add two extra header lines starting with 'X-Spam-Status' and 'Authentication-Results' the outcome of the test. Later will be explained how amavisd returns the result via port 10025, on which a postfix process is listening, for further processing. This port is defined in master.cf as localhost:10025.

Messages coming in via the submission port (587) are only allowed when the user is authenticated as local users and, depending on the configuration, need to receive a DKIM signature. So we need to add the option content_filter=amavis:[127.0.0.1]:10026 to the submission definition in master.cf.

postconf -M submission/type='submission inet n       -       n       -       -       smtpd\
  -o syslog_name=postfix/submission\
  -o smtpd_tls_security_level=encrypt\
  -o content_filter=amavis:[127.0.0.1]:10026\
  -o smtpd_sasl_auth_enable=yes\
  -o smtpd_tls_auth_only=yes\
  -o smtpd_reject_unlisted_recipient=no\
  -o smtpd_recipient_restrictions=\
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject\
  -o milter_macro_daemon_name=ORIGINATING\
  -o disable_vrfy_command=yes'

However this parameter content_filter is already present with an empty value in /etc/postfix/master.cf, so it is easier to use sed to enter this value between the line which starts with "submission" and the line which ends with "content_filter=".

sed -i '/^submission /,/content_filter=$/ \
    s/content_filter=$/content_filter=amavis:[127.0.0.1]:10026/g' \
    /etc/postfix/master.cf

So we need a listener for port [127.0.0.1]:10026, which needs to be enabled in /etc/amavisd.conf:

  • Make the line "$inet_socket_port = 10024; # listen on this local TCP port(s)" into a comment line by prefixing the line with "# ".
  • Uncomment "# $inet_socket_port = [10024,10026]; # listen on multiple TCP ports"

When you inspect this configuration file you will see that port 10026 is associated with the policy ORIGINATING and in that policy messages will be forwarded to [127.0.0.1]:10027, which is unneeded. So we need to replace 10027 by 10025, on which postfix is already listening.

When you inspect this configuration file you will notice that there is a parameter $enable_dkim_signing which already has the value 1, meaning it is enabled, but we do not have such key. Generating the key is done using:

mkdir -p /etc/amavisd
amavisd -c /etc/amavisd.conf genrsa /etc/amavisd/example.com.dkim20230421.pem 2048

This gives the output:

Private RSA key successfully written to file "/etc/amavisd/example.com.dkim20230421.pem" (2048 bits, PEM format)

This file needs to be protected using:

chmod 640 /etc/amavisd/example.com.dkim20230421.pem
chown root:vscan /etc/amavisd/example.com.dkim20230421.pem

This file contains both the private as well as the public key. Next we need to add a reference to this file to the configuration of amavisd. This is done as follows:

cat >> /etc/amavisd.conf <<EOF
dkim_key(
   'example.com',
   'dkim20230421',
   '/etc/amavisd/example.com.dkim20230421.pem'
   );
 @dkim_signature_options_bysender_maps = ( {
   "example.com" => {
     d   => 'example.com',
     a   => 'rsa-sha256',
     c   => 'relaxed/simple',
     ttl => 10*24*3600
     }
   } );
EOF

In the first part the DKIM signing key is defined. In the second part is defined how this key must be used. The first example.com there defines that only messages with sender address containing @example.com should be signed. The second example.com defines that this signing must be done with de example.com key using the algoritm rsa-sha256, where the header gets the property relaxed and the body gets the property simple. The signature will have a lifetime of 10 days to allow this amount of delay between sending and processing on the receiving server. The properties relaxed and simple define the precision by which the generation and verification must be done. The header must be done more precise that the body. It has to do with conversions back and forth in the header and body to normalize these elements.

In the above example only messages with a sender address containing @example.com will be signed. However there is in principle no relation between the sending domain of the sender and the signing domain. So it is possible to use this key also for other sending domains. This is done by inserting between the two }} at the end the following:

,"anotherexample.com" => { d => 'example.com', a => 'rsa-sha256', ttl => 10*24*3600 }

In case you want all messages singed, use:

@dkim_signature_options_bysender_maps = ( { "." => { d  => 'example.com', a => 'rsa-sha256', c => 'relaxed/simple', ttl => 10*24*3600 }} );

In case you have access to domain name servers for another domain, you append another dkim_key like above to /etc/amavisd.conf.

The last thing to do is entering the public key in the DNS. Give the following command:

amavisd -c /etc/amavisd.conf showkeys

And you get the following output:

; key#1 2048 bits, s=dkim20230421, d=example.com, /etc/amavisd/example.com.dkim20230421.pem
dkim20230421._domainkey.example.com.    3600 TXT (
 "v=DKIM1; p="
 "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxEy3vw/Mq4G1fpTRcPKw"
 "+X4duvsTuO7VHpaxMA2x7Omd9Xi/nm/WZ2on1a6cFZ12MarLV/WAvm37yFGrBCrO"
 "kU81kKtL5WI5ju93geUEls0fG/ApcHguRKLoTWuV5unSuBdIKdNAKKO/p3+FFKYo"
 "elHDFFx7Wh3bAosRq/+9ZZTZha0rUQqvGt0HW3E4c7RPyhh23udnVh8xIQQPHH+d"
 "8WNRFK+1seeXbIIUAnUsI0W5GFci/8CaJ+Hk5J0FlMNHm74HuVCkMqGE1tXS8GE7"
 "uHWfdB562VrCorNoUsrveNwuQx3Z3VOe3KMhu/djExoTVCbrPHmbYbBDR4tkOM39"
 "2wIDAQAB")

and you get the public key to put in the DNS. As you see the selector dkim20230421, which will be present in the information that is generated in the signature of the message, is part of the DNS TXT record the receiving server needs to retrieve to validate the received message. As long as this selector is unique other servers in this domain can have different keys for signing, so you do not need to exchange these keys among these other servers.

After entering this TXT record in the DNS you can check the functioning of it with:

amavisd -c /etc/amavisd.conf testkeys

of which the output should be:

TESTING#1 example.com: dkim20230321._domainkey.example.com => pass

The next test is to see if the DKIM signature has been added to your message. First we need to have the encoded username/password to login to the server. This is done with:

echo -en "\0username\0password" | base64

The output of this command is: AHVzZXJuYW1lAHBhc3N3b3Jk , but obviously you need to enter the proper username and password. Next we start use the following command:

openssl s_client -connect localhost:587 -starttls smtp -quiet

The output ends with:

250 CHUNKING

After with you enter line by line:

EHLO smtp.example.com
AUTH PLAIN AHVzZXJuYW1lAHBhc3N3b3Jk
mail from:<root@example.com>
rcpt to:<root@localhost.example.com>
data

After the EHLO line the server answers: 250-smtp.example.com and a bunch of capabilities among which 250-AUTH PLAIN. After entering your line AUTH PLAIN the answer should be 334 235 2.7.0 Authentication successful. The answer after the lines with mail and rcpt the answers should contain 250 at the beginning. After data you get 250 2.1.5 Ok 354 End data with <CR><LF>.<CR><LF>. Next you enter the following:

Date: Fri, 21 Apr 2023 16:18:16 +0200
From: root@example.com
To: root@localhost.example.com
Subject: Test DKIM
Message-ID: <64453e28.J9HVa2f5XCUgKoNC%root@example.com>
User-Agent: Heirloom mailx 12.5 7/5/10
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

Test DKIM
.
QUIT

After which you should see: 250 2.0.0 Ok: queued as CCA582049F 221 2.0.0 Bye. The message should now be available in /root/Maildir/new/ and contains in the header the items Authentication-Results: and DKIM-Signature: .

Maintenance of DKIM keys

It is good practice to renew DKIM keys occasionally. The best way is to use a new signing key like the one in the previous example dkim20230421. The name can be any name that can be used in the DNS. After having implemented a new signing key the previous one can be removed from the DNS after the ttl time (10 days in the example).

Using DMARC

DMARC, short for Domain-based Message Authentication, Reporting and Conformance, is an addition to the other 2 security standards for email, SPF and DKIM. DMARC gives receiving servers an indication on how to process incoming email messages that do not have signatures that provide valid SPF and DKIM results. These can be discarded or quarantined.

DMARC can check if de sender domain in the ‘From’ header matches with the sender domain in the envelope (SPF) and with the signing domain in the DKIM header. This means that DMARC forces a relation between the validated SPF and DKIM sender domains and the sender domain in the 'From'. This means that a message that gives a positive result on the validation of SPF and DKIM, still can be rejected by DMARC.

The DMARC policy is published via a record in the DNS. This record can also contain email addresses which can be used by mail systems to report accepted/refused messages. This way the manager of the mail domain will get some insight in the delivery of both real as falsified messages.

The DMARC TXT record in the DNS

To enable DMARC you need a DMARC TXT record in your DNS. An example of such a record is:

_dmarc    IN    TXT    "v=DMARC1; p=none; sp=none; adkim=s; aspf=s; rua=mailto:dmarc-reports@example.com; ruf=mailto:dmarc-reports@example.com fo=1;"

The meaning of the elements in this record is as follows:

  • v=DMARC1 indicates together with the name of the record _dmarc that the TXT record is a DMARC record
  • p=none indicates the policy for the domain
  • sp=none indicates the policy for subdomains, when these subdomains do not have a DMARC TXT record
  • adkim=s indicated the strength with which the receiving server should test the alignment between the domain name in the FROM address and the DKIM key inserted in the message
  • aspf=s indicates the strength of with which the receiving server should test the alignment between the sending IP address and the allowed IP addresses in the SPF record
  • rua=mailto:dmarc-reports@example.com indicates the email address where aggregated reports should be send to, by default once a day (another parameter, ri, may change this value, parameter pct, default 100, indicates that only a percentage of the reports need to be send)
  • ruf=mailto:dmarc-reports@example.com indicates the email address where a reports should be send to in case the server does not accept the message
  • fo=1 indicates that reports about not accepted messages should be send (see rfc7489 section 6.3 for additonal values)

The possible policies are none, quarantine and reject. One should start with none which means that tests at the receiving end should be performed and reports should be send, but the message should be accepted. It is meant for situations where there are several systems being able to send message with your domain name in the FROM address and you expect that your SPF record may not be correct or not all sending servers have implemented the DKIM signing.

The strength in testing in aspf and adkim can be s for strict and r for relaxed. With adkim=s the domain part of the from address in the header must match the domain name in the DKIM signature. For adkim=r the domain part of from address in the header is allowed to be a subdomain. For aspf=s the domain part of from address must exactly match the domain part in the smtp protocol command MAIL FROM, also called the envelop sender. For aspf=r this match is allowed to be partly.

OpenDMARC

For making your own reports on how DMARC protects your incoming email and eventually send reports to other users of DMARC there is the the package opendmarc. Installation is done by:

zypper in opendmarc

OpenDMARC requires two configuration files, /etc/opendmarc.conf and /etc/opendmarc/ignore.hosts. The first one comes with the installed package but it is recommended to change it using the following sed script:

cat <<EOF > /tmp/sedscript.txt
/^# AuthservID name/ c\AuthservID OpenDMARC
/^# CopyFailuresTo postmaster@localhost/ c\CopyFailuresTo dmarc-failures@example.com
/^# FailureReports false/ c\FailureReports true
/^# FailureReportsBcc postmaster@example.coom/ c\FailureReportsBcc dmarc-reports-sent@example.com
/^# FailureReportsOnNone false/ c\FailureReportsOnNone true
/^# FailureReportsSentBy USER@HOSTNAME/ c\FailureReportsSentBy postmaster@example.com
/^# HistoryFile / s/^# //
/^# IgnoreAuthenticatedClients false$/ c\IgnoreAuthenticatedClients true
/^# IgnoreHosts / s/^# //
/^# RejectFailures false/ s/# //
/^# ReportCommand / s/^# //
/^# RequiredHeaders false$/ c\RequiredHeaders true
/^# TrustedAuthservIDs HOSTNAME$/ c\TrustedAuthservIDs example.com
EOF
sed -i -f /tmp/sedscript.txt /etc/opendkim.conf
rm /tmp/sedscript.txt

You may need to enter the email addresses mentioned here to be added in the file /etc/aliases by using:

echo -e "dmarc...:\t\tpostmaster" >> /etc/aliases

The second one should be created by:

touch /etc/opendmarc/ignore.hosts
chown opendmarc:opendmarc /etc/opendmarc/ignore.hosts
chmod 664 /etc/opendmarc/ignore.hosts

As can be seen from RejectFailures false, we start with adding our own records to the header of the message. But messages will not be rejected. So later you can change false in true, when all is as you wish it should be, which means that you obey the policy of the sending domain and reject all messages that do not pass that policy.

The line SPFIgnoreResults true means that OpenDMARC does it own SPF policy check and ignores any SPF-header line. Setting this to true means that only an SPF-validation will be performed when such a SPF-header line is not present. Setting the value of SPFSelfValidate to false means the this will not be done and OpenDMARC relies totally on possible present SPF-header lines. The opendmarc process can now be started.

systemctl enable opendmarc.service
systemctl start opendmarc.service
systemctl status opendmarc.service

As can be seen in the configuration file, this service is listening on port 8893 of localhost. The next step is to direct incoming messages in postfix to this process. This is only needed for incoming messages on port 25, so we need to add

-o smtpd_milters=inet:127.0.0.1:8893

in /etc/postfix/master.cf in the line with smtp inet..., which means giving the command

postconf -M smtp/type='smtp    inet    n    -    n    -    -    smtpd -o smtpd_helo_required=yes -o smtpd_milters=inet:127.0.0.1:8893'

Now we should see that incoming messages have DMARC lines in the header.