From: corubba Date: Fri, 5 Aug 2022 12:09:52 +0000 (+0200) Subject: Add support for more bases in $GENERATE X-Git-Tag: v2.3.0rc1~55^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F828%2Fhead;p=thirdparty%2Fdnspython.git Add support for more bases in $GENERATE When reading a zone from a zonefile, the `$GENERATE` resolution now not only supports decimal but also octal, hexadecimal and nibbles. When using nibbles with an even width, the generated index may end with a dot, and alone is interpreted as a absolute name. This behaviour is consistent with bind, but may cause these records to be dropped by the subdomain-check in `zonefile.py:398` (see also the `h.*` labels in the testcase that are missing from the result). --- diff --git a/dns/zonefile.py b/dns/zonefile.py index 4e0013a6..68c63148 100644 --- a/dns/zonefile.py +++ b/dns/zonefile.py @@ -244,7 +244,7 @@ class Reader: self.txn.add(name, ttl, rd) - def _parse_modify(self, side): + def _parse_modify(self, side: str) -> Tuple[str, str, int, int, str]: # Here we catch everything in '{' '}' in a group so we can replace it # with ''. is_generate1 = re.compile(r"^.*\$({(\+|-?)(\d+),(\d+),(.)}).*$") @@ -279,8 +279,17 @@ class Reader: width = 0 base = "d" - if base != "d": - raise NotImplementedError() + offset = int(offset) + width = int(width) + + if sign not in ["+", "-"]: + raise dns.exception.SyntaxError( + "invalid offset sign %s" % sign + ) + if base not in ["d", "o", "x", "X", "n", "N"]: + raise dns.exception.SyntaxError( + "invalid type %s" % base + ) return mod, sign, offset, width, base @@ -349,25 +358,35 @@ class Reader: # rhs (required) rhs = token.value - # The code currently only supports base 'd', so the last value - # in the tuple _parse_modify returns is ignored - lmod, lsign, loffset, lwidth, _ = self._parse_modify(lhs) - rmod, rsign, roffset, rwidth, _ = self._parse_modify(rhs) + def _calculate_index(counter: int, offset_sign: str, offset: int) -> int: + """Calculate the index from the counter and offset.""" + if offset_sign == "-": + offset *= -1 + return counter + offset + + def _format_index(index: int, base: str, width: int) -> str: + """Format the index with the given base, and zero-fill it + to the given width.""" + if base in ["d", "o", "x", "X"]: + return format(index, base).zfill(width) + + # base can only be n or N here + hexa = _format_index(index, "x", width) + nibbles = ".".join(hexa[::-1])[:width] + if base == "N": + nibbles = nibbles.upper() + return nibbles + + lmod, lsign, loffset, lwidth, lbase = self._parse_modify(lhs) + rmod, rsign, roffset, rwidth, rbase = self._parse_modify(rhs) for i in range(start, stop + 1, step): # +1 because bind is inclusive and python is exclusive - if lsign == "+": - lindex = i + int(loffset) - elif lsign == "-": - lindex = i - int(loffset) - - if rsign == "-": - rindex = i - int(roffset) - elif rsign == "+": - rindex = i + int(roffset) + lindex = _calculate_index(i, lsign, loffset) + rindex = _calculate_index(i, rsign, roffset) - lzfindex = str(lindex).zfill(int(lwidth)) - rzfindex = str(rindex).zfill(int(rwidth)) + lzfindex = _format_index(lindex, lbase, lwidth) + rzfindex = _format_index(rindex, rbase, rwidth) name = lhs.replace("$%s" % (lmod), lzfindex) rdata = rhs.replace("$%s" % (rmod), rzfindex) diff --git a/tests/test_zone.py b/tests/test_zone.py index de7ec015..2d10274f 100644 --- a/tests/test_zone.py +++ b/tests/test_zone.py @@ -58,6 +58,43 @@ ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.2 """ +example_generate = """@ 3600 IN SOA foo bar 1 2 3 4 5 +@ 3600 IN NS ns +$GENERATE 9-12 a.$ A 10.0.0.$ +$GENERATE 80-254/173 b.${0,5,d} A 10.0.1.$ +$GENERATE 80-254/173 c.${0,5,o} A 10.0.2.$ +$GENERATE 80-254/173 d.${0,5,x} A 10.0.3.$ +$GENERATE 80-254/173 e.${0,5,X} A 10.0.4.$ +$GENERATE 80-254/173 f.${0,5,n} A 10.0.5.$ +$GENERATE 80-254/173 g.${0,5,N} A 10.0.6.$ +$GENERATE 218-218/1 h.${0,4,N} A 10.0.7.$ +$GENERATE 218-218/1 i.${0,4,N}j A 10.0.8.$ +$GENERATE 23-24 k.${1,2,d} A 10.0.9.${-1,2,d} +""" + +example_generate_output = """@ 3600 IN SOA foo bar 1 2 3 4 5 +@ 3600 IN NS ns +a.9 5 IN A 10.0.0.9 +a.10 5 IN A 10.0.0.10 +a.11 5 IN A 10.0.0.11 +a.12 5 IN A 10.0.0.12 +b.00080 5 IN A 10.0.1.80 +b.00253 5 IN A 10.0.1.253 +c.00120 5 IN A 10.0.2.80 +c.00375 5 IN A 10.0.2.253 +d.00050 5 IN A 10.0.3.80 +d.000fd 5 IN A 10.0.3.253 +e.00050 5 IN A 10.0.4.80 +e.000FD 5 IN A 10.0.4.253 +f.0.5.0 5 IN A 10.0.5.80 +f.d.f.0 5 IN A 10.0.5.253 +g.0.5.0 5 IN A 10.0.6.80 +g.D.F.0 5 IN A 10.0.6.253 +i.A.D.j 5 IN A 10.0.8.218 +k.24 5 IN A 10.0.9.22 +k.25 5 IN A 10.0.9.23 +""" + something_quite_similar = """@ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 @@ -431,6 +468,15 @@ class ZoneTestCase(unittest.TestCase): f.write("\n") self.assertEqual(f.getvalue(), example_text_output) + def testGenerate(self): + z = dns.zone.from_text(example_generate, "example.", relativize=True) + f = StringIO() + names = list(z.nodes.keys()) + for n in names: + f.write(z[n].to_text(n)) + f.write("\n") + self.assertEqual(f.getvalue(), example_generate_output) + def testTorture1(self): # # Read a zone containing all our supported RR types, and