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 from io import BytesIO
29 import base64
30 import binascii
31
32 import dns.exception
33 import dns.name
34 import dns.rdataclass
35 import dns.rdatatype
36 import dns.tokenizer
37 import dns.wiredata
38 from ._compat import xrange, string_types, text_type
39
40 _hex_chunksize = 32
44 """Convert a binary string into its hex encoding, broken up into chunks
45 of I{chunksize} characters separated by a space.
46
47 @param data: the binary string
48 @type data: string
49 @param chunksize: the chunk size. Default is L{dns.rdata._hex_chunksize}
50 @rtype: string
51 """
52
53 line = binascii.hexlify(data)
54 return b' '.join([line[i:i + chunksize]
55 for i
56 in range(0, len(line), chunksize)]).decode()
57
58 _base64_chunksize = 32
62 """Convert a binary string into its base64 encoding, broken up into chunks
63 of I{chunksize} characters separated by a space.
64
65 @param data: the binary string
66 @type data: string
67 @param chunksize: the chunk size. Default is
68 L{dns.rdata._base64_chunksize}
69 @rtype: string
70 """
71
72 line = base64.b64encode(data)
73 return b' '.join([line[i:i + chunksize]
74 for i
75 in range(0, len(line), chunksize)]).decode()
76
77 __escaped = bytearray(b'"\\')
80 """Escape the characters in a quoted string which need it.
81
82 @param qstring: the string
83 @type qstring: string
84 @returns: the escaped string
85 @rtype: string
86 """
87
88 if isinstance(qstring, text_type):
89 qstring = qstring.encode()
90 if not isinstance(qstring, bytearray):
91 qstring = bytearray(qstring)
92
93 text = ''
94 for c in qstring:
95 if c in __escaped:
96 text += '\\' + chr(c)
97 elif c >= 0x20 and c < 0x7F:
98 text += chr(c)
99 else:
100 text += '\\%03d' % c
101 return text
102
105 """Determine the index of greatest byte that isn't all zeros, and
106 return the bitmap that contains all the bytes less than that index.
107
108 @param what: a string of octets representing a bitmap.
109 @type what: string
110 @rtype: string
111 """
112
113 for i in xrange(len(what) - 1, -1, -1):
114 if what[i] != 0:
115 return what[0: i + 1]
116 return what[0:1]
117
120
121 """Base class for all DNS rdata types.
122 """
123
124 __slots__ = ['rdclass', 'rdtype']
125
127 """Initialize an rdata.
128 @param rdclass: The rdata class
129 @type rdclass: int
130 @param rdtype: The rdata type
131 @type rdtype: int
132 """
133
134 self.rdclass = rdclass
135 self.rdtype = rdtype
136
138 """DNS SIG/RRSIG rdatas apply to a specific type; this type is
139 returned by the covers() function. If the rdata type is not
140 SIG or RRSIG, dns.rdatatype.NONE is returned. This is useful when
141 creating rdatasets, allowing the rdataset to contain only RRSIGs
142 of a particular type, e.g. RRSIG(NS).
143 @rtype: int
144 """
145
146 return dns.rdatatype.NONE
147
149 """Return a 32-bit type value, the least significant 16 bits of
150 which are the ordinary DNS type, and the upper 16 bits of which are
151 the "covered" type, if any.
152 @rtype: int
153 """
154
155 return self.covers() << 16 | self.rdtype
156
157 - def to_text(self, origin=None, relativize=True, **kw):
158 """Convert an rdata to text format.
159 @rtype: string
160 """
161 raise NotImplementedError
162
163 - def to_wire(self, file, compress=None, origin=None):
164 """Convert an rdata to wire format.
165 @rtype: string
166 """
167
168 raise NotImplementedError
169
171 """Convert rdata to a format suitable for digesting in hashes. This
172 is also the DNSSEC canonical form."""
173 f = BytesIO()
174 self.to_wire(f, None, origin)
175 return f.getvalue()
176
178 """Check that the current contents of the rdata's fields are
179 valid. If you change an rdata by assigning to its fields,
180 it is a good idea to call validate() when you are done making
181 changes.
182 """
183 dns.rdata.from_text(self.rdclass, self.rdtype, self.to_text())
184
194
197
198 - def _cmp(self, other):
199 """Compare an rdata with another rdata of the same rdtype and
200 rdclass. Return < 0 if self < other in the DNSSEC ordering,
201 0 if self == other, and > 0 if self > other.
202 """
203 our = self.to_digestable(dns.name.root)
204 their = other.to_digestable(dns.name.root)
205 if our == their:
206 return 0
207 if our > their:
208 return 1
209
210 return -1
211
218
225
232
238
244
250
253
254 @classmethod
255 - def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
256 """Build an rdata object from text format.
257
258 @param rdclass: The rdata class
259 @type rdclass: int
260 @param rdtype: The rdata type
261 @type rdtype: int
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
269 """
270
271 raise NotImplementedError
272
273 @classmethod
274 - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
275 """Build an rdata object from wire format
276
277 @param rdclass: The rdata class
278 @type rdclass: int
279 @param rdtype: The rdata type
280 @type rdtype: int
281 @param wire: The wire-format message
282 @type wire: string
283 @param current: The offset in wire of the beginning of the rdata.
284 @type current: int
285 @param rdlen: The length of the wire-format rdata
286 @type rdlen: int
287 @param origin: The origin to use for relative names
288 @type origin: dns.name.Name
289 @rtype: dns.rdata.Rdata instance
290 """
291
292 raise NotImplementedError
293
295 """Convert any domain names in the rdata to the specified
296 relativization.
297 """
298
299 pass
300
303
304 """Generate Rdata Class
305
306 This class is used for rdata types for which we have no better
307 implementation. It implements the DNS "unknown RRs" scheme.
308 """
309
310 __slots__ = ['data']
311
312 - def __init__(self, rdclass, rdtype, data):
315
316 - def to_text(self, origin=None, relativize=True, **kw):
317 return r'\# %d ' % len(self.data) + _hexify(self.data)
318
319 @classmethod
320 - def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
321 token = tok.get()
322 if not token.is_identifier() or token.value != '\#':
323 raise dns.exception.SyntaxError(
324 r'generic rdata does not start with \#')
325 length = tok.get_int()
326 chunks = []
327 while 1:
328 token = tok.get()
329 if token.is_eol_or_eof():
330 break
331 chunks.append(token.value.encode())
332 hex = b''.join(chunks)
333 data = binascii.unhexlify(hex)
334 if len(data) != length:
335 raise dns.exception.SyntaxError(
336 'generic rdata hex data has wrong length')
337 return cls(rdclass, rdtype, data)
338
339 - def to_wire(self, file, compress=None, origin=None):
340 file.write(self.data)
341
342 @classmethod
343 - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
345
346 _rdata_modules = {}
347 _module_prefix = 'dns.rdtypes'
351
352 def import_module(name):
353 mod = __import__(name)
354 components = name.split('.')
355 for comp in components[1:]:
356 mod = getattr(mod, comp)
357 return mod
358
359 mod = _rdata_modules.get((rdclass, rdtype))
360 rdclass_text = dns.rdataclass.to_text(rdclass)
361 rdtype_text = dns.rdatatype.to_text(rdtype)
362 rdtype_text = rdtype_text.replace('-', '_')
363 if not mod:
364 mod = _rdata_modules.get((dns.rdatatype.ANY, rdtype))
365 if not mod:
366 try:
367 mod = import_module('.'.join([_module_prefix,
368 rdclass_text, rdtype_text]))
369 _rdata_modules[(rdclass, rdtype)] = mod
370 except ImportError:
371 try:
372 mod = import_module('.'.join([_module_prefix,
373 'ANY', rdtype_text]))
374 _rdata_modules[(dns.rdataclass.ANY, rdtype)] = mod
375 except ImportError:
376 mod = None
377 if mod:
378 cls = getattr(mod, rdtype_text)
379 else:
380 cls = GenericRdata
381 return cls
382
383
384 -def from_text(rdclass, rdtype, tok, origin=None, relativize=True):
385 """Build an rdata object from text format.
386
387 This function attempts to dynamically load a class which
388 implements the specified rdata class and type. If there is no
389 class-and-type-specific implementation, the GenericRdata class
390 is used.
391
392 Once a class is chosen, its from_text() class method is called
393 with the parameters to this function.
394
395 If I{tok} is a string, then a tokenizer is created and the string
396 is used as its input.
397
398 @param rdclass: The rdata class
399 @type rdclass: int
400 @param rdtype: The rdata type
401 @type rdtype: int
402 @param tok: The tokenizer or input text
403 @type tok: dns.tokenizer.Tokenizer or string
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"""
409
410 if isinstance(tok, string_types):
411 tok = dns.tokenizer.Tokenizer(tok)
412 cls = get_rdata_class(rdclass, rdtype)
413 if cls != GenericRdata:
414
415 token = tok.get()
416 tok.unget(token)
417 if token.is_identifier() and \
418 token.value == r'\#':
419
420
421
422
423
424 rdata = GenericRdata.from_text(rdclass, rdtype, tok, origin,
425 relativize)
426 return from_wire(rdclass, rdtype, rdata.data, 0, len(rdata.data),
427 origin)
428 return cls.from_text(rdclass, rdtype, tok, origin, relativize)
429
430
431 -def from_wire(rdclass, rdtype, wire, current, rdlen, origin=None):
432 """Build an rdata object from wire format
433
434 This function attempts to dynamically load a class which
435 implements the specified rdata class and type. If there is no
436 class-and-type-specific implementation, the GenericRdata class
437 is used.
438
439 Once a class is chosen, its from_wire() class method is called
440 with the parameters to this function.
441
442 @param rdclass: The rdata class
443 @type rdclass: int
444 @param rdtype: The rdata type
445 @type rdtype: int
446 @param wire: The wire-format message
447 @type wire: string
448 @param current: The offset in wire of the beginning of the rdata.
449 @type current: int
450 @param rdlen: The length of the wire-format rdata
451 @type rdlen: int
452 @param origin: The origin to use for relative names
453 @type origin: dns.name.Name
454 @rtype: dns.rdata.Rdata instance"""
455
456 wire = dns.wiredata.maybe_wrap(wire)
457 cls = get_rdata_class(rdclass, rdtype)
458 return cls.from_wire(rdclass, rdtype, wire, current, rdlen, origin)
459