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

# Source Code for Module dns.rdata

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