Summary
The internal SPF implementation in libopendmarc/opendmarc_spf.c (compiled when libspf2 is not present) has several known compliance gaps relative to RFC 7208. We believe libspf2 should be a hard build dependency and the internal fallback should be removed.
Known failure modes in the internal implementation
1. Void lookup limit not enforced (RFC 7208 §4.6.4)
RFC 7208 requires that void DNS lookups — queries that return NXDOMAIN or empty results — be limited to 2 separately from the overall 10-lookup cap. The internal implementation tracks total lookups (MAX_SPF_DNS_LOOKUPS) but has no void lookup counter. A domain with a pathological SPF record can cause more void lookups than the spec permits, leading to divergent results from a compliant evaluator.
2. Multiple SPF TXT records not detected (RFC 7208 §3.2)
If a domain publishes more than one v=spf1 TXT record, the result MUST be permerror. The internal DNS layer (opendmarc_spf_dns.c) returns the first matching TXT record and silently ignores any subsequent ones. A domain with duplicate or conflicting SPF records will be evaluated rather than returning permerror, potentially producing a pass where a conformant evaluator would refuse to evaluate.
3. IPv4-mapped IPv6 addresses (RFC 7208 §5)
When a sender connects over IPv6 with an IPv4-mapped address (::ffff:192.0.2.1), SPF evaluation should use the IPv4 address and mechanisms. The code contains comments acknowledging uncertainty about this mapping ("we don't care at this point if it is ipv6 or ipv4"), suggesting this case is not reliably handled.
4. Dual CIDR-length notation (RFC 7208 §5.6, §5.7)
The a and mx mechanisms support a dual CIDR notation (a/24//48) specifying separate prefix lengths for IPv4 and IPv6 matches. Incorrect or absent handling here produces wrong pass/fail results for affected records.
Why libspf2 should be required
libspf2 is a dedicated SPF implementation with years of real-world testing and bug fixes across all of the above cases. Maintaining a parallel SPF implementation inside OpenDMARC duplicates that effort poorly. The internal fallback also creates a situation where two installations of OpenDMARC with identical configuration can produce different DMARC verdicts depending on whether libspf2 happened to be present at build time — which is a hard problem to diagnose in production.
The HAVE_SPF2_H conditional should become a hard build requirement, and the #else branch should be removed.
We would welcome a patch to do this, or to at minimum make ./configure fail with a clear error when libspf2 is absent and SPFSelfValidate support is requested.
Summary
The internal SPF implementation in
libopendmarc/opendmarc_spf.c(compiled when libspf2 is not present) has several known compliance gaps relative to RFC 7208. We believe libspf2 should be a hard build dependency and the internal fallback should be removed.Known failure modes in the internal implementation
1. Void lookup limit not enforced (RFC 7208 §4.6.4)
RFC 7208 requires that void DNS lookups — queries that return NXDOMAIN or empty results — be limited to 2 separately from the overall 10-lookup cap. The internal implementation tracks total lookups (
MAX_SPF_DNS_LOOKUPS) but has no void lookup counter. A domain with a pathological SPF record can cause more void lookups than the spec permits, leading to divergent results from a compliant evaluator.2. Multiple SPF TXT records not detected (RFC 7208 §3.2)
If a domain publishes more than one
v=spf1TXT record, the result MUST bepermerror. The internal DNS layer (opendmarc_spf_dns.c) returns the first matching TXT record and silently ignores any subsequent ones. A domain with duplicate or conflicting SPF records will be evaluated rather than returningpermerror, potentially producing apasswhere a conformant evaluator would refuse to evaluate.3. IPv4-mapped IPv6 addresses (RFC 7208 §5)
When a sender connects over IPv6 with an IPv4-mapped address (
::ffff:192.0.2.1), SPF evaluation should use the IPv4 address and mechanisms. The code contains comments acknowledging uncertainty about this mapping ("we don't care at this point if it is ipv6 or ipv4"), suggesting this case is not reliably handled.4. Dual CIDR-length notation (RFC 7208 §5.6, §5.7)
The
aandmxmechanisms support a dual CIDR notation (a/24//48) specifying separate prefix lengths for IPv4 and IPv6 matches. Incorrect or absent handling here produces wrong pass/fail results for affected records.Why libspf2 should be required
libspf2 is a dedicated SPF implementation with years of real-world testing and bug fixes across all of the above cases. Maintaining a parallel SPF implementation inside OpenDMARC duplicates that effort poorly. The internal fallback also creates a situation where two installations of OpenDMARC with identical configuration can produce different DMARC verdicts depending on whether libspf2 happened to be present at build time — which is a hard problem to diagnose in production.
The
HAVE_SPF2_Hconditional should become a hard build requirement, and the#elsebranch should be removed.We would welcome a patch to do this, or to at minimum make
./configurefail with a clear error when libspf2 is absent andSPFSelfValidatesupport is requested.