Introduction
Here we’ll work on DMARC Postfix Setup. I’ll avoid long story here. Domain-based Message Authentication, Reporting, and Conformance (DMARC) is a standard that helps protect email senders and recipients from spoofing, spam and phishing. It relies on SPF and DKIM to do so. For more details check “DMARC email validation system“. Without further ado, we’ll go straight to DMARC Postfix Setup.
Outgoing mail – DMARC DNS Record
Create a subdomain (_dmarc) and add TXT record. Adjust values to your linking. Example:
v=DMARC1;p=quarantine;sp=quarantine;adkim=r;aspf=r;fo=1;rf=afrf;rua=mailto:dmarc@cyberpunk.rs;ruf=mailto:dmarc@cyberpunk.rs
Count in DNS propagation time.
Incoming mail – DMARC with postfix
Install opendmarc:
# apt-get install opendmarc
Open conf and change lines:
# nano /etc/opendmarc.conf AuthservID mail.cyberpunk.rs PidFile /var/run/opendmarc.pid #Debian default RejectFailures false Syslog true TrustedAuthservIDs mail.cyberpunk.rs, cyberpunk.rs UMask 0002 UserID opendmarc:opendmarc IgnoreHosts /etc/opendmarc/ignore.hosts HistoryFile /var/run/opendmarc/opendmarc.dat #for testing: SoftwareHeader true
Some explanations (check more details on trusteddomains.org):
AuthservID
: hostname of your mail server (or unique string)PidFile
: Path to the PID fileRejectFailures
: if true, E-Mails that fail DMARC verification will be rejected by your mail server. To only tag them, set this to falseSyslog
: True/False. Tells opendmarc, whether it should log to syslog or notTrustedAuthservIDs
: these AuthservIDs are assumed to be valid inputs for DMARC assessment. This can prevent the DMARC tests from running several times if you have multiple mail servers in your organizationUMask
: the PID file and the socket file are created with this umaskUserID
: User and group running the opendmarc service separated by “:”IgnoreHosts
: Ignored Hosts list file pathHistoryFile
: The path under which the History file should be created. This file is necessary if you want to be able to create aggregate reports to send out to other organizationsSoftwareHeader
: adds a “Dmarc-Filter” header in every processed mail (with the opendmarc version). Good to have during testing, disable when finished setting up.
For some reason Forensic Reporting option, doesn’t work. Setting ForensicReports to true, causes fatal error. Although there is a mentioned online on information leakage related to ForensicReports and mailing lists. We’ll ignore it for now.
Create ignore hosts file defined in configuration:
# mkdir /etc/opendmarc # nano /etc/opendmarc/ignore.hosts
File should contain a list of networks and hosts that you trust. Their mail will not go through openDMARC checker:
localhost 192.168.1.0/24
When you create /etc/opendmarc
directory, make sure ownership is set to “opendmarc” user. I had some errors like: “opendmarc-reports: can’t create report file for domain” and the problem was just that. Opendmarc-reports creates temporary files in the current directory, and due to wrong ownership/permissions it was unable to generate them.
To continue, add another line to default conf: /etc/default/opendmarc
:
SOCKET="inet:12345@localhost"
Start OpenDMARC:
/etc/init.d/opendmarc start or service opendmarc start
Adjust Postfix, add to /etc/postfix/main.cf:
smtpd_milters=inet:localhost:12345 non_smtpd_milters=inet:localhost:12345
Reload postfix:
# /etc/init.d/postfix reload
DMARC verification on incoming mail should now be active. Easiest way to check is to send email to one of email accounts on your server. Look at the “Authentication-Results” field(s) and dmarc=pass
.
DMARC Reporting
The server performs DMARC checks but it’s not DMARC compliant yet. We’re missing reporting capabilities. To implement that missing segment, you’ll need access to MySQL DB. This might come in handy: MySQL Server Install
DMARC DB schema can be found on:
/usr/share/doc/opendmarc/schema.mysql
Default is ok, but if you want to avoid creating user database manually, uncomment:
-- CREATE USER 'opendmarc'@'localhost' IDENTIFIED BY 'changeme'; -- GRANT ALL ON opendmarc.* to 'opendmarc'@'localhost';
Those two lines will create user and assign appropriate rights. Adjust if needed (at least chose your password instead of “changeme”).
Import database:
# mysql -u root -p < /path/to/schema.mysql/schema.sql
Note: Import (or report_script execution, mentioned later on) might break due to “invalid default value for repurl” or “Invalid default value for ‘lastsent’. To circumvent it temporarily, you could place SET sql_mode = '';
at the top of the schema file. I’ve seen some schema alterations offered online:
ALTER TABLE messages MODIFY COLUMN policy_domain int(10) UNSIGNED DEFAULT NULL; ALTER TABLE requests MODIFY COLUMN adkim tinyint(4) DEFAULT NULL; ALTER TABLE requests MODIFY COLUMN aspf tinyint(4) DEFAULT NULL; ALTER TABLE requests MODIFY COLUMN pct tinyint(4) DEFAULT NULL; ALTER TABLE requests MODIFY COLUMN policy tinyint(4) DEFAULT NULL; ALTER TABLE requests MODIFY COLUMN repuri VARCHAR(255) DEFAULT NULL; ALTER TABLE requests MODIFY COLUMN spolicy tinyint(4) DEFAULT NULL;
Here is another “fixed” opendmarc schema. Use ALTER instead of CREATE if you have some data already present. When you run “dmarc_report” script mentioned below, errors might be reported. Correct schema manually if you have to. If you encounter additional “Incorrect datetime value: ‘0000-00-00 00:00:00’ for column” errors, correct the scripts (opendmarc-import/reports/expire). MySQL probably runs in “strict” mode, and a setting NO_ZERO_DATE prevents such default timestamps.A raw fix, replace “0000-00-00 00:00:00” with “1971-01-01 00:00:01”. For instance, opendmarc-expire script have a hardcoded value:
$dbi_s = $dbi_h->prepare("DELETE FROM requests WHERE lastsent <= DATE_SUB(CURRENT_TIMESTAMP(), INTERVAL ? DAY) AND NOT lastsent = '0000-00-00 00:00:00'");
Replace that with “1971-01-01 00:00:01”.
Once that step is done, we’ll create a script to input history file into DB and send the reports.
/etc/opendmarc/dmarc_report
I dug this script somewhere:
#!/bin/bash DB_SERVER='database.example.com' DB_USER='opendmarc' DB_PASS='password' DB_NAME='opendmarc' WORK_DIR='/var/run/opendmarc' REPORT_EMAIL='dmarc [at] example [dot] com' REPORT_ORG=example.com' mv ${WORK_DIR}/opendmarc.dat ${WORK_DIR}/opendmarc_import.dat -f cat /dev/null > ${WORK_DIR}/opendmarc.dat /usr/sbin/opendmarc-import --dbhost=${DB_SERVER} --dbuser=${DB_USER} --dbpasswd=${DB_PASS} --dbname=${DB_NAME} --verbose < ${WORK_DIR}/opendmarc_import.dat /usr/sbin/opendmarc-reports --dbhost=${DB_SERVER} --dbuser=${DB_USER} --dbpasswd=${DB_PASS} --dbname=${DB_NAME} --verbose --interval=86400 --report-email $REPORT_EMAIL --report-org $REPORT_ORG /usr/sbin/opendmarc-expire --dbhost=${DB_SERVER} --dbuser=${DB_USER} --dbpasswd=${DB_PASS} --dbname=${DB_NAME} --verbose
Adjust the details in this script, with the ones you used in previous step(s).
opendmarc-import
reads per-message data recorded by an instance of opendmarc and inserts it into an SQL database, for later use by opendmarc-reports to generate aggregate reports. Records are read from standard input.opendmarc-reports
pulls data from an OpenDMARC database and generates periodic aggregate reports. The database is populated by a running opendmarc-import on a message history file generated by an opendmarc filter as messages arrive and are processed. This includes the collection of reporting URIs, which this script uses to make reports available to those that request them.opendmarc-expire
expires old records from the database that is part of the OpenDMARC aggregate reporting feature.
Make the script executable:
chmod +x /etc/opendmarc/dmarc_report
Run under the opendmarc user as a test:
su -c "/etc/opendmarc/dmarc_report" -s /bin/bash opendmarc
Use of uninitialized value in concatenation (.) or string at /usr/sbin/opendmarc-expire line 291
” error. You must patch that. Replace:if ($dbi_s->execute)
with:
if (!$dbi_s->execute)
Use of uninitialized value $answer in scalar chomp at /usr/sbin/opendmarc-reports line 938/939
$answer = ${${*$smtp}{'net_cmd_resp'}}[1]; if (!defined($answer)){ $answer = $smtp->message(); } chomp($answer);
When finished, add that script to crontab:
nano /etc/crontab or crontab -e 0 1 * * * opendmarc /etc/opendmarc/dmarc_report
With this, execution is scheduled to run each day at 01:00.
To receive a copy of every outgoing DMARC report during testing, add one of you Mailboxes as bcc for every Message sent by the DMARC address. Add following line to your postfix main.cf:
sender_bcc_maps = hash:/etc/postfix/bcc_map
Create the file bcc_map
with following content:
info@cyberpunk.rs dmarc@cyberpunk.rs
Apply mapping:
# postmap /etc/postfix/bcc_map
Restart Postfix:
# service postfix restart
Note: Additional problem appeared while sending emails. For each mail sent, I had 3 mails reported back to dmarc@cyberpunk.rs. One advice was to add receive_override_options
in master.cf
:
submission inet n - n - - smtpd -o receive_override_options=no_address_mappings
no_address_mappings
: Disable canonical address mapping, virtual alias map expansion, address masquerading, and automatic BCC (blind carbon-copy) recipients. This is typically specified BEFORE an external content filter.
To test DMARC we need to send an email from our server to external server that support DMARC and send it from there to us. You can play with GMail, they support this. In mail header there should be a DMARC header.
Delete debug header in configuration after you get everything running:
nano /etc/opendmarc.conf #SoftwareHeader true
Conclusion
This process was more difficult than I initially thought. A lot of inconsistencies, adjustments, schema issues (MySQL), including some things which were to be absorbed. It was a journey.. I probably didn’t cover everything, but it is a start. We’ll upgrade this as we go.
Things to work on:
- Continue Testing, Playing around, see about the reports