python-checkdmarc/tests.py

211 lines
8.8 KiB
Python

import unittest
import checkdmarc
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 testUppercaseSPFMechanism(self):
"""Treat uppercase SPF"SPF mechanisms as valid"""
spf_record = "v=spf1 IP4:147.75.8.208 -ALL"
domain = "example.no"
results = checkdmarc.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.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.parse_spf_record(rec, domain)
self.assertEqual(len(parsed_record["warnings"]), 1)
def testDNSSEC(self):
"""Test known good DNSSEC"""
self.assertEqual(checkdmarc.test_dnssec("whalensolutions.com"), 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.SPFRecordNotFound,
checkdmarc.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.SPFTooManyDNSLookups,
checkdmarc.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.SPFSyntaxError,
checkdmarc.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.SPFSyntaxError,
checkdmarc.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.SPFSyntaxError,
checkdmarc.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.SPFSyntaxError,
checkdmarc.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.SPFSyntaxError,
checkdmarc.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.SPFIncludeLoop,
checkdmarc.parse_spf_record, spf_record, domain)
def testSPFMissingMXRecord(self):
"""A warning is issued if a 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.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 a SPF record contains a mx mechanism
pointing to a domain that has no A records"""
spf_record = '"v=spf1 include:_spf.bibsyst.no a mx ~all"'
domain = "sogne.folkebibl.no"
results = checkdmarc.parse_spf_record(spf_record, domain)
self.assertIn("sogne.folkebibl.no 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.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.InvalidDMARCReportURI,
checkdmarc.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.InvalidDMARCReportURI,
checkdmarc.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.InvalidDMARCTagValue,
checkdmarc.parse_dmarc_record,
dmarc_record,
domain)
def testInvalidDMARCfo(self):
"""An invalid DMARC fo tag value raises InvalidDMARCTagValue"""
dmarc_record = "v=DMARC1;p=none;aspf=s;adkim=s;fo=0:1:d:s;" \
"ruf=mailto:dmarcreports@omb.gov;" \
"rua=mailto:dmarcreports@omb.gov"
domain = "omb.gov"
self.assertRaises(checkdmarc.InvalidDMARCTagValue,
checkdmarc.parse_dmarc_record, dmarc_record, domain)
if __name__ == "__main__":
unittest.main(verbosity=2)