From: Bob Halley Date: Thu, 2 Dec 2021 15:22:46 +0000 (-0800) Subject: cname and other data check in zonefile reader X-Git-Tag: v2.2.0rc1~19^2~5 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=bfaa95c85e258a8c1ac808c7af87a22bdf79c857;p=thirdparty%2Fdnspython.git cname and other data check in zonefile reader --- diff --git a/dns/node.py b/dns/node.py index 3267de77..12e40801 100644 --- a/dns/node.py +++ b/dns/node.py @@ -102,16 +102,10 @@ class Node: """ # Make having just one rdataset at the node fast. if len(self.rdatasets) > 0: - # We don't want adding RRSIG(CNAME) to delete CNAMEs, - # so we treat it as expressing "CNAME intent" for classifying - # the node as a CNAME node, even if we haven't added the CNAME - # yet. - if rdataset.rdtype == dns.rdatatype.CNAME or \ - (rdataset.rdtype == dns.rdatatype.RRSIG and - rdataset.covers == dns.rdatatype.CNAME): + if rdataset.implies_cname(): self.rdatasets = [rds for rds in self.rdatasets if rds.ok_for_cname()] - else: + elif rdataset.implies_other_data(): self.rdatasets = [rds for rds in self.rdatasets if rds.ok_for_other_data()] self.rdatasets.append(rdataset) diff --git a/dns/rdataset.py b/dns/rdataset.py index 242e30c7..f948d761 100644 --- a/dns/rdataset.py +++ b/dns/rdataset.py @@ -353,6 +353,14 @@ class Rdataset(dns.set.Set): node?""" return not self.implies_cname() + def implies_other_data(self): + """Does this rdataset imply a node is an other data node? + + Note that implies_other_data() is not simply "not implies_cname()" as + some types, e.g. NSEC and RRSIG(NSEC) are neutral. + """ + return not self.ok_for_cname() + @dns.immutable.immutable class ImmutableRdataset(Rdataset): diff --git a/dns/zonefile.py b/dns/zonefile.py index 39c7a384..4d72c718 100644 --- a/dns/zonefile.py +++ b/dns/zonefile.py @@ -38,6 +38,26 @@ class UnknownOrigin(dns.exception.DNSException): """Unknown origin""" +class CNAMEAndOtherData(dns.exception.DNSException): + """A node has a CNAME and other data""" + + +def _check_cname_and_other_data(txn, name, rdataset): + rdatasets = txn.get_rdatasets(name) + if any(rds.implies_cname() for rds in rdatasets): + # This is a CNAME node. + if not rdataset.ok_for_cname(): + raise CNAMEAndOtherData('rdataset not ok for CNAME node') + elif any(rds.implies_other_data() for rds in rdatasets): + # This is an other data node + if not rdataset.ok_for_other_data(): + raise CNAMEAndOtherData('rdataset is a CNAME but node ' + 'has other data') + # Otherwise the node consists of neutral types that can be + # present at either a CNAME or an other data node, e.g. NSEC or + # RRSIG(NSEC) + + class Reader: """Read a DNS zone file into a transaction.""" @@ -71,6 +91,7 @@ class Reader: self.force_ttl = force_ttl self.force_rdclass = force_rdclass self.force_rdtype = force_rdtype + self.txn.check_put_rdataset(_check_cname_and_other_data) def _eat_line(self): while 1: @@ -445,6 +466,13 @@ class RRsetsReaderTransaction(dns.transaction.Transaction): def _get_rdataset(self, name, rdtype, covers): return self.rdatasets.get((name, rdtype, covers)) + def _get_rdatasets(self, name): + rdatasets = [] + for (rdataset_name, _, _), rdataset in self.rdatasets.items(): + if name == rdataset_name: + rdatasets.append(rdataset) + return rdatasets + def _put_rdataset(self, name, rdataset): self.rdatasets[(name, rdataset.rdtype, rdataset.covers)] = rdataset diff --git a/tests/test_zone.py b/tests/test_zone.py index 1cd58dd1..bdc99a35 100644 --- a/tests/test_zone.py +++ b/tests/test_zone.py @@ -224,9 +224,22 @@ web a 10.0.0.4 nsec @ A RRSIG rrsig A 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= rrsig NSEC 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= - rrsig CNAME 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= """ +example_cname_and_other_data = """$TTL 3600 +$ORIGIN example. +@ soa foo bar (1 2 3 4 5) +@ ns ns1 +@ ns ns2 +ns1 a 10.0.0.1 +ns2 a 10.0.0.2 +www a 10.0.0.3 +web a 10.0.0.4 + cname www + nsec @ A RRSIG + rrsig A 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= + rrsig NSEC 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= +""" _keep_output = True @@ -904,7 +917,7 @@ class ZoneTestCase(unittest.TestCase): rds = dns.rdataset.from_text('in', 'cname', 300, 'www') z.replace_rdataset('web', rds) n = z.find_node('web') - self.assertEqual(len(n.rdatasets), 4) + self.assertEqual(len(n.rdatasets), 3) self.assertEqual(n.find_rdataset(dns.rdataclass.IN, dns.rdatatype.CNAME), rds) @@ -913,9 +926,11 @@ class ZoneTestCase(unittest.TestCase): self.assertIsNotNone(n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.NSEC)) - self.assertIsNotNone(n.get_rdataset(dns.rdataclass.IN, - dns.rdatatype.RRSIG, - dns.rdatatype.CNAME)) + + def testCnameAndOtherDataInZonefile(self): + with self.assertRaises(dns.zonefile.CNAMEAndOtherData): + dns.zone.from_text(example_cname_and_other_data, 'example.', + relativize=True) def testNameInZoneWithStr(self): z = dns.zone.from_text(example_text, 'example.', relativize=False) @@ -1030,7 +1045,7 @@ class VersionedZoneTestCase(unittest.TestCase): with z.writer() as txn: txn.replace('web', rds) n = z.find_node('web') - self.assertEqual(len(n.rdatasets), 4) + self.assertEqual(len(n.rdatasets), 3) self.assertEqual(n.find_rdataset(dns.rdataclass.IN, dns.rdatatype.CNAME), rds) @@ -1039,9 +1054,6 @@ class VersionedZoneTestCase(unittest.TestCase): self.assertIsNotNone(n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.NSEC)) - self.assertIsNotNone(n.get_rdataset(dns.rdataclass.IN, - dns.rdatatype.RRSIG, - dns.rdatatype.CNAME)) if __name__ == '__main__':