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"""
37 def _hexify(data, chunksize=None):
38 """Convert a binary string into its hex encoding, broken up into chunks
39 of I{chunksize} characters separated by a space.
41 @param data: the binary string
43 @param chunksize: the chunk size. Default is L{dns.rdata._hex_chunksize}
48 chunksize = _hex_chunksize
49 hex = data.encode('hex_codec')
55 chunks.append(hex[i : i + chunksize])
57 hex = ' '.join(chunks)
60 _base64_chunksize = 32
62 def _base64ify(data, chunksize=None):
63 """Convert a binary string into its base64 encoding, broken up into chunks
64 of I{chunksize} characters separated by a space.
66 @param data: the binary string
68 @param chunksize: the chunk size. Default is
69 L{dns.rdata._base64_chunksize}
74 chunksize = _base64_chunksize
75 b64 = data.encode('base64_codec')
76 b64 = b64.replace('\n', '')
82 chunks.append(b64[i : i + chunksize])
84 b64 = ' '.join(chunks)
92 def _escapify(qstring):
93 """Escape the characters in a quoted string which need it.
95 @param qstring: the string
97 @returns: the escaped string
105 elif ord(c) >= 0x20 and ord(c) < 0x7F:
108 text += '\\%03d' % ord(c)
111 def _truncate_bitmap(what):
112 """Determine the index of greatest byte that isn't all zeros, and
113 return the bitmap that contains all the bytes less than that index.
115 @param what: a string of octets representing a bitmap.
120 for i in xrange(len(what) - 1, -1, -1):
121 if what[i] != '\x00':
123 return ''.join(what[0 : i + 1])
126 """Base class for all DNS rdata types.
129 __slots__ = ['rdclass', 'rdtype']
131 def __init__(self, rdclass, rdtype):
132 """Initialize an rdata.
133 @param rdclass: The rdata class
135 @param rdtype: The rdata type
139 self.rdclass = rdclass
143 """DNS SIG/RRSIG rdatas apply to a specific type; this type is
144 returned by the covers() function. If the rdata type is not
145 SIG or RRSIG, dns.rdatatype.NONE is returned. This is useful when
146 creating rdatasets, allowing the rdataset to contain only RRSIGs
147 of a particular type, e.g. RRSIG(NS).
151 return dns.rdatatype.NONE
153 def extended_rdatatype(self):
154 """Return a 32-bit type value, the least significant 16 bits of
155 which are the ordinary DNS type, and the upper 16 bits of which are
156 the "covered" type, if any.
160 return self.covers() << 16 | self.rdtype
162 def to_text(self, origin=None, relativize=True, **kw):
163 """Convert an rdata to text format.
166 raise NotImplementedError
168 def to_wire(self, file, compress = None, origin = None):
169 """Convert an rdata to wire format.
173 raise NotImplementedError
175 def to_digestable(self, origin = None):
176 """Convert rdata to a format suitable for digesting in hashes. This
177 is also the DNSSEC canonical form."""
178 f = cStringIO.StringIO()
179 self.to_wire(f, None, origin)
183 """Check that the current contents of the rdata's fields are
184 valid. If you change an rdata by assigning to its fields,
185 it is a good idea to call validate() when you are done making
188 dns.rdata.from_text(self.rdclass, self.rdtype, self.to_text())
191 covers = self.covers()
192 if covers == dns.rdatatype.NONE:
195 ctext = '(' + dns.rdatatype.to_text(covers) + ')'
196 return '<DNS ' + dns.rdataclass.to_text(self.rdclass) + ' ' + \
197 dns.rdatatype.to_text(self.rdtype) + ctext + ' rdata: ' + \
201 return self.to_text()
203 def _cmp(self, other):
204 """Compare an rdata with another rdata of the same rdtype and
205 rdclass. Return < 0 if self < other in the DNSSEC ordering,
206 0 if self == other, and > 0 if self > other.
209 raise NotImplementedError
211 def __eq__(self, other):
212 if not isinstance(other, Rdata):
214 if self.rdclass != other.rdclass or \
215 self.rdtype != other.rdtype:
217 return self._cmp(other) == 0
219 def __ne__(self, other):
220 if not isinstance(other, Rdata):
222 if self.rdclass != other.rdclass or \
223 self.rdtype != other.rdtype:
225 return self._cmp(other) != 0
227 def __lt__(self, other):
228 if not isinstance(other, Rdata) or \
229 self.rdclass != other.rdclass or \
230 self.rdtype != other.rdtype:
231 return NotImplemented
232 return self._cmp(other) < 0
234 def __le__(self, other):
235 if not isinstance(other, Rdata) or \
236 self.rdclass != other.rdclass or \
237 self.rdtype != other.rdtype:
238 return NotImplemented
239 return self._cmp(other) <= 0
241 def __ge__(self, other):
242 if not isinstance(other, Rdata) or \
243 self.rdclass != other.rdclass or \
244 self.rdtype != other.rdtype:
245 return NotImplemented
246 return self._cmp(other) >= 0
248 def __gt__(self, other):
249 if not isinstance(other, Rdata) or \
250 self.rdclass != other.rdclass or \
251 self.rdtype != other.rdtype:
252 return NotImplemented
253 return self._cmp(other) > 0
255 def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
256 """Build an rdata object from text format.
258 @param rdclass: The rdata class
260 @param rdtype: The rdata type
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
271 raise NotImplementedError
273 from_text = classmethod(from_text)
275 def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
276 """Build an rdata object from wire format
278 @param rdclass: The rdata class
280 @param rdtype: The rdata type
282 @param wire: The wire-format message
284 @param current: The offet in wire of the beginning of the rdata.
286 @param rdlen: The length of the wire-format rdata
288 @param origin: The origin to use for relative names
289 @type origin: dns.name.Name
290 @rtype: dns.rdata.Rdata instance
293 raise NotImplementedError
295 from_wire = classmethod(from_wire)
297 def choose_relativity(self, origin = None, relativize = True):
298 """Convert any domain names in the rdata to the specified
305 class GenericRdata(Rdata):
306 """Generate Rdata Class
308 This class is used for rdata types for which we have no better
309 implementation. It implements the DNS "unknown RRs" scheme.
314 def __init__(self, rdclass, rdtype, data):
315 super(GenericRdata, self).__init__(rdclass, rdtype)
318 def to_text(self, origin=None, relativize=True, **kw):
319 return r'\# %d ' % len(self.data) + _hexify(self.data)
321 def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
323 if not token.is_identifier() or token.value != '\#':
324 raise dns.exception.SyntaxError(r'generic rdata does not start with \#')
325 length = tok.get_int()
329 if token.is_eol_or_eof():
331 chunks.append(token.value)
332 hex = ''.join(chunks)
333 data = hex.decode('hex_codec')
334 if len(data) != length:
335 raise dns.exception.SyntaxError('generic rdata hex data has wrong length')
336 return cls(rdclass, rdtype, data)
338 from_text = classmethod(from_text)
340 def to_wire(self, file, compress = None, origin = None):
341 file.write(self.data)
343 def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
344 return cls(rdclass, rdtype, wire[current : current + rdlen])
346 from_wire = classmethod(from_wire)
348 def _cmp(self, other):
349 return cmp(self.data, other.data)
352 _module_prefix = 'dns.rdtypes'
354 def get_rdata_class(rdclass, rdtype):
356 def import_module(name):
357 mod = __import__(name)
358 components = name.split('.')
359 for comp in components[1:]:
360 mod = getattr(mod, comp)
363 mod = _rdata_modules.get((rdclass, rdtype))
364 rdclass_text = dns.rdataclass.to_text(rdclass)
365 rdtype_text = dns.rdatatype.to_text(rdtype)
366 rdtype_text = rdtype_text.replace('-', '_')
368 mod = _rdata_modules.get((dns.rdatatype.ANY, rdtype))
371 mod = import_module('.'.join([_module_prefix,
372 rdclass_text, rdtype_text]))
373 _rdata_modules[(rdclass, rdtype)] = mod
376 mod = import_module('.'.join([_module_prefix,
377 'ANY', rdtype_text]))
378 _rdata_modules[(dns.rdataclass.ANY, rdtype)] = mod
382 cls = getattr(mod, rdtype_text)
387 def from_text(rdclass, rdtype, tok, origin = None, relativize = True):
388 """Build an rdata object from text format.
390 This function attempts to dynamically load a class which
391 implements the specified rdata class and type. If there is no
392 class-and-type-specific implementation, the GenericRdata class
395 Once a class is chosen, its from_text() class method is called
396 with the parameters to this function.
398 @param rdclass: The rdata class
400 @param rdtype: The rdata type
402 @param tok: The tokenizer
403 @type tok: dns.tokenizer.Tokenizer
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"""
410 if isinstance(tok, str):
411 tok = dns.tokenizer.Tokenizer(tok)
412 cls = get_rdata_class(rdclass, rdtype)
413 if cls != GenericRdata:
414 # peek at first token
417 if token.is_identifier() and \
418 token.value == r'\#':
420 # Known type using the generic syntax. Extract the
421 # wire form from the generic syntax, and then run
424 rdata = GenericRdata.from_text(rdclass, rdtype, tok, origin,
426 return from_wire(rdclass, rdtype, rdata.data, 0, len(rdata.data),
428 return cls.from_text(rdclass, rdtype, tok, origin, relativize)
430 def from_wire(rdclass, rdtype, wire, current, rdlen, origin = None):
431 """Build an rdata object from wire format
433 This function attempts to dynamically load a class which
434 implements the specified rdata class and type. If there is no
435 class-and-type-specific implementation, the GenericRdata class
438 Once a class is chosen, its from_wire() class method is called
439 with the parameters to this function.
441 @param rdclass: The rdata class
443 @param rdtype: The rdata type
445 @param wire: The wire-format message
447 @param current: The offet in wire of the beginning of the rdata.
449 @param rdlen: The length of the wire-format rdata
451 @param origin: The origin to use for relative names
452 @type origin: dns.name.Name
453 @rtype: dns.rdata.Rdata instance"""
455 cls = get_rdata_class(rdclass, rdtype)
456 return cls.from_wire(rdclass, rdtype, wire, current, rdlen, origin)