Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Some tests require network access and are skipped when `GITHUB_ACTIONS` env var
**Modules** (each has a primary `check_*()` function):

- `spf.py` — SPF record parsing, DNS lookup counting
- `dmarc.py` — DMARC/DMARCbis record parsing with DNS tree walk algorithm
- `dmarc.py` — DMARC/RFC 9989 record parsing with DNS tree walk algorithm
- `bimi.py` — BIMI record and certificate validation
- `mta_sts.py` — MTA-STS policy fetching and validation
- `smtp_tls_reporting.py` — TLSRPT record validation
Expand Down
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
# Changelog

## 5.16.0

### Changes

- Rename DMARCbis references to RFC 9989
- In compliance with RFC 9989, treat a DMARC `p` tag as `p=none`, instead of requiring it
- Instead, a warning is raised that older versions of DMARC require it
- DMARC: the `pct`, `rf`, and `ri` tags are removed in RFC 9989. They are no
longer implicitly added to parsed results, are no longer strictly validated
(invalid values that previously raised now just warn), and explicit use
emits a "removed in RFC 9989" warning. Pre-9989 readers may still honor
them, so the value is left intact for those consumers.
- DMARC: unknown tags are now ignored with a warning instead of raising
`InvalidDMARCTag`, per RFC 9989 ("Unknown tags MUST be ignored").
- DMARC: the order constraint that `p` must immediately follow `v` is now a
warning rather than a hard syntax error. RFC 9989 permits any tag ordering
after `v`; older RFC 7489 readers may still expect `p` second.
- DMARC: the `!size` suffix on `rua`/`ruf` URIs is now flagged as obsolete
syntax (RFC 9989 says reporters MUST ignore it). The warning still fires
because pre-9989 readers may still honor it.
- DMARC: the RFC 9989 tree walk now continues all the way to single-label
parents (TLDs). PSD operators publish their policy at e.g. `_dmarc.gov`
with `psd=y`, and the previous "don't query TLDs" short-circuit prevented
PSD discovery (the main reason RFC 9989 added the tree walk).
- DMARC: during the tree walk, parent queries no longer trigger the
apex-fallback "wrong-location" check. A stray `v=DMARC1` at a parent
domain's apex used to spuriously abort the walk with
`DMARCRecordInWrongLocation`; that check is now only applied to the
originally requested name.

## 5.15.5

### Fixes
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Please consider [sponsoring my work](https://github.com/sponsors/seanthegeek) if
- Counting of lookups per mechanism
- DMARC
- Validation and parsing of DMARC records
- Shows warnings when the DMARC record is made ineffective by `pct` or `sp` values
- Shows warnings when the DMARC record is made ineffective by `sp` values, or by use of the `pct`/`rf`/`ri` tags that were removed in RFC 9989
- Checks for authorization records on reporting email addresses
- BIMI
- Validation of the mark format and certificate against the [Minimum Security Requirements for Issuance of Mark Certificates](https://bimigroup.org/resources/VMC_Requirements_latest.pdf)
Expand Down
6 changes: 0 additions & 6 deletions checkdmarc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,9 +430,6 @@ def results_to_csv_rows(
row["dmarc_aspf"] = _dmarc["tags"]["aspf"]["value"]
row["dmarc_fo"] = ":".join(_dmarc["tags"]["fo"]["value"])
row["dmarc_p"] = _dmarc["tags"]["p"]["value"]
row["dmarc_pct"] = _dmarc["tags"]["pct"]["value"]
row["dmarc_rf"] = ":".join(_dmarc["tags"]["rf"]["value"])
row["dmarc_ri"] = _dmarc["tags"]["ri"]["value"]
row["dmarc_sp"] = _dmarc["tags"]["sp"]["value"]
if "rua" in _dmarc["tags"]:
addresses = _dmarc["tags"]["rua"]["value"]
Expand Down Expand Up @@ -482,9 +479,6 @@ def results_to_csv(
"dmarc_aspf",
"dmarc_fo",
"dmarc_p",
"dmarc_pct",
"dmarc_rf",
"dmarc_ri",
"dmarc_rua",
"dmarc_ruf",
"dmarc_sp",
Expand Down
2 changes: 1 addition & 1 deletion checkdmarc/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
See the License for the specific language governing permissions and
limitations under the License."""

__version__ = "5.15.5"
__version__ = "5.16.0"

OS = platform.system()
OS_RELEASE = platform.release()
Expand Down
8 changes: 6 additions & 2 deletions checkdmarc/bimi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1178,9 +1178,13 @@ def parse_bimi_record(
warnings.append(
"The DMARC subdomain policy (sp tag) must be set to quarantine or reject if it is used."
)
if parsed_dmarc_record["tags"]["pct"]["value"] != 100:
# The pct tag was removed in RFC 9989; pre-9989 BIMI guidance
# required pct=100, so flag any leftover pct that isn't 100.
pct_tag = parsed_dmarc_record["tags"].get("pct")
if pct_tag is not None and pct_tag["value"] != 100:
warnings.append(
"The DMARC pct tag must be set to 100 (the implicit default) if it is used."
"The DMARC pct tag was removed in RFC 9989; pre-9989 "
"BIMI guidance required pct=100 when it was present."
)
if cert_metadata:
valid_cert = hash_match and cert_metadata["valid"]
Expand Down
Loading
Loading