diff --git a/checkdmarc-5.5.0.tar.gz b/checkdmarc-5.5.0.tar.gz deleted file mode 100644 index 35a4182..0000000 --- a/checkdmarc-5.5.0.tar.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d775f566f8fdd584adb9a26e792cfe1565cdf6bd50c54fe7b6d8443ce6db68cf -size 36103 diff --git a/checkdmarc-5.7.8.tar.gz b/checkdmarc-5.7.8.tar.gz new file mode 100644 index 0000000..d100282 --- /dev/null +++ b/checkdmarc-5.7.8.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1affa797ec976165932787ac8b46a291d5a5328e7412bf306011a1e60f6e873e +size 58464 diff --git a/python-checkdmarc.changes b/python-checkdmarc.changes index 67dfe2b..20d9a1d 100644 --- a/python-checkdmarc.changes +++ b/python-checkdmarc.changes @@ -1,3 +1,62 @@ +------------------------------------------------------------------- +Sun Nov 10 10:27:03 UTC 2024 - Martin Hauke + +- 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 diff --git a/python-checkdmarc.spec b/python-checkdmarc.spec index 92d3f11..4b4d5bc 100644 --- a/python-checkdmarc.spec +++ b/python-checkdmarc.spec @@ -2,7 +2,7 @@ # spec file for package python-checkdmarc # # Copyright (c) 2024 SUSE LLC -# Copyright (c) 2021, Martin Hauke +# Copyright (c) 2021-2024, Martin Hauke # # 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.5.0 +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 diff --git a/skip-network-tests.patch b/skip-network-tests.patch index 245176a..665c203 100644 --- a/skip-network-tests.patch +++ b/skip-network-tests.patch @@ -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" diff --git a/tests.py b/tests.py deleted file mode 100644 index e175d6a..0000000 --- a/tests.py +++ /dev/null @@ -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)