]> git.ipfire.org Git - thirdparty/dnspython.git/commitdiff
Add support for more bases in $GENERATE 828/head
authorcorubba <corubba@gmx.de>
Fri, 5 Aug 2022 12:09:52 +0000 (14:09 +0200)
committercorubba <corubba@gmx.de>
Fri, 5 Aug 2022 12:09:52 +0000 (14:09 +0200)
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).

dns/zonefile.py
tests/test_zone.py

index 4e0013a67b23b1bf47e708300aaa3615d669607b..68c6314827db4c8cd673de36ead9128ad358d53b 100644 (file)
@@ -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)
index de7ec0153aae77d1eda534678f47a53b6f1b7f8d..2d10274f7c3b6efaba30fe1872b6cefcbbc32c5a 100644 (file)
@@ -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