1
0

Compare commits

...

4 Commits

Author SHA256 Message Date
Dominique Leuenberger
a2263292c4 Accepting request 1223345 from devel:languages:python
OBS-URL: https://build.opensuse.org/request/show/1223345
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python-checkdmarc?expand=0&rev=4
2024-11-11 13:58:19 +00:00
ab93a451fd - Update to version 5.7.8
* Move SVG validation errors from ["bimi"]["warnings"] to
    ["bimi"]["image"]["validation_errors"] (#150)
- Update to version 5.7.7
  * Fix VMC validation errors not appearing.
- Update to version 5.7.6
  * Fix crash when trying to output to CSV format
- Update to version 5.7.5
  * Fix BIMI lookup for subdomains that do not have a BIMI record.
- Update to version 5.7.4
  * Add additional checks for tiny-ps SVG requirements
- Update to version 5.7.3
  * BIMI images and mark certificates
    + Better error handling
    + Simplified warning messages
    + sha256_hash output fields renamed to sha256
- Update to version 5.7.2
  * Account for float SVG sizes
- Update to version 5.7.1
  * Properly parse a certificate SAN
  * Certificate warnings fire properly
  * Make the expires timestamp more readable
- Update to version 5.7.0
  * checkdmarc will now validate Verified Mark Certificates (VMCs)
    and Common Mark Certificates (CMC), snd will verify that
    SHA256 hash of the logo embedded in the certificate matches
    the SHA256 hash logo at the URL at the BIMI l tag.
    Additionally, SVG and certificate metadata is now included in
    the checkdmarc.bimi.parse_bimi_record() API and JSON CLI
    output.

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python/python-checkdmarc?expand=0&rev=9
2024-11-11 11:34:51 +00:00
Ana Guerrero
fe304a6e17 Accepting request 1206745 from devel:languages:python
- update to 5.5.0:
  * Support `redirect` in SPF

OBS-URL: https://build.opensuse.org/request/show/1206745
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python-checkdmarc?expand=0&rev=3
2024-10-10 20:15:06 +00:00
2aa4f41f73 - update to 5.5.0:
* Support `redirect` in SPF

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python/python-checkdmarc?expand=0&rev=7
2024-10-10 15:49:32 +00:00
6 changed files with 107 additions and 307 deletions

View File

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1d71e7fa611fa8faa36fad09416b5e2c3265d026d3b5209c051f4e292565e332
size 36307

3
checkdmarc-5.7.8.tar.gz Normal file
View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1affa797ec976165932787ac8b46a291d5a5328e7412bf306011a1e60f6e873e
size 58464

View File

@ -1,3 +1,68 @@
-------------------------------------------------------------------
Sun Nov 10 10:27:03 UTC 2024 - Martin Hauke <mardnh@gmx.de>
- Update to version 5.7.8
* Move SVG validation errors from ["bimi"]["warnings"] to
["bimi"]["image"]["validation_errors"] (#150)
- Update to version 5.7.7
* Fix VMC validation errors not appearing.
- Update to version 5.7.6
* Fix crash when trying to output to CSV format
- Update to version 5.7.5
* Fix BIMI lookup for subdomains that do not have a BIMI record.
- Update to version 5.7.4
* Add additional checks for tiny-ps SVG requirements
- Update to version 5.7.3
* BIMI images and mark certificates
+ Better error handling
+ Simplified warning messages
+ sha256_hash output fields renamed to sha256
- Update to version 5.7.2
* Account for float SVG sizes
- Update to version 5.7.1
* Properly parse a certificate SAN
* Certificate warnings fire properly
* Make the expires timestamp more readable
- Update to version 5.7.0
* checkdmarc will now validate Verified Mark Certificates (VMCs)
and Common Mark Certificates (CMC), snd will verify that
SHA256 hash of the logo embedded in the certificate matches
the SHA256 hash logo at the URL at the BIMI l tag.
Additionally, SVG and certificate metadata is now included in
the checkdmarc.bimi.parse_bimi_record() API and JSON CLI
output.
- Update to version 5.6.2
* Add a warning when BIMI records do not provide a mark
certificate.
* Use the correct dependency (xmltodict, not xml2dict).
- Update to version 5.6.1
* Fix SVG base profile detection
- Update to version 5.6.0
* Automatically check for a BIMI DNS record at the default
selector when using the CLI
* Fix parsing of BIMI record tags when they are separated by
a ";" without a space.
* Validate the file at the URL in the BIMI l tag value
+ Must be an SVG file
+ The SVG version must be 1.2
+ The SVG base profile must be tiny-ps
+ The SVG dimensions must be square
+ The file size must not exceed 32 KB
- Update to version 5.5.1
* SPF record validation fixes (PR #147)
+ Accept mechanisms with domains that start with all.
+ Ignore multiple trailing mechanisms and random text with
spaces.
- Rebase skip-network-tests.patch
- Remove tests.py
* included in the now used source tarball from github
-------------------------------------------------------------------
Thu Oct 10 15:49:23 UTC 2024 - Dirk Müller <dmueller@suse.com>
- update to 5.5.0:
* Support `redirect` in SPF
-------------------------------------------------------------------
Thu Feb 29 01:43:19 UTC 2024 - Steve Kowalik <steven.kowalik@suse.com>

View File

@ -2,7 +2,7 @@
# spec file for package python-checkdmarc
#
# Copyright (c) 2024 SUSE LLC
# Copyright (c) 2021, Martin Hauke <mardnh@gmx.de>
# Copyright (c) 2021-2024, Martin Hauke <mardnh@gmx.de>
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@ -18,13 +18,12 @@
Name: python-checkdmarc
Version: 5.3.1
Version: 5.7.8
Release: 0
Summary: A Python module and command line parser for SPF and DMARC records
License: Apache-2.0
URL: https://domainaware.github.io/checkdmarc
Source: https://files.pythonhosted.org/packages/source/c/checkdmarc/checkdmarc-%{version}.tar.gz
Source1: https://raw.githubusercontent.com/domainaware/checkdmarc/master/tests.py
Source: https://github.com/domainaware/checkdmarc/archive/refs/tags/%{version}.tar.gz#/checkdmarc-%{version}.tar.gz
Patch0: skip-network-tests.patch
BuildRequires: %{python_module hatchling}
BuildRequires: %{python_module pip}
@ -34,20 +33,26 @@ BuildRequires: python-rpm-macros
Requires: python-cryptography
Requires: python-dnspython >= 2.0.0
Requires: python-expiringdict >= 1.1.4
Requires: python-pem >= 23.1.0
Requires: python-publicsuffixlist
Requires: python-pyOpenSSL >= 24.2.1
Requires: python-pyleri >= 1.3.2
Requires: python-requests >= 2.25.0
Requires: python-timeout-decorator >= 0.4.1
Requires: python-xmltodict
Requires(post): update-alternatives
Requires(postun): update-alternatives
BuildArch: noarch
# SECTION test requirements
BuildRequires: %{python_module dnspython >= 2.0.0}
BuildRequires: %{python_module expiringdict >= 1.1.4}
BuildRequires: %{python_module pem >= 23.1.0}
BuildRequires: %{python_module publicsuffixlist}
BuildRequires: %{python_module pyOpenSSL >= 24.2.1}
BuildRequires: %{python_module pyleri >= 1.3.2}
BuildRequires: %{python_module requests >= 2.25.0}
BuildRequires: %{python_module timeout-decorator >= 0.4.1}
BuildRequires: %{python_module xmltodict}
# /SECTION
%python_subpackages
@ -56,8 +61,7 @@ A Python module and command line parser for SPF and DMARC records.
%prep
%setup -q -n checkdmarc-%{version}
cp %{SOURCE1} .
%patch -P 0 -p0
%autopatch -p1
%build
%pyproject_wheel
@ -81,6 +85,6 @@ cp %{SOURCE1} .
%doc README.md
%python_alternative %{_bindir}/checkdmarc
%{python_sitelib}/checkdmarc
%{python_sitelib}/checkdmarc-%{version}.dist-info
%{python_sitelib}/checkdmarc-*.dist-info
%changelog

View File

@ -1,5 +1,7 @@
--- tests.py~ 2024-02-29 12:22:56.007309853 +1100
+++ tests.py 2024-02-29 12:25:49.618057933 +1100
diff --git a/tests.py b/tests.py
index 8e58708..65605d4 100755
--- a/tests.py
+++ b/tests.py
@@ -3,6 +3,7 @@
"""Automated tests"""
@ -8,7 +10,7 @@
import unittest
from collections import OrderedDict
@@ -94,6 +95,7 @@
@@ -99,6 +100,7 @@ class Test(unittest.TestCase):
self.assertEqual(len(results["warnings"]), 0)
@ -16,43 +18,51 @@
def testSplitSPFRecord(self):
"""Split SPF records are parsed properly"""
@@ -129,6 +131,7 @@
self.assertRaises(checkdmarc.spf.SPFRecordNotFound,
checkdmarc.spf.parse_spf_record, spf_record, domain)
@@ -140,6 +142,7 @@ class Test(unittest.TestCase):
domain,
)
+ @unittest.skipUnless(os.path.exists("/etc/resolv.conf"), "no network")
def testTooManySPFDNSLookups(self):
"""SPF records with > 10 SPF mechanisms that cause DNS lookups raise
SPFTooManyDNSLookups"""
@@ -144,6 +147,7 @@
self.assertRaises(checkdmarc.spf.SPFTooManyDNSLookups,
checkdmarc.spf.parse_spf_record, spf_record, domain)
@@ -161,6 +164,7 @@ class Test(unittest.TestCase):
domain,
)
+ @unittest.skipUnless(os.path.exists("/etc/resolv.conf"), "no network")
def testTooManySPFVoidDNSLookups(self):
"""SPF records with > 2 void DNS lookups"""
@@ -216,6 +220,7 @@
self.assertRaises(checkdmarc.spf.SPFIncludeLoop,
checkdmarc.spf.parse_spf_record, spf_record, domain)
@@ -274,6 +278,7 @@ class Test(unittest.TestCase):
domain,
)
+ @unittest.skipUnless(os.path.exists("/etc/resolv.conf"), "no network")
def testSPFMissingMXRecord(self):
"""A warning is issued if an SPF record contains a mx mechanism
pointing to a domain that has no MX records"""
@@ -226,6 +231,7 @@
self.assertIn("{0} does not have any MX records".format(domain),
results["warnings"])
@@ -285,6 +290,7 @@ class Test(unittest.TestCase):
"{0} does not have any MX records".format(domain), results["warnings"]
)
+ @unittest.skipUnless(os.path.exists("/etc/resolv.conf"), "no network")
def testSPFMissingARecord(self):
"""A warning is issued if an SPF record contains a mx mechanism
pointing to a domain that has no A records"""
@@ -236,6 +242,7 @@
self.assertIn("cardinalhealth.net does not have any A/AAAA records",
results["warnings"])
@@ -296,6 +302,7 @@ class Test(unittest.TestCase):
"cardinalhealth.net does not have any A/AAAA records", results["warnings"]
)
+ @unittest.skipUnless(os.path.exists("/etc/resolv.conf"), "no network")
def testDMARCPctLessThan100Warning(self):
"""A warning is issued if the DMARC pvt value is less than 100"""
@@ -347,6 +354,7 @@ class Test(unittest.TestCase):
domain,
)
+ @unittest.skipUnless(os.path.exists("/etc/resolv.conf"), "no network")
def testBIMI(self):
"""Test BIMI checks"""
domain = "chase.com"

279
tests.py
View File

@ -1,279 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Automated tests"""
import unittest
from collections import OrderedDict
import checkdmarc
import checkdmarc.utils
import checkdmarc.spf
import checkdmarc.dmarc
import checkdmarc.dnssec
known_good_domains = [
"fbi.gov",
"pm.me"
]
class Test(unittest.TestCase):
@unittest.skip
def testKnownGood(self):
"""Domains with known good STARTTLS support, SPF and DMARC records"""
results = checkdmarc.check_domains(known_good_domains)
for result in results:
spf_error = None
dmarc_error = None
for mx in result["mx"]["hosts"]:
self.assertEqual(
mx["starttls"], True,
"Host of known good domain {0} failed STARTTLS check: {1}"
"\n\n{0}".format(result["domain"], mx["hostname"])
)
if "error" in result["spf"]:
spf_error = result["spf"]["error"]
if "error" in result["dmarc"]:
dmarc_error = result["dmarc"]["error"]
self.assertEqual(result["spf"]["valid"], True,
"Known good domain {0} failed SPF check:"
"\n\n{1}".format(result["domain"], spf_error))
self.assertEqual(result["dmarc"]["valid"], True,
"Known good domain {0} failed DMARC check:"
"\n\n{1}".format(result["domain"], dmarc_error))
def testDMARCMixedFormatting(self):
"""DMARC records with extra spaces and mixed case are still valid"""
examples = [
"v=DMARC1;p=ReJect",
"v = DMARC1;p=reject;",
"v = DMARC1\t;\tp=reject\t;",
"v = DMARC1\t;\tp\t\t\t=\t\t\treject\t;",
"V=DMARC1;p=reject;"
]
for example in examples:
parsed_record = checkdmarc.dmarc.parse_dmarc_record(example, "")
self.assertIsInstance(parsed_record, OrderedDict)
def testGetBaseDomain(self):
subdomain = "foo.example.com"
result = checkdmarc.utils.get_base_domain(subdomain)
assert result == "example.com"
# Test reserved domains
subdomain = "_dmarc.nonauth-rua.invalid.example"
result = checkdmarc.utils.get_base_domain(subdomain)
assert result == "invalid.example"
subdomain = "_dmarc.nonauth-rua.invalid.test"
result = checkdmarc.utils.get_base_domain(subdomain)
assert result == "invalid.test"
subdomain = "_dmarc.nonauth-rua.invalid.invalid"
result = checkdmarc.utils.get_base_domain(subdomain)
assert result == "invalid.invalid"
subdomain = "_dmarc.nonauth-rua.invalid.localhost"
result = checkdmarc.utils.get_base_domain(subdomain)
assert result == "invalid.localhost"
# Test newer PSL entries
subdomain = "e3191.c.akamaiedge.net"
result = checkdmarc.utils.get_base_domain(subdomain)
assert result == "c.akamaiedge.net"
def testUppercaseSPFMechanism(self):
"""Treat uppercase SPF"SPF mechanisms as valid"""
spf_record = "v=spf1 IP4:147.75.8.208 -ALL"
domain = "example.no"
results = checkdmarc.spf.parse_spf_record(spf_record, domain)
self.assertEqual(len(results["warnings"]), 0)
def testSplitSPFRecord(self):
"""Split SPF records are parsed properly"""
rec = '"v=spf1 ip4:147.75.8.208 " "include:_spf.salesforce.com -all"'
parsed_record = checkdmarc.spf.parse_spf_record(rec, "example.com")
self.assertEqual(parsed_record["parsed"]["all"], "fail")
def testJunkAfterAll(self):
"""Ignore any mechanisms after the all mechanism, but warn about it"""
rec = "v=spf1 ip4:213.5.39.110 -all MS=83859DAEBD1978F9A7A67D3"
domain = "avd.dk"
parsed_record = checkdmarc.spf.parse_spf_record(rec, domain)
self.assertEqual(len(parsed_record["warnings"]), 1)
@unittest.skip
def testDNSSEC(self):
"""Test known good DNSSEC"""
self.assertEqual(checkdmarc.dnssec.test_dnssec("fbi.gov"), True)
def testIncludeMissingSPF(self):
"""SPF records that include domains that are missing SPF records
raise SPFRecordNotFound"""
spf_record = '"v=spf1 include:spf.comendosystems.com ' \
'include:bounce.peytz.dk include:etrack.indicia.dk ' \
'include:etrack1.com include:mail1.dialogportal.com ' \
'include:mail2.dialogportal.com a:mailrelay.jppol.dk ' \
'a:sendmail.jppol.dk ?all"'
domain = "ekstrabladet.dk"
self.assertRaises(checkdmarc.spf.SPFRecordNotFound,
checkdmarc.spf.parse_spf_record, spf_record, domain)
def testTooManySPFDNSLookups(self):
"""SPF records with > 10 SPF mechanisms that cause DNS lookups raise
SPFTooManyDNSLookups"""
spf_record = "v=spf1 a include:_spf.salesforce.com " \
"include:spf.protection.outlook.com " \
"include:spf.constantcontact.com " \
"include:_spf.elasticemail.com " \
"include:servers.mcsv.net " \
"include:_spf.google.com " \
"~all"
domain = "example.com"
self.assertRaises(checkdmarc.spf.SPFTooManyDNSLookups,
checkdmarc.spf.parse_spf_record, spf_record, domain)
def testTooManySPFVoidDNSLookups(self):
"""SPF records with > 2 void DNS lookups"""
spf_record = "v=spf1 a:13Mk4olS9VWhQqXRl90fKJrD.example.com " \
"mx:SfGiqBnQfRbOMapQJhozxo2B.example.com " \
"a:VAFeyU9N2KJX518aGsN3w6VS.example.com " \
"~all"
domain = "example.com"
self.assertRaises(checkdmarc.spf.SPFTooManyVoidDNSLookups,
checkdmarc.spf.parse_spf_record, spf_record, domain)
def testSPFSyntaxErrors(self):
"""SPF record syntax errors raise SPFSyntaxError"""
spf_record = '"v=spf1 mx a:mail.cohaesio.net ' \
'include: trustpilotservice.com ~all"'
domain = "2021.ai"
self.assertRaises(checkdmarc.spf.SPFSyntaxError,
checkdmarc.spf.parse_spf_record, spf_record, domain)
def testSPFInvalidIPv4(self):
"""Invalid ipv4 SPF mechanism values raise SPFSyntaxError"""
spf_record = "v=spf1 ip4:78.46.96.236 +a +mx +ip4:138.201.239.158 " \
"+ip4:78.46.224.83 " \
"+ip4:relay.mailchannels.net +ip4:138.201.60.20 ~all"
domain = "surftown.dk"
self.assertRaises(checkdmarc.spf.SPFSyntaxError,
checkdmarc.spf.parse_spf_record, spf_record, domain)
def testSPFInvalidIPv6inIPv4(self):
"""Invalid ipv4 SPF mechanism values raise SPFSyntaxError"""
spf_record = "v=spf1 ip4:1200:0000:AB00:1234:0000:2552:7777:1313 ~all"
domain = "surftown.dk"
self.assertRaises(checkdmarc.spf.SPFSyntaxError,
checkdmarc.spf.parse_spf_record, spf_record, domain)
def testSPFInvalidIPv4Range(self):
"""Invalid ipv4 SPF mechanism values raise SPFSyntaxError"""
spf_record = "v=spf1 ip4:78.46.96.236/99 ~all"
domain = "surftown.dk"
self.assertRaises(checkdmarc.spf.SPFSyntaxError,
checkdmarc.spf.parse_spf_record, spf_record, domain)
def testSPFInvalidIPv6(self):
"""Invalid ipv6 SPF mechanism values raise SPFSyntaxError"""
spf_record = "v=spf1 ip6:1200:0000:AB00:1234:O000:2552:7777:1313 ~all"
domain = "surftown.dk"
self.assertRaises(checkdmarc.spf.SPFSyntaxError,
checkdmarc.spf.parse_spf_record, spf_record, domain)
def testSPFInvalidIPv4inIPv6(self):
"""Invalid ipv6 SPF mechanism values raise SPFSyntaxError"""
spf_record = "v=spf1 ip6:78.46.96.236 ~all"
domain = "surftown.dk"
self.assertRaises(checkdmarc.spf.SPFSyntaxError,
checkdmarc.spf.parse_spf_record, spf_record, domain)
def testSPFInvalidIPv6Range(self):
"""Invalid ipv6 SPF mechanism values raise SPFSyntaxError"""
record = "v=spf1 ip6:1200:0000:AB00:1234:0000:2552:7777:1313/130 ~all"
domain = "surftown.dk"
self.assertRaises(checkdmarc.spf.SPFSyntaxError,
checkdmarc.spf.parse_spf_record, record, domain)
def testSPFIncludeLoop(self):
"""SPF record with include loop raises SPFIncludeLoop"""
spf_record = '"v=spf1 include:example.com"'
domain = "example.com"
self.assertRaises(checkdmarc.spf.SPFIncludeLoop,
checkdmarc.spf.parse_spf_record, spf_record, domain)
def testSPFMissingMXRecord(self):
"""A warning is issued if an SPF record contains a mx mechanism
pointing to a domain that has no MX records"""
spf_record = '"v=spf1 mx ~all"'
domain = "seanthegeek.net"
results = checkdmarc.spf.parse_spf_record(spf_record, domain)
self.assertIn("{0} does not have any MX records".format(domain),
results["warnings"])
def testSPFMissingARecord(self):
"""A warning is issued if an SPF record contains a mx mechanism
pointing to a domain that has no A records"""
spf_record = '"v=spf1 a ~all"'
domain = "cardinalhealth.net"
results = checkdmarc.spf.parse_spf_record(spf_record, domain)
self.assertIn("cardinalhealth.net does not have any A/AAAA records",
results["warnings"])
def testDMARCPctLessThan100Warning(self):
"""A warning is issued if the DMARC pvt value is less than 100"""
dmarc_record = "v=DMARC1; p=none; sp=none; fo=1; pct=50; adkim=r; " \
"aspf=r; rf=afrf; ri=86400; " \
"rua=mailto:eits.dmarcrua@energy.gov; " \
"ruf=mailto:eits.dmarcruf@energy.gov"
domain = "energy.gov"
results = checkdmarc.dmarc.parse_dmarc_record(dmarc_record, domain)
self.assertIn("pct value is less than 100",
results["warnings"][0])
def testInvalidDMARCURI(self):
"""An invalid DMARC report URI raises InvalidDMARCReportURI"""
dmarc_record = "v=DMARC1; p=none; rua=reports@dmarc.cyber.dhs.gov," \
"mailto:dmarcreports@usdoj.gov"
domain = "dea.gov"
self.assertRaises(checkdmarc.dmarc.InvalidDMARCReportURI,
checkdmarc.dmarc.parse_dmarc_record, dmarc_record,
domain)
dmarc_record = "v=DMARC1; p=none; rua=__" \
"mailto:reports@dmarc.cyber.dhs.gov," \
"mailto:dmarcreports@usdoj.gov"
self.assertRaises(checkdmarc.dmarc.InvalidDMARCReportURI,
checkdmarc.dmarc.parse_dmarc_record, dmarc_record,
domain)
def testInvalidDMARCPolicyValue(self):
"""An invalid DMARC policy value raises InvalidDMARCTagValue """
dmarc_record = "v=DMARC1; p=foo; rua=mailto:dmarc@example.com"
domain = "example.com"
self.assertRaises(checkdmarc.dmarc.InvalidDMARCTagValue,
checkdmarc.dmarc.parse_dmarc_record,
dmarc_record,
domain)
if __name__ == "__main__":
unittest.main(verbosity=2)