Hands-on: implementing SPF, DKIM and DMARC in Exim
A solid basis for a secure mail system
A solid basis for a secure mail system
This article describes the installation and configuration of the internet security standards SPF, DKIM and DMARC in the Exim mail server. The three standards protect against phishing, spam, viruses and other malware by securing the sender (e-mail address/domain), the sending host (mail system) and the authenticity (contents) of an e-mail message. The set-up described in this article involves a CentOS 8.2 Linux server running a fully functional standard installation of Exim, and a straightforward zone file for the DNSSEC-enabled domain example.nl. We describe how to enable each of the following with such a set-up:
• Publication of SPF records • Validation of SPF • Validation of DKIM • Signing with DKIM • Publication of DKIM records • Publication of DMARC records • Validation of DMARC Where relevant, various options are presented (albeit restricted to ready-to-use packages available from the repositories for CentOS) and the factors influencing the choice of option are considered. Configuration examples are included with the explanatory material about how the standards work.
Although the initial configuration (on CentOS) doesn't include policies for the three standards, full support for SPF, DKIM and DMARC is built into the software. However, before proceeding to build a secure mail system, it's important to be familiar with the meaning and effect of the various statements in the Exim configuration. The payback for your efforts will then be a robust mail system that conforms to the latest internet standards. What's more, domain-level mail security is an important enabler for transitioning from IPv4 to IPv6, because IP address-based filtering isn't possible with IPv6.
The SPF, DKIM and DMARC protocols protect against phishing, spam, viruses and other malware by securing the sender (e-mail address/domain), the sending host (mail system) and the authenticity (contents) of an e-mail message. Their deployment implies modifying the way that the server processes messages and adding various records to the information you publish via the DNS. Although signing with DNSSEC isn't strictly necessary (i.e. required by the standard), it's a valuable additional procedure. DKIM, SPF and DMARC represent the first practical application of the cryptographically secured infrastructure built up over the last decade using DNSSEC. The three standards are normally used together, but they don't have to be. You can use DKIM or SPF on its own, or DKIM and SPF together without DMARC. For that reason, the standards and their implementation are considered below individually. Each of the resulting configurations is a standalone solution capable of operating independently from the others.
Basic knowledge of the SMTP and mail protocols, and of the UNIX operating system, is required for interpretation of the following. When performing the SMTP handshake, the sending mail server states the IP address it will use to connect to the receiving mail server, plus the following information:
With the 'EHLO'/'HELO' command: the host name of the sending mail server
With the 'MAIL FROM' command: the sender/return address of the message
With the 'RCPT TO' command: the addressee(s) for whom the message is intended
In the 'From' header: the sender name and address (display name) to be displayed to the receiving user
That immediately brings us to the heart of the problem with the basic (E)SMTP protocol: the sending server can give any information it likes in the 'EHLO'/'HELO' and 'MAIL FROM' commands (which, together with the information in the 'RCPT TO' command, form the envelope of the mail message). It is therefore very easy to spoof the sender's mail address (subsequently used in the 'Return-Path' header) and the sending host name (subsequently used in the 'Received' header).
This is how the three security standards confirm (validate) the information in the envelope and the display name:
SPF: validates the 'EHLO'/'HELO' and 'MAIL FROM' information (the mail envelope)
(Optional) check the 'EHLO'/'HELO' host name to verify that the associated IP address resolves to the client's IP address, thus confirming that the identity of the client (the sending mail server) is as claimed
(Mandatory) check the sending domain in the 'MAIL FROM' address to verify that the client's IP address is authorised to send mail from the domain in question, thus confirming that the client (the sending mail server) has the authority to send/forward messages for the sender
If no 'MAIL FROM' information is available (typically for bounce messages), SPF falls back on the 'EHLO'/'HELO' host name in preference to the sending domain
The (positive) result is a validated mail envelope
Validation is performed as early as possible in the mail pipeline (on the MX gateway), so that, in the event of a hard fail, the software can return error messages via the active SMTP connection
If validation of a message/envelope fails, the connection/message can be blocked immediately, or the message can be forwarded with the negative result in a 'Received-SPF' and/or 'Authentication-Results' header
DKIM: signs outbound mail messages and their headers, and validates the signatures on incoming messages
Check the validity of the digital signature accompanying the message (in the 'DKIM-Signature' header), using the public key published in the signing domain specified in the header
Since the signing domain is not necessarily the sending domain specified in the 'MAIL FROM' or 'From' header, DKIM initially merely confirms that the signing domain is responsible for initiating the message exchange
Outbound messages are signed as late as possible in the sending mail pipeline (the final hop on your infrastructure), so that the signature covers as may headers as possible; signatures on incoming messages are validated as early as possible in the receiving pipeline (the first hop on your infrastructure), so that the addition or modification of headers has as little impact as possible on the validation process
DKIM does not block messages; the result of the validation process is added to the message in an 'Authentication-Results' header and, for example, reflected in a spam filtering score or in a DMARC evaluation
DMARC: processes the results of the SPF and/or DKIM validation on the basis of a policy published by the sending domain in the 'From' header
Check whether the sending domain in the 'From' header matches both the sending domain in the envelope (for SPF) and the signing domain (for DKIM); this alignment check connects the SPF/DKIM-validated sending domains and the information in the 'From' header (the display name)
The policy published in the sending domain specified in the 'From' header is used to determine what to do with incoming mail messages if the SPF and DKIM validation results (including alignment results) are negative: allow, block or quarantine
If requested in the policy, aggregated reports and failure reports are sent to the operator of the sending domain; report messages are standardised to enable automated processing
In the following paragraphs, we consider the implementation of SPF, DKIM and DMARC in Exim, the most widely used mail server. Configuration of the standards in the Postfix server is considered in a separate hands-on article. Together, the two articles will cover more than 90 per cent of the current mail server market. In a another article, we described how to use DANE, another protocol that builds on the DNSSEC infrastructure, to secure your mail gateway's TLS certificates. There is no interdependency between the SPF-DKIM-DMARC trio and DANE for mail. It doesn't therefore matter whether the trio is implemented first, or DANE for mail. However, the use of DNSSEC is mandatory with DANE, whereas it's merely recommended with SPF, DKIM and DMARC. On our DNSSEC page you'll find hands-on guides to the implementation of DNSSEC signing and validation in Unbound, Infoblox, PowerDNS and BIND. If your systems also support IPv6, implementation of the five standards as described should ensure a 100 per cent score on internet.nl. If you use the modern internet (security) standards described in this article, your mail systems will be fully up-to-date and in compliance with the latest requirements and guidance from the Forum for Standardisation [1, 2], the National Cyber Security Centre (NCSC) [1, 2] and others. In any event, the internet.nl portal is a good tool for determining your current status and for checking as you proceed with the implementation of new internet standards whether everything is working as intended.
This article is based on the CentOS Linux distribution version 8.2, the community edition of Red Hat Enterprise Linux (RHEL). The implementation we describe involves the following software packages:
BIND named (in a chroot jail) version 9.11.13
Exim version 4.94
OpenSSL version 1.1.1c
Dovecot version 2.3.8
Cyrus SASL version 2.1.27
Dovecot Pigeonhole version 2.3.8
Clam AntiVirus (ClamAV) version 0.102.3
SpamAssassin version 3.4.2
libspf2 version 1.2.10
libopendmarc (OpenDMARC) version 1.3.2
At the time of writing (June 2020), the listed versions are the latest versions of the packages available from the standard repositories of CentOS version 8.2. The ClamAV, libspf2 and OpenDMARC packages came from the EPEL repository. They are Extra Packages for Enterprise Linux, compiled using Fedora software, the development distribution for RHEL. You can therefore create an exact copy of our set-up in RHEL or Fedora. To enable and install the necessary PowerTools and EPEL repositories, use the following sequence of commands:
dnf config-manager --set-enabled PowerTools dnf install epel-release dnf config-manager --set-enabled epel
In our set-up, ClamAV and SpamAssassin run as standalone daemons: a configuration suitable for larger production environments. In smaller environments, some independent daemons can also be run locally on a UNIX domain socket.
The basis for our configuration is a fully operational authoritative DNS server for the domain example.nl. We have used this zone file:
$TTL 1d ; ttl is 1 day @ IN SOA dns1.example.nl. dns.example.nl. ( 2020050800 ; serial (date & version) 8h ; refresh every 8 hours 20m ; retry after 20 minutes 4w ; expire after 4 weeks 20m ; negative caching ttl is 20 minutes ) ; DNS name servers IN NS dns1.example.nl. ; primary name server IN NS dns2.example.nl. ; secondary name server ; SMTP mail gateways IN MX 10 mx.example.nl. ; MX gateway IN MX 100 fallback-mx.example.nl. ; fallback MX gateway ; hosts IN A 192.0.2.162 ; server IN AAAA 2001:db8:1192::2:162 ; server (IPv6) www IN CNAME example.nl. ; WWW server ftp IN CNAME example.nl. ; FTP server mx IN A 192.0.2.106 ; mail gateway mx IN AAAA 2001:db8:1192::2:106 ; mail gateway (IPv6) mail IN A 192.0.2.107 ; mail server mail IN AAAA 2001:db8:1192::2:107 ; mail server (IPv6) imap IN A 192.0.2.108 ; IMAP server imap IN AAAA 2001:db8:1192::2:108 ; IMAP server (IPv6) smtp IN A 192.0.2.109 ; SMTP gateway smtp IN AAAA 2001:db8:1192::2:109 ; SMTP gateway (IPv6) ; exterior hosts dns1 IN A 192.0.2.5 ; primary name server dns1 IN AAAA 2001:db8:1192::2:5 ; primary name server (IPv6) dns2 IN A 198.51.100.6 ; secondary name server dns2 IN AAAA 2001:db8:2198::16:6 ; secondary name server (IPv6) fallback-mx IN A 198.51.100.106 ; fallback mail gateway fallback-mx IN AAAA 2001:db8:2198::32:106 ; fallback mail gateway (IPv6)
The above zone is signed with DNSSEC, meaning that a digital signature is attached to each Resource Record Set (RRset) for the domain. That enables the recipient of a DNS response to validate the information it contains -- in other words, to confirm its authenticity. In our case, the signature-containing records and the other DNSSEC records are not included in the zone file. That is because, in a modern configuration, BIND named generates the signed zone, information about which is then published on the internet via the authoritative DNS servers. In another hands-on article, we describe the entire procedure for configuring BIND to automatically sign your domains. On our DNSSEC page, you will also find similar hands-on articles about signing and validation in various other DNS servers and resolvers. As you will see below, the configuration of SPF, DKIM and DMARC involves the addition of extra records to your DNS configuration, plus changes to the configuration of your mail server. Because the Exim software supports the three protocols by default, the necessary changes are not extensive. However, the configuration of Exim is complicated by the fact that the various options are not entirely intuitive or consistent, making it necessary to establish the precise meaning and effect of each option before proceeding. We begin by looking at the general configuration options, before presenting an overview of the various Access Control List (ACL) options and their characteristics. It is vital to be familiar with the options in order to follow the narrative of this article and configure your own set-up.
Before going further, it may be helpful to provide some background information about the set-up of our Exim installation. That will make it easier to follow the procedures for adding settings for SPF, DKIM and DMARC to our configuration. As you will see, all the appropriate facilities are already available in the form of actions (verbs), tests (conditions), variables and switches (controls); the configuration of a mail server consists mainly of selecting options and adding new lines to the ACLs.
First, we have set/changed the following options in the basic Exim configuration (in the file '/etc/exim/exim.conf'). For the precise significance of each option, refer to the relevant documentation.
primary_hostname = mail.example.nl domainlist local_domains = @ : example.nl : localhost : localhost.localdomain domainlist relay_to_domains = hostlist relay_from_hosts = localhost : 192.168.0.0/16 av_scanner = clamd:127.0.0.1 3310 spamd_address = 127.0.0.1 783 tls_certificate = /etc/pki/tls/certs/exim.pem tls_privatekey = /etc/pki/tls/private/exim.pem tls_require_ciphers = ECDSA:RSA:!COMPLEMENTOFDEFAULT #daemon_smtp_ports = 25 : 465 : 587 #tls_on_connect_ports = 465 daemon_smtp_ports = 25 : 587 qualify_domain = example.nl auth_advertise_hosts = ${if eq {$tls_cipher}{}{}{*}} dns_dnssec_ok = 1 accept_8bitmime = false begin acl acl_check_mail: # reject messages from senders listed in these DNSBLs deny dnslists = zen.spamhaus.org acl_check_rcpt: accept hosts = : control = dkim_disable_verify control = dmarc_disable_verify accept hosts = +relay_from_hosts control = submission control = dkim_disable_verify control = dmarc_disable_verify accept authenticated = * control = submission control = dkim_disable_verify control = dmarc_disable_verify acl_check_data: deny malware = * message = This message contains a virus ($malware_name). accept condition = ${if >={$message_size}{100000} {1}} add_header = X-Spam-Note: SpamAssassin run bypassed due to message size warn spam = nobody/defer_ok add_header = X-Spam-Flag: YES accept condition = ${if !def:spam_score_int {1}} add_header = X-Spam-Note: SpamAssassin invocation failed warn add_header = X-Spam-Score: $spam_score ($spam_bar)\n\ X-Spam-Report: $spam_report deny condition = ${if >{$spam_score_int}{100} {1}} message = Your message scored $spam_score SpamAssassin point. Report follows:\n\ $spam_report warn condition = ${if >{$spam_score_int}{5} {1}} set acl_m_greylistreasons = Message has $spam_score SpamAssassin points\n$acl_m_greylistreasons .include /etc/exim/exim-greylist.conf.inc begin routers userforward: local_part_suffix = +* : -* local_part_suffix_optional localuser: local_part_suffix = +* : -* local_part_suffix_optional begin transports local_delivery: #file = /var/mail/$local_part directory = $home/Maildir maildir_format
Before continuing, it's important to consider the structure of and options available in the Exim configuration file ('/etc/exim/exim.conf'). Because some options are not very intuitive or consistent, it's vital to understand the meaning, effect and characteristics of the various options in order to follow the narrative of this article and configure your own set-up. In addition to the general configuration options referred to above, the Exim configuration file includes hooks, which can be used to define ACLs at various points during the SMTP handshake and messages processing. You can insert a (brief) expression immediately after the hooks, but it's more common to insert a name referring to a more extensive (inline) ACL definition in the 'acl' section of the configuration file. Our configuration uses the following hooks and associated ACLs:
acl_smtp_mail → acl_check_mail Called up on receipt of the 'MAIL FROM' command (and the preceding 'EHLO'/'HELO' command)
acl_smtp_rcpt → acl_check_rcpt Called up on receipt of the 'RCPT TO' command
acl_smtp_dkim → acl_check_dkim Called up on receipt of the message, for each individual DKIM signature (This hook is not defined in the standard configuration and therefore needs to be added as described below)
acl_smtp_mime → acl_check_mime Called up as soon as all data has been received, enabling immediate scanning of all MIME components of the message
acl_smtp_data → acl_check_data The final ACL called up once the entire message (all data) has been received
An ACL itself lists a series of actions (verbs), each of which is accompanied by a list of tests (conditions), switches (controls) and (message) modifiers. A verb is actioned only if the results for all items on the list are 'true'. Evaluation is aborted (and the remaining tests not therefore performed) if any test result is 'false'. Whether evaluation then proceeds to the following verb differs from case to case. The verbs and tests relevant in the context of our set-up are listed below. To facilitate understanding of the list and the explanation above, it can help to have an ACL from your own Exim configuration to hand for reference. Actions (verbs):
'accept': proceed to evaluation of the next verb if all test results are 'true'; if any result is 'false', perform a 'deny' and discontinue evaluation of the list If any test whose result is 'false' is followed by an 'endpass' statement, evaluation of the list does continue. In other words, tests listed after an 'endpass' statement can only cause an 'accept' to be changed to a 'deny' (in the event of a 'false' result); all tests listed before the 'endpass' statement are performed, regardless of the result. Having been found to be confusing, use of the 'endpass' statement is now discouraged. In line with that advice, we do not use 'endpass' statements anywhere in our configurations.
'discard': as 'accept' (implying that the client receives a positive response), but dump the message
'deny': refuse the message (permanently) if the results of all tests are 'true'; otherwise abort evaluation and proceed to the next verb
'drop': as 'accept', but drop the SMTP connection immediately in the event of a 'deny'
'defer': refuse the message (temporarily) if the results of all tests are 'true'; otherwise abort evaluation and proceed to the next verb
'require': if the results of all tests are 'true', proceed to the next verb; otherwise perform a 'deny'
'warn': send the 'log_message' to the log if the results of all tests are 'true'; then proceed to the next verb in all cases If the list does not include a 'log_message' statement, all controls in the list are performed until the first failed condition. If the result of a test is 'defer', that result is recorded, but the 'log_message' is not. Evaluation of the active list is then aborted and the procedure moves to the next verb.
'add_header=': add a header to a message
'continue=': do nothing; used mainly where necessitated by the side-effects of expansion of the expression that follows
'control=': change the operation of Exim (locally) by setting switches
'delay=': wait for a stated interval
'endpass' (only for 'accept' and 'discard'): process all previous statements regardless of the results (and perform an 'accept' even if the results of some are 'fail'); where subsequent statements are concerned, only a 'deny' should be performed immediately in the event of a test fail Having been found to be confusing, use of the 'endpass' statement is now discouraged.
'log_message=': send a message to the log in the event of a 'deny' or if all the conditions linked to a 'warn' are 'true' (in the latter event preceded by 'Warning: '); do likewise in the event of a valid 'discard' verb NB: failure to specify a 'message' or 'log_message' does not mean that nothing is sent/logged. If such a modifier is left blank, internally available information is used instead. In some cases, your message is added to the internally available information, while in other cases your message is used instead of the internally available information.
'log_reject_target=': specify when a message should and should not be sent to the log
'logwrite=': always write a message to the log (in contrast to the 'log_message' modifier, which causes a message to be written only in the event of a 'deny', 'warn' or 'discard')
'message=': send a message with the response to an SMTP command (only in the event of an 'accept', 'deny' or 'defer', not in the event of a 'discard', and only if all test results are 'true') In some cases, the specified message is sent instead of another message previously specified in this modifier by Exim.
'queue=': specify the spool queue for the message (possible only before the DATA has been received)
'remove_header=': delete one or more headers
'set <name>=<value>': set a variable for later use
The last three modifiers listed above are not very relevant to us, and, as previously indicated, 'endpass' should not be used. The particular control modifiers of immediate interest in the present context are:
'control = dkim_disable_verify': disable DKIM validation
'control = dmarc_disable_verify': disable DMARC validation
'control = submission/<options>': switch Exim to submission mode (possible only before the DATA has been received)
The diagram below shows the workflow following the basic configuration of Exim. Incoming mail messages are received via the public (well-known) port 25 (SMTP). They are then checked by ClamAV (on port 3310) and SpamAssassin (on port 783) before internal forwarding for delivery. Following authentication (via SASL), Port 587 (submission) is available as an SMTP gateway for use by internal users to send messages.
With the configuration of the submission service on port 587, the main things are to ensure that the service is accessible only to authenticated users and that the use of TLS encryption is mandatory for all connections. On this port, that always involves use of the STARTTLS command. IANA did at one stage reserve a TCP port (number 465) for SMTP-over-SSL (SMTPS), but the arrangement was never standardised and the port has since been assigned to a completely different service. The sticking point is that there is no way of specifying in an e-mail address whether transmission should be secured, and a receiving mail system has no way of interacting directly with a sender, other than via a bounce. Consequently, with DANE/StartTLS, the aim is to secure transmission 'wherever possible'. The configuration of DANE for mail is described in another article. In that article, we also consider the scope for increasing the security of Exim by utilising the TLS cryptography settings (hardening).
The only hard information that can be checked at this stage is the IP address used by the sending system (the client) to make contact with our server. The IP address in question is checked (in the context of the 'acl_check_mail' ACL) against the DNSBL service blacklist maintained by the Spamhaus Project. As you'll shortly see below, the same IP address is used at the same point ('acl_check_mail') to check whether the sending system is authorised (in the SPF policy) to send messages from the sending domain specified in the 'MAIL FROM' command. The same approach is used to validate the DKIM signatures on incoming messages, That should be done at the earliest possible point in the pipeline (the first hop on your infrastructure), before your own systems add any new headers to incoming messages.
SPF, DKIM and DMARC are standard features of the Exim software (if the relevant flags have been set when compiling the code). The 'libspf2' library is used for SPF validation. From Exim version 4.93, the status of this component has been upgraded from 'Experimental' to 'Supported'. For DMARC validation, Exim uses the OpenDMARC library 'libopendmarc'. Both libraries are available from the EPEL repository, and it is important to ensure that they are installed:
dnf install libspf2 opendmarc
DKIM and DMARC validation are enabled by default for all incoming messages. Hence, the ACLs in the factory configuration (and in the configuration above) consistently repeat the following statements for messages from local senders, relay hosts and authenticated connections:
control = dkim_disable_verify control = dmarc_disable_verify
Those statements explicitly disable the DKIM and DMARC checks for all messages except those from remote, non-authenticated senders (received on port 25 of the MX gateway).
If you refer to the log file '/var/log/exim/main.log' following installation, you will see that the DMARC configuration is not complete:
2020-06-06 15:41:35 DMARC failure to load tld list '/usr/share/publicsuffix/public_suffix_list.dat': No such file or directory
The 'public suffix' list in question is required to determine the root domains for DMARC alignment. Root domains need to be specified because they are not necessarily the same as the top-level domains (TLDs) – as with example.co.uk, where the root domain is a second-level domain. The list is installed using the following two commands:
cd /usr/share/publicsuffix/ wget https://publicsuffix.org/list/public_suffix_list.dat
The path '/usr/share/publicsuffix/public_suffix_list.dat' is the default location for the file defined in Exim. It can be modified using the configuration option 'dmarc_tld_file'. We have added the file manually, but it is advisable to automate installation/updating by means of a cron job or systemd timer.
If we look at the logs and headers of an incoming message, we can see that SPF, DKIM and DMARC are indeed now being validated:
2020-06-09 12:21:17 1jibNd-000300-Qk DMARC results: spf_domain=afzenderdomein.nl dmarc_domain=afzenderdomein.nl spf_align=no dkim_align=yes enforcement='Accept' 2020-06-09 12:21:34 1jibNd-000300-Qk <= tester@afzenderdomein.nl H=(sender.afzenderdomein.nl) [203.0.113.109] P=esmtps X=TLS1.3:TLS_AES_256_GCM_SHA384:256 CV=no S=9261 DKIM=afzenderdomein.nl id=a3a31cf8-4d95-886d-b460-314d41bb5563@afzenderdomein.nl
Return-path: <tester@afzenderdomein.nl> Envelope-to: ontvanger@example.nl Delivery-date: Mon, 08 Jun 2020 19:44:59 +0200 Received: from [203.0.113.109] (helo=sender.afzenderdomein.nl) by mail.example.nl with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.93) (envelope-from <tester@afzenderdomein.nl>) id 1jiLpP-0007an-4p for ontvanger@example.nl; Mon, 08 Jun 2020 19:44:59 +0200 Received: from localhost (localhost [127.0.0.1]) by sender.afzenderdomein.nl (Postfix) with ESMTP id BEDE62800204; Mon, 8 Jun 2020 19:44:54 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=afzenderdomein.nl; h=content-type:content-type:mime-version:user-agent:date:date :message-id:organization:subject:subject:from:from:reply-to :received:received; s=dkim20191228; t=1591638292; x=1592502293; bh=pivdjK0vpM4A51Ei6TSEGj8ztCZ6DKRJxhL2ZwrggJ0=; b=ezQ/7THIjpP6 +EHXUZ8GTv7l4GT4lJodz10kF7ECG4qXrUxwPHxeAJgodC+Xc08BhdOfcP/zOYM6 UOxsad6uZO6kI5M3Kh8F2PwDz/qAoKM3x4lUkoPayO4nZ6yiXX9af/c/Fv8/18zx AXSYIciIu2K+6CYa480QZBndO/UoEvOZ+g7azyPp/DkB5L809dwMD6lylabX1mE8 qidzDjJ3WfNhHH8akaviawpr8hrF7KJPmcw7FNcvrLBY9mB7gYTU6kfha5Wz0QWr Ua+TG4eMRluxk2pGSJ72jOKvc0mmHP4= X-Virus-Scanned: amavisd-new at afzenderdomein.nl Received: from mail.afzenderdomein.nl ([127.0.0.1]) by localhost (mail.afzenderdomein.nl [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id X8CgECyIYi3X; Mon, 8 Jun 2020 19:44:52 +0200 (CEST) Received: from host.afzenderdomein.nl (host.afzenderdomein.nl [203.0.113.128]) by mail.afzenderdomein.nl (Postfix) with ESMTPSA id 025CC2800629; Mon, 8 Jun 2020 19:44:51 +0200 (CEST) Reply-To: tester@afzenderdomein.nl To: ontvanger@example.nl From: "Tester" <tester@afzenderdomein.nl> Subject: Test initial Exim config Message-ID: <0aa1daa2-3e7f-2a63-7127-a2fab95d9b7b@afzenderdomein.nl> Date: Mon, 8 Jun 2020 19:44:50 +0200 X-Spam-Score: 1.1 (+) X-Spam-Report: Spam detection software, running on the system "mail.example.nl", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see @@CONTACT_ADDRESS@@ for details. Content preview: ... Content analysis details: (1.1 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: afzenderdomein.nl] -0.0 SPF_PASS SPF: sender matches SPF record -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS
As you can see, SPF and DKIM are also both (positively) validated by SpamAssassin, and a detailed report is attached in the form of an 'X-Spam-Report' header. The only factor that raises the spam score slightly is the observation that the sending mail server has no reverse DNS entry.
A regrettable feature of this set-up is that the new (trace) headers are added at the end of the message, not the start. That may create problems with the DKIM signature in the 'DKIM-Signature' header, which now comes before the two new headers (and also covers the headers below). In any case, trace fields – headers added to a message at successive stages of the transmission process by the sending and receiving mail servers – should always be placed at the start of the header list. Yet, by default, Exim adds new headers at the end of the header list. In order to change that, we need to add an ':at_start:' prefix to all 'add_header' statements in the configuration file '/etc/exim/exim.conf':
accept condition = ${if >={$message_size}{100000} {1}} add_header = :at_start: X-Spam-Note: SpamAssassin run bypassed due to message size ... warn spam = nobody/defer_ok add_header = :at_start: X-Spam-Flag: YES accept condition = ${if !def:spam_score_int {1}} add_header = :at_start: X-Spam-Note: SpamAssassin invocation failed ... warn add_header = :at_start: X-Spam-Score: $spam_score ($spam_bar)\n\ :at_start: X-Spam-Report: $spam_report
After restarting Exim, we see that the 'X-Spam-Score' and 'X-Spam-Report' headers are now added at the top of the header list. As well as making it easier to follow the sequence of headers, our set-up now conforms to the standards more faithfully.
The SPF and DKIM validations reported in the 'X-Spam-Score' and 'X-Spam-Report' headers are performed as part of the spam scan. By contrast, the SPF, DKIM and DMARC validations reported in the logs are performed by Exim itself. As you can see from the configuration file, nothing is yet being done with the results of those validations. In order to make use of the results, we have to implement our own policies, in the form van ACLs.
It is not unusual to perform two identical checks in succession. A separate validation step enables you to block non-validating messages early in the pipeline. Furthermore, with your own implementation you can apply your own rules. Because the responses to the DNS queries are cached, such an arrangement has no significant cost. The basic set-up process is now complete, and we are ready to implement SPF, DKIM and DMARC.
SPF stands for Sender Policy Framework, a protocol that prevents MX gateways accepting mail messages from unauthorised systems. It involves the owner of a mail domain publishing a list of valid sending addresses via the DNS. The listed addresses are typically the SMTP gateways that end users must use for their outbound messages (via port 587), but may also include the addresses of external service providers that send marketing mail for the organisation. Receiving gateways can check (validate) incoming messages against the published address list and refuse any that come from unlisted addresses. That implies adding the following record to the zone file for example.nl presented above; the record is added somewhere after the origin '@':
IN TXT "v=spf1 mx a ip4:192.0.2.109 ip6:2001:db8:1192::/64" " a:senders.example.nl a:senders.bizbooster.com -all"
One sometimes sees a specific SPF record (with exactly the same content) in addition to a TXT record. Such records are a legacy from the early days of SPF. However, the current standard (RFC 7208) says that the SPF record type should no longer be used. If no 'MAIL FROM' information is available (typically for bounce messages), SPF falls back on the 'EHLO'/'HELO' host name in preference to the sending domain. That is therefore stated in a separate SPF record, as follows:
mail IN TXT "v=spf1 a -all" ; SPF record for fallback to EHLO hostname
Turning to the content of the record itself, we see that the version indicator, 'v=spf1', is followed by a typical SPF policy: first a list of systems that are authorised to send mail for the domain, then '-all', which means that all other systems are not authorised. The alternative to ending the record with '-all' is to end with '~all'. That is known as a 'soft fail', meaning that messages from non-validating systems should not be blocked, but forwarded with a tag. This crops up again in the discussion of DMARC. While an 'a' or an 'mx' on its own indicates that the systems specified in the domain's relevant records are authorised, a policy such as 'a:senders.bizbooster.com' enables you to delegate specification of the authorised servers to another party or parties. So, for example, you can authorise a bulk mailing service provider to mail your customers on your behalf (i.e. to send mail that appears to originate from your domain). You could take this a step further by using 'include:bizbooster.com', thus telling receiving gateways to treat the SPF policy specified for bizbooster.com as part of the SPF policy for your domain. The benefit of an arrangement like that is that your service provider doesn't have to ask you to update your DNS configuration every time they change the addresses used for their mail infrastructure. A list of the various policy elements that can be used is provided here; otherwise refer to the source, RFC 7208. As stated in the RFC, 'a' covers both the IPv4 addresses in the A records and the IPv6 addresses in the AAAA records.
SPF validation should take place as early in the workflow as possible, i.e. in the 'acl_check_mail' ACL immediately after receipt of the 'MAIL FROM' command (on port 25 of the MX gateway). That way, incoming connections from unauthorised systems can be dropped immediately after transfer of the mail envelope, and an error message can be sent back as part of the existing SMTP session. The diagram below shows where the SPF check will be added to the workflow.
The following is the SPF configuration that we have added to the 'acl_check_mail' ACL (in the file '/etc/exim/exim.conf'). As you can see, SPF validation takes place immediately after the Spamhaus DNSBL check (which is itself performed immediately after the 'EHLO'/'HELO' command check):
acl_check_mail: # Hosts are required to say HELO (or EHLO) before sending mail. # So don't allow them to use the MAIL command if they haven't # done so. deny condition = ${if eq{$sender_helo_name}{} {1}} message = Nice boys say HELO first # reject messages from senders listed in these DNSBLs deny dnslists = zen.spamhaus.org # SPF validation deny spf = fail : softfail message = SPF validation failed: \ $sender_host_address is not allowed to send mail from \ ${if def:sender_address_domain \ {$sender_address_domain}{$sender_helo_name}} log_message = SPF validation failed\ ${if eq{$spf_result}{softfail} { (softfail)}{}}: \ $sender_host_address is not allowed to send mail from \ ${if def:sender_address_domain \ {$sender_address_domain}{$sender_helo_name}} deny spf = permerror message = SPF validation failed: \ syntax error in SPF record(s) for \ ${if def:sender_address_domain \ {$sender_address_domain}{$sender_helo_name}} log_message = SPF validation failed (permerror): \ syntax error in SPF record(s) for \ ${if def:sender_address_domain \ {$sender_address_domain}{$sender_helo_name}} defer spf = temperror message = temporary error during SPF validation; \ please try again later log_message = SPF validation failed temporary; deferred # Log SPF none/neutral result warn spf = none : neutral log_message = SPF validation none/neutral # Use the lack of reverse DNS to trigger greylisting. Some people # even reject for it but that would be a little excessive. warn condition = ${if eq{$sender_host_name}{} {1}} set acl_m_greylistreasons = Host $sender_host_address \ lacks reverse DNS\n$acl_m_greylistreasons accept # Add an SPF-Received header to the message add_header = :at_start: $spf_received logwrite = SPF validation passed
Our SPF configuration starts with a block on all messages whose SPF validation results are 'fail', 'softfail', 'permerror' or 'temperror'. If the SPF validation result is 'none' or 'neutral', the message is allowed to continue, but a warning is entered in the log. We also add a log message and an SPF header to the final 'accept', making it clear that SPF validation has been successful.
After restarting Exim, we can check whether SPF validation is in fact taking place by looking at the headers of an incoming mail message (reading up from the bottom). The 'Received-SPF' header should be present, as in the following test message submitted by the system sender.afzenderdomein.nl. The reported result ('Pass') tells us that the system in question is indeed authorised for afzenderdomein.nl.
systemctl restart exim.service
Return-path: <tester@afzenderdomein.nl> Envelope-to: ontvanger@example.nl Delivery-date: Sat, 13 Jun 2020 23:04:37 +0200 X-Spam-Report: Spam detection software, running on the system "mail.example.nl", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see @@CONTACT_ADDRESS@@ for details. Content preview: ... Content analysis details: (1.1 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: afzenderdomein.nl] -0.0 SPF_PASS SPF: sender matches SPF record -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS X-Spam-Score: 1.1 (+) Authentication-Results: mail.example.nl; iprev=fail smtp.remote-ip=203.0.113.109; spf=pass smtp.mailfrom=afzenderdomein.nl; dkim=pass header.d=afzenderdomein.nl header.s=dkim20191228 header.a=rsa-sha256; dmarc=pass header.from=afzenderdomein.nl Received-SPF: pass (mail.example.nl: domain of afzenderdomein.nl designates 203.0.113.109 as permitted sender) client-ip=203.0.113.109; envelope-from=tester@afzenderdomein.nl; helo=sender.afzenderdomein.nl; Received: from [203.0.113.109] (helo=sender.afzenderdomein.nl) by mail.example.nl with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.93) (envelope-from <tester@afzenderdomein.nl>) id 1jkDKL-0008AH-3e Received: from localhost (localhost [127.0.0.1]) by sender.afzenderdomein.nl (Postfix) with ESMTP id DBAB02800204; Sat, 13 Jun 2020 23:04:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=afzenderdomein.nl; h=content-type:content-type:mime-version:user-agent:date:date :message-id:organization:reply-to:subject:subject:from:from :received:received; s=dkim20191228; t=1592082271; x=1592946272; bh=FFnxOPm2eaxN2BNVUU25NsLX/TgLxXzkRZya9T48Bog=; b=Lh7BFoIbwkAN E7RmFAhpGXRTSj7Jo5hOBvjY6+nYuGbUOOA4hBMIDu9UBrNq2Du0la6as0VuPpqz e3Jl2xEvzuspY8QVIJg+m+tAwhIeZ0a/xpiZl4zcU6YR3D0rEIeaSNrU5ay7jZ7X d2urEMcCrMmN/tbMYgEk8UmMGVxyPaihw8wK+5T7Wfvt+b3ksK4NABgjy1Jzjwwt eK8ti3UHwn+yVbBMRSRib/KG9g0FmKyK4Fd3XwNzUoPGw0boLVJJiflKvOll3/jI F/UWQKgUZFP/+aW3r8JvxkBkXHOTH+8= X-Virus-Scanned: amavisd-new at afzenderdomein.nl Received: from mail.afzenderdomein.nl ([127.0.0.1]) by localhost (mail.afzenderdomein.nl [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id 9wN82CEi70N7; Sat, 13 Jun 2020 23:04:31 +0200 (CEST) Received: from host.afzenderdomein.nl (host.afzenderdomei.nl [203.0.113.128]) by mail.afzenderdomein.nl (Postfix) with ESMTPSA id 5B6E7280063A; Sat, 13 Jun 2020 23:04:31 +0200 (CEST) From: "Tester" <tester@afzenderdomein.nl> Subject: Test SPF validatie Reply-To: tester@afzenderdomein.nl To: ontvanger@example.nl Message-ID: <172523f3-8d2b-8a7d-5368-d38806695e59@afzenderdomein.nl> Date: Sat, 13 Jun 2020 23:04:30 +0200
The successful SPF validation and delivery are recorded in the log file as follows:
2020-06-13 23:10:48 SPF validation passed 2020-06-13 23:10:52 1jkDQO-0008BT-CA => ontvanger <ontvanger@example.nl> R=localuser T=local_delivery 2020-06-13 23:10:52 1jkDQO-0008BT-CA Completed
However, if the Exim server thus configured is asked to deliver a message from a system that has not been authorised by means of the SPF record, the server will refuse:
envelope from address tester@afzenderdomein.nl not accepted by the server server message: 550 SPF validation failed: 203.0.113.192 is not allowed to send mail from afzenderdomein.nl
2020-06-13 22:23:13 H=rpi4.afzenderdomein.nl (203.0.113.192) [203.0.113.192] rejected MAIL <tester@afzenderdomein.nl>: SPF validation failed: 203.0.113.192 is not allowed to send mail from afzenderdomein.nl
In order to check that non-validating mail messages are flagged up correctly, you can use this online tool to send unauthorised messages.
Please note: In February 2024, we were alerted to a problem involving a large Dutch access provider's SPF records. The company's 'include' statement contained a long list of IP addresses for import. The libspf2 library used by Exim was apparently unable to process the whole of the long TXT record, and therefore truncated the list of IP addresses/blocks after 64 items. The addresses/blocks after the truncation point were consequently not taken into account for validation purposes. As a result, mail from systems whose addresses had been dropped was erroneously refused.
Diagnosing errors of that kind is very labour-intensive. We are therefore highlighting the problem, so as to save other mail managers a lot of time.
In light of the incident, we advise DNS operators to split their SPF records into separate TXT records containing no more than 64 IP addresses/blocks. The access provider in question has also been informed about the issues associated with its SPF records.
With DKIM, short for DomainKeys Identified Mail, a digital signature is added to the body and header of every outbound mail message. The relevant public key is published via the DNS, so that receiving MX gateways can verify (validate) the digital signatures on incoming messages. By attaching a DKIM signature, a sending host (signing domain) accepts responsibility for initiating the exchange of a message. If the sending host has a good reputation, the acceptance of responsibility increases the 'deliverability' of the message. In other words, it reduces the risk of the message being interpreted as spam. DKIM doesn't normally block messages itself. DKIM merely provides validation results for interpretation by spam filters such as SpamAssassin. Furthermore, DKIM is explicitly not intended as a means of assuring message authenticity, integrity or confidentiality. That requires the user-level implementation of end-to-end encryption. DKIM is standardised in RFC 6376, 8301 and 8463.
Exim's DKIM engine is enabled by default from version 4.70, for both signing and validation. In the initial configuration you'll see that the control modifier 'dkim_disable_verify' is set (along with the 'dmarc_disable_verify' modifier) for messages from local senders, relay hosts and authenticated connections. Thus, DKIM validation is explicitly disabled for all messages except those from remote, non-authenticated senders (received via the MX gateway on port 25). Although it's not defined in the initial configuration, Exim has a separate 'acl_smtp_dkim' hook for DKIM validation, which is called up for each DKIM signature in the message individually. Validation fails if one or more of the signatures cannot be verified. If you want to change that behaviour, so that if any of the signatures can be verified the message is validated, you can set the global option 'dkim_verify_minimal' (in the file '/etc/exim/exim.conf'). As with SPF, the definition of strict rules for DKIM validation requires the addition of an entire new ACL. To that end, the following statement (hook) is inserted at the start of the configuration file:
acl_smtp_dkim = acl_check_dkim
The following diagram shows where the new ACL intervenes in our set-up:
Next, we add the following completely new ACL to the 'acl' section of our configuration:
acl_check_dkim: deny dkim_status = fail message = DKIM validation failed: $dkim_verify_status log_message = DKIM validation failed: $dkim_verify_status \ (address=$sender_address, domain=$dkim_cur_signer), \ signature is bad defer dkim_status = invalid message = DKIM signature invalid: $dkim_verify_status log_message = DKIM signature invalid: $dkim_verify_status \ (address=$sender_address, domain=$dkim_cur_signer), \ invalid signature # NOTE: dkim_status = none should never happen in this ACL accept # Add an X-DKIM header to the message add_header = :at_start: X-DKIM: DKIM validation passed: \ (address=$sender_address domain=$dkim_cur_signer), \ signature is good logwrite = DKIM validation passed
The structure of the ACL is similar to that of the SPF check: DKIM signatures whose check results are 'fail' or 'invalid' are rejected (temporarily and permanently, respectively). If the check result is 'accept', a DKIM header is added to the message, so that it's clear (even if everything is in order) that DKIM validation has been successfully performed.
After restarting Exim, we can check whether DKIM validation is in fact taking place by looking at the headers of an incoming mail message (reading up from the bottom). The 'X-DKIM' header should be present (along with the 'Received-SPF' header), as in the following test message submitted by the system sender.afzenderdomein.nl. It is apparent from the header that the DKIM signature (in the 'DKIM-Signature' header) has been successfully validated ('passed').
systemctl restart exim.service
Return-path: <tester@afzenderdomein.nl> Envelope-to: ontvanger@example.nl Delivery-date: Mon, 15 Jun 2020 20:28:39 +0200 X-Spam-Report: Spam detection software, running on the system "mail.example.nl", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see @@CONTACT_ADDRESS@@ for details. Content preview: ... Content analysis details: (1.1 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 SPF_PASS SPF: sender matches SPF record -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: afzenderdomein.nl] 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS X-Spam-Score: 1.1 (+) X-DKIM: DKIM validation passed: (address=tester@afzenderdomein.nl domain=afzenderdomein.nl), signature is good Received-SPF: pass (mail.example.nl: domain of afzenderdomein.nl designates 203.0.113.109 as permitted sender) client-ip=203.0.113.109; envelope-from=tester@afzenderdomein.nl; helo=sender.afzenderdomein.nl; Received: from [203.0.113.109] (helo=sender.afzenderdomein.nl) by mail.example.nl with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.93) (envelope-from <tester@afzenderdomein.nl>) id 1jktqV-0000im-7l for ontvanger@example.nl; Mon, 15 Jun 2020 20:28:39 +0200 Received: from localhost (localhost [127.0.0.1]) by sender.afzenderdomein.nl (Postfix) with ESMTP id 0AC7F2800204; Mon, 15 Jun 2020 20:28:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=afzenderdomein.nl; h=content-type:content-type:mime-version:user-agent:date:date :message-id:organization:reply-to:subject:subject:from:from :received:received; s=dkim20191228; t=1592245712; x=1593109713; bh=E669Sbf1Sdb6VFp7EfDLmhBitNGMtvmPdXIbuFsVwJU=; b=N5dcAuG3dTP0 p0OqZv0ULrZm8u1tWzc4AiRudWrm0AFedYGpRvtvex+xz8ioNITpIb+vQJ8z9x/1 GdQ+jk3TrX9g07EhY5ClqH34VCPV03ygbMhfmnKptxcD1V/q97k+rAh4mGrQY065 E6arCs6vulNyIoSC4D7QDNEdC7Mw/YcqhoCimj+lCwgLdjLR+OrRwSzF+1D68jIC uMzc3W+F3RZpqokmWzoROQsagj5tTFbKOyGiGVtdW7/bywMYbGiJ3ux0fp7wiy4Z HY64U1v+Bz2Yc607SGtLJsu/LLiXF6c= X-Virus-Scanned: amavisd-new at afzenderdomein.nl Received: from mail.afzenderdomein.nl ([127.0.0.1]) by localhost (mail.afzenderdomein.nl [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id wzhpWR14LlfM; Mon, 15 Jun 2020 20:28:32 +0200 (CEST) Received: from host.afzenderdomein.nl (host.afzenderdomein.nl [203.0.113.128]) by mail.afzenderdomein.nl (Postfix) with ESMTPSA id 93DA12800608; Mon, 15 Jun 2020 20:28:32 +0200 (CEST) From: "Tester" <tester@afzenderdomein.nl> Subject: Test DKIM validatie Reply-To: tester@afzenderdomein.nl To: ontvanger@example.nl Message-ID: <1ef59aee-fc13-0e2f-4a46-38d595c30557@afzenderdomein.nl> Date: Mon, 15 Jun 2020 20:28:32 +0200
The successful DKIM validation and delivery are recorded in the log file as follows:
2020-06-15 20:28:35 SPF validation passed 2020-06-15 20:28:35 1jktqV-0000im-7l DKIM validation passed 2020-06-15 20:28:39 1jktqV-0000im-7l <= tester@afzenderdomein.nl H=(sender.afzenderdomein.nl) [203.0.113.109] P=esmtps X=TLS1.3:TLS_AES_256_GCM_SHA384:256 CV=no S=9696 DKIM=afzenderdomein.nl id=1ef59aee-fc13-0e2f-4a46-38d595c30557@afzenderdomein.nl 2020-06-15 20:28:39 1jktqV-0000im-7l => ontvanger <ontvanger@example.nl> R=localuser T=local_delivery 2020-06-15 20:28:39 1jktqV-0000im-7l Completed
If its DKIM signature can't be validated, a message is immediately rejected – in contrast to the initial set-up, where an SPF or DKIM fail was merely reflected (negatively) in the spam score. The fail message triggered by non-validation of the DKIM signature is as follows:
the server did not accept the mail server message: 550 DKIM validation failed: fail
2020-06-27 12:47:32 SPF validation passed 2020-06-27 12:47:32 1jp8Mu-000O2d-Rx H=(sender.afzendermail.nl) [203.0.113.109] rejected DKIM : DKIM validation failed: fail (address=tester@afzenderdomein.nl, domain=afzenderdomein.nl), signature is bad
Having dealt with the DKIM validation of incoming messages, we are now ready to set up the DKIM-signing of outbound messages from internal users. First, we need to generate a new cryptographic key pair and modify our Exim configuration. Once that is done, the public key can be published in our DNS. Other (receiving) mail servers can then use our public key to validate the DKIM signature attached to outbound messages from our users, just as we validate signed messages that we receive from others (see above). The diagram below shows where DKIM signing comes in the workflow: messages submitted to port 587 (submission) by our internal (authenticated) users are signed before being queued for delivery. As explained earlier, signing needs to take place as late in the pipeline as possible (the final hop on your infrastructure), so that the signature covers as many headers as possible (including those added by Exim itself), thus increasing the deliverability of our signed message.
The DKIM key pair is generated as follows:
mkdir /etc/exim/keys/ cd /etc/exim/keys/ openssl genrsa -out dkim_rsa.private 2048
The new file 'dkim_rsa.private' contains the private key, which has to be kept secret. It's therefore important to ensure that the key file access rights provide appropriate security:
chmod 640 dkim_rsa.private chown root:exim dkim_rsa.private
Although generating a longer key (4096 bits, rather than 2048 bits) is an option, DKIM signatures remain valid for relatively short periods (in the example below, ten days). They are, after all, used exclusively for delivering messages, which, even in the worst-case scenario, only takes a few days. Restricting the key length to 2048 bits allows DNS traffic to go via the efficient UDP protocol, whereas it would be necessary to switch to the more onerous TCP protocol if longer keys were used. Take care not to use the insecure SHA-1 algorithm (for the reasons explained in RFC 8301). RFC 8463 defines the use of ECDSA for DKIM. We have previously set out the advantages of using this modern cryptographic algorithm for DNSSEC in this Q&A. However, at the time of writing (June 2020), we believe it is too soon to use ECDSA for DKIM. If you sign your mail with a new standard that is not supported by all DKIM-validating MX gateways, you run the risk that your messages will not be delivered. If you nevertheless want to use ECDSA, you can opt for dual signing. That involves adding two digital signatures (DKIM-Signature headers) to each outgoing message. Refer to the documentation for guidance on generating an ECDSA key pair and adding two keys to the Exim configuration at the same time
For the newly generated key pair to be used by Exim for signing outbound messages, the following configuration options need to be added to the 'remote_smtp' transport (in the file '/etc/exim/exim.conf'):
dkim_domain = example.nl dkim_selector = dkim20200615 dkim_private_key = /etc/exim/keys/dkim_rsa.private dkim_hash = sha256 dkim_canon = relaxed dkim_strict = 1 dkim_timestamps = 864000
The options used above are explained below, together with a number of other DKIM signing options:
'dkim_domain': the signing domain Note that, where DKIM itself is concerned, the signing domain doesn't necessarily have to be the same as the message's 'MAIL FROM' sending domain. The signing domain is responsible for initiating the exchange of a message, and it can play that role for multiple sending domains. However, if you are also using DMARC, the signing domain does need to be the same as the sending domain; matching the two is known as alignment.
'dkim_selector': the DKIM key selector The selector determines which key the DKIM-validating mail server should use to validate a message. As explained later in this article, every public DKIM key is recorded in the DNS under its own name (selector). The most straightforward approach is to use the file name.
'dkim_private_key': the location where the private DKIM key is recorded If you opt for dual signing, this option (and the selector) can have multiple values.
'dkim_hash = sha256': the hash function used to set the digital signature Use the value 'sha256' (the default). As explained above, 'sha1' should no longer be used, since it is outdated and insecure. Although 'sha512' is more secure than 'sha256', it may not be supported by all DKIM-validating mail servers.
'dkim_identity': the identity of the sender This option specifies the e-mail address of the responsible sending host within the signing domain (or a subdomain of that domain). The information is provided in the 'i=' tag of the message's 'DKIM-Signature' header. Thus, a link is made with a particular sending host (although DKIM itself operates exclusively at the domain level).
'dkim_canon': the canonicalisation When processing mail, some mail servers change the spacing/line breaks, thus invalidating the digital signature. By standardising the spacing/line breaks in a message before setting or validating the signature (canonicalisation), you make the message more resilient against in-transit modification. Exim supports single canonicalisation settings only. In other words, the same setting must be used for both the header and the body. The two possible settings are 'simple' and 'relaxed', of which the latter is the default. Canonicalisation, including the distinction between the two possible settings, is considered more closely in a separate section below.
'dkim_strict = 1': results in (temporary) deferral of a message if signing is unsuccessful for any reason
'dkim_sign_headers': a list of headers to be included in the digital signature
'dkim_timestamps = 864000': validity period of the signature, expressed in seconds (in this example, ten days) This setting causes timestamp information (a validity period duration) to be included in the 'DKIM-Signature' header. The most common value is ten days (10*24*3600 = 864000 seconds), which is ample for the delivery of a mail message. You'll shortly see that the 'DKIM Signature' header includes the start and end times of the validity period in the 't=' and 'x=' tags, respectively.
The 'dkim_canon' option ('relaxed'/'simple') specifies what approach should be taken when generating and checking the digital signature on, respectively, the header and body sections of a message. Certain features of a message, particularly the spacing/hyphenation, are sometimes modified in transit. Consequently, a strict checking policy can lead to problems, because of the way that cryptographic hash functions work. Hashes are digital abbreviations, which are essential for signing. And the modification of a single bit in a message yields a completely different hash, and therefore an invalid signature. To get around that problem, you can convert your message headers and body to a standard format (a normal form) before attaching the signature. Providing that the recipient does likewise, they will get exactly the same hash. Your signature is therefore much less likely to be invalidated by in-transit modification of the spacing/hyphenation in the message. The approach is comparable to a programmer checking the match between two words by first converting them both to lower-case letters. Needless to say, conversion to standard format (canonicalisation) requires additional computation power. RFC 6376 2 accordingly defines various conversion levels: 'relaxed' is more resource-intensive than 'simple', but more likely to result in your signature being accepted by the recipient. We therefore use 'relaxed' for our message headers, but the less resource-intensive 'simple' conversion for the body. Clearly, the option is relevant mainly for organisations that process large volumes of mail. For similar reasons, we previously used the following option in the global configuration. By filtering out eight-bit characters (and restricting ourselves to ASCII), we reduce the danger of messages being corrupted en route to the receiving mail server.
accept_8bitmime = false
Note that, in the configuration above, a single signing domain is used for multiple sending domains (all the sending domains that your local users can send from). Because validation is always based on your signing domain ('dkim_domain = example.nl'), the associated DKIM record needs to be linked to that domain only. If you want each sending domain to use its own key pair and its own signing domain (as required with DMARC), you need to create a separate DKIM record for each domain. Your Exim configuration therefore needs to specify various signing domains (and, where appropriate, various selectors and key files) for use on a sending domain-dependent (dynamic) basis. That implies including the following macros in your configuration (right at the top, in the file '/etc/exim/exim.conf')
# DKIM macros # get the sender domain from the outgoing mail SENDER_DOMAIN = ${if def:h_from:{${lc:${domain:${address:$h_from:}}}}{$qualify_domain}} # the key file name will be based on the domain name in the From header DKIM_KEY_PATH = /etc/exim/keys DKIM_KEY_FILE = SENDER_DOMAIN-20201213.pem
The first macro 'SENDER_DOMAIN' selects the domain element in the 'From' header and converts it to lower case (with 'lc:'). If the message doesn't include a 'From' header, the pre-set variable $qualify_domain is used by default. In the last macro, 'DKIM_KEY_FILE', we use the selected sender domain so that (by combination with the macro 'DKIM_KEY_PATH') we can shortly construct the full path to the associated key file, e.g. '/etc/exim/keys/example.nl-20201213.pem' or '/etc/exim/keys/anotherexample.nl-20201213.pem'. These macros are then used in our DKIM configuration in the 'remote_smtp' transport to specify the appropriate signing domain and select the corresponding key:
dkim_domain = SENDER_DOMAIN dkim_selector = dkim20201213 dkim_private_key = ${lookup {DKIM_KEY_FILE} dsearch,ret=full {DKIM_KEY_PATH}} dkim_hash = sha256 dkim_canon = relaxed dkim_strict = 1 dkim_timestamps = 864000
At first sight, the 'lookup dsearch' command used to load the correct key file appears a little over-elaborate. After all, in earlier versions of Exim, we could compile a complete path simply by putting together the elements: '/etc/exim/keys/{DKIM_SENDER_DOMAIN}-20200615.pem'. However, from version 4.94, you can't freely use all the variables in your configuration. The label 'tainted' is now attached to variables derived from inbound mail messages, since such messages are not necessarily trustworthy. The rationale being that, otherwise, carelessness in the compilation of your configuration file could lead to security issues. A good example of that would be the direct use of 'tainted data' from an externally derived variable to open a particular file, as in the illustration above.
Conceptually speaking, the approach adopted here is the same as that used to protect against SQL injections: all input that can be defined or manipulated by users has to be carefully scanned and limited prior to use. A detailed explanation of all string processing operations is provided in the Exim manual, here. Refer to the same page for a list of the variables that are labelled 'tainted'. Whether a configuration like the one presented in this section results directly in improved deliverability is hard to say. As indicated in the discussion of old DKIM records below, multiple attempts have been made to strengthen the link between sending domain and signing domain. Furthermore, if you want to use DMARC to secure your 'From' sending domain, each sending domain needs to be set up as its own signing domain anyway.
To enable DKIM-validating mail servers to validate our digital signatures, the public key from the DKIM key pair generated earlier has to be published in the zone file of the signing domain. The first step is to generate the public key from the DKIM key file:
[root@system keys]# openssl rsa -in dkim_rsa.private -out /dev/stdout -pubout -outform PEM writing RSA key -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxMUk9Ac+aZVcqPkgSPny UOkWGrIvXcMJvUHjObpWlMNix3D74hE4KZ+Z18ZvOCUlUQGftzv0MJND/S4kXMlJ xuoxNMCKGozD/O71Rblz7RDUHxrhud2rjtSmXdmDHpH713djNiIxxZgeEeNBzfX3 UGdCJlRMVQJXUcEozqgI5BmUTsdYtrb2Trr99IZtgaLEI92yXVdholtIyt83gnhA YLnvAzOQRV4zE/eBB/pfpbFrkPh1uQQxVIBi0pARj3xk9B8yXiCXUX+gyyBrw3zi /rnXFDe0ORjtDo/3WsSrwaivJ6KjywauYgnwYAx1eNyBGnPquVR6d8OlI15YIXy+ 1wIDAQAB -----END PUBLIC KEY-----
The public key can be inserted directly into a DKIM record as follows:
dkim202005615._domainkey.example.nl. 3600 TXT ( "v=DKIM1; p=" "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxMUk9Ac+aZVcqPkgSPny" "UOkWGrIvXcMJvUHjObpWlMNix3D74hE4KZ+Z18ZvOCUlUQGftzv0MJND/S4kXMlJ" "xuoxNMCKGozD/O71Rblz7RDUHxrhud2rjtSmXdmDHpH713djNiIxxZgeEeNBzfX3" "UGdCJlRMVQJXUcEozqgI5BmUTsdYtrb2Trr99IZtgaLEI92yXVdholtIyt83gnhA" "YLnvAzOQRV4zE/eBB/pfpbFrkPh1uQQxVIBi0pARj3xk9B8yXiCXUX+gyyBrw3zi" "/rnXFDe0ORjtDo/3WsSrwaivJ6KjywauYgnwYAx1eNyBGnPquVR6d8OlI15YIXy+" "1wIDAQAB")
Note the 'dkim20200615': that is the 'selector', which specifies the key pair used for signing. As you'll see shortly, the selector is also included in the 'DKIM Signature' header, so that when the receiving mail server follows the validation procedure, it knows exactly which public key to request from the DNS. The use of selectors enables you to have multiple systems sending DKIM-signed messages for your domain, without the need for those systems to exchange private key files. Each sending mail system can generate its DKIM key pair locally, providing that a unique selector is published in the DNS, along with the relevant public key. Be sure not to use one of the many online tools that generate a DKIM record plus a private key and a public key. Using such a tool implies sharing your private key with the tool provider, enabling them to send mail with your signature.
Before adding the DKIM record above to our zone file, we need to verify that our outbound mail messages (reaching the SMTP gateway via port 587) do indeed bear a valid signature, otherwise they will be blocked by validating MX gateways. After restarting Exim, we send a mail message from our domain through the SMTP gateway, in order to check whether a digital signature is actually being added to the headers. In the following header, that is indeed the case:
dnf restart exim.service
Return-Path: <gebruiker@example.nl> X-Original-To: ontvanger@externdomein.nl Delivered-To: ontvanger@mail.exterdomein.nl Received: from localhost (localhost [127.0.0.1]) by mail.externdomein.nl (Postfix) with ESMTP id 3ADA52800204 for <ontvanger@externdomein.nl>; Sun, 21 Jun 2020 22:57:09 +0200 (CEST) X-Virus-Scanned: amavisd-new at externdomein.nl Received: from mail.externdomein.nl ([127.0.0.1]) by localhost (mail.externdomein.nl [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id fQzUbYri8hun for <ontvanger@externdomein.nl>; Sun, 21 Jun 2020 22:57:08 +0200 (CEST) Received-SPF: None (mailfrom) identity=mailfrom; client-ip=192.0.2.107; helo=mail.example.nl; envelope-from=gebruiker@example.nl; receiver=<UNKNOWN> DMARC-Filter: OpenDMARC Filter v1.3.2 mail.externdomein.nl 3DF992800240 Authentication-Results: OpenDMARC; dmarc=none (p=none dis=none) header.from=example.nl Authentication-Results: OpenDMARC; spf=none smtp.mailfrom=gebruiker@example.nl DKIM-Filter: OpenDKIM Filter v2.11.0 mail.externdomein.nl 3DF992800240 Received: from mail.example.nl (mail.example.nl [192.0.2.107]) by mail.externdomein.nl (Postfix) with ESMTPS id 3DF992800240 for <ontvanger@externdomein.nl>; Sun, 21 Jun 2020 22:57:07 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=example.nl; s=dkim20200615; h=To:Date:Message-ID:Subject:From:Sender:Reply-To:Cc: MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=gzO8d4Zrge5Vc/EpI/kfLPDgD080p2oyA8Azxm/Pqfw=; t=1592773028; x=1593637028; b=F6BJ5g5qb8T06OumiezfgktG03TjaDK32zPPCIGXDhXbqDeYZrM79Ash55m3DxDuflu1zJClyT oLjCBYDQ7KtsSL62ozkNY8WcTUt97Vqof/rJcTCFMLHO7TfR3iEMwcdOUeg8XOBA0ILMrVUedAeJg wqX9ImQLtWSOBcGYbhBF4OmtndK6rhnFTFBLkffUxIWsAhfmIYV/t/WcJmR9qvDCe/uSl95epcOdG E5Y3fuX92tCLllEyGmqjHwaDq07fLxJzQit5SQs5srx2mRN6sEydScHSc7fifGGaqkUJe2SKaEm7s KClKZ0MV88j3U56qyDIFZU2NJvfKNM+KVyPlw==; Received: from gebruiker by mail.example.nl with local (Exim 4.93) (envelope-from <gebruiker@example.nl>) id 1jn6yA-0005RB-8Y for ontvanger@externdomein.nl; Sun, 21 Jun 2020 22:54:58 +0200 From: "Gebruiker" <gebruiker@example.nl> Subject: Test DKIM ondertekening Message-ID: <8a2d394a-789d-9686-7092-b7d5cf2a8619@example.nl> Date: Sun, 21 Jun 2020 22:17:45 +0200 To: ontvanger@externdomein.nl
You'll see that the 'DKIM Signature' header includes both the signing domain ('d=example.nl;') and the selector ('s=dkim20200615;'). Together, those two pieces of information are sufficient to enable the recipient-side DKIM engine (in our example mail.externdomein.nl) to request the appropriate DKIM record from our DNS server. We can also check the availability of that record as follows:
[user@system ~]# dig TXT dkim20200615._domainkey.example.nl. ; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el8 <<>> TXT dkim20200615._domainkey.example.nl. ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19884 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 5 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ; COOKIE: e9836ed80afd3232b4a3a8db5ebafe4bd80d43ef7ae55689 (good) ;; QUESTION SECTION: ;dkim20200615._domainkey.example.nl. IN TXT ;; ANSWER SECTION: dkim20200615._domainkey.example.nl. 86400 IN TXT "v=DKIM1; p=" "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxMUk9Ac+aZVcqPkgSPny" "UOkWGrIvXcMJvUHjObpWlMNix3D74hE4KZ+Z18ZvOCUlUQGftzv0MJND/S4kXMlJ" "xuoxNMCKGozD/O71Rblz7RDUHxrhud2rjtSmXdmDHpH713djNiIxxZgeEeNBzfX3" "UGdCJlRMVQJXUcEozqgI5BmUTsdYtrb2Trr99IZtgaLEI92yXVdholtIyt83gnhA" "YLnvAzOQRV4zE/eBB/pfpbFrkPh1uQQxVIBi0pARj3xk9B8yXiCXUX+gyyBrw3zi" "/rnXFDe0ORjtDo/3WsSrwaivJ6KjywauYgnwYAx1eNyBGnPquVR6d8OlI15YIXy+" "1wIDAQAB" ;; AUTHORITY SECTION: example.nl. 86400 IN NS dns1.example.nl. example.nl. 86400 IN NS dns2.example.nl. ;; ADDITIONAL SECTION: dns1.example.nl. 86400 IN A 192.0.2.5 dns2.example.nl. 86400 IN A 198.51.100.6 dns1.example.nl. 86400 IN AAAA 2001:db8:2198::16:6 dns2.example.nl. 86400 IN AAAA 2001:db8:2198::32:106 ;; Query time: 0 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Mon Jun 15 21:51:39 CEST 2020 ;; MSG SIZE rcvd: 1054
The public key and the other information contained in the record are then used by the receiving mail server to validate the digital signature in the 'DKIM Signature' header. If the validation result is positive, the receiving server accepts the message as authentic (i.e. the message and its content originate from the identified signing domain example.nl). After all, only the DKIM key pair's legitimate owner can publish the public key in the signing domain. And only the legitimate owner has possession of the private key needed to sign the message.
Of course, when you change your DKIM key pair, the public key in the DKIM record needs to be changed as well. That implies performing a minor rollover when you make the change: the new DKIM record needs to be published alongside the old one before the key pair is changed. The new key pair cannot be activated on the server until the TTL has expired and the old DKIM information has been flushed from all DNS caches. If the new key pair is activated too soon, you run the risk that validating mail servers will not accept a DKIM signature based on the new key pair, due to a mismatch with the old information. If you use a new selector for your new key pair, you don't have to wait for all DNS caches to clear before you start using your new key pair. The reason being that no DNS information will have been cached for the new selector. Once you've activated the new key pair, the old DKIM record shouldn't be deleted immediately. Mail can sometimes be in transit for several days before finally being delivered or bounced. We recommend retaining the old DKIM record for at least a week.
In addition to the DKIM records discussed above, you may come across an ADSP record here and there, like the following:
adsp._domainkey IN TXT "dkim=all"
The Author Domain Signing Practices (defined in RFC 5617) were an extension to the DKIM standard, which enabled a domain's owner to indicate that all mail messages from the domain were DKIM-secured. That effectively created a hard link between the sending domain specified in the 'From' header, and the content and authenticity of the message. In much the same way as SPF secures the list of sending mail servers for a given domain in the envelope ('MAIL FROM'), ADSP secures the mail chain for a given sending domain in the 'From' header. However, the ADSP record has now been declared 'historic'. The standard was rarely used and made the delivery of mailing list messages problematic. Another option with a similar purpose was an additional DKIM record, as follows:
_domainkey IN TXT "o=-;"
A record like that told other gateways that all genuine mail messages from the domain in question were signed. However, after appearing in the first DKIM (Sender Signing Policy) drafts, the option was later discontinued and should no longer be used. In the discussion of DMARC below, you'll see that the standard is now used to create a link between the sender address in the 'From' header and the SPF and DKIM-validated sending domains.
DMARC, short for Domain-based Message Authentication, Reporting and Conformance, supplements the other two mail security standards, SPF and DKIM. DMARC lays down a policy (i.e. instructions) telling receiving MX gateways what to do with any incoming mail messages that cannot be validated with either DKIM or SPF. That might involve discarding or quarantining all non-validated mail, for example. DMARC also checks whether the sending domain named in the 'From' header matches the sending domain specified in the envelope (SPF) and the signing domain specified in the DKIM header. DMARC therefore enforces a relationship between the validated SPF and DKIM sending domains and the 'From' sending domain. Consequently, a message that has passed both the SPF and the DKIM sec validation procedures may nevertheless be rejected if the DMARC validation result is negative (indicating a mismatch). A domain's DMARC policy is published via the DNS. In it, a mail domain's operator can specify e-mail addresses to which mail systems can send reports detailing the messages they have accepted and rejected. The operator can then monitor the delivery of both genuine and falsified messages. Note that DMARC implementation requires SPF and/or DKIM (preferably both) to be fully configured for all outbound mail flows. DMARC is defined in RFC 7489.
DMARC introduces nuance to the hard rejection of messages with negative SPF validation results. If the DKIM signature is correct, such a message can be forwarded, albeit tagged. That's particularly important in terms of enabling the use of mailing lists and mail forwarding, where the sender isn't normally specified in the SPF record. If you use DMARC, therefore, you will generally want to modify the SPF record as follows (note the tilde '~' at the end):
IN TXT "v=spf1 mx a ip4:192.0.2.109 ip6:2001:db8:1192::/64" " a:senders.example.nl a:senders.bizbooster.com ~all"
Thus, DMARC (like DKIM) usually serves as a check performed in the context of spam filtering. From the DMARC specification RFC 7489, it's apparent that the security standard is concerned mainly with the security of business mail, with a view to reducing the risk of CEO fraud (whaling), for example.
However, a set-up such as the one we describe here could easily be created for malicious purposes. In itself, therefore, DMARC is not a complete solution. In the context of the spam filter, the decision as to whether a message should be allowed to continue ultimately depends largely on the reputation of the sender (host and sending domain), as reflected in the spam score. Hence, domain-level mail security is an important enabler in the transition from IPv4 to IPv6, where filtering on the basis of IP addresses is not possible.
The first step towards enabling DMARC for our domain example.nl is to add the following DNS record to the zone file:
; DMARC _dmarc IN TXT "v=DMARC1; p=none; sp=none; adkim=s; aspf=s; rua=mailto:dmarc-reports@example.nl; ruf=mailto:dmarc-reports@example.nl; fo=1;"
Let us consider the various elements of the policy in turn:
'v=DMARC1;' We are using DMARC version 1 (currently the only version available)
'p=none;' The policy for the root domain
'sp=none;' The policy for the subdomains (if they don't publish separate DMARC policies of their own)
'adkim=s;' The alignment between the 'From' address and the signing domain specified in the DKIM-validated digital signature
'aspf=s;' The alignment between the 'From' address and the SPF-validated 'MAIL FROM' sending domain
'rua=mailto:dmarc-reports@example.nl;' The mail address that we want aggregated reports sent to (by default once a day, adjustable using the 'ri' tag)
'ruf=mailto:dmarc-reports@example.nl;' The mail address that we want forensic (failure) reports (about individual rejected mail messages) sent to
'fo=1;' Request for receiving mail servers to generate a forensic report for each rejected message
As you can see, we start with the tags 'p=none;' and 'sp=none;'. Those tags tell receiving mail servers to perform all checks, but not to block a message if the SPF and/or DMARC validation result is negative. Other possible values for the tags are 'quarantine' and 'reject'. To begin with, the value is set to 'none' so that valid messages don't get blocked, e.g. because you've forgotten to include an (external) mail server in your SPF policy. The setting is used in a similar way to enable DKIM signing for each valid mail server in turn. Once everything is running smoothly – which you ascertain by reviewing the incoming DMARC reports – you can change the value to 'reject'. That will cause all non-validating messages to be blocked. Opinion differs as to whether you should send out failure reports yourself. Some people argue that sending out bounce messages, some of which will inevitably be for legitimate messages that contain errors, implies sharing potentially sensitive information. The counterargument is that all the information in a failure report has already been transmitted across the entire network on its outward journey, so what you share in a bounce message is already in the public domain. We therefore favour comprehensive mail security based on the use of STARTTLS, DANE and end-to-end encryption.
The tags 'adkim=s' and 'aspf=s;' specify how strict the recipient should be regarding the alignment between the 'From' sending domain stated for an incoming message and the SPF and DKIM-validated domain names. If the 'adkim' tag value is 's' (strict), the 'From' domain must exactly match the signing domain stated in the 'd=' tag of the DKIM header. So, for example, you will not be able send messages from a subdomain (e.g. alerts@news.example.nl) signed with your root domain's digital signature [1, 2] (example.nl). You can do that, however, if you use the tag 'adkim=r;' (where 'r' is 'relaxed', the default value). Note that the use of DKIM implies that the signing domain and your root domain are the same. Also, take care not to confuse this tag with the canonicalisation tag ('c=relaxed/simple;') in the DKIM header. With the 'aspf' tag too, you have a choice between 'strict' and 'relaxed'. However, this tag relates to alignment between the message's 'From' sending domain and the 'MAIL FROM' sending domain stated in the envelope. In other words, while SPF and DKIM enable you to secure your 'MAIL FROM' sending domain and the sending mail servers, DMARC enforces a link with the 'From' header. After all, the sender information (display name) in that header is what the end user ultimately sees in their mail client. The 'adkim=s' and 'aspf=s;' tags are there for fine-tuning the DMARC validation alignment requirements.
It will come as no surprise that you can't use any old e-mail addresses in the 'rua=' and 'ruf=' tags. If you want DMARC reports sent to an e-mail address in a domain other than the one that the policy is for – if you use a DMARC service provider, for example, and want the reports to go to them – the nominated recipient needs to give permission. To that end, the recipient has to insert a DMARC authorisation record in their zone file:
example.nl._report._dmarc IN TXT "v=DMARC1;"
The authorisation record tells receiving mail servers that their DMARC reports for the domain example.nl can legitimately be sent to an address at the other domain in question. For full details and other available tags, see section 6.3 of RFC 7489.
Be sure not to forget the final semicolon ';' at the end of the DMARC Authorization record. The semicolon is mandatory, but was nevertheless omitted from earlier versions of RFC 7489.
Once you've published your DMARC record, you'll see that report messages from the big mail handlers start arriving at your nominated address. We get them from Google, Yahoo, Microsoft, Amazon, Fastmail and various smaller service providers, for example. Dutch government organisations and XS4ALL often send us DMARC reports as well. Each report message is accompanied by a (zipped) XML file containing details of all incoming mail messages. If you wish, you can arrange for a commercial service provider such as DMARC Advisor and DMARC Analyzer to process the data and generate fancy graphics. That involves opening an account with the service provider and modifying your DMARC record so that their e-mail address is the nominated reporting address. As explained above, the service provider will also need to add an authorisation record to their zone file. You'll then get a user-friendly (online) analysis and visualisation of the incoming report messages.
However, various (low-level) open-source tools are now available for processing and analysing DMARC reports:
You might consider using such a tool as a basis for your own analysis and dashboard [1, 2, 3, 4].
Exim's DMARC engine is enabled by default. In the initial configuration you'll see that the control modifier 'dmarc_disable_verify' is set (along with the 'dkim_disable_verify' modifier) for messages from local senders, relay hosts and authenticated connections. Thus, DMARC validation is explicitly disabled for all messages except those from remote, non-authenticated senders (received on port 25 of the MX gateway). Being dependent on the DKIM checks, the results of the DMARC checks are not known until all the data has been received. The 'acl_check_data' ACL (immediately after receipt of the complete message body) is the first point at which our DMARC configuration can be added (on port 25 of the MX-gateway). Incoming messages with negative validation results can then be dropped immediately after transfer, and an error message can be sent back as part of the existing SMTP session. The diagram below shows where the DMARC checks are added to the workflow.
The following are the DMARC-related global configuration options that we have added to the file '/etc/exim/exim.conf'.
# DMARC dmarc_tld_file=/usr/share/publicsuffix/public_suffix_list.dat dmarc_history_file=/var/spool/exim/opendmarc/history.dat dmarc_forensic_sender=postmaster@example.nl
The first option ('dmarc_tld_file') was discussed at the start of this article, in connection with the initial DMARC configuration. The 'dmarc_history_file' option specifies where data should be stored to enable aggregated reports to be sent. The location in question doesn't yet exist, however, and therefore needs to be created manually:
mkdir /var/spool/exim/opendmarc/ chmod 750 /var/spool/exim/opendmarc/ chown exim:exim /var/spool/exim/opendmarc
The final option ('dmarc_forensic_sender') specifies the sending address for failure reports. No failure reports can be sent, however, until the control modifier 'dmarc_enable_forensic' has also been set in the ACL (as below).
The global configuration options referred to above are followed by the tests, which are added to the 'acl_check_data' ACL (between the header checks and the virus/spam checks, in the file '/etc/exim/exim.conf'):
# DMARC warn dmarc_status = quarantine !authenticated = * log_message = Message from $dmarc_used_domain failed sender's DMARC policy; quarantine control = dmarc_enable_forensic set acl_m_quarantine = 1 # this variable to use in a router/transport deny dmarc_status = reject !authenticated = * message = Message from $dmarc_used_domain failed sender's DMARC policy; reject control = dmarc_enable_forensic warn add_header = :at_start: ${authresults {$primary_hostname}}
If the result of the DMARC check ('dmarc_status') on an incoming message is 'deny', the message is blocked. If the result is 'quarantine', a flag is set ('set acl_m_quarantine = 1'), which can subsequently be used in a router or transport to quarantine the message.
The most straightforward approach is to create a dedicated user for DMARC quarantine purposes, and to have quarantine-related mail delivered to the account in question. An administrator can then access the messages using conventional mail tools. To that end, our first step is to create a new user called 'mail-quarantine':
useradd -c "Quarantine Mail" -s "/bin/bash" mail-quarantine passwd mail-quarantine
There are two points at which the previously set flag 'dmarc_enable_forensic' can be used to divert a message to the new user: in the 'localuser' router or in the 'local_delivery' transport. However, the first option (which simply requires modification of the 'directory' and 'user' variables) tends to be problematic with regard to local delivery agent access rights. Our preference is therefore to modify the transport. That involves changing the existing transport (in our case 'local_delivery') to 'local_delivery_quarantine', as follows:
local_delivery: driver = appendfile #file = /var/mail/$local_part directory = $home/Maildir maildir_format delivery_date_add envelope_to_add return_path_add group = mail mode = 0660 local_delivery_quarantine: driver = appendfile #file = /var/mail/$local_part directory = /home/mail-quarantine/Maildir user = mail-quarantine home_directory = /home/mail-quarantine current_directory = /home/mail-quarantine maildir_format delivery_date_add envelope_to_add return_path_add group = mail mode = 0660
As you'll see, the 'directory', 'user', 'home_directory' and 'current_directory' variables in the second transport have been modified so that messages are delivered to the Maildir of the user 'mail-quarantine'. The appropriate transport is selected in the 'localuser' router, by simply basing the transport selection on the variable 'acl_m_quarantine':
#transport = local_delivery transport = ${if =={$acl_m_quarantine}{1} {local_delivery_quarantine}{local_delivery}}
From the following logs we can see that, once Exim has been restarted, an incoming message whose DMARC check result is 'quarantine' is indeed delivered to the user 'mail-quarantine' via the 'local_delivery_quarantine' transport
2020-06-13 21:27:39 1jkBoZ-0007qb-3a DMARC results: spf_domain=afzenderdomein.nl dmarc_domain=afzenderdomein.nl spf_align=no dkim_align=no enforcement='Quarantine' 2020-06-13 21:27:39 1jkBoZ-0007qb-3a H=(sender.afzenderdomein.nl) [203.0.113.109] Warning: Message from afzenderdomein.nl failed sender's DMARC policy; quarantine 2020-06-13 21:27:43 1jkBoZ-0007qb-3a <= tester@afzenderdomein.nl H=(sender.afzenderdomein.nl) [203.0.113.109] P=esmtps X=TLS1.3:TLS_AES_256_GCM_SHA384:256 CV=no S=9548 DKIM=afzenderdomein.nl id=f894a5b6-3775-42ad-0d2b-f399a7498ca2@afzenderdomein.nl 2020-06-13 21:27:43 1jkBoZ-0007qb-3a => ontvanger <ontvanger@example.nl> R=localuser T=local_delivery_quarantine 2020-06-13 21:27:43 1jkBoZ-0007qb-3a Completed
The DMARC logs and headers of non-problematic messages will now include an 'Authentication-Results' header containing the results of the DMARC validation:
2020-06-13 12:56:33 1jk3px-00061C-La DMARC results: spf_domain=afzenderdomein.nl dmarc_domain=afzenderdomein.nl spf_align=no dkim_align=yes enforcement='Accept' 2020-06-13 12:56:34 1jk3px-00061C-La <= tester@afzenderdomein.nl H=(sender.afzenderdomein.nl) [203.0.113.109] P=esmtps X=TLS1.3:TLS_AES_256_GCM_SHA384:256 CV=no S=9492 DKIM=afzenderdomein.nl id=5adb8f49-d6a3-0669-95e6-cd1c81ee2885@afzenderdomein.nl 2020-06-13 12:56:34 1jk3px-00061C-La => ontvanger <ontvanger@example.nl> R=localuser T=local_delivery 2020-06-13 12:56:34 1jk3px-00061C-La Completed Return-path: <tester@afzenderdomein.nl> Envelope-to: ontvanger@example.nl Delivery-date: Sat, 13 Jun 2020 12:56:34 +0200 X-Spam-Report: Spam detection software, running on the system "mail.example.nl", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see @@CONTACT_ADDRESS@@ for details. Content preview: ... Content analysis details: (1.1 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: afzenderdomein.nl] -0.0 SPF_PASS SPF: sender matches SPF record -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS X-Spam-Score: 1.1 (+) Authentication-Results: mail.example.nl; iprev=fail smtp.remote-ip=203.0.113.109; dkim=pass header.d=afzenderdomein.nl header.s=dkim20191228 header.a=rsa-sha256; dmarc=pass header.from=afzenderdomein.nl Received: from [203.0.113.109] (helo=sender.afzenderdomein.nl) by mail.example.nl with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.93) (envelope-from <tester@afzenderdomein.nl>) id 1jk3px-00061C-La for ontvanger@example.nl; Sat, 13 Jun 2020 12:56:34 +0200 Received: from localhost (localhost [127.0.0.1]) by sender.afzenderdomein.nl (Postfix) with ESMTP id 6B1202800207; Sat, 13 Jun 2020 12:56:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=afzenderdomein.nl; h=content-type:content-type:mime-version:user-agent:date:date :message-id:organization:reply-to:subject:subject:from:from :received:received; s=dkim20191228; t=1592045792; x=1592909793; bh=fV/HSSablAO9dg+XB6nf25aOMgrAYic/C/18QLMt1I0=; b=ergZe22ZNwbI J9tCBPqIUNZZa85wX43BcbC3tjWUND39PVo3Zn/sRmeQf11HtebGhyWXTkKe8F9e S9pYgIbGVpObkBXKD6nq6ugoe6zC0W3/MObTdi+xGBkulYF4mFTr6TK61Yv+h7Kc /wWsVxuUjkgirBZ1UQnHL5r33Fnxi99ENjCB2rHZTdwbfUbpIxJmYbLxoAL9fjin SwOuz2UT9vwQhtdPwohu5wWSGyjKvYwGw8Kisx4J5qCp+lJcXcMrt3PxSEM+xey7 5GLo/wQ8rfxPDiXtN5phRa6G09lrCYY= X-Virus-Scanned: amavisd-new at afzenderdomein.nl Received: from mail.afzenderdomein.nl ([127.0.0.1]) by localhost (mail.afzenderdomein.nl [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id EJs6Rg0U356M; Sat, 13 Jun 2020 12:56:32 +0200 (CEST) Received: from host.afzenderdomein.nl (host.afzenderdomein.nl [203.0.113.128]) by mail.afzenderdomein.nl (Postfix) with ESMTPSA id 2555B280063C; Sat, 13 Jun 2020 12:56:32 +0200 (CEST) From: "Tester" <tester@afzenderdomein.nl> Subject: Test DMARC validatie Reply-To: tester@afzenderdomein.nl To: ontvanger@example.nl Message-ID: <5adb8f49-d6a3-0669-95e6-cd1c81ee2885@afzenderdomein.nl> Date: Sat, 13 Jun 2020 12:56:31 +0200
A test message that fails the DMARC validation gets rejected as follows:
the server did not accept the mail server message: 550 Message from afzenderdomein.nl failed sender's DMARC policy; reject could not send mail 2020-06-09 13:04:29 1jic3R-000372-I7 DMARC results: spf_domain=afzenderdomein.nl dmarc_domain=afzenderdomein.nl spf_align=no dkim_align=no enforcement='Reject' 2020-06-09 13:04:29 1jic3R-000372-I7 H=(sender.afzenderdomein.nl) [203.0.113.109] F=<tester@afzenderdomein.nl> rejected after DATA: Message from afzenderdomein.nl failed sender's DMARC policy; reject
Our SPF/DKIM/DMARC set-up is now complete!
You can test your mail configuration on Internet.nl and via this portals:
After some minor tweaking, Exim on CentOS 8.2 (in combination with the EPEL repository) comes with built-in support for SPF, DKIM and DMARC (the first two also as part of the spam scanner). Although the initial configuration (on CentOS) doesn't include policies for the three standards, full support for SPF, DKIM and DMARC is built into the software. You therefore have a solid basis for setting up your own secure Exim-based mail system. Before starting work, however, it is important to familiarise yourself with what the various statements in the Exim configuration mean and what their effect is. In this hands-on article, we have explained how to realise the comprehensive implementation of all three mail security standards: SPF, DKIM and DMARC. Combining the set-up described with support for IPv6 and DANE should ensure a 100 per cent score on internet.nl, implying that your mail systems are completely up-to-date and consistent with the latest requirements and guidance of the Forum for Standardisation, the NCSC and others. What's more, domain-level mail security is an important enabler for transitioning from IPv4 to IPv6, because IP address-based filtering isn't possible with IPv6. If and when new SPF/DKIM/DMARC functionality is added to Exim, we will update this article accordingly. In the meantime, we're pleased to hear any feedback you may have on this article. If what you want is a modular mail system with greater emphasis on scalability and security, we advise looking at Postfix. We have written a companion hands-on article about the configuration of SPF, DKIM and DMARC on that platform. Postfix does require more extension and integration work, but allows you to create a mail infrastructure customised and dimensioned to your own needs.