%PDF- %PDF-
Direktori : /lib/python3/dist-packages/dnslib/ |
Current File : //lib/python3/dist-packages/dnslib/label.py |
# -*- coding: utf-8 -*- """ DNSLabel/DNSBuffer - DNS label handling & encoding/decoding """ from __future__ import print_function import fnmatch,re,string from dnslib.bit import get_bits,set_bits from dnslib.buffer import Buffer, BufferError # In theory valid label characters should be letters,digits,hyphen,underscore (LDH) # LDH = set(bytearray(b'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_')) # For compatibility we only escape non-printable characters LDH = set(range(33,127)) ESCAPE = re.compile(r'\\([0-9][0-9][0-9])') class DNSLabelError(Exception): pass class DNSLabel(object): """ Container for DNS label Supports IDNA encoding for unicode domain names >>> l1 = DNSLabel("aaa.bbb.ccc.") >>> l2 = DNSLabel([b"aaa",b"bbb",b"ccc"]) >>> l1 == l2 True >>> l3 = DNSLabel("AAA.BBB.CCC") >>> l1 == l3 True >>> l1 == 'AAA.BBB.CCC' True >>> x = { l1 : 1 } >>> x[l1] 1 >>> l1 <DNSLabel: 'aaa.bbb.ccc.'> >>> str(l1) 'aaa.bbb.ccc.' >>> l3 = l1.add("xxx.yyy") >>> l3 <DNSLabel: 'xxx.yyy.aaa.bbb.ccc.'> >>> l3.matchSuffix(l1) True >>> l3.matchSuffix("xxx.yyy.") False >>> l3.stripSuffix("bbb.ccc.") <DNSLabel: 'xxx.yyy.aaa.'> >>> l3.matchGlob("*.[abc]aa.BBB.ccc") True >>> l3.matchGlob("*.[abc]xx.bbb.ccc") False # Too hard to get unicode doctests to work on Python 3.2 # (works on 3.3) # >>> u1 = DNSLabel(u'\u2295.com') # >>> u1.__str__() == u'\u2295.com.' # True # >>> u1.label == ( b"xn--keh", b"com" ) # True """ def __init__(self,label): """ Create DNS label instance Label can be specified as: - a list/tuple of byte strings - a byte string (split into components separated by b'.') - a unicode string which will be encoded according to RFC3490/IDNA """ if type(label) == DNSLabel: self.label = label.label elif type(label) in (list,tuple): self.label = tuple(label) else: if not label or label in (b'.','.'): self.label = () elif type(label) is not bytes: if type('') != type(b''): # Py3 label = ESCAPE.sub(lambda m:chr(int(m[1])),label) self.label = tuple(label.encode("idna").\ rstrip(b".").split(b".")) else: if type('') == type(b''): # Py2 label = ESCAPE.sub(lambda m:chr(int(m.groups()[0])),label) self.label = tuple(label.rstrip(b".").split(b".")) def add(self,name): """ Prepend name to label """ new = DNSLabel(name) if self.label: new.label += self.label return new def matchGlob(self,pattern): if type(pattern) != DNSLabel: pattern = DNSLabel(pattern) return fnmatch.fnmatch(str(self).lower(),str(pattern).lower()) def matchSuffix(self,suffix): """ Return True if label suffix matches """ suffix = DNSLabel(suffix) return self.label[-len(suffix.label):] == suffix.label def stripSuffix(self,suffix): """ Strip suffix from label """ suffix = DNSLabel(suffix) if self.label[-len(suffix.label):] == suffix.label: return DNSLabel(self.label[:-len(suffix.label)]) else: return self def idna(self): return ".".join([ s.decode("idna") for s in self.label ]) + "." def _decode(self,s): if set(s).issubset(LDH): # All chars in LDH return s.decode() else: # Need to encode return "".join([(chr(c) if (c in LDH) else "\%03d" % c) for c in s]) def __str__(self): return ".".join([ self._decode(bytearray(s)) for s in self.label ]) + "." def __repr__(self): return "<DNSLabel: '%s'>" % str(self) def __hash__(self): return hash(tuple(map(lambda x:x.lower(),self.label))) def __ne__(self,other): return not self == other def __eq__(self,other): if type(other) != DNSLabel: return self.__eq__(DNSLabel(other)) else: return [ l.lower() for l in self.label ] == \ [ l.lower() for l in other.label ] def __len__(self): return len(b'.'.join(self.label)) class DNSBuffer(Buffer): """ Extends Buffer to provide DNS name encoding/decoding (with caching) # Needed for Python 2/3 doctest compatibility >>> def p(s): ... if not isinstance(s,str): ... return s.decode() ... return s >>> b = DNSBuffer() >>> b.encode_name(b'aaa.bbb.ccc.') >>> len(b) 13 >>> b.encode_name(b'aaa.bbb.ccc.') >>> len(b) 15 >>> b.encode_name(b'xxx.yyy.zzz') >>> len(b) 28 >>> b.encode_name(b'zzz.xxx.bbb.ccc.') >>> len(b) 38 >>> b.encode_name(b'aaa.xxx.bbb.ccc') >>> len(b) 44 >>> b.offset = 0 >>> print(b.decode_name()) aaa.bbb.ccc. >>> print(b.decode_name()) aaa.bbb.ccc. >>> print(b.decode_name()) xxx.yyy.zzz. >>> print(b.decode_name()) zzz.xxx.bbb.ccc. >>> print(b.decode_name()) aaa.xxx.bbb.ccc. >>> b = DNSBuffer() >>> b.encode_name([b'a.aa',b'b.bb',b'c.cc']) >>> b.offset = 0 >>> len(b.decode_name().label) 3 >>> b = DNSBuffer() >>> b.encode_name_nocompress(b'aaa.bbb.ccc.') >>> len(b) 13 >>> b.encode_name_nocompress(b'aaa.bbb.ccc.') >>> len(b) 26 >>> b.offset = 0 >>> print(b.decode_name()) aaa.bbb.ccc. >>> print(b.decode_name()) aaa.bbb.ccc. """ def __init__(self,data=b''): """ Add 'names' dict to cache stored labels """ super(DNSBuffer,self).__init__(data) self.names = {} def decode_name(self,last=-1): """ Decode label at current offset in buffer (following pointers to cached elements where necessary) """ label = [] done = False while not done: (length,) = self.unpack("!B") if get_bits(length,6,2) == 3: # Pointer self.offset -= 1 pointer = get_bits(self.unpack("!H")[0],0,14) save = self.offset if last == save: raise BufferError("Recursive pointer in DNSLabel [offset=%d,pointer=%d,length=%d]" % (self.offset,pointer,len(self.data))) if pointer < self.offset: self.offset = pointer else: # Pointer can't point forwards raise BufferError("Invalid pointer in DNSLabel [offset=%d,pointer=%d,length=%d]" % (self.offset,pointer,len(self.data))) label.extend(self.decode_name(save).label) self.offset = save done = True else: if length > 0: l = self.get(length) try: l.decode() except UnicodeDecodeError: raise BufferError("Invalid label <%s>" % l) label.append(l) else: done = True return DNSLabel(label) def encode_name(self,name): """ Encode label and store at end of buffer (compressing cached elements where needed) and store elements in 'names' dict """ if not isinstance(name,DNSLabel): name = DNSLabel(name) if len(name) > 253: raise DNSLabelError("Domain label too long: %r" % name) name = list(name.label) while name: if tuple(name) in self.names: # Cached - set pointer pointer = self.names[tuple(name)] pointer = set_bits(pointer,3,14,2) self.pack("!H",pointer) return else: self.names[tuple(name)] = self.offset element = name.pop(0) if len(element) > 63: raise DNSLabelError("Label component too long: %r" % element) self.pack("!B",len(element)) self.append(element) self.append(b'\x00') def encode_name_nocompress(self,name): """ Encode and store label with no compression (needed for RRSIG) """ if not isinstance(name,DNSLabel): name = DNSLabel(name) if len(name) > 253: raise DNSLabelError("Domain label too long: %r" % name) name = list(name.label) while name: element = name.pop(0) if len(element) > 63: raise DNSLabelError("Label component too long: %r" % element) self.pack("!B",len(element)) self.append(element) self.append(b'\x00') if __name__ == '__main__': import doctest,sys sys.exit(0 if doctest.testmod().failed == 0 else 1)