Set up DMARC (verification) for Postfix on Debian server

Set up DMARC (verification) for Postfix on Debian server

What is DMARC

DMARC (Domain-based Message Authentication, Reporting and Conformance) is an Internet standard (RFC 7489 ) that allows domain owners to prevent their domain names from being used by email spoofers. Before DMARC is invented, it is very easy for bad actors to use other people’s domain name in the From address .

If a domain owner created DMARC DNS record for his/her domain name and a receiving email server implemented DMARC check, then bad actors need to pass SPF alignment or DKIM alignment in order to pass DMARC check. If DMARC check fails, the spoofed email could be rejected. Never to be seen by end-users. It’s difficult for the bad actor to pass SPF or DKIM , unless the domain owner’s email server is compromised.

Install OpenDMARC

OpenDMARC is an open-source software that can perform DMARC verification and reporting. It’s already in the Debian repository, so you can run the following command to install it.

~] apt-get install opendmarc

If you are asked to configure a database for OpenDMARC, you can safely choose No. You only need to configure a database for OpenDMARC if you want to generate DMARC reports for other mailbox providers. It’s not very useful for small mail server operators like us to generate DMARC reports, so we can skip it.

Once installed, it will be automatically started. Check its status with:

~] systemctl status opendmarc
● opendmarc.service - OpenDMARC Milter
     Loaded: loaded (/lib/systemd/system/opendmarc.service; enabled; preset: enabled)
     Active: active (running) since Thu 2024-04-04 11:02:44 CEST; 53s ago
       Docs: man:opendmarc(8)
             man:opendmarc.conf(5)
    Process: 8975 ExecStart=/usr/sbin/opendmarc (code=exited, status=0/SUCCESS)
   Main PID: 8976 (opendmarc)
      Tasks: 6 (limit: 4646)
     Memory: 2.3M
        CPU: 13ms
     CGroup: /system.slice/opendmarc.service
             └─8976 /usr/sbin/opendmarc

Apr 04 11:02:44 mailin1 systemd[1]: Starting opendmarc.service - OpenDMARC Milter...
Apr 04 11:02:44 mailin1 systemd[1]: Started opendmarc.service - OpenDMARC Milter.
Apr 04 11:02:44 mailin1 opendmarc[8976]: OpenDMARC Filter v1.4.2 starting ()
Apr 04 11:02:44 mailin1 opendmarc[8976]: additional trusted authentication services: (none)

Note that auto-start at system boot time is disabled. We can enable it by:

~] systemctl enable opendmarc

Configure OpenDmarc

For our set-up, we'll modify the configuration in the file '/etc/opendmarc.conf' as follows:

AuthservID                    OpenDMARC-mailin1.secar.cz
TrustedAuthservIDs            mailin1.secar.cz

PidFile                       /run/opendmarc/opendmarc.pid
PublicSuffixList              /usr/share/publicsuffix/public_suffix_list.dat
Socket                        inet:8888@127.0.0.1
Syslog                        true
SyslogFacility                mail
UMask                         0000
UserID                        opendmarc

RejectFailures                true
SPFIgnoreResults              true
SPFSelfValidate               true
IgnoreAuthenticatedClients    true
IgnoreHosts                   /etc/opendmarc/ignore.hosts

ReportCommand                 /usr/sbin/sendmail -t
HistoryFile                   /run/opendmarc/opendmarc.dat
AuthservID (string)
Sets the "authserv-id" to use when generating the Authentication-Results: header field after verifying a message. The default is to use the name of the MTA processing the message. If the string "HOSTNAME" is provided, the name of the host running the filter (as returned by the gethostname(3) function) will be used.
TrustedAuthservIDs (string)
Provides a list of authserv-ids that are to be used to identify Authentication-Results header fields whose contents are to be assumed as valid input for the DMARC assessment. To provide a list, separate values by commas. If the string "HOSTNAME" is provided, the name of the host running the filter (as returned by the gethostname(3) function) will be used. Matching against this list is case-insensitive. The default is to use the value of AuthservID.
PidFile (string)
Specifies the path to a file that should be created at process start containing the process ID.
PublicSuffixList (string)
Specifies the path to a file that contains top-level domains (TLDs) that will be used to compute the Organizational Domain for a given domain name, as described in the DMARC specification. If not provided, the filter will not be able to determine the Organizational Domain and only the presented domain will be evaluated.
Socket (string)
Specifies the socket that should be established by the filter to receive connections from sendmail(8) in order to provide service. socketspec is in one of two forms: local:path, which creates a UNIX domain socket at the specified path, or inet:port[@host] or inet6:port[@host] which creates a TCP socket on the specified port for the appropriate protocol family. If the host is not given as either a hostname or an IP address, the socket will be listening on all interfaces. This option is mandatory either in the configuration file or on the command line. If an IP address is used, it must be enclosed in square brackets.
UMask (integer)
Requests a specific permissions mask to be used for file creation. This only really applies to creation of the socket when Socket specifies a UNIX domain socket, and to the PidFile (if any); temporary files are created by the mkstemp(3) function that enforces a specific file mode on creation regardless of the process umask. See umask(2) for more information.
UserID (string)
Attempts to become the specified userid before starting operations. The value is of the form userid[:group]. The process will be assigned all of the groups and primary group ID of the named userid unless an alternate group is specified.
RejectFailures (Boolean)
If set, messages will be rejected if they fail the DMARC evaluation, or temp-failed if evaluation could not be completed. By default, no message will be rejected or temp-failed regardless of the outcome of the DMARC evaluation of the message. Instead, an Authentication-Results header field will be added. The default is "false".
SPFIgnoreResults (Boolean)
Causes the filter to ignore any SPF results in the header of the message. This is useful if you want the filter to perfrom SPF checks itself, or because you don’t trust the arriving header. The default is "false".
SPFSelfValidate (Boolean)
Causes the filter to perform a fallback SPF check itself when it can find no SPF results in the message header. If SPFIgnoreResults is also set, it never looks for SPF results in headers and always performs the SPF check itself when this is set. The default is "false".
IgnoreHosts (string)
Specifies the path to a file that contains a list of hostnames, IP addresses, and/or CIDR expressions identifying hosts whose SMTP connections are to be ignored by the filter. If not specified, defaults to "127.0.0.1" only.
IgnoreAuthenticatedClients (Boolean)
If set, causes mail from authenticated clients (i.e., those that used SMTP AUTH) to be ignored by the filter. The default is "false".
ReportCommand (string)
Indicates the shell command to which failure reports should be passed for delivery when FailureReports is enabled. Defaults to /usr/sbin/sendmail.

Create files for OpenDMARC

Create OpenDmarc directory:

~] mkdir /etc/opendmarc

Create file /etc/opendmarc/ignore.hosts with content:

/etc/opendmarc/ignore.hosts
127.0.0.1

Restart opendmarc service:

~] systemctl restart opendmarc

Linking OpenDMARC to Postfix

Now that the configuration is complete, we can restart up our new OpenDMARC service as follows:

systemctl restart opendmarc.service
# check opendmarc status
systemctl status opendmarc.service

Check OpenDMARC listen on inet port 8888:

~] netstat -lnp | grep 8888
tcp        0      0 127.0.0.1:8888          0.0.0.0:*               LISTEN      10916/opendmarc

Next, we add the service (now available on port 8888) to the milters listed for the Postfix smtp/25 service in the file /etc/postfix/master.cf:

-o smtpd_milters=inet:127.0.0.1:8888

Testing DMARC with Telnet

You can use telnet to spoof another domain name, such as paypal.com. First, run the following command on your local computer to connect to port 25 of your mail server.

# use domain name or ip address of your mail server
telnet mail.yourdomain.com 25

Then use the following steps to send a spoof email. (You type in the bold texts.)

EHELO mail.paypal.com
250 mail.yourdomain.com
MAIL FROM:<help@paypal.com>
250 2.1.0 Ok
RCPT TO:<someone@yourdomain.com>
250 2.1.5 Ok
DATA
354 End data with .
From:     help@paypal.com
To:       someone@yourdomain.com
Subject:  Please update your password.

Click this link to update your password.
.
550 5.7.1 rejected by DMARC policy for paypal.com
quit
221 2.0.0 Bye
Connection closed by foreign host.

As you can see, my mail server rejected this email because it didn’t pass DMARC check and Paypal deployed a p=reject policy.

dns query to paypal.com dmarc txt record:

~] dig txt _dmarc.paypal.com
_dmarc.paypal.com.      3600    IN      TXT     "v=DMARC1; p=reject; rua=mailto:d@rua.agari.com; ruf=mailto:d@ruf.agari.com"

And here is output from postfix log file:

1
2
3
4
5
2024-04-05T11:37:24.105912+02:00 mail.yourdomain.com postfix/smtpd[18132]: 4V9tgS0kbLzsRQZ: client=pc.yourdomain.com[192.168.0.221]
2024-04-05T11:37:44.046358+02:00 mail.yourdomain.com postfix/cleanup[18131]: 4V9tgS0kbLzsRQZ: message-id=<4V9tgS0kbLzsRQZ@mail.yourdomain.com>
2024-04-05T11:37:44.160667+02:00 mail.yourdomain.com opendmarc[14038]: 4V9tgS0kbLzsRQZ: SPF(mailfrom): paypal.com fail
2024-04-05T11:37:44.168624+02:00 mail.yourdomain.com opendmarc[14038]: 4V9tgS0kbLzsRQZ: paypal.com fail
2024-04-05T11:37:44.200883+02:00 mail.yourdomain.com postfix/cleanup[18131]: 4V9tgS0kbLzsRQZ: milter-reject: END-OF-MESSAGE from mailin1.secar.cz[192.168.0.221]: 5.7.1 rejected by DMARC policy for paypal.com; from=<help@paypal.com> to=<someone@yourdomain.com> proto=ESMTP helo=<mail.paypal.com>

Test mail configuration

You can test your mail configuration via this portals:

Sources

SUBSCRIBE FOR NEW ARTICLES

@
comments powered by Disqus