1 # Copyright (C) 2001-2007, 2009-2011 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"""
39 def _hexify(data
, chunksize
=None):
40 """Convert a binary string into its hex encoding, broken up into chunks
41 of I{chunksize} characters separated by a space.
43 @param data: the binary string
45 @param chunksize: the chunk size. Default is L{dns.rdata._hex_chunksize}
50 chunksize
= _hex_chunksize
51 hex = data
.encode('hex_codec')
57 chunks
.append(hex[i
: i
+ chunksize
])
59 hex = ' '.join(chunks
)
62 _base64_chunksize
= 32
64 def _base64ify(data
, chunksize
=None):
65 """Convert a binary string into its base64 encoding, broken up into chunks
66 of I{chunksize} characters separated by a space.
68 @param data: the binary string
70 @param chunksize: the chunk size. Default is
71 L{dns.rdata._base64_chunksize}
76 chunksize
= _base64_chunksize
77 b64
= data
.encode('base64_codec')
78 b64
= b64
.replace('\n', '')
84 chunks
.append(b64
[i
: i
+ chunksize
])
86 b64
= ' '.join(chunks
)
94 def _escapify(qstring
):
95 """Escape the characters in a quoted string which need it.
97 @param qstring: the string
99 @returns: the escaped string
107 elif ord(c
) >= 0x20 and ord(c
) < 0x7F:
110 text
+= '\\%03d' % ord(c
)
113 def _truncate_bitmap(what
):
114 """Determine the index of greatest byte that isn't all zeros, and
115 return the bitmap that contains all the bytes less than that index.
117 @param what: a string of octets representing a bitmap.
122 for i
in xrange(len(what
) - 1, -1, -1):
123 if what
[i
] != '\x00':
125 return ''.join(what
[0 : i
+ 1])
128 """Base class for all DNS rdata types.
131 __slots__
= ['rdclass', 'rdtype']
133 def __init__(self
, rdclass
, rdtype
):
134 """Initialize an rdata.
135 @param rdclass: The rdata class
137 @param rdtype: The rdata type
141 self
.rdclass
= rdclass
145 """DNS SIG/RRSIG rdatas apply to a specific type; this type is
146 returned by the covers() function. If the rdata type is not
147 SIG or RRSIG, dns.rdatatype.NONE is returned. This is useful when
148 creating rdatasets, allowing the rdataset to contain only RRSIGs
149 of a particular type, e.g. RRSIG(NS).
153 return dns
.rdatatype
.NONE
155 def extended_rdatatype(self
):
156 """Return a 32-bit type value, the least significant 16 bits of
157 which are the ordinary DNS type, and the upper 16 bits of which are
158 the "covered" type, if any.
162 return self
.covers() << 16 | self
.rdtype
164 def to_text(self
, origin
=None, relativize
=True, **kw
):
165 """Convert an rdata to text format.
168 raise NotImplementedError
170 def to_wire(self
, file, compress
= None, origin
= None):
171 """Convert an rdata to wire format.
175 raise NotImplementedError
177 def to_digestable(self
, origin
= None):
178 """Convert rdata to a format suitable for digesting in hashes. This
179 is also the DNSSEC canonical form."""
180 f
= cStringIO
.StringIO()
181 self
.to_wire(f
, None, origin
)
185 """Check that the current contents of the rdata's fields are
186 valid. If you change an rdata by assigning to its fields,
187 it is a good idea to call validate() when you are done making
190 dns
.rdata
.from_text(self
.rdclass
, self
.rdtype
, self
.to_text())
193 covers
= self
.covers()
194 if covers
== dns
.rdatatype
.NONE
:
197 ctext
= '(' + dns
.rdatatype
.to_text(covers
) + ')'
198 return '<DNS ' + dns
.rdataclass
.to_text(self
.rdclass
) + ' ' + \
199 dns
.rdatatype
.to_text(self
.rdtype
) + ctext
+ ' rdata: ' + \
203 return self
.to_text()
205 def _cmp(self
, other
):
206 """Compare an rdata with another rdata of the same rdtype and
207 rdclass. Return < 0 if self < other in the DNSSEC ordering,
208 0 if self == other, and > 0 if self > other.
211 raise NotImplementedError
213 def __eq__(self
, other
):
214 if not isinstance(other
, Rdata
):
216 if self
.rdclass
!= other
.rdclass
or \
217 self
.rdtype
!= other
.rdtype
:
219 return self
._cmp
(other
) == 0
221 def __ne__(self
, other
):
222 if not isinstance(other
, Rdata
):
224 if self
.rdclass
!= other
.rdclass
or \
225 self
.rdtype
!= other
.rdtype
:
227 return self
._cmp
(other
) != 0
229 def __lt__(self
, other
):
230 if not isinstance(other
, Rdata
) or \
231 self
.rdclass
!= other
.rdclass
or \
232 self
.rdtype
!= other
.rdtype
:
233 return NotImplemented
234 return self
._cmp
(other
) < 0
236 def __le__(self
, other
):
237 if not isinstance(other
, Rdata
) or \
238 self
.rdclass
!= other
.rdclass
or \
239 self
.rdtype
!= other
.rdtype
:
240 return NotImplemented
241 return self
._cmp
(other
) <= 0
243 def __ge__(self
, other
):
244 if not isinstance(other
, Rdata
) or \
245 self
.rdclass
!= other
.rdclass
or \
246 self
.rdtype
!= other
.rdtype
:
247 return NotImplemented
248 return self
._cmp
(other
) >= 0
250 def __gt__(self
, other
):
251 if not isinstance(other
, Rdata
) or \
252 self
.rdclass
!= other
.rdclass
or \
253 self
.rdtype
!= other
.rdtype
:
254 return NotImplemented
255 return self
._cmp
(other
) > 0
258 return hash(self
.to_digestable(dns
.name
.root
))
260 def _wire_cmp(self
, other
):
261 # A number of types compare rdata in wire form, so we provide
262 # the method here instead of duplicating it.
264 # We specifiy an arbitrary origin of '.' when doing the
265 # comparison, since the rdata may have relative names and we
266 # can't convert a relative name to wire without an origin.
267 b1
= cStringIO
.StringIO()
268 self
.to_wire(b1
, None, dns
.name
.root
)
269 b2
= cStringIO
.StringIO()
270 other
.to_wire(b2
, None, dns
.name
.root
)
271 return cmp(b1
.getvalue(), b2
.getvalue())
273 def from_text(cls
, rdclass
, rdtype
, tok
, origin
= None, relativize
= True):
274 """Build an rdata object from text format.
276 @param rdclass: The rdata class
278 @param rdtype: The rdata type
280 @param tok: The tokenizer
281 @type tok: dns.tokenizer.Tokenizer
282 @param origin: The origin to use for relative names
283 @type origin: dns.name.Name
284 @param relativize: should names be relativized?
285 @type relativize: bool
286 @rtype: dns.rdata.Rdata instance
289 raise NotImplementedError
291 from_text
= classmethod(from_text
)
293 def from_wire(cls
, rdclass
, rdtype
, wire
, current
, rdlen
, origin
= None):
294 """Build an rdata object from wire format
296 @param rdclass: The rdata class
298 @param rdtype: The rdata type
300 @param wire: The wire-format message
302 @param current: The offet in wire of the beginning of the rdata.
304 @param rdlen: The length of the wire-format rdata
306 @param origin: The origin to use for relative names
307 @type origin: dns.name.Name
308 @rtype: dns.rdata.Rdata instance
311 raise NotImplementedError
313 from_wire
= classmethod(from_wire
)
315 def choose_relativity(self
, origin
= None, relativize
= True):
316 """Convert any domain names in the rdata to the specified
323 class GenericRdata(Rdata
):
324 """Generate Rdata Class
326 This class is used for rdata types for which we have no better
327 implementation. It implements the DNS "unknown RRs" scheme.
332 def __init__(self
, rdclass
, rdtype
, data
):
333 super(GenericRdata
, self
).__init
__(rdclass
, rdtype
)
336 def to_text(self
, origin
=None, relativize
=True, **kw
):
337 return r
'\# %d ' % len(self
.data
) + _hexify(self
.data
)
339 def from_text(cls
, rdclass
, rdtype
, tok
, origin
= None, relativize
= True):
341 if not token
.is_identifier() or token
.value
!= '\#':
342 raise dns
.exception
.SyntaxError(r
'generic rdata does not start with \#')
343 length
= tok
.get_int()
347 if token
.is_eol_or_eof():
349 chunks
.append(token
.value
)
350 hex = ''.join(chunks
)
351 data
= hex.decode('hex_codec')
352 if len(data
) != length
:
353 raise dns
.exception
.SyntaxError('generic rdata hex data has wrong length')
354 return cls(rdclass
, rdtype
, data
)
356 from_text
= classmethod(from_text
)
358 def to_wire(self
, file, compress
= None, origin
= None):
359 file.write(self
.data
)
361 def from_wire(cls
, rdclass
, rdtype
, wire
, current
, rdlen
, origin
= None):
362 return cls(rdclass
, rdtype
, wire
[current
: current
+ rdlen
])
364 from_wire
= classmethod(from_wire
)
366 def _cmp(self
, other
):
367 return cmp(self
.data
, other
.data
)
370 _module_prefix
= 'dns.rdtypes'
372 def get_rdata_class(rdclass
, rdtype
):
374 def import_module(name
):
375 mod
= __import__(name
)
376 components
= name
.split('.')
377 for comp
in components
[1:]:
378 mod
= getattr(mod
, comp
)
381 mod
= _rdata_modules
.get((rdclass
, rdtype
))
382 rdclass_text
= dns
.rdataclass
.to_text(rdclass
)
383 rdtype_text
= dns
.rdatatype
.to_text(rdtype
)
384 rdtype_text
= rdtype_text
.replace('-', '_')
386 mod
= _rdata_modules
.get((dns
.rdatatype
.ANY
, rdtype
))
389 mod
= import_module('.'.join([_module_prefix
,
390 rdclass_text
, rdtype_text
]))
391 _rdata_modules
[(rdclass
, rdtype
)] = mod
394 mod
= import_module('.'.join([_module_prefix
,
395 'ANY', rdtype_text
]))
396 _rdata_modules
[(dns
.rdataclass
.ANY
, rdtype
)] = mod
400 cls
= getattr(mod
, rdtype_text
)
405 def from_text(rdclass
, rdtype
, tok
, origin
= None, relativize
= True):
406 """Build an rdata object from text format.
408 This function attempts to dynamically load a class which
409 implements the specified rdata class and type. If there is no
410 class-and-type-specific implementation, the GenericRdata class
413 Once a class is chosen, its from_text() class method is called
414 with the parameters to this function.
416 If I{tok} is a string, then a tokenizer is created and the string
417 is used as its input.
419 @param rdclass: The rdata class
421 @param rdtype: The rdata type
423 @param tok: The tokenizer or input text
424 @type tok: dns.tokenizer.Tokenizer or string
425 @param origin: The origin to use for relative names
426 @type origin: dns.name.Name
427 @param relativize: Should names be relativized?
428 @type relativize: bool
429 @rtype: dns.rdata.Rdata instance"""
431 if isinstance(tok
, str):
432 tok
= dns
.tokenizer
.Tokenizer(tok
)
433 cls
= get_rdata_class(rdclass
, rdtype
)
434 if cls
!= GenericRdata
:
435 # peek at first token
438 if token
.is_identifier() and \
439 token
.value
== r
'\#':
441 # Known type using the generic syntax. Extract the
442 # wire form from the generic syntax, and then run
445 rdata
= GenericRdata
.from_text(rdclass
, rdtype
, tok
, origin
,
447 return from_wire(rdclass
, rdtype
, rdata
.data
, 0, len(rdata
.data
),
449 return cls
.from_text(rdclass
, rdtype
, tok
, origin
, relativize
)
451 def from_wire(rdclass
, rdtype
, wire
, current
, rdlen
, origin
= None):
452 """Build an rdata object from wire format
454 This function attempts to dynamically load a class which
455 implements the specified rdata class and type. If there is no
456 class-and-type-specific implementation, the GenericRdata class
459 Once a class is chosen, its from_wire() class method is called
460 with the parameters to this function.
462 @param rdclass: The rdata class
464 @param rdtype: The rdata type
466 @param wire: The wire-format message
468 @param current: The offet in wire of the beginning of the rdata.
470 @param rdlen: The length of the wire-format rdata
472 @param origin: The origin to use for relative names
473 @type origin: dns.name.Name
474 @rtype: dns.rdata.Rdata instance"""
476 wire
= dns
.wiredata
.maybe_wrap(wire
)
477 cls
= get_rdata_class(rdclass
, rdtype
)
478 return cls
.from_wire(rdclass
, rdtype
, wire
, current
, rdlen
, origin
)