pkgsrc-Changes archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
CVS commit: pkgsrc/mail/py-checkdmarc
Module Name: pkgsrc
Committed By: adam
Date: Thu Sep 10 07:27:43 UTC 2026
Modified Files:
pkgsrc/mail/py-checkdmarc: Makefile distinfo
Log Message:
py-checkdmarc: updated to 6.0.1
6.0.1
Fixed
SPF: the RFC 6652 reporting modifiers ra=, rp=, and rr=, added in 5.8.0 and removed later, are recognized again; their values are validated against RFC 6652 §3 (rp= per erratum 6579) and surfaced in
the parsed result as ra, rp, and rr next to exp, and a malformed value is warned about and ignored instead of failing the record. Warnings also cover the two RFC 6652 §3 semantic rules — rp= and rr=
do nothing without ra=, and ra= is ignored in a record reached through an include — and exp= and these modifiers are now honored after all in any order
6.0.0
An RFC conformance audit compared every module line-by-line against its governing specification (SPF: RFC 7208; DMARC: RFC 9989/9990; TLSRPT: RFC 8460; MTA-STS: RFC 8461; SMTP/MX: RFC 5321/7505/2181;
DNSSEC: RFC 4033-4035; SOA: RFC 1035/2181; BIMI: draft-brand-indicators-14) and found 81 discrepancies, most confirmed by executing the old code. This release fixes all of them. Many fixes change
validation verdicts — records the specs call valid are no longer rejected, and records they call invalid are no longer accepted — hence the major version. This release also contains everything staged
for 5.18.0, which was never released.
Breaking changes
MX STARTTLS/TLS testing is now opt-in: pass --check-mx-tls on the CLI or check_mx_tls=True to check_domains(), check_mx(), or get_mx_hosts(). The --skip-tls flag and skip_tls parameter are still
accepted but do nothing, and passing skip_tls emits a DeprecationWarning. As a result, MX host results no longer carry the tls and starttls keys, and the CSV tls/starttls columns are empty, unless
TLS testing is turned on
check_dnssec() performs a real chain-of-trust check anchored at the parent zone's DS record instead of verifying a zone's DNSKEY against itself. A zone with no DS at its parent (including "island of
security" zones) is insecure per RFC 4033 §4.3 and returns False; a broken zone such as dnssec-failed.org returns False through any resolver, where it previously returned True through non-validating
resolvers. Bogus (SERVFAIL with DS present) is now warned about distinctly from unsigned
Unknown and extension tags/fields now parse with a warning instead of failing validation, as each spec requires: SPF unknown modifiers (RFC 7208 §6), DMARC unknown tags (RFC 9989 §4.7), TLSRPT
extension fields (RFC 8460 §3), MTA-STS extension fields and policy keys (RFC 8461 §3.2), and BIMI unknown tags (draft §4.3). An unknown field is ignored only when it fits the spec's own extension
grammar; a malformed extension name or value still fails the record or policy
A TXT record unrelated to the record type being queried is now discarded instead of failing validation, per each spec's discard rule: TLSRPT (RFC 8460 §3.1), MTA-STS (RFC 8461 §3.1), BIMI (draft
§7.2), and DMARC report authorization records (RFC 9990 §4). Each query also now returns the record carrying the version tag rather than whichever TXT record the resolver listed first. When a real
record sits beside the unrelated one, the unrelated record is reported as a warning; when it is the only record present, the check reports that no record exists. DMARC authorization records are
discarded silently, and a wildcard authorization record that is unrelated is still an error. Callers catching UnrelatedTXTRecordFoundAtTLSRPT or UnrelatedTXTRecordFoundAtBIMI should note that those
queries now raise SMTPTLSReportingRecordNotFound and BIMIRecordNotFound instead
Records the old code wrongly accepted are now invalid: SPF records exceeding the 10-DNS-lookup limit through a, ptr, or macro-valued terms (RFC 7208 §4.6.4 — the limit was previously only enforced
for some mechanism types), SPF include of a domain with no SPF record (permerror per RFC 7208 §5.2), MTA-STS policies missing a required key (RFC 8461 §3.2 — the check was dead code), BIMI records
missing the required l= tag (draft §4.3), lowercase v=dmarc1 (RFC 9989 §4.8), and an SPF qualifier on a modifier or a value on the all mechanism (RFC 7208 §12)
MTA-STS and TLSRPT records are now matched case-sensitively, with no whitespace allowed around the version tag's =, because RFC 8461 §3.1 and RFC 8460 §3 spell those literals with the case-sensitive
%s notation of RFC 7405. Records such as V=STSv1; id=…, v = STSv1; id=…, v=STSv1; ID=…, and v=TLSRPTv1; RUA=… parsed before and are now syntax errors. DMARC is unaffected: RFC 9989 §4.8 makes its tag
names case-insensitive and allows whitespace around =, which this release starts honoring
The DMARC tree walk now applies RFC 9989 §4.10.2 Organizational Domain selection (psd=n wins; a psd=y record hands off to the record one label below it, warning when none is published there;
otherwise the record with the fewest labels applies) instead of stopping at the closest parent record, which selected the opposite policy in the RFC's own worked example
Added
--check-mx-tls CLI flag and check_mx_tls API parameter (see breaking changes)
get_mx_record_set() in checkdmarc.utils, returning MX hosts, warnings, null MX status, and the number of MX records in the answer (the new MXRecordSet type), parsed from dnspython rdata instead of
text splitting. The record count is what separates "no MX records at all" from "MX records that produced no usable host"
Null MX (RFC 7505) handling: a lone 0 . record yields an explicit "does not accept mail" warning distinct from having no MX records (which now notes the RFC 5321 §5.1 implicit MX rule); a null MX
coexisting with other MX records is flagged as an RFC 7505 §3 violation instead of producing an empty-hostname host entry; a root (.) target with a non-zero preference, such as 10 ., is warned about
as malformed instead of becoming an empty-hostname host entry
MX target sanity warnings: IP-address literals (RFC 5321 §5.1), hostnames failing RFC 5321 §2.3.5 label syntax, and targets that are CNAME aliases (RFC 2181 §10.3)
DNS over HTTPS (DoH) and DNS over TLS (DoT) support through the existing nameservers option, matching parsedmarc. Each entry picks its own transport: an IP address means plain DNS on port 53 exactly
as before, an https:// URL means DoH, and tls://ip[:port][#hostname] means DoT, where the port defaults to 853 and the optional #hostname names the server's TLS certificate identity, matching
systemd-resolved's syntax. DoH queries go through a shared httpx client that honors the HTTP_PROXY/HTTPS_PROXY/NO_PROXY and SSL_CERT_FILE/SSL_CERT_DIR environment variables, so checks can run on
networks that block outbound DNS but provide an HTTP proxy. The DNSSEC, DNSKEY, and TLSA checks use the configured transports too. The dnspython requirement is now dnspython[doh]>=2.7.0, and
httpx>=0.26.0 is a new direct dependency for the shared DoH client
Changed
Update the GitHub Actions used by the workflows to their latest major versions: checkout v7, setup-python v7, codecov-action v7, upload-artifact v7, download-artifact v8, configure-pages v6,
upload-pages-artifact v5, and deploy-pages v5. Most now run on Node.js 24 (upload-pages-artifact and codecov-action are composite actions). The deprecated codecov/test-results-action is replaced by
codecov/codecov-action@v7 with report_type: test_results, which is the same upload it performed before; no other workflow behavior changes
Renamed identifiers whose names misdescribed what they hold, keeping the old names as deprecated aliases where they were public API:
checkdmarc.dnssec.check_dnssec() replaces test_dnssec(), matching every other module's check_*() entry point; test_dnssec() remains as a deprecated alias that warns
get_mx_hosts() takes approved_mx_hostnames, matching check_mx() and check_domains(); the old approved_hostnames keyword remains as a deprecated alias that warns
The CLI accepts --nameservers as an alias for -n/--nameserver, and --approved-ns/--approved-mx as clearer aliases for --ns/--mx
MTASTSQueryResult, MTASTSCheckResult, SMTPTLSReportingQueryResult, and SMTPTLSReportingResult replace their plural forms, matching the singular BIMI result types; the plural names remain as aliases
MTA_STS_TAGS and SMTP_TLS_REPORTING_TAGS replace the lowercase mta_sts_tags and smtp_rpt_tags constants, matching BIMI_TAGS; the lowercase names remain as aliases
The MXHost type now declares the fields MX host dicts actually carry (addresses, dnssec, tlsa, tls, starttls as optional keys, with hostname and preference required in a new MXRecord base); the
declared ip_addresses field never existed in any produced data
Removed unused module-level copies of the DMARC grammar internals (checkdmarc.dmarc.version_tag, tag_value, and START), which duplicated the private grammar class and shadow-collided with unrelated
locals
Many internal variables renamed so a name no longer changes type or meaning mid-function (split results, parse results, pyleri grammar results, joined display strings); no behavior changes
Removed
The pyopenssl dependency, as planned in 5.17.5. checkdmarc stopped importing pyOpenSSL in that release; the floor was kept for one release only so upgrades would also move any leftover pyOpenSSL to a
version compatible with cryptography 50
Fixed
SPF: the 10-lookup and 2-void-lookup limits are enforced after every counted term rather than in some branches only; redirect is ignored when an all mechanism is present (RFC 7208 §6.1); a
v=spf10-style sibling TXT record is discarded instead of hiding a valid record (§4.5); multiple all mechanisms are valid with first-match semantics (§4.6.2); terms after all are no longer processed
or counted; duplicate includes count their lookups like real evaluation; the term-value charset matches the §12 ABNF (so %{ir=} and ! parse); exp-only macro letters c/r/t are rejected outside exp
text (§7.2); the uppercase R transformer is accepted; exp is honored anywhere in the record and its name is matched case-insensitively after all (§4.6.1); mx CIDR suffixes are stripped before the DNS
query instead of being sent as part of the name; dual-CIDR lengths and leading-zero CIDRs are validated; empty a:/mx:/ptr: domain-specs are rejected; an MX host with no A/AAAA records is a warning
rath
er than a void DNS lookup, because §4.6.4 counts void lookups per term query, so records that used to fail the 2-void limit for that reason now pass; and a domain-spec that is not a fully qualified
domain name (such as exists:localhost) is warned about, since receivers may treat it as a no-match (§4.8)
DMARC: the grammar matches the §4.8 ABNF (any-length tag names, full value charset — mandatory percent-encoding like %2C in report URIs now parses, and bare v=DMARC1 is valid); non-mailto report URIs
are kept with a warning instead of invalidating the record (§4.7) and are validated against the RFC 3986 URI structure (scheme, authority with a real IP-literal when bracketed, path, query, and at
most one fragment); rua/ruf values keep the case they were published with instead of being lowercased along with other tag values, since RFC 3986 makes only the scheme and host case-insensitive;
invalid adkim/aspf values fall back to r with a warning; fo=0:1 is reported as invalid (mutually exclusive), not "redundant", and a repeated value such as fo=1:1 is invalid too; both fall back to
fo=0 with a warning; the record-detection filter tolerates ABNF-legal whitespace and case around v=; an unrelated TXT record no longer causes a false "authorization record not found"; an NXDOMAIN on
the courtesy apex query no longer discards an already-found record; two citations to nonexistent RFC sections corrected
BIMI: the organizational-domain fallback keeps the caller's selector instead of reverting to default (draft §7.2 step 6); the SVG-vs-certificate logotype hash check is tag-order-independent and
hashes the raw bytes; SVG file size is measured in actual bytes instead of sys.getsizeof of a decoded string; the raw SVG bytes are handed to the XML parser instead of a lossy errors="ignore" decode,
so malformed bytes fail validation rather than being silently dropped, and non-UTF-8 encodings declared in the XML declaration parse correctly; the pct warning fires only for p=quarantine per §7.1
step 9; empty lps= parses to an empty list; l=/a= URIs are validated against the bimi-uri ABNF (no raw spaces or unencoded commas, at most one # per RFC 3986); an IP-literal host is rejected in a=,
whose prose requires an FQDN, but allowed in l=, which imports the URI grammar with no FQDN rule; unknown tags are ignored only when they fit the DKIM tag-value grammar the draft imports (RFC 6376 §
3.2: no . or - in tag names, no control characters in values); the PEM-bytes certificate path reads the leaf certificate
MTA-STS: the policy fetch no longer follows redirects and requires HTTP 200 exactly (RFC 8461 §3.3); MX pattern matching is anchored with * matching a single label, so *.example.com no longer matches
mail.example.com.evil.com (§4.1); a TXT record without id returns valid: False instead of crashing; duplicate fields are first-wins with a warning (§3.2); mixed LF/CRLF policies parse; mx values are
validated against the ["*."] Domain ABNF with each label capped at 63 octets (RFC 1035 §2.3.4); record detection keys on v=STSv1; including the semicolon, while the grammar-legal v=STSv1 ; form is
accepted with a warning that senders applying the §3.1 discard rule literally will ignore the record; a policy field value may contain colons, since §3.2 separates the name from the value at the
first colon; the query raises SPFRecordFoundWhereMTASTSRecordShouldBe when an SPF record sits at the MTA-STS name; id is capped at 32 alphanumerics; max_age accepts only plain digits; a CR that
is not part of a CRLF line ending (such as \r\r\n) is a policy syntax error instead of being silently stripped (§3.2); whitespace before a policy field name or between the name and the colon is a
syntax error, since the §3.2 delimiter is ":" *WSP
TLSRPT: whitespace around commas between rua URIs is accepted per the §3 ABNF; repeated rua fields merge their destinations, since §3 supports declaring more than one rua (other repeated tags still
warn with first-wins); record detection keys on v=TLSRPTv1; per §3.1, while the grammar-legal v=TLSRPTv1 ; form is accepted with a warning that senders applying the discard rule literally will ignore
the record; URI validation is anchored so garbage-prefixed schemes are rejected; a bracketed https authority must be a real IPv6 address (RFC 4291) and # may appear only once, as the RFC 3986
fragment delimiter; a mailto local part follows the RFC 5322 dot-atom rules (no leading, trailing, or doubled dots); the parser returns the matching TLSRPT record instead of whichever TXT record
sorted first
SMTP: negative STARTTLS results are cached like positive ones; the port-465 fallback failure warning names the implicit-TLS probe instead of reading as a port 25 failure
SOA: check_soa() walks from the domain up through each ancestor to the base domain and reports the first SOA it finds, so a delegated child zone reports its own SOA and contact rather than the base
domain's (RFC 2181 §7); soa_rname_to_email() handles RFC 1035 §5.1 escapes, including an escaped backslash before a real label separator, and quotes a decoded local part that is not a plain dot-atom
(RFC 5322 §3.2.4) so the returned address is always syntactically valid; escapes in the domain labels are decoded too, with each label validated on its own before joining so an escaped dot (one DNS
label holding a literal dot) is an error rather than silently moving the label boundary to a different mailbox domain
DNSSEC: caller-supplied caches are forwarded to get_dnskey(); the dnssec result-key documentation states what the boolean actually means; a nameserver answering REFUSED, FORMERR, or the like is
treated as a failed attempt and the next nameserver is tried, instead of the error being mistaken for a clean empty answer that means "no records". This applies to the chain check, get_dnskey(), and
get_tlsa_records() alike: previously the first server's error ended the lookup, so a second nameserver holding the answer was never asked, and get_dnskey() cached the failure as though the zone were
unsigned. A lookup that could not complete is no longer cached at all. In the chain check a SERVFAIL is still reported as bogus when the parent publishes a DS record, while a SERVFAIL on the DS query
itself is reported as a check that could not complete. TLSA records whose signature does not verify are now reported as such rather than being logged as a query error; the record types consulted bel
ow a zone apex now include AAAA and TXT, so a name whose only signed record set is one of those is no longer reported as uncovered
An SPF a mechanism's CIDR suffix now applies to the returned addresses. A split result was reassigned over the same variable, so the length check inspected the hostname string: a:example.com/24
silently lost its suffix, a two-character hostname had its second character used as one, and a/24 failed to default to the current domain
The DNS_CACHE_MAX_AGE_SECONDS environment variable now actually configures the DNS cache, which was wired to the DNSSEC constant, so the documented variable had no effect and
DNSSEC_CACHE_MAX_AGE_SECONDS silently controlled both caches
The domains field of BIMI certificate metadata now stays a list when the checked domain does not match the certificate; building the error message rebound the list to a joined string. That message
now separates the domains with commas rather than periods
MTA-STS policy mx values are now validated against the whole value instead of any substring; an unanchored search accepted any value containing a single legal character, so mx: not a hostname! passed
as an MX entry, and the "Invalid mx value" error was nearly unreachable
parse_mta_sts_record()'s docstring no longer claims tag values carry descriptions; MTA-STS tag values are plain strings and include_tag_descriptions currently adds none
An SPF exp modifier with an empty value (exp=) now raises SPFSyntaxError as intended; the check compared the value to the integer 0, which never matches a string, so the empty modifier was silently
accepted
A BIMI record with an empty l tag no longer warns about DMARC policy requirements that only apply when a logo is published; the check compared the tag's dict to an empty string, which is always
unequal. (A record with no l tag at all is now rejected outright — see the breaking changes above — where it previously crashed with a KeyError on this comparison)
check_bimi() no longer discards the warnings raised while locating the record — an unrelated TXT record beside it, or a record published at the root of the domain — by overwriting them with the
parser's warnings. check_mta_sts() already merged both sets
A certificate's wordMark attribute is now labeled wordMark in BIMI certificate metadata instead of its raw dotted OID string; a trailing comma made the label table's key a one-element tuple
check_mta_sts() no longer passes its DNS timeout as the HTTP timeout for the policy download; the download uses DEFAULT_HTTP_TIMEOUT, as the BIMI check already did
DMARCRecordNotFound now calls its parent constructor, so its message is carried explicitly rather than through a CPython quirk and its data attribute exists like other DMARC errors
SOA range and type errors now name the field that failed (retry, expire, or minimum) instead of always naming refresh
The BIMI check's DMARC policy warning stated the opposite of the requirement; it now reads "The DMARC policy (p tag) must be set to quarantine or reject"
The certificate metadata field for the X.509 subjectAlternativeName extension was misspelled serviceAlternativeName; code reading that key from BIMI certificate results should update
The User-Agent header sent on HTTP requests had doubled parentheses around the OS name
The example authorization record in the "does not indicate that it accepts DMARC reports" error contained a stray quote and a run of spaces from a misplaced line continuation
SPF TXT length warnings now report sizes in bytes, matching what is measured and what RFC 7208 § 3.3 limits, instead of calling them characters
Cleaned up user-facing messages across the package: corrected articles ("Found an SPF record…"), IPv4/IPv6 capitalization, double spaces, missing periods, inconsistent RFC citation punctuation, the
garbled DMARC fo tag redundancy warning, and the parked-domain MX warning, which now reads "MX records found on a parked domain"
Corrected inaccurate docstrings throughout: copy-paste artifacts ("MTA-HTS", "SIS-MTA", "Tne", the wrong TLSRPT record location), missing parameters and return keys, the stale claim that BIMI file
content is not analyzed, and wrong exception cross-references
Fixed documentation drift: the CLI usage block in the docs was missing --retries and is now generated from the real --help output, the docs index pointed at a nonexistent CI workflow badge, and
README/docs typos are corrected
To generate a diff of this commit:
cvs rdiff -u -r1.35 -r1.36 pkgsrc/mail/py-checkdmarc/Makefile
cvs rdiff -u -r1.33 -r1.34 pkgsrc/mail/py-checkdmarc/distinfo
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: pkgsrc/mail/py-checkdmarc/Makefile
diff -u pkgsrc/mail/py-checkdmarc/Makefile:1.35 pkgsrc/mail/py-checkdmarc/Makefile:1.36
--- pkgsrc/mail/py-checkdmarc/Makefile:1.35 Tue Aug 11 15:47:05 2026
+++ pkgsrc/mail/py-checkdmarc/Makefile Thu Sep 10 07:27:43 2026
@@ -1,6 +1,6 @@
-# $NetBSD: Makefile,v 1.35 2026/08/11 15:47:05 adam Exp $
+# $NetBSD: Makefile,v 1.36 2026/09/10 07:27:43 adam Exp $
-DISTNAME= checkdmarc-5.17.4
+DISTNAME= checkdmarc-6.0.1
PKGNAME= ${PYPKGPREFIX}-${DISTNAME}
CATEGORIES= mail python
MASTER_SITES= ${MASTER_SITE_PYPI:=c/checkdmarc/}
@@ -11,10 +11,10 @@ COMMENT= Python module and command line
LICENSE= apache-2.0
TOOL_DEPENDS+= ${PYPKGPREFIX}-hatchling>=1.27.0:../../devel/py-hatchling
-DEPENDS+= ${PYPKGPREFIX}-OpenSSL>=24.2.1:../../security/py-OpenSSL
-DEPENDS+= ${PYPKGPREFIX}-cryptography>=48.0.1:../../security/py-cryptography
-DEPENDS+= ${PYPKGPREFIX}-dns>=2.0.0:../../net/py-dns
+DEPENDS+= ${PYPKGPREFIX}-cryptography>=50.0.0:../../security/py-cryptography
+DEPENDS+= ${PYPKGPREFIX}-dns>=2.7.0:../../net/py-dns
DEPENDS+= ${PYPKGPREFIX}-expiringdict>=1.1.4:../../devel/py-expiringdict
+DEPENDS+= ${PYPKGPREFIX}-httpx>=0.26.0:../../www/py-httpx
DEPENDS+= ${PYPKGPREFIX}-importlib-resources>=6.0:../../devel/py-importlib-resources
DEPENDS+= ${PYPKGPREFIX}-pem>=23.1.0:../../security/py-pem
DEPENDS+= ${PYPKGPREFIX}-pyleri>=1.3.2:../../devel/py-pyleri
Index: pkgsrc/mail/py-checkdmarc/distinfo
diff -u pkgsrc/mail/py-checkdmarc/distinfo:1.33 pkgsrc/mail/py-checkdmarc/distinfo:1.34
--- pkgsrc/mail/py-checkdmarc/distinfo:1.33 Tue Aug 11 15:47:05 2026
+++ pkgsrc/mail/py-checkdmarc/distinfo Thu Sep 10 07:27:43 2026
@@ -1,5 +1,5 @@
-$NetBSD: distinfo,v 1.33 2026/08/11 15:47:05 adam Exp $
+$NetBSD: distinfo,v 1.34 2026/09/10 07:27:43 adam Exp $
-BLAKE2s (checkdmarc-5.17.4.tar.gz) = 8301613fffc506597cc31d70abd3dee35416553a8eafa2a3b29954ccd5b9467a
-SHA512 (checkdmarc-5.17.4.tar.gz) = 4cd6b3fb7029e12e58cc4f3a46ea6011823bb92c76a02bf6b09d6d812ba65e7125643ec1b1bf7402837ff94a31df3b1150fdb4a1b27ee65239d44b080d6029b0
-Size (checkdmarc-5.17.4.tar.gz) = 66658 bytes
+BLAKE2s (checkdmarc-6.0.1.tar.gz) = 98804fb0bd7bf1e20da643af343b8e3705cfaf946436cd2fb51e671b0004e22b
+SHA512 (checkdmarc-6.0.1.tar.gz) = 14f7574fa12b166b8b5b73d75c83b0a9016ed991d80155a08447e7375357c2baf3f41da23c6cc38c82aa3f02eafcb2fa64380aa07fc7aa9d025f0396c21626f3
+Size (checkdmarc-6.0.1.tar.gz) = 96769 bytes
Home |
Main Index |
Thread Index |
Old Index