1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 import dns.wiredata
36
37 _hex_chunksize = 32
38
40 """Convert a binary string into its hex encoding, broken up into chunks
41 of I{chunksize} characters separated by a space.
42
43 @param data: the binary string
44 @type data: string
45 @param chunksize: the chunk size. Default is L{dns.rdata._hex_chunksize}
46 @rtype: string
47 """
48
49 if chunksize is None:
50 chunksize = _hex_chunksize
51 hex = data.encode('hex_codec')
52 l = len(hex)
53 if l > chunksize:
54 chunks = []
55 i = 0
56 while i < l:
57 chunks.append(hex[i : i + chunksize])
58 i += chunksize
59 hex = ' '.join(chunks)
60 return hex
61
62 _base64_chunksize = 32
63
65 """Convert a binary string into its base64 encoding, broken up into chunks
66 of I{chunksize} characters separated by a space.
67
68 @param data: the binary string
69 @type data: string
70 @param chunksize: the chunk size. Default is
71 L{dns.rdata._base64_chunksize}
72 @rtype: string
73 """
74
75 if chunksize is None:
76 chunksize = _base64_chunksize
77 b64 = data.encode('base64_codec')
78 b64 = b64.replace('\n', '')
79 l = len(b64)
80 if l > chunksize:
81 chunks = []
82 i = 0
83 while i < l:
84 chunks.append(b64[i : i + chunksize])
85 i += chunksize
86 b64 = ' '.join(chunks)
87 return b64
88
89 __escaped = {
90 '"' : True,
91 '\\' : True,
92 }
93
95 """Escape the characters in a quoted string which need it.
96
97 @param qstring: the string
98 @type qstring: string
99 @returns: the escaped string
100 @rtype: string
101 """
102
103 text = ''
104 for c in qstring:
105 if c in __escaped:
106 text += '\\' + c
107 elif ord(c) >= 0x20 and ord(c) < 0x7F:
108 text += c
109 else:
110 text += '\\%03d' % ord(c)
111 return text
112
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.
116
117 @param what: a string of octets representing a bitmap.
118 @type what: string
119 @rtype: string
120 """
121
122 for i in xrange(len(what) - 1, -1, -1):
123 if what[i] != '\x00':
124 break
125 return ''.join(what[0 : i + 1])
126
128 """Base class for all DNS rdata types.
129 """
130
131 __slots__ = ['rdclass', 'rdtype']
132
134 """Initialize an rdata.
135 @param rdclass: The rdata class
136 @type rdclass: int
137 @param rdtype: The rdata type
138 @type rdtype: int
139 """
140
141 self.rdclass = rdclass
142 self.rdtype = rdtype
143
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).
150 @rtype: int
151 """
152
153 return dns.rdatatype.NONE
154
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.
159 @rtype: int
160 """
161
162 return self.covers() << 16 | self.rdtype
163
164 - def to_text(self, origin=None, relativize=True, **kw):
165 """Convert an rdata to text format.
166 @rtype: string
167 """
168 raise NotImplementedError
169
170 - def to_wire(self, file, compress = None, origin = None):
171 """Convert an rdata to wire format.
172 @rtype: string
173 """
174
175 raise NotImplementedError
176
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)
182 return f.getvalue()
183
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
188 changes.
189 """
190 dns.rdata.from_text(self.rdclass, self.rdtype, self.to_text())
191
201
204
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.
209 """
210 return cmp(self.to_digestable(dns.name.root),
211 other.to_digestable(dns.name.root))
212
214 if not isinstance(other, Rdata):
215 return False
216 if self.rdclass != other.rdclass or \
217 self.rdtype != other.rdtype:
218 return False
219 return self._cmp(other) == 0
220
222 if not isinstance(other, Rdata):
223 return True
224 if self.rdclass != other.rdclass or \
225 self.rdtype != other.rdtype:
226 return True
227 return self._cmp(other) != 0
228
235
242
249
256
259
260 - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
261 """Build an rdata object from text format.
262
263 @param rdclass: The rdata class
264 @type rdclass: int
265 @param rdtype: The rdata type
266 @type rdtype: int
267 @param tok: The tokenizer
268 @type tok: dns.tokenizer.Tokenizer
269 @param origin: The origin to use for relative names
270 @type origin: dns.name.Name
271 @param relativize: should names be relativized?
272 @type relativize: bool
273 @rtype: dns.rdata.Rdata instance
274 """
275
276 raise NotImplementedError
277
278 from_text = classmethod(from_text)
279
280 - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
281 """Build an rdata object from wire format
282
283 @param rdclass: The rdata class
284 @type rdclass: int
285 @param rdtype: The rdata type
286 @type rdtype: int
287 @param wire: The wire-format message
288 @type wire: string
289 @param current: The offet in wire of the beginning of the rdata.
290 @type current: int
291 @param rdlen: The length of the wire-format rdata
292 @type rdlen: int
293 @param origin: The origin to use for relative names
294 @type origin: dns.name.Name
295 @rtype: dns.rdata.Rdata instance
296 """
297
298 raise NotImplementedError
299
300 from_wire = classmethod(from_wire)
301
303 """Convert any domain names in the rdata to the specified
304 relativization.
305 """
306
307 pass
308
309
311 """Generate Rdata Class
312
313 This class is used for rdata types for which we have no better
314 implementation. It implements the DNS "unknown RRs" scheme.
315 """
316
317 __slots__ = ['data']
318
319 - def __init__(self, rdclass, rdtype, data):
322
323 - def to_text(self, origin=None, relativize=True, **kw):
324 return r'\# %d ' % len(self.data) + _hexify(self.data)
325
326 - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
327 token = tok.get()
328 if not token.is_identifier() or token.value != '\#':
329 raise dns.exception.SyntaxError(r'generic rdata does not start with \#')
330 length = tok.get_int()
331 chunks = []
332 while 1:
333 token = tok.get()
334 if token.is_eol_or_eof():
335 break
336 chunks.append(token.value)
337 hex = ''.join(chunks)
338 data = hex.decode('hex_codec')
339 if len(data) != length:
340 raise dns.exception.SyntaxError('generic rdata hex data has wrong length')
341 return cls(rdclass, rdtype, data)
342
343 from_text = classmethod(from_text)
344
345 - def to_wire(self, file, compress = None, origin = None):
346 file.write(self.data)
347
348 - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
350
351 from_wire = classmethod(from_wire)
352
353 _rdata_modules = {}
354 _module_prefix = 'dns.rdtypes'
355
357
358 def import_module(name):
359 mod = __import__(name)
360 components = name.split('.')
361 for comp in components[1:]:
362 mod = getattr(mod, comp)
363 return mod
364
365 mod = _rdata_modules.get((rdclass, rdtype))
366 rdclass_text = dns.rdataclass.to_text(rdclass)
367 rdtype_text = dns.rdatatype.to_text(rdtype)
368 rdtype_text = rdtype_text.replace('-', '_')
369 if not mod:
370 mod = _rdata_modules.get((dns.rdatatype.ANY, rdtype))
371 if not mod:
372 try:
373 mod = import_module('.'.join([_module_prefix,
374 rdclass_text, rdtype_text]))
375 _rdata_modules[(rdclass, rdtype)] = mod
376 except ImportError:
377 try:
378 mod = import_module('.'.join([_module_prefix,
379 'ANY', rdtype_text]))
380 _rdata_modules[(dns.rdataclass.ANY, rdtype)] = mod
381 except ImportError:
382 mod = None
383 if mod:
384 cls = getattr(mod, rdtype_text)
385 else:
386 cls = GenericRdata
387 return cls
388
389 -def from_text(rdclass, rdtype, tok, origin = None, relativize = True):
390 """Build an rdata object from text format.
391
392 This function attempts to dynamically load a class which
393 implements the specified rdata class and type. If there is no
394 class-and-type-specific implementation, the GenericRdata class
395 is used.
396
397 Once a class is chosen, its from_text() class method is called
398 with the parameters to this function.
399
400 If I{tok} is a string, then a tokenizer is created and the string
401 is used as its input.
402
403 @param rdclass: The rdata class
404 @type rdclass: int
405 @param rdtype: The rdata type
406 @type rdtype: int
407 @param tok: The tokenizer or input text
408 @type tok: dns.tokenizer.Tokenizer or string
409 @param origin: The origin to use for relative names
410 @type origin: dns.name.Name
411 @param relativize: Should names be relativized?
412 @type relativize: bool
413 @rtype: dns.rdata.Rdata instance"""
414
415 if isinstance(tok, (str, unicode)):
416 tok = dns.tokenizer.Tokenizer(tok)
417 cls = get_rdata_class(rdclass, rdtype)
418 if cls != GenericRdata:
419
420 token = tok.get()
421 tok.unget(token)
422 if token.is_identifier() and \
423 token.value == r'\#':
424
425
426
427
428
429 rdata = GenericRdata.from_text(rdclass, rdtype, tok, origin,
430 relativize)
431 return from_wire(rdclass, rdtype, rdata.data, 0, len(rdata.data),
432 origin)
433 return cls.from_text(rdclass, rdtype, tok, origin, relativize)
434
435 -def from_wire(rdclass, rdtype, wire, current, rdlen, origin = None):
436 """Build an rdata object from wire format
437
438 This function attempts to dynamically load a class which
439 implements the specified rdata class and type. If there is no
440 class-and-type-specific implementation, the GenericRdata class
441 is used.
442
443 Once a class is chosen, its from_wire() class method is called
444 with the parameters to this function.
445
446 @param rdclass: The rdata class
447 @type rdclass: int
448 @param rdtype: The rdata type
449 @type rdtype: int
450 @param wire: The wire-format message
451 @type wire: string
452 @param current: The offet in wire of the beginning of the rdata.
453 @type current: int
454 @param rdlen: The length of the wire-format rdata
455 @type rdlen: int
456 @param origin: The origin to use for relative names
457 @type origin: dns.name.Name
458 @rtype: dns.rdata.Rdata instance"""
459
460 wire = dns.wiredata.maybe_wrap(wire)
461 cls = get_rdata_class(rdclass, rdtype)
462 return cls.from_wire(rdclass, rdtype, wire, current, rdlen, origin)
463