Hiho Dears,
after investigating my EXIM DMARC (opendmarc EXP) setup and the current docs about Exim DMARC in more detail i've found that anything seems to work except that the SPF variable(s) - expescially "spf_domain=" are not filled correctly within dmarc.c. This leads to bad XML reports too, because of "failed" empty SPF fields.
Could someone pls explain how the "spf_domain" vs. spf data within DMARC whould work?
Here is an example of a googlemail.com email going through DMARC here:
--- snip ---
2017-05-31 10:40:11 1dFzAp-0007cW-UY DKIM: d=googlemail.com s=20161025 c=relaxed/relaxed a=rsa-sha256 b=2048 [verification succeeded]
2017-05-31 10:40:11 1dFzAp-0007cW-UY DMARC results: spf_domain= dmarc_domain=googlemail.com spf_align=no dkim_align=yes enforcement='Accept'
2017-05-31 10:40:11 1dFzAp-0007cW-UY H=mail-wr0-f195.google.com [209.85.128.195] Warning: [DMARC] ACCEPTED: accept googlemail.com
2017-05-31 10:40:11 1dFzAp-0007cW-UY H=mail-wr0-f195.google.com [209.85.128.195] Warning: [DMARC] DEBUG: 'accept' for googlemail.com STATUS Accept USED_DOMAIN googlemail.com DMARC_HEADER Authentication-Results: mail.syndicat.com; dmarc=pass header.from=googlemail.com
--- snap ---
I use EXIM 4.89nb1 on NetBSD
build against:
- libspf2-1.2.10
- opendmarc 1.3.1
with:
LOOKUP_LIBS=-lmysqlclient -lssl -lcrypto -lopendmarc -Wl,-R/usr/pkg/lib -L/usr/pkg/lib -lsasl2 -lspf2
EXPERIMENTAL_SPF=yes
EXPERIMENTAL_DMARC=yes
WITH_CONTENT_SCAN=YES
...
OpenDMARC is build with SPF support (tried it without too):
opendmarc: OpenDMARC Filter v1.3.1
SMFI_VERSION 0x1000001
libmilter version 1.0.1
Active code options:
WITH_SPF
in opendmarc.conf these are commented our / default:
## SPFIgnoreResults { true | false }
## default "false"
#SPFSelfValidate true
## Syslog { true | false }
## default "false"
I'm not sure if Exim DMARC uses this over i.e. libopendmarc or SPF directly from libspf2.
As described, i do the SPF checks "before" DMARC checks within
acl_check_rcpt:
...
### SPF native
warn set acl_m_spf_record = ${lookup dnsdb{txt=$sender_address_domain}{$value}}
# No record
warn !condition = ${if def:acl_m_spf_record}
!hosts = +3rdmxes : +relay_from_hosts
log_message = [SPF] no record
# SPF +all is meaningless
warn condition = ${if match {$acl_m_spf_record}{\\+all}}
log_message = [SPF] meaningless +all
!hosts = +3rdmxes : +relay_from_hosts
warn spf = fail
!hosts = +3rdmxes : +relay_from_hosts : +nosa_from_hosts
log_message = [SPF] $sender_host_address is not allowed to send mail from $sender_address_domain
# Add a SPF-Received: header to the message
warn message = $spf_received
!hosts = +3rdmxes : +relay_from_hosts
accept spf = pass
log_message = [SPF] pass
!hosts = +3rdmxes : +relay_from_hosts
### DMARC niels
# --- check sender's DMARC policy
warn domains = +local_domains
hosts = +3rdmxes : +relay_from_hosts
log_message = [DMARC] no check for OUR hosts
control = dmarc_disable_verify
warn !domains = +screwed_up_dmarc_records
#log_message = [DMARC] check forensics
control = dmarc_enable_forensic
###
and then DMARC (as described) in
acl_check_data:
...
## test
# --- check sender's DMARC policy
warn dmarc_status = *
add_header = $dmarc_ar_header
deny dmarc_status = reject
message = Rejected by sender's DMARC policy
warn dmarc_status = quarantine
set acl_c0 = ${eval:$acl_c0+40}
set acl_c1 = QDMARC(40) suspicious message according DMARC policy; $acl_c1
## test
For me it seems, in dmarc.c spf_domain is set not correctly (however?)., but seems relatively "hard wired" there Any idea, what could be wrong here?
https://github.com/Exim/exim/blob/master/src/src/dmarc.c
--- snip ---
/* Use the envelope sender domain for this part of DMARC */
spf_sender_domain = expand_string(US"$sender_address_domain");
if (!spf_response)
{
/* No spf data means null envelope sender so generate a domain name
* from the sender_helo_name */
if (!spf_sender_domain)
{
spf_sender_domain = sender_helo_name;
log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n",
spf_sender_domain);
DEBUG(D_receive)
debug_printf("DMARC using synthesized SPF sender domain = %s\n",
spf_sender_domain);
}
dmarc_spf_result = DMARC_POLICY_SPF_OUTCOME_NONE;
dmarc_spf_ares_result = ARES_RESULT_UNKNOWN;
origin = DMARC_POLICY_SPF_ORIGIN_HELO;
spf_human_readable = US"";
}
else
{
sr = spf_response->result;
dmarc_spf_result = sr == SPF_RESULT_NEUTRAL ? DMARC_POLICY_SPF_OUTCOME_NONE :
sr == SPF_RESULT_PASS ? DMARC_POLICY_SPF_OUTCOME_PASS :
sr == SPF_RESULT_FAIL ? DMARC_POLICY_SPF_OUTCOME_FAIL :
sr == SPF_RESULT_SOFTFAIL ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL :
DMARC_POLICY_SPF_OUTCOME_NONE;
dmarc_spf_ares_result = sr == SPF_RESULT_NEUTRAL ? ARES_RESULT_NEUTRAL :
sr == SPF_RESULT_PASS ? ARES_RESULT_PASS :
sr == SPF_RESULT_FAIL ? ARES_RESULT_FAIL :
sr == SPF_RESULT_SOFTFAIL ? ARES_RESULT_SOFTFAIL :
sr == SPF_RESULT_NONE ? ARES_RESULT_NONE :
sr == SPF_RESULT_TEMPERROR ? ARES_RESULT_TEMPERROR :
sr == SPF_RESULT_PERMERROR ? ARES_RESULT_PERMERROR :
ARES_RESULT_UNKNOWN;
origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM;
spf_human_readable = (uschar *)spf_response->header_comment;
DEBUG(D_receive)
debug_printf("DMARC using SPF sender domain = %s\n", spf_sender_domain);
}
if (strcmp( CCS spf_sender_domain, "") == 0)
dmarc_abort = TRUE;
--- snap ---
...and here the "spf_sender_domain" is used for rendering of "spf_sender":
--- snip ---
{
log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s "
"spf_align=%s dkim_align=%s enforcement='%s'",
spf_sender_domain, dmarc_used_domain,
(sa==DMARC_POLICY_SPF_ALIGNMENT_PASS) ?"yes":"no",
(da==DMARC_POLICY_DKIM_ALIGNMENT_PASS)?"yes":"no",
dmarc_status_text);
history_file_status = dmarc_write_history_file();
/* Now get the forensic reporting addresses, if any */
ruf = opendmarc_policy_fetch_ruf(dmarc_pctx, NULL, 0, 1);
dmarc_send_forensic_report(ruf);
}
}
--- snap ---
many thanks in advance for any help or hint!
best regards,
Niels.
--
---
Niels Dettenbach
Syndicat IT & Internet
http://www.syndicat.com
PGP:
https://syndicat.com/pub_key.asc
---