Package dns :: Module rdata |
[hide private]
[frames] | no frames]

# Source Code for Module dns.rdata

      1  # Copyright (C) 2001-2007, 2009-2011 Nominum, Inc.
      2  #
      3  # Permission to use, copy, modify, and distribute this software and its
      4  # documentation for any purpose with or without fee is hereby granted,
      5  # provided that the above copyright notice and this permission notice
      6  # appear in all copies.
      7  #
      8  # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
      9  # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
     11  # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
     14  # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15
     16  """DNS rdata.
     17
     18  @var _rdata_modules: A dictionary mapping a (rdclass, rdtype) tuple to
     19  the module which implements that type.
     20  @type _rdata_modules: dict
     21  @var _module_prefix: The prefix to use when forming modules names.  The
     22  default is 'dns.rdtypes'.  Changing this value will break the library.
     23  @type _module_prefix: string
     24  @var _hex_chunk: At most this many octets that will be represented in each
     25  chunk of hexstring that _hexify() produces before whitespace occurs.
     26  @type _hex_chunk: int"""
     27
     28  from io import BytesIO
     29  import base64
     30  import binascii
     31
     32  import dns.exception
     33  import dns.name
     34  import dns.rdataclass
     35  import dns.rdatatype
     36  import dns.tokenizer
     37  import dns.wiredata
     38  from ._compat import xrange, string_types, text_type
     39
     40  _hex_chunksize = 32
     41
     42
     43 -def _hexify(data, chunksize=_hex_chunksize):
     44      """Convert a binary string into its hex encoding, broken up into chunks
     45      of I{chunksize} characters separated by a space.
     46
     47      @param data: the binary string
     48      @type data: string
     49      @param chunksize: the chunk size.  Default is L{dns.rdata._hex_chunksize}
     50      @rtype: string
     51      """
     52
     53      line = binascii.hexlify(data)
     54      return b' '.join([line[i:i + chunksize]
     55                        for i
     56                        in range(0, len(line), chunksize)]).decode()
     57
     58  _base64_chunksize = 32
     59
     60
     61 -def _base64ify(data, chunksize=_base64_chunksize):
     62      """Convert a binary string into its base64 encoding, broken up into chunks
     63      of I{chunksize} characters separated by a space.
     64
     65      @param data: the binary string
     66      @type data: string
     67      @param chunksize: the chunk size.  Default is
     68      L{dns.rdata._base64_chunksize}
     69      @rtype: string
     70      """
     71
     72      line = base64.b64encode(data)
     73      return b' '.join([line[i:i + chunksize]
     74                        for i
     75                        in range(0, len(line), chunksize)]).decode()
     76
     77  __escaped = bytearray(b'"\\')
     78
     79 -def _escapify(qstring):
     80      """Escape the characters in a quoted string which need it.
     81
     82      @param qstring: the string
     83      @type qstring: string
     84      @returns: the escaped string
     85      @rtype: string
     86      """
     87
     88      if isinstance(qstring, text_type):
     89          qstring = qstring.encode()
     90      if not isinstance(qstring, bytearray):
     91          qstring = bytearray(qstring)
     92
     93      text = ''
     94      for c in qstring:
     95          if c in __escaped:
     96              text += '\\' + chr(c)
     97          elif c >= 0x20 and c < 0x7F:
     98              text += chr(c)
     99          else:
    100              text += '\\%03d' % c
    101      return text
    102
    103
    104 -def _truncate_bitmap(what):
    105      """Determine the index of greatest byte that isn't all zeros, and
    106      return the bitmap that contains all the bytes less than that index.
    107
    108      @param what: a string of octets representing a bitmap.
    109      @type what: string
    110      @rtype: string
    111      """
    112
    113      for i in xrange(len(what) - 1, -1, -1):
    114          if what[i] != 0:
    115              return what[0: i + 1]
    116      return what[0:1]
    117
    118
    119 -class Rdata(object):
    120
    121      """Base class for all DNS rdata types.
    122      """
    123
    124      __slots__ = ['rdclass', 'rdtype']
    125
    126 -    def __init__(self, rdclass, rdtype):
    127          """Initialize an rdata.
    128          @param rdclass: The rdata class
    129          @type rdclass: int
    130          @param rdtype: The rdata type
    131          @type rdtype: int
    132          """
    133
    134          self.rdclass = rdclass
    135          self.rdtype = rdtype
    136
    137 -    def covers(self):
    138          """DNS SIG/RRSIG rdatas apply to a specific type; this type is
    139          returned by the covers() function.  If the rdata type is not
    140          SIG or RRSIG, dns.rdatatype.NONE is returned.  This is useful when
    141          creating rdatasets, allowing the rdataset to contain only RRSIGs
    142          of a particular type, e.g. RRSIG(NS).
    143          @rtype: int
    144          """
    145
    146          return dns.rdatatype.NONE
    147
    148 -    def extended_rdatatype(self):
    149          """Return a 32-bit type value, the least significant 16 bits of
    150          which are the ordinary DNS type, and the upper 16 bits of which are
    151          the "covered" type, if any.
    152          @rtype: int
    153          """
    154
    155          return self.covers() << 16 | self.rdtype
    156
    157 -    def to_text(self, origin=None, relativize=True, **kw):
    158          """Convert an rdata to text format.
    159          @rtype: string
    160          """
    161          raise NotImplementedError
    162
    163 -    def to_wire(self, file, compress=None, origin=None):
    164          """Convert an rdata to wire format.
    165          @rtype: string
    166          """
    167
    168          raise NotImplementedError
    169
    170 -    def to_digestable(self, origin=None):
    171          """Convert rdata to a format suitable for digesting in hashes.  This
    172          is also the DNSSEC canonical form."""
    173          f = BytesIO()
    174          self.to_wire(f, None, origin)
    175          return f.getvalue()
    176
    177 -    def validate(self):
    178          """Check that the current contents of the rdata's fields are
    179          valid.  If you change an rdata by assigning to its fields,
    180          it is a good idea to call validate() when you are done making
    181          changes.
    182          """
    183          dns.rdata.from_text(self.rdclass, self.rdtype, self.to_text())
    184
    185 -    def __repr__(self):
    186          covers = self.covers()
    187          if covers == dns.rdatatype.NONE:
    188              ctext = ''
    189          else:
    190              ctext = '(' + dns.rdatatype.to_text(covers) + ')'
    191          return '<DNS ' + dns.rdataclass.to_text(self.rdclass) + ' ' + \
    192                 dns.rdatatype.to_text(self.rdtype) + ctext + ' rdata: ' + \
    193                 str(self) + '>'
    194
    195 -    def __str__(self):
    196          return self.to_text()
    197
    198 -    def _cmp(self, other):
    199          """Compare an rdata with another rdata of the same rdtype and
    200          rdclass.  Return < 0 if self < other in the DNSSEC ordering,
    201          0 if self == other, and > 0 if self > other.
    202          """
    203          our = self.to_digestable(dns.name.root)
    204          their = other.to_digestable(dns.name.root)
    205          if our == their:
    206              return 0
    207          if our > their:
    208              return 1
    209
    210          return -1
    211
    212 -    def __eq__(self, other):
    213          if not isinstance(other, Rdata):
    214              return False
    215          if self.rdclass != other.rdclass or self.rdtype != other.rdtype:
    216              return False
    217          return self._cmp(other) == 0
    218
    219 -    def __ne__(self, other):
    220          if not isinstance(other, Rdata):
    221              return True
    222          if self.rdclass != other.rdclass or self.rdtype != other.rdtype:
    223              return True
    224          return self._cmp(other) != 0
    225
    226 -    def __lt__(self, other):
    227          if not isinstance(other, Rdata) or \
    228                  self.rdclass != other.rdclass or self.rdtype != other.rdtype:
    229
    230              return NotImplemented
    231          return self._cmp(other) < 0
    232
    233 -    def __le__(self, other):
    234          if not isinstance(other, Rdata) or \
    235                  self.rdclass != other.rdclass or self.rdtype != other.rdtype:
    236              return NotImplemented
    237          return self._cmp(other) <= 0
    238
    239 -    def __ge__(self, other):
    240          if not isinstance(other, Rdata) or \
    241                  self.rdclass != other.rdclass or self.rdtype != other.rdtype:
    242              return NotImplemented
    243          return self._cmp(other) >= 0
    244
    245 -    def __gt__(self, other):
    246          if not isinstance(other, Rdata) or \
    247                  self.rdclass != other.rdclass or self.rdtype != other.rdtype:
    248              return NotImplemented
    249          return self._cmp(other) > 0
    250
    251 -    def __hash__(self):
    252          return hash(self.to_digestable(dns.name.root))
    253
    254      @classmethod
    255 -    def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
    256          """Build an rdata object from text format.
    257
    258          @param rdclass: The rdata class
    259          @type rdclass: int
    260          @param rdtype: The rdata type
    261          @type rdtype: int
    262          @param tok: The tokenizer
    263          @type tok: dns.tokenizer.Tokenizer
    264          @param origin: The origin to use for relative names
    265          @type origin: dns.name.Name
    266          @param relativize: should names be relativized?
    267          @type relativize: bool
    268          @rtype: dns.rdata.Rdata instance
    269          """
    270
    271          raise NotImplementedError
    272
    273      @classmethod
    274 -    def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
    275          """Build an rdata object from wire format
    276
    277          @param rdclass: The rdata class
    278          @type rdclass: int
    279          @param rdtype: The rdata type
    280          @type rdtype: int
    281          @param wire: The wire-format message
    282          @type wire: string
    283          @param current: The offset in wire of the beginning of the rdata.
    284          @type current: int
    285          @param rdlen: The length of the wire-format rdata
    286          @type rdlen: int
    287          @param origin: The origin to use for relative names
    288          @type origin: dns.name.Name
    289          @rtype: dns.rdata.Rdata instance
    290          """
    291
    292          raise NotImplementedError
    293
    294 -    def choose_relativity(self, origin=None, relativize=True):
    295          """Convert any domain names in the rdata to the specified
    296          relativization.
    297          """
    298
    299          pass
    300
    301
    302 -class GenericRdata(Rdata):
    303
    304      """Generate Rdata Class
    305
    306      This class is used for rdata types for which we have no better
    307      implementation.  It implements the DNS "unknown RRs" scheme.
    308      """
    309
    310      __slots__ = ['data']
    311
    312 -    def __init__(self, rdclass, rdtype, data):
    313          super(GenericRdata, self).__init__(rdclass, rdtype)
    314          self.data = data
    315
    316 -    def to_text(self, origin=None, relativize=True, **kw):
    317          return r'\# %d ' % len(self.data) + _hexify(self.data)
    318
    319      @classmethod
    320 -    def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
    321          token = tok.get()
    322          if not token.is_identifier() or token.value != '\#':
    323              raise dns.exception.SyntaxError(
    324                  r'generic rdata does not start with \#')
    325          length = tok.get_int()
    326          chunks = []
    327          while 1:
    328              token = tok.get()
    329              if token.is_eol_or_eof():
    330                  break
    331              chunks.append(token.value.encode())
    332          hex = b''.join(chunks)
    333          data = binascii.unhexlify(hex)
    334          if len(data) != length:
    335              raise dns.exception.SyntaxError(
    336                  'generic rdata hex data has wrong length')
    337          return cls(rdclass, rdtype, data)
    338
    339 -    def to_wire(self, file, compress=None, origin=None):
    340          file.write(self.data)
    341
    342      @classmethod
    343 -    def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
    344          return cls(rdclass, rdtype, wire[current: current + rdlen])
    345
    346  _rdata_modules = {}
    347  _module_prefix = 'dns.rdtypes'
    348
    349
    350 -def get_rdata_class(rdclass, rdtype):
    351
    352      def import_module(name):
    353          mod = __import__(name)
    354          components = name.split('.')
    355          for comp in components[1:]:
    356              mod = getattr(mod, comp)
    357          return mod
    358
    359      mod = _rdata_modules.get((rdclass, rdtype))
    360      rdclass_text = dns.rdataclass.to_text(rdclass)
    361      rdtype_text = dns.rdatatype.to_text(rdtype)
    362      rdtype_text = rdtype_text.replace('-', '_')
    363      if not mod:
    364          mod = _rdata_modules.get((dns.rdatatype.ANY, rdtype))
    365          if not mod:
    366              try:
    367                  mod = import_module('.'.join([_module_prefix,
    368                                                rdclass_text, rdtype_text]))
    369                  _rdata_modules[(rdclass, rdtype)] = mod
    370              except ImportError:
    371                  try:
    372                      mod = import_module('.'.join([_module_prefix,
    373                                                    'ANY', rdtype_text]))
    374                      _rdata_modules[(dns.rdataclass.ANY, rdtype)] = mod
    375                  except ImportError:
    376                      mod = None
    377      if mod:
    378          cls = getattr(mod, rdtype_text)
    379      else:
    380          cls = GenericRdata
    381      return cls
    382
    383
    384 -def from_text(rdclass, rdtype, tok, origin=None, relativize=True):
    385      """Build an rdata object from text format.
    386
    387      This function attempts to dynamically load a class which
    388      implements the specified rdata class and type.  If there is no
    389      class-and-type-specific implementation, the GenericRdata class
    390      is used.
    391
    392      Once a class is chosen, its from_text() class method is called
    393      with the parameters to this function.
    394
    395      If I{tok} is a string, then a tokenizer is created and the string
    396      is used as its input.
    397
    398      @param rdclass: The rdata class
    399      @type rdclass: int
    400      @param rdtype: The rdata type
    401      @type rdtype: int
    402      @param tok: The tokenizer or input text
    403      @type tok: dns.tokenizer.Tokenizer or string
    404      @param origin: The origin to use for relative names
    405      @type origin: dns.name.Name
    406      @param relativize: Should names be relativized?
    407      @type relativize: bool
    408      @rtype: dns.rdata.Rdata instance"""
    409
    410      if isinstance(tok, string_types):
    411          tok = dns.tokenizer.Tokenizer(tok)
    412      cls = get_rdata_class(rdclass, rdtype)
    413      if cls != GenericRdata:
    414          # peek at first token
    415          token = tok.get()
    416          tok.unget(token)
    417          if token.is_identifier() and \
    418             token.value == r'\#':
    419              #
    420              # Known type using the generic syntax.  Extract the
    421              # wire form from the generic syntax, and then run
    422              # from_wire on it.
    423              #
    424              rdata = GenericRdata.from_text(rdclass, rdtype, tok, origin,
    425                                             relativize)
    426              return from_wire(rdclass, rdtype, rdata.data, 0, len(rdata.data),
    427                               origin)
    428      return cls.from_text(rdclass, rdtype, tok, origin, relativize)
    429
    430
    431 -def from_wire(rdclass, rdtype, wire, current, rdlen, origin=None):
    432      """Build an rdata object from wire format
    433
    434      This function attempts to dynamically load a class which
    435      implements the specified rdata class and type.  If there is no
    436      class-and-type-specific implementation, the GenericRdata class
    437      is used.
    438
    439      Once a class is chosen, its from_wire() class method is called
    440      with the parameters to this function.
    441
    442      @param rdclass: The rdata class
    443      @type rdclass: int
    444      @param rdtype: The rdata type
    445      @type rdtype: int
    446      @param wire: The wire-format message
    447      @type wire: string
    448      @param current: The offset in wire of the beginning of the rdata.
    449      @type current: int
    450      @param rdlen: The length of the wire-format rdata
    451      @type rdlen: int
    452      @param origin: The origin to use for relative names
    453      @type origin: dns.name.Name
    454      @rtype: dns.rdata.Rdata instance"""
    455
    456      wire = dns.wiredata.maybe_wrap(wire)
    457      cls = get_rdata_class(rdclass, rdtype)
    458      return cls.from_wire(rdclass, rdtype, wire, current, rdlen, origin)
    459