Skip to content

DMARCbis compliance gap analysis (RFC 9989 / 9990 / 9991 vs RFC 7489) #371

@thegushi

Description

@thegushi

RFC 7489 was replaced by three documents published in May 2026: RFC 9989 (core protocol), RFC 9990 (aggregate reporting), and RFC 9991 (failure reporting). None of those documents include a single consolidated list of behavioral changes, so this issue collects them in one place along with a column showing where OpenDMARC currently stands.

References: rfc9989.txt, rfc9990.txt, rfc9991.txt (copies in repo root).


RFC 9989: Core protocol changes

Tags removed from the DMARC Policy Record

Removed tag What it did Current OpenDMARC state
pct (sec. 4.7, Appendix A.6, Appendix C.5.2) Apply policy to a percentage (0-100) of failing messages Fully implemented: DMARC_POLICY_T.pct, parsed in opendmarc_policy.c, applied in opendmarc.c. Code needs to learn to ignore pct from incoming records rather than act on it.
rf (sec. C.5.2) Requested failure report format Parsed into DMARC_POLICY_T.rf; field can be removed from the struct and parser.
ri (sec. C.5.2) Requested interval between aggregate reports Parsed into DMARC_POLICY_T.ri; field can be removed from the struct and parser.

Receivers that encounter these tags MUST ignore them (per RFC 9989 sec. 4.8: "unknown tags MUST be ignored"). Since pct was a known tag in RFC 7489, receivers that still act on it will diverge from compliant behavior.

Tags added to the DMARC Policy Record

New tag Meaning Current OpenDMARC state
t (sec. 4.7, Appendix A.6, Appendix C.5.1) Testing mode; replaces pct=0/100. t=y means apply one level below the stated policy (reject -> quarantine; quarantine -> none); t=n (default) means apply as stated. Has no effect on p=none. Not implemented. No field in DMARC_POLICY_T, no parser support, no enforcement logic. The pct-based random sampling in opendmarc.c needs to be replaced with this deterministic one-level downgrade.
np (sec. 4.7, Appendix C.5.1) Non-existent subdomain policy; distinct from sp (existing subdomains). Falls back to sp then p if absent. Not implemented. No field in DMARC_POLICY_T, no parser support. The policy selection logic needs a third branch after the existing p/sp check.
psd (sec. 4.7, Appendix C.5.1) Flags whether the publishing domain is a Public Suffix Domain (y/n/u). Used by the DNS Tree Walk to identify Organizational Domain boundaries. Not implemented. Required for correct Tree Walk behavior (see below).

Policy discovery: DNS Tree Walk replaces PSL

RFC 7489 required a Public Suffix List (PSL) to determine the Organizational Domain. RFC 9989 replaces this with a "DNS Tree Walk" (sec. 4.10, sec. 4.10.1): starting from _dmarc.<author-domain>, query progressively shorter labels until a valid DMARC Policy Record is found or the walk is exhausted. The psd tag in a discovered record signals whether to stop.

Aspect RFC 7489 behavior RFC 9989 behavior OpenDMARC state
Org domain discovery (sec. 4.10) PSL lookup (configurable via PublicSuffixList) DNS Tree Walk from author domain upward PSL path fixed and working. When no PSL is configured, a label-walk fallback now queries successive parent labels until a DMARC record is found, stopping before bare TLDs (#378, issue #54). This is structurally similar to the RFC 9989 Tree Walk but uses a dot-count heuristic as the stopping condition rather than psd= tags. The full RFC 9989 Tree Walk (stopping on psd=n/psd=y, querying through TLDs, 8-label jump for long domains) is not yet implemented and will replace this fallback before release.
PSD DMARC (sec. 5.2) Not supported Supported via psd=y in the record and the Tree Walk Not implemented. Depends on psd tag support and full Tree Walk.
Interoperability during transition (sec. C.3) N/A RFC 9989 notes that strict alignment + per-domain records avoids interop issues No code changes needed for this note, but worth documenting for operators.

Note: in practice, the RFC 9989 Tree Walk's psd= stopping condition provides no benefit until PSO operators publish psd=y in their DMARC records -- adoption of which is expected to be slow. Until then, the label-walk fallback and the RFC 9989 Tree Walk produce equivalent results for the overwhelming majority of real mail.

The Tree Walk involves multiple DNS queries and has different failure modes than a PSL lookup. Implementing it requires changes in libopendmarc/opendmarc_policy.c (the DNS query and record fetch logic) and possibly a new config option to select PSL vs. Tree Walk behavior during a transition period.

Authentication-Results header

RFC 9989 sec. 9.1 formally registers policy.dmarc as an AR result property. This was implemented in OpenDMARC in PR #365 and is already present.


RFC 9990: Aggregate reporting changes

RFC 9990 sec. 6.1 formally registers urn:ietf:params:xml:ns:dmarc-2.0 as the XML namespace for DMARC aggregate reports. RFC 7489 did not define or register an XML namespace; reports generated under RFC 7489 used bare <feedback> with no namespace declaration. Reports not using the correct namespace may be rejected by consumers.

Change Details OpenDMARC state
XML namespace (sec. 6.1) dmarc-2.0 Currently generates <feedback> with no namespace declaration. Needs update in opendmarc-reports.
selector element in DKIM results now REQUIRED (sec. 3.1.1.12) Was optional in RFC 7489 Already emitted unconditionally in opendmarc-reports (line 1335). A NULL selector from the DB produces <selector></selector> rather than a missing element, which satisfies the REQUIRED constraint. No code change needed.
testing element in policy_published (sec. 3.1.1.5) Reports the value of the t tag Not present; added once t tag support is implemented.
np element in policy_published (sec. 3.1.1.5) Reports the non-existent subdomain policy Not present; added once np tag support is implemented.
discovery_method element in policy_published (sec. 3.1.1.5) Reports how the record was found: psl or treewalk Not present; added once Tree Walk is implemented.
pass disposition value (sec. 3.1.1.9) ActionDispositionType now includes pass (message passed DMARC with an enforcing policy) alongside none, quarantine, reject Not implemented. Current code emits only none/quarantine/reject.
generator element in metadata (sec. 3.1.1.3) Optional; identifies the report-generating software Not present. Low-priority addition.
error element in metadata (sec. 3.1.1.3, sec. 3.1.5) Describes processing errors encountered while evaluating the DMARC Policy Record Not present.
Extension mechanism (sec. 3.2, sec. 5) <extension> element at file level and namespaced elements at record level Not present. Low-priority; only needed if extensions are adopted.
Max report size in URI removed (sec. C) The !NNNk syntax on rua URIs is now obsolete; receivers MUST ignore it The parser likely ignores unknown URI suffixes already; needs verification.
DKIM signature priority in reports (sec. 3.1.3) Defines a priority order for which signatures to include (strict-aligned pass first, then relaxed, then others) and caps at 100 signatures per row Not implemented as specified.
policy_test_mode override reason (sec. 3.1.6) New value for <reason><type> when t=y caused a downgrade Not present; added once t tag support is implemented.

RFC 9991: Failure reporting changes

Failure reports use the ARF format (RFC 6591) with DMARC-specific fields. RFC 9991 clarifies and tightens a few requirements.

Change Details OpenDMARC state
Identity-Alignment ARF field is REQUIRED (sec. 4) Must be present in every DMARC failure report Needs verification in opendmarc.c failure report generation.
Rate-limiting of outgoing failure reports is REQUIRED (sec. 2) Generators MUST implement a rate limit to prevent DoS toward report consumers Not implemented. No rate-limit logic in failure report generation.
ruf MUST be ignored for PSD records (sec. 2) If the DMARC Policy Record contains psd=y, failure reports MUST NOT be generated for it Not implemented; depends on psd tag support being added first.
External destination verification (sec. 5) Same procedure as RFC 9990 sec. 4 (substituting ruf for rua) Existing external destination verification should cover this; needs verification.

Summary of implementation priority

The changes fall into roughly three tiers:

Required for basic RFC 9989 conformance: t tag (replaces pct), np tag, stop acting on pct/rf/ri from incoming records.

Required for full conformance including PSD DMARC: DNS Tree Walk (replacing the current label-walk fallback), psd tag, PSD policy handling.

Required for updated reporting: XML namespace update, pass disposition value, t/np/discovery_method elements in policy_published, failure report rate-limiting.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DMARCbisRelated to RFC 9989/9990/9991 DMARCbis compliance

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions