Source code for pytomo.dns.rdata

# Copyright (C) 2001-2007, 2009, 2010 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted,
# provided that the above copyright notice and this permission notice
# appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

"""DNS rdata.

@var _rdata_modules: A dictionary mapping a (rdclass, rdtype) tuple to
the module which implements that type.
@type _rdata_modules: dict
@var _module_prefix: The prefix to use when forming modules names.  The
default is 'dns.rdtypes'.  Changing this value will break the library.
Changed for absolute imports
@type _module_prefix: string
@var _hex_chunk: At most this many octets that will be represented in each
chunk of hexstring that _hexify() produces before whitespace occurs.
@type _hex_chunk: int"""

from __future__ import absolute_import

import cStringIO

from . import exception as dns_exception
from . import rdataclass as dns_rdataclass
from . import rdatatype as dns_rdatatype
from . import tokenizer as dns_tokenizer

if False:
    # to trick the module finder of pyinstaller
    # but do not compile in python2.5
    from .rdtypes.IN import *
    from .rdtypes.ANY import *


_hex_chunksize = 32

def _hexify(data, chunksize=None):
    """Convert a binary string into its hex encoding, broken up into chunks
    of I{chunksize} characters separated by a space.

    @param data: the binary string
    @type data: string
    @param chunksize: the chunk size.  Default is L{dns_rdata._hex_chunksize}
    @rtype: string
    """

    if chunksize is None:
        chunksize = _hex_chunksize
    hex = data.encode('hex_codec')
    l = len(hex)
    if l > chunksize:
        chunks = []
        i = 0
        while i < l:
            chunks.append(hex[i : i + chunksize])
            i += chunksize
        hex = ' '.join(chunks)
    return hex

_base64_chunksize = 32

def _base64ify(data, chunksize=None):
    """Convert a binary string into its base64 encoding, broken up into chunks
    of I{chunksize} characters separated by a space.

    @param data: the binary string
    @type data: string
    @param chunksize: the chunk size.  Default is
    L{dns_rdata._base64_chunksize}
    @rtype: string
    """

    if chunksize is None:
        chunksize = _base64_chunksize
    b64 = data.encode('base64_codec')
    b64 = b64.replace('\n', '')
    l = len(b64)
    if l > chunksize:
        chunks = []
        i = 0
        while i < l:
            chunks.append(b64[i : i + chunksize])
            i += chunksize
        b64 = ' '.join(chunks)
    return b64

__escaped = {
    '"' : True,
    '\\' : True,
    }

def _escapify(qstring):
    """Escape the characters in a quoted string which need it.

    @param qstring: the string
    @type qstring: string
    @returns: the escaped string
    @rtype: string
    """

    text = ''
    for c in qstring:
        if c in __escaped:
            text += '\\' + c
        elif ord(c) >= 0x20 and ord(c) < 0x7F:
            text += c
        else:
            text += '\\%03d' % ord(c)
    return text

def _truncate_bitmap(what):
    """Determine the index of greatest byte that isn't all zeros, and
    return the bitmap that contains all the bytes less than that index.

    @param what: a string of octets representing a bitmap.
    @type what: string
    @rtype: string
    """

    for i in xrange(len(what) - 1, -1, -1):
        if what[i] != '\x00':
            break
    return ''.join(what[0 : i + 1])

[docs]class Rdata(object): """Base class for all DNS rdata types. """ __slots__ = ['rdclass', 'rdtype'] def __init__(self, rdclass, rdtype): """Initialize an rdata. @param rdclass: The rdata class @type rdclass: int @param rdtype: The rdata type @type rdtype: int """ self.rdclass = rdclass self.rdtype = rdtype
[docs] def covers(self): """DNS SIG/RRSIG rdatas apply to a specific type; this type is returned by the covers() function. If the rdata type is not SIG or RRSIG, dns_rdatatype.NONE is returned. This is useful when creating rdatasets, allowing the rdataset to contain only RRSIGs of a particular type, e.g. RRSIG(NS). @rtype: int """ return dns_rdatatype.NONE
[docs] def extended_rdatatype(self): """Return a 32-bit type value, the least significant 16 bits of which are the ordinary DNS type, and the upper 16 bits of which are the "covered" type, if any. @rtype: int """ return self.covers() << 16 | self.rdtype
[docs] def to_text(self, origin=None, relativize=True, **kw): """Convert an rdata to text format. @rtype: string """ raise NotImplementedError
[docs] def to_wire(self, file, compress = None, origin = None): """Convert an rdata to wire format. @rtype: string """ raise NotImplementedError
[docs] def to_digestable(self, origin = None): """Convert rdata to a format suitable for digesting in hashes. This is also the DNSSEC canonical form.""" f = cStringIO.StringIO() self.to_wire(f, None, origin) return f.getvalue()
[docs] def validate(self): """Check that the current contents of the rdata's fields are valid. If you change an rdata by assigning to its fields, it is a good idea to call validate() when you are done making changes. """ from_text(self.rdclass, self.rdtype, self.to_text())
def __repr__(self): covers = self.covers() if covers == dns_rdatatype.NONE: ctext = '' else: ctext = '(' + dns_rdatatype.to_text(covers) + ')' return '<DNS ' + dns_rdataclass.to_text(self.rdclass) + ' ' + \ dns_rdatatype.to_text(self.rdtype) + ctext + ' rdata: ' + \ str(self) + '>' def __str__(self): return self.to_text() def _cmp(self, other): """Compare an rdata with another rdata of the same rdtype and rdclass. Return < 0 if self < other in the DNSSEC ordering, 0 if self == other, and > 0 if self > other. """ raise NotImplementedError def __eq__(self, other): if not isinstance(other, Rdata): return False if self.rdclass != other.rdclass or \ self.rdtype != other.rdtype: return False return self._cmp(other) == 0 def __ne__(self, other): if not isinstance(other, Rdata): return True if self.rdclass != other.rdclass or \ self.rdtype != other.rdtype: return True return self._cmp(other) != 0 def __lt__(self, other): if not isinstance(other, Rdata) or \ self.rdclass != other.rdclass or \ self.rdtype != other.rdtype: return NotImplemented return self._cmp(other) < 0 def __le__(self, other): if not isinstance(other, Rdata) or \ self.rdclass != other.rdclass or \ self.rdtype != other.rdtype: return NotImplemented return self._cmp(other) <= 0 def __ge__(self, other): if not isinstance(other, Rdata) or \ self.rdclass != other.rdclass or \ self.rdtype != other.rdtype: return NotImplemented return self._cmp(other) >= 0 def __gt__(self, other): if not isinstance(other, Rdata) or \ self.rdclass != other.rdclass or \ self.rdtype != other.rdtype: return NotImplemented return self._cmp(other) > 0
[docs] def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): """Build an rdata object from text format. @param rdclass: The rdata class @type rdclass: int @param rdtype: The rdata type @type rdtype: int @param tok: The tokenizer @type tok: dns_tokenizer.Tokenizer @param origin: The origin to use for relative names @type origin: dns_name.Name @param relativize: should names be relativized? @type relativize: bool @rtype: dns_rdata.Rdata instance """ raise NotImplementedError
from_text = classmethod(from_text)
[docs] def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): """Build an rdata object from wire format @param rdclass: The rdata class @type rdclass: int @param rdtype: The rdata type @type rdtype: int @param wire: The wire-format message @type wire: string @param current: The offet in wire of the beginning of the rdata. @type current: int @param rdlen: The length of the wire-format rdata @type rdlen: int @param origin: The origin to use for relative names @type origin: dns_name.Name @rtype: dns_rdata.Rdata instance """ raise NotImplementedError
from_wire = classmethod(from_wire)
[docs] def choose_relativity(self, origin = None, relativize = True): """Convert any domain names in the rdata to the specified relativization. """ pass
[docs]class GenericRdata(Rdata): """Generate Rdata Class This class is used for rdata types for which we have no better implementation. It implements the DNS "unknown RRs" scheme. """ __slots__ = ['data'] def __init__(self, rdclass, rdtype, data): super(GenericRdata, self).__init__(rdclass, rdtype) self.data = data
[docs] def to_text(self, origin=None, relativize=True, **kw): return r'\# %d ' % len(self.data) + _hexify(self.data)
[docs] def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): token = tok.get() if not token.is_identifier() or token.value != '\#': raise dns_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 dns_exception.SyntaxError('generic rdata hex data has wrong length') return cls(rdclass, rdtype, data)
from_text = classmethod(from_text)
[docs] def to_wire(self, file, compress = None, origin = None): file.write(self.data)
[docs] def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): return cls(rdclass, rdtype, wire[current : current + rdlen])
from_wire = classmethod(from_wire) def _cmp(self, other): return cmp(self.data, other.data)
_rdata_modules = {} _module_prefix = 'rdtypes'
[docs]def get_rdata_class(rdclass, rdtype): def import_module(name): # for absolute imports need more arguments: to test mod = __import__(name, globals=globals(), level=1) components = name.split('.') for comp in components[1:]: mod = getattr(mod, comp) return mod mod = _rdata_modules.get((rdclass, rdtype)) rdclass_text = dns_rdataclass.to_text(rdclass) rdtype_text = dns_rdatatype.to_text(rdtype) rdtype_text = rdtype_text.replace('-', '_') if not mod: mod = _rdata_modules.get((dns_rdatatype.ANY, rdtype)) if not mod: try: mod = import_module('.'.join([_module_prefix, rdclass_text, rdtype_text])) _rdata_modules[(rdclass, rdtype)] = mod except ImportError: try: mod = import_module('.'.join([_module_prefix, 'ANY', rdtype_text])) _rdata_modules[(dns_rdataclass.ANY, rdtype)] = mod except ImportError: mod = None if mod: cls = getattr(mod, rdtype_text) else: cls = GenericRdata return cls
[docs]def from_text(rdclass, rdtype, tok, origin = None, relativize = True): """Build an rdata object from text format. This function attempts to dynamically load a class which implements the specified rdata class and type. If there is no class-and-type-specific implementation, the GenericRdata class is used. Once a class is chosen, its from_text() class method is called with the parameters to this function. @param rdclass: The rdata class @type rdclass: int @param rdtype: The rdata type @type rdtype: int @param tok: The tokenizer @type tok: dns_tokenizer.Tokenizer @param origin: The origin to use for relative names @type origin: dns_name.Name @param relativize: Should names be relativized? @type relativize: bool @rtype: dns_rdata.Rdata instance""" if isinstance(tok, str): tok = dns_tokenizer.Tokenizer(tok) cls = get_rdata_class(rdclass, rdtype) if cls != GenericRdata: # peek at first token token = tok.get() tok.unget(token) if token.is_identifier() and \ token.value == r'\#': # # Known type using the generic syntax. Extract the # wire form from the generic syntax, and then run # from_wire on it. # rdata = GenericRdata.from_text(rdclass, rdtype, tok, origin, relativize) return from_wire(rdclass, rdtype, rdata.data, 0, len(rdata.data), origin) return cls.from_text(rdclass, rdtype, tok, origin, relativize)
[docs]def from_wire(rdclass, rdtype, wire, current, rdlen, origin = None): """Build an rdata object from wire format This function attempts to dynamically load a class which implements the specified rdata class and type. If there is no class-and-type-specific implementation, the GenericRdata class is used. Once a class is chosen, its from_wire() class method is called with the parameters to this function. @param rdclass: The rdata class @type rdclass: int @param rdtype: The rdata type @type rdtype: int @param wire: The wire-format message @type wire: string @param current: The offet in wire of the beginning of the rdata. @type current: int @param rdlen: The length of the wire-format rdata @type rdlen: int @param origin: The origin to use for relative names @type origin: dns_name.Name @rtype: dns_rdata.Rdata instance""" cls = get_rdata_class(rdclass, rdtype) return cls.from_wire(rdclass, rdtype, wire, current, rdlen, origin)