def _decode_size(what, desc): exponent = what & 0x0F if exponent > 9: raise exception.SyntaxError("bad %s exponent" % desc) base = (what & 0xF0) >> 4 if base > 9: raise exception.SyntaxError("bad %s base" % desc) return long(base) * pow(10, exponent)
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): address = tok.get_string() t = tok.get_eol() if address[0:2] != '0x': raise exception.SyntaxError('string does not start with 0x') address = address[2:].replace('.', '') if len(address) % 2 != 0: raise exception.SyntaxError('hexstring has odd length') address = address.decode('hex_codec') return cls(rdclass, rdtype, address)
def get_int(self): """Read the next token and interpret it as an integer. @raises exception.SyntaxError: @rtype: int """ token = self.get().unescape() if not token.is_identifier(): raise exception.SyntaxError('expecting an identifier') if not token.value.isdigit(): raise exception.SyntaxError('expecting an integer') return int(token.value)
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): strings = [] while 1: token = tok.get().unescape() if token.is_eol_or_eof(): break if not (token.is_quoted_string() or token.is_identifier()): raise exception.SyntaxError("expected a string") if len(token.value) > 255: raise exception.SyntaxError("string too long") strings.append(token.value) if len(strings) == 0: raise exception.UnexpectedEnd return cls(rdclass, rdtype, strings)
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): algorithm = tok.get_uint8() flags = tok.get_uint8() iterations = tok.get_uint16() salt = tok.get_string() if salt == '-': salt = '' else: salt = salt.decode('hex-codec') next = tok.get_string().upper().translate(b32_hex_to_normal) next = base64.b32decode(next) rdtypes = [] while 1: token = tok.get().unescape() if token.is_eol_or_eof(): break nrdtype = rdatatype.from_text(token.value) if nrdtype == 0: raise exception.SyntaxError("NSEC3 with bit 0") if nrdtype > 65535: raise exception.SyntaxError("NSEC3 with bit > 65535") rdtypes.append(nrdtype) rdtypes.sort() window = 0 octets = 0 prior_rdtype = 0 bitmap = ['\0'] * 32 windows = [] for nrdtype in rdtypes: if nrdtype == prior_rdtype: continue prior_rdtype = nrdtype new_window = nrdtype // 256 if new_window != window: if octets != 0: windows.append((window, ''.join(bitmap[0:octets]))) bitmap = ['\0'] * 32 window = new_window offset = nrdtype % 256 byte = offset // 8 bit = offset % 8 octets = byte + 1 bitmap[byte] = chr(ord(bitmap[byte]) | (0x80 >> bit)) if octets != 0: windows.append((window, ''.join(bitmap[0:octets]))) return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next, windows)
def get_uint32(self): """Read the next token and interpret it as a 32-bit unsigned integer. @raises exception.SyntaxError: @rtype: int """ token = self.get().unescape() if not token.is_identifier(): raise exception.SyntaxError('expecting an identifier') if not token.value.isdigit(): raise exception.SyntaxError('expecting an integer') value = long(token.value) if value < 0 or value > 4294967296L: raise exception.SyntaxError('%d is not an unsigned 32-bit integer' % value) return value
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): token = tok.get() if not token.is_identifier() or token.value != '\#': raise exception.SyntaxError( r'generic rdata does not start with \#') length = tok.get_int() chunks = [] while 1: token = tok.get() if token.is_eol_or_eof(): break chunks.append(token.value) hex = ''.join(chunks) data = hex.decode('hex_codec') if len(data) != length: raise exception.SyntaxError( 'generic rdata hex data has wrong length') return cls(rdclass, rdtype, data)
def get_name(self, origin=None): """Read the next token and interpret it as a DNS name. @raises exception.SyntaxError: @rtype: name.Name object""" token = self.get() if not token.is_identifier(): raise exception.SyntaxError('expecting an identifier') return name.from_text(token.value, origin)
def _exponent_of(what, desc): if what == 0: return 0 exp = None for i in xrange(len(_pows)): if what // _pows[i] == 0L: exp = i - 1 break if exp is None or exp < 0: raise exception.SyntaxError("%s value out of bounds" % desc) return exp
def get_identifier(self, origin=None): """Read the next token and raise an exception if it is not an identifier. @raises exception.SyntaxError: @rtype: string """ token = self.get().unescape() if not token.is_identifier(): raise exception.SyntaxError('expecting an identifier') return token.value
def get_string(self, origin=None): """Read the next token and interpret it as a string. @raises exception.SyntaxError: @rtype: string """ token = self.get().unescape() if not (token.is_identifier() or token.is_quoted_string()): raise exception.SyntaxError('expecting a string') return token.value
def get_eol(self): """Read the next token and raise an exception if it isn't EOL or EOF. @raises exception.SyntaxError: @rtype: string """ token = self.get() if not token.is_eol_or_eof(): raise exception.SyntaxError('expected EOL or EOF, got %d "%s"' % (token.ttype, token.value)) return token.value
def get_uint16(self): """Read the next token and interpret it as a 16-bit unsigned integer. @raises exception.SyntaxError: @rtype: int """ value = self.get_int() if value < 0 or value > 65535: raise exception.SyntaxError('%d is not an unsigned 16-bit integer' % value) return value
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): next = tok.get_name() next = next.choose_relativity(origin, relativize) rdtypes = [] while 1: token = tok.get().unescape() if token.is_eol_or_eof(): break nrdtype = rdatatype.from_text(token.value) if nrdtype == 0: raise exception.SyntaxError("NSEC with bit 0") if nrdtype > 65535: raise exception.SyntaxError("NSEC with bit > 65535") rdtypes.append(nrdtype) rdtypes.sort() window = 0 octets = 0 prior_rdtype = 0 bitmap = ['\0'] * 32 windows = [] for nrdtype in rdtypes: if nrdtype == prior_rdtype: continue prior_rdtype = nrdtype new_window = nrdtype // 256 if new_window != window: windows.append((window, ''.join(bitmap[0:octets]))) bitmap = ['\0'] * 32 window = new_window offset = nrdtype % 256 byte = offset // 8 bit = offset % 8 octets = byte + 1 bitmap[byte] = chr(ord(bitmap[byte]) | (0x80 >> bit)) windows.append((window, ''.join(bitmap[0:octets]))) return cls(rdclass, rdtype, next, windows)
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): algorithm = tok.get_uint8() hit = tok.get_string().decode('hex-codec') if len(hit) > 255: raise exception.SyntaxError("HIT too long") key = tok.get_string().decode('base64-codec') servers = [] while 1: token = tok.get() if token.is_eol_or_eof(): break server = name.from_text(token.value, origin) server.choose_relativity(origin, relativize) servers.append(server) return cls(rdclass, rdtype, hit, algorithm, key, servers)
def from_text(text): """Convert the text form of a range in a GENERATE statement to an integer. @param text: the textual range @type text: string @return: The start, stop and step values. @rtype: tuple """ # TODO, figure out the bounds on start, stop and step. import pdb step = 1 cur = '' state = 0 # state 0 1 2 3 4 # x - y / z for c in text: if c == '-' and state == 0: start = int(cur) cur = '' state = 2 elif c == '/': stop = int(cur) cur = '' state = 4 elif c.isdigit(): cur += c else: raise exception.SyntaxError("Could not parse %s" % (c)) if state in (1, 3): raise exception.SyntaxError if state == 2: stop = int(cur) if state == 4: step = int(cur) assert step >= 1 assert start >= 0 assert start <= stop # TODO, can start == stop? return (start, stop, step)
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): certificate_type = _ctype_from_text(tok.get_string()) key_tag = tok.get_uint16() algorithm = dnssec.algorithm_from_text(tok.get_string()) if algorithm < 0 or algorithm > 255: raise exception.SyntaxError("bad algorithm type") chunks = [] while 1: t = tok.get().unescape() if t.is_eol_or_eof(): break if not t.is_identifier(): raise exception.SyntaxError chunks.append(t.value) b64 = ''.join(chunks) certificate = b64.decode('base64_codec') return cls(rdclass, rdtype, certificate_type, key_tag, algorithm, certificate)
def to_e164(name, origin=public_enum_domain, want_plus_prefix=True): """Convert an ENUM domain name into an E.164 number. @param name: the ENUM domain name. @type name: name.Name object. @param origin: A domain containing the ENUM domain name. The name is relativized to this domain before being converted to text. @type origin: name.Name object or None @param want_plus_prefix: if True, add a '+' to the beginning of the returned number. @rtype: str """ if not origin is None: name = name.relativize(origin) dlabels = [d for d in name.labels if (d.isdigit() and len(d) == 1)] if len(dlabels) != len(name.labels): raise exception.SyntaxError('non-digit labels in ENUM domain name') dlabels.reverse() text = ''.join(dlabels) if want_plus_prefix: text = '+' + text return text
def _rr_line(self): """Process one line from a DNS master file.""" # Name if self.current_origin is None: raise UnknownOrigin token = self.tok.get(want_leading=True) if not token.is_whitespace(): self.last_name = name.from_text(token.value, self.current_origin) else: token = self.tok.get() if token.is_eol_or_eof(): # treat leading WS followed by EOL/EOF as if they were EOL/EOF. return self.tok.unget(token) name = self.last_name if not name.is_subdomain(self.zone.origin): self._eat_line() return if self.relativize: name = name.relativize(self.zone.origin) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError # TTL try: ttl = ttl.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except ttl.BadTTL: ttl = self.ttl # Class try: rdclass = rdataclass.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except exception.SyntaxError: raise exception.SyntaxError except: rdclass = self.zone.rdclass if rdclass != self.zone.rdclass: raise exception.SyntaxError("RR class is not zone's class") # Type try: rdtype = rdatatype.from_text(token.value) except: raise exception.SyntaxError("unknown rdatatype '%s'" % token.value) n = self.zone.nodes.get(name) if n is None: n = self.zone.node_factory() self.zone.nodes[name] = n try: rd = rdata.from_text(rdclass, rdtype, self.tok, self.current_origin, False) except exception.SyntaxError: # Catch and reraise. (ty, va) = sys.exc_info()[:2] raise va except: # All exceptions that occur in the processing of rdata # are treated as syntax errors. This is not strictly # correct, but it is correct almost all of the time. # We convert them to syntax errors so that we can emit # helpful filename:line info. (ty, va) = sys.exc_info()[:2] raise exception.SyntaxError("caught exception %s: %s" % (str(ty), str(va))) rd.choose_relativity(self.zone.origin, self.relativize) covers = rd.covers() rds = n.find_rdataset(rdclass, rdtype, covers, True) rds.add(rd, ttl)
def get_ttl(self): token = self.get().unescape() if not token.is_identifier(): raise exception.SyntaxError('expecting an identifier') return ttl.from_text(token.value)
def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): latitude = [0, 0, 0, 0] longitude = [0, 0, 0, 0] size = _default_size hprec = _default_hprec vprec = _default_vprec latitude[0] = tok.get_int() t = tok.get_string() if t.isdigit(): latitude[1] = int(t) t = tok.get_string() if '.' in t: (seconds, milliseconds) = t.split('.') if not seconds.isdigit(): raise exception.SyntaxError('bad latitude seconds value') latitude[2] = int(seconds) if latitude[2] >= 60: raise exception.SyntaxError('latitude seconds >= 60') l = len(milliseconds) if l == 0 or l > 3 or not milliseconds.isdigit(): raise exception.SyntaxError('bad latitude milliseconds value') if l == 1: m = 100 elif l == 2: m = 10 else: m = 1 latitude[3] = m * int(milliseconds) t = tok.get_string() elif t.isdigit(): latitude[2] = int(t) t = tok.get_string() if t == 'S': latitude[0] *= -1 elif t != 'N': raise exception.SyntaxError('bad latitude hemisphere value') longitude[0] = tok.get_int() t = tok.get_string() if t.isdigit(): longitude[1] = int(t) t = tok.get_string() if '.' in t: (seconds, milliseconds) = t.split('.') if not seconds.isdigit(): raise exception.SyntaxError('bad longitude seconds value') longitude[2] = int(seconds) if longitude[2] >= 60: raise exception.SyntaxError('longitude seconds >= 60') l = len(milliseconds) if l == 0 or l > 3 or not milliseconds.isdigit(): raise exception.SyntaxError('bad longitude milliseconds value') if l == 1: m = 100 elif l == 2: m = 10 else: m = 1 longitude[3] = m * int(milliseconds) t = tok.get_string() elif t.isdigit(): longitude[2] = int(t) t = tok.get_string() if t == 'W': longitude[0] *= -1 elif t != 'E': raise exception.SyntaxError('bad longitude hemisphere value') t = tok.get_string() if t[-1] == 'm': t = t[0 : -1] altitude = float(t) * 100.0 # m -> cm token = tok.get().unescape() if not token.is_eol_or_eof(): value = token.value if value[-1] == 'm': value = value[0 : -1] size = float(value) * 100.0 # m -> cm token = tok.get().unescape() if not token.is_eol_or_eof(): value = token.value if value[-1] == 'm': value = value[0 : -1] hprec = float(value) * 100.0 # m -> cm token = tok.get().unescape() if not token.is_eol_or_eof(): value = token.value if value[-1] == 'm': value = value[0 : -1] vprec = float(value) * 100.0 # m -> cm tok.get_eol() return cls(rdclass, rdtype, latitude, longitude, altitude, size, hprec, vprec)
def read(self): """Read a DNS master file and build a zone object. @raises zone.NoSOA: No SOA RR was found at the zone origin @raises zone.NoNS: No NS RRset was found at the zone origin """ try: while 1: token = self.tok.get(True, True) if token.is_eof(): if not self.current_file is None: self.current_file.close() if len(self.saved_state) > 0: (self.tok, self.current_origin, self.last_name, self.current_file, self.ttl) = self.saved_state.pop(-1) continue break elif token.is_eol(): continue elif token.is_comment(): self.tok.get_eol() continue elif token.value[0] == '$': u = token.value.upper() if u == '$TTL': token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError("bad $TTL") self.ttl = ttl.from_text(token.value) self.tok.get_eol() elif u == '$ORIGIN': self.current_origin = self.tok.get_name() self.tok.get_eol() if self.zone.origin is None: self.zone.origin = self.current_origin elif u == '$INCLUDE' and self.allow_include: token = self.tok.get() filename = token.value token = self.tok.get() if token.is_identifier(): new_origin = name.from_text(token.value, \ self.current_origin) self.tok.get_eol() elif not token.is_eol_or_eof(): raise exception.SyntaxError( "bad origin in $INCLUDE") else: new_origin = self.current_origin self.saved_state.append( (self.tok, self.current_origin, self.last_name, self.current_file, self.ttl)) self.current_file = file(filename, 'r') self.tok = tokenizer.Tokenizer(self.current_file, filename) self.current_origin = new_origin elif u == '$GENERATE': self._generate_line() else: raise exception.SyntaxError( "Unknown master file directive '" + u + "'") continue self.tok.unget(token) self._rr_line() except exception.SyntaxError, detail: (filename, line_number) = self.tok.where() if detail is None: detail = "syntax error" raise exception.SyntaxError("%s:%d: %s" % (filename, line_number, detail))
def _generate_line(self): # range lhs [ttl] [class] type rhs [ comment ] """Process one line containing the GENERATE statement from a DNS master file.""" if self.current_origin is None: raise UnknownOrigin token = self.tok.get() # Range (required) try: start, stop, step = grange.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except: raise exception.SyntaxError # lhs (required) try: lhs = token.value token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except: raise exception.SyntaxError # TTL try: ttl = ttl.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except ttl.BadTTL: ttl = self.ttl # Class try: rdclass = rdataclass.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except exception.SyntaxError: raise exception.SyntaxError except: rdclass = self.zone.rdclass if rdclass != self.zone.rdclass: raise exception.SyntaxError("RR class is not zone's class") # Type try: rdtype = rdatatype.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise exception.SyntaxError except: raise exception.SyntaxError("unknown rdatatype '%s'" % token.value) # lhs (required) try: rhs = token.value except: raise exception.SyntaxError 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) lzfindex = str(lindex).zfill(int(lwidth)) rzfindex = str(rindex).zfill(int(rwidth)) name = lhs.replace('$%s' % (lmod), lzfindex) rdata = rhs.replace('$%s' % (rmod), rzfindex) self.last_name = name.from_text(name, self.current_origin) name = self.last_name if not name.is_subdomain(self.zone.origin): self._eat_line() return if self.relativize: name = name.relativize(self.zone.origin) n = self.zone.nodes.get(name) if n is None: n = self.zone.node_factory() self.zone.nodes[name] = n try: rd = rdata.from_text(rdclass, rdtype, rdata, self.current_origin, False) except exception.SyntaxError: # Catch and reraise. (ty, va) = sys.exc_info()[:2] raise va except: # All exceptions that occur in the processing of rdata # are treated as syntax errors. This is not strictly # correct, but it is correct almost all of the time. # We convert them to syntax errors so that we can emit # helpful filename:line info. (ty, va) = sys.exc_info()[:2] raise exception.SyntaxError("caught exception %s: %s" % (str(ty), str(va))) rd.choose_relativity(self.zone.origin, self.relativize) covers = rd.covers() rds = n.find_rdataset(rdclass, rdtype, covers, True) rds.add(rd, ttl)
def get(self, want_leading = False, want_comment = False): """Get the next token. @param want_leading: If True, return a WHITESPACE token if the first character read is whitespace. The default is False. @type want_leading: bool @param want_comment: If True, return a COMMENT token if the first token read is a comment. The default is False. @type want_comment: bool @rtype: Token object @raises exception.UnexpectedEnd: input ended prematurely @raises exception.SyntaxError: input was badly formed """ if not self.ungotten_token is None: token = self.ungotten_token self.ungotten_token = None if token.is_whitespace(): if want_leading: return token elif token.is_comment(): if want_comment: return token else: return token skipped = self.skip_whitespace() if want_leading and skipped > 0: return Token(WHITESPACE, ' ') token = '' ttype = IDENTIFIER has_escape = False while True: c = self._get_char() if c == '' or c in self.delimiters: if c == '' and self.quoting: raise exception.UnexpectedEnd if token == '' and ttype != QUOTED_STRING: if c == '(': self.multiline += 1 self.skip_whitespace() continue elif c == ')': if not self.multiline > 0: raise exception.SyntaxError self.multiline -= 1 self.skip_whitespace() continue elif c == '"': if not self.quoting: self.quoting = True self.delimiters = _QUOTING_DELIMITERS ttype = QUOTED_STRING continue else: self.quoting = False self.delimiters = _DELIMITERS self.skip_whitespace() continue elif c == '\n': return Token(EOL, '\n') elif c == ';': while 1: c = self._get_char() if c == '\n' or c == '': break token += c if want_comment: self._unget_char(c) return Token(COMMENT, token) elif c == '': if self.multiline: raise exception.SyntaxError('unbalanced parentheses') return Token(EOF) elif self.multiline: self.skip_whitespace() token = '' continue else: return Token(EOL, '\n') else: # This code exists in case we ever want a # delimiter to be returned. It never produces # a token currently. token = c ttype = DELIMITER else: self._unget_char(c) break elif self.quoting: if c == '\\': c = self._get_char() if c == '': raise exception.UnexpectedEnd if c.isdigit(): c2 = self._get_char() if c2 == '': raise exception.UnexpectedEnd c3 = self._get_char() if c == '': raise exception.UnexpectedEnd if not (c2.isdigit() and c3.isdigit()): raise exception.SyntaxError c = chr(int(c) * 100 + int(c2) * 10 + int(c3)) elif c == '\n': raise exception.SyntaxError('newline in quoted string') elif c == '\\': # # It's an escape. Put it and the next character into # the token; it will be checked later for goodness. # token += c has_escape = True c = self._get_char() if c == '' or c == '\n': raise exception.UnexpectedEnd token += c if token == '' and ttype != QUOTED_STRING: if self.multiline: raise exception.SyntaxError('unbalanced parentheses') ttype = EOF return Token(ttype, token, has_escape)