X-Git-Url: https://git.ralfj.de/zonemaker.git/blobdiff_plain/4b221937f5e8ca0fd6bc8d3331529f3502615b38..53f825fef45e8d09bd04ab44fd4a5e9e6e0c7626:/zone.py?ds=sidebyside diff --git a/zone.py b/zone.py index 1e1251a..a74ee18 100644 --- a/zone.py +++ b/zone.py @@ -33,7 +33,7 @@ week = 7*day REGEX_label = r'[a-zA-Z90-9]([a-zA-Z90-9-]{0,61}[a-zA-Z90-9])?' # max. 63 characters; must not start or end with hyphen REGEX_ipv4 = r'^\d{1,3}(\.\d{1,3}){3}$' -REGEX_ipv6 = r'^[a-fA-F0-9]{1,4}(:[a-fA-F0-9]{1,4}){7}$' +REGEX_ipv6 = r'^[a-fA-F0-9]{1,4}(::?[a-fA-F0-9]{1,4}){1,7}$' def check_label(label: str) -> str: label = str(label) @@ -101,6 +101,20 @@ def column_widths(datas: 'Sequence', widths: 'Sequence[int]'): # last data point return result+str(datas[-1]) +def concatenate(root, path): + if path == '' or root == '': + raise Exception("Empty domain name is not valid") + if path == '@': + return root + if root == '@' or path.endswith('.'): + return path + return path+"."+root + +def escape_TXT(text): + for c in ('\\', '\"'): + text = text.replace(c, '\\'+c) + return text + ## Enums class Protocol: @@ -131,15 +145,7 @@ class RR: return self def relativize(self, root): - def _relativize(path): - if path == '' or root == '': - raise Exception("Empty domain name is not valid") - if path == '@': - return root - if root == '@' or path.endswith('.'): - return path - return path+"."+root - return self.mapPath(_relativize) + return self.mapPath(lambda path: concatenate(root, path)) def mapTTL(self, f): '''Run the current TTL and the recordType through f.''' @@ -181,13 +187,17 @@ class TXT: for c in ('\n', '\r', '\t'): if c in text: raise Exception("TXT record {0} contains invalid character") - # escape text - for c in ('\\', '\"'): - text = text.replace(c, '\\'+c) self._text = text def generate_rr(self): - return RR('@', 'TXT', '"{0}"'.format(self._text)) + text = escape_TXT(self._text) + # split into chunks of max. 255 characters; be careful not to split right after a backslash + chunks = re.findall(r'.{0,254}[^\\]', text) + assert sum(len(c) for c in chunks) == len (text) + chunksep = '"\n' + ' '*20 + '"' + chunked = '( "' + chunksep.join(chunks) + '" )' + # generate the chunks + return RR('@', 'TXT', chunked) class DKIM(TXT): # helper class to treat DKIM more antively @@ -295,12 +305,12 @@ def CName(name: str) -> Name: return Name(CNAME(name)) -def Delegation(name: str) -> Name: - return Name(NS(name)) +def Delegation(*names) -> Name: + return Name(list(map(NS, names))) -def SecureDelegation(name: str, tag: int, alg: int, digest: int, key: str) -> Name: - return Name(NS(name), DS(tag, alg, digest, key)) +def SecureDelegation(tag: int, alg: int, digest: int, key: str, *names) -> Name: + return Name(DS(tag, alg, digest, key), list(map(NS, names))) class Zone: @@ -346,6 +356,20 @@ class Zone: # be done return cur_serial + @staticmethod + def generate_rrs_from_dict(root, domains): + for name in sorted(domains.keys(), key=lambda s: s.split('.')): + if name.endswith('.'): + raise Exception("You are trying to add a record outside of your zone. This is not supported. Use '@' for the zone root.") + domain = domains[name] + name = concatenate(root, name) + if isinstance(domain, dict): + for rr in Zone.generate_rrs_from_dict(name, domain): + yield rr + else: + for rr in domain.generate_rrs(): + yield rr.relativize(name) + def generate_rrs(self) -> 'Iterator': # SOA record serial = self.inc_serial() @@ -360,11 +384,8 @@ class Zone: for name in self._NS: yield NS(name).generate_rr() # all the rest - for name in sorted(self._domains.keys(), key=lambda s: list(reversed(s.split('.')))): - if name.endswith('.'): - raise Exception("You are trying to add a record outside of your zone. This is not supported. Use '@' for the zone root.") - for rr in self._domains[name].generate_rrs(): - yield rr.relativize(name) + for rr in Zone.generate_rrs_from_dict('@', self._domains): + yield rr def write(self) -> None: print(";; {} zone file, generated by zonemaker on {}".format(self._name, datetime.datetime.now()))