1 # Copyright (C) 2001-2007, 2009, 2010 Nominum, Inc.
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.
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.
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"""
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.
42 @param data: the binary string
44 @param chunksize: the chunk size. Default is L{dns.rdata._hex_chunksize}
49 chunksize = _hex_chunksize
50 hex = data.encode('hex_codec')
56 chunks.append(hex[i : i + chunksize])
58 hex = ' '.join(chunks)
61 _base64_chunksize = 32
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.
67 @param data: the binary string
69 @param chunksize: the chunk size. Default is
70 L{dns.rdata._base64_chunksize}
75 chunksize = _base64_chunksize
76 b64 = data.encode('base64_codec')
77 b64 = b64.replace('\n', '')
83 chunks.append(b64[i : i + chunksize])
85 b64 = ' '.join(chunks)
93 def _escapify(qstring):
94 """Escape the characters in a quoted string which need it.
96 @param qstring: the string
98 @returns: the escaped string
106 elif ord(c) >= 0x20 and ord(c) < 0x7F:
109 text += '\\%03d' % ord(c)
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.
116 @param what: a string of octets representing a bitmap.
121 for i in xrange(len(what) - 1, -1, -1):
122 if what[i] != '\x00':
124 return ''.join(what[0 : i + 1])
127 """Base class for all DNS rdata types.
130 __slots__ = ['rdclass', 'rdtype']
132 def __init__(self, rdclass, rdtype):
133 """Initialize an rdata.
134 @param rdclass: The rdata class
136 @param rdtype: The rdata type
140 self.rdclass = rdclass
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).
152 return dns.rdatatype.NONE
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.
161 return self.covers() << 16 | self.rdtype
163 def to_text(self, origin=None, relativize=True, **kw):
164 """Convert an rdata to text format.
167 raise NotImplementedError
169 def to_wire(self, file, compress = None, origin = None):
170 """Convert an rdata to wire format.
174 raise NotImplementedError
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)
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
189 dns.rdata.from_text(self.rdclass, self.rdtype, self.to_text())
192 covers = self.covers()
193 if covers == dns.rdatatype.NONE:
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: ' + \
202 return self.to_text()
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.
210 raise NotImplementedError
212 def __eq__(self, other):
213 if not isinstance(other, Rdata):
215 if self.rdclass != other.rdclass or \
216 self.rdtype != other.rdtype:
218 return self._cmp(other) == 0
220 def __ne__(self, other):
221 if not isinstance(other, Rdata):
223 if self.rdclass != other.rdclass or \
224 self.rdtype != other.rdtype:
226 return self._cmp(other) != 0
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
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
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
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
257 return hash(self.to_digestable(dns.name.root))
259 def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
260 """Build an rdata object from text format.
262 @param rdclass: The rdata class
264 @param rdtype: The rdata type
266 @param tok: The tokenizer
267 @type tok: dns.tokenizer.Tokenizer
268 @param origin: The origin to use for relative names
269 @type origin: dns.name.Name
270 @param relativize: should names be relativized?
271 @type relativize: bool
272 @rtype: dns.rdata.Rdata instance
275 raise NotImplementedError
277 from_text = classmethod(from_text)
279 def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
280 """Build an rdata object from wire format
282 @param rdclass: The rdata class
284 @param rdtype: The rdata type
286 @param wire: The wire-format message
288 @param current: The offet in wire of the beginning of the rdata.
290 @param rdlen: The length of the wire-format rdata
292 @param origin: The origin to use for relative names
293 @type origin: dns.name.Name
294 @rtype: dns.rdata.Rdata instance
297 raise NotImplementedError
299 from_wire = classmethod(from_wire)
301 def choose_relativity(self, origin = None, relativize = True):
302 """Convert any domain names in the rdata to the specified
309 class GenericRdata(Rdata):
310 """Generate Rdata Class
312 This class is used for rdata types for which we have no better
313 implementation. It implements the DNS "unknown RRs" scheme.
318 def __init__(self, rdclass, rdtype, data):
319 super(GenericRdata, self).__init__(rdclass, rdtype)
322 def to_text(self, origin=None, relativize=True, **kw):
323 return r'\# %d ' % len(self.data) + _hexify(self.data)
325 def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
327 if not token.is_identifier() or token.value != '\#':
328 raise dns.exception.SyntaxError(r'generic rdata does not start with \#')
329 length = tok.get_int()
333 if token.is_eol_or_eof():
335 chunks.append(token.value)
336 hex = ''.join(chunks)
337 data = hex.decode('hex_codec')
338 if len(data) != length:
339 raise dns.exception.SyntaxError('generic rdata hex data has wrong length')
340 return cls(rdclass, rdtype, data)
342 from_text = classmethod(from_text)
344 def to_wire(self, file, compress = None, origin = None):
345 file.write(self.data)
347 def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
348 return cls(rdclass, rdtype, wire[current : current + rdlen])
350 from_wire = classmethod(from_wire)
352 def _cmp(self, other):
353 return cmp(self.data, other.data)
356 _module_prefix = 'dns.rdtypes'
358 def get_rdata_class(rdclass, rdtype):
360 def import_module(name):
361 mod = __import__(name)
362 components = name.split('.')
363 for comp in components[1:]:
364 mod = getattr(mod, comp)
367 mod = _rdata_modules.get((rdclass, rdtype))
368 rdclass_text = dns.rdataclass.to_text(rdclass)
369 rdtype_text = dns.rdatatype.to_text(rdtype)
370 rdtype_text = rdtype_text.replace('-', '_')
372 mod = _rdata_modules.get((dns.rdatatype.ANY, rdtype))
375 mod = import_module('.'.join([_module_prefix,
376 rdclass_text, rdtype_text]))
377 _rdata_modules[(rdclass, rdtype)] = mod
380 mod = import_module('.'.join([_module_prefix,
381 'ANY', rdtype_text]))
382 _rdata_modules[(dns.rdataclass.ANY, rdtype)] = mod
386 cls = getattr(mod, rdtype_text)
391 def from_text(rdclass, rdtype, tok, origin = None, relativize = True):
392 """Build an rdata object from text format.
394 This function attempts to dynamically load a class which
395 implements the specified rdata class and type. If there is no
396 class-and-type-specific implementation, the GenericRdata class
399 Once a class is chosen, its from_text() class method is called
400 with the parameters to this function.
402 @param rdclass: The rdata class
404 @param rdtype: The rdata type
406 @param tok: The tokenizer
407 @type tok: dns.tokenizer.Tokenizer
408 @param origin: The origin to use for relative names
409 @type origin: dns.name.Name
410 @param relativize: Should names be relativized?
411 @type relativize: bool
412 @rtype: dns.rdata.Rdata instance"""
414 if isinstance(tok, str):
415 tok = dns.tokenizer.Tokenizer(tok)
416 cls = get_rdata_class(rdclass, rdtype)
417 if cls != GenericRdata:
418 # peek at first token
421 if token.is_identifier() and \
422 token.value == r'\#':
424 # Known type using the generic syntax. Extract the
425 # wire form from the generic syntax, and then run
428 rdata = GenericRdata.from_text(rdclass, rdtype, tok, origin,
430 return from_wire(rdclass, rdtype, rdata.data, 0, len(rdata.data),
432 return cls.from_text(rdclass, rdtype, tok, origin, relativize)
434 def from_wire(rdclass, rdtype, wire, current, rdlen, origin = None):
435 """Build an rdata object from wire format
437 This function attempts to dynamically load a class which
438 implements the specified rdata class and type. If there is no
439 class-and-type-specific implementation, the GenericRdata class
442 Once a class is chosen, its from_wire() class method is called
443 with the parameters to this function.
445 @param rdclass: The rdata class
447 @param rdtype: The rdata type
449 @param wire: The wire-format message
451 @param current: The offet in wire of the beginning of the rdata.
453 @param rdlen: The length of the wire-format rdata
455 @param origin: The origin to use for relative names
456 @type origin: dns.name.Name
457 @rtype: dns.rdata.Rdata instance"""
459 cls = get_rdata_class(rdclass, rdtype)
460 return cls.from_wire(rdclass, rdtype, wire, current, rdlen, origin)