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 import struct
32
33 import dns.exception
34 import dns.name
35 import dns.rdataclass
36 import dns.rdatatype
37 import dns.tokenizer
38 import dns.wiredata
39 from ._compat import xrange, string_types, text_type
40
41 _hex_chunksize = 32
45 """Convert a binary string into its hex encoding, broken up into chunks
46 of I{chunksize} characters separated by a space.
47
48 @param data: the binary string
49 @type data: string
50 @param chunksize: the chunk size. Default is L{dns.rdata._hex_chunksize}
51 @rtype: string
52 """
53
54 line = binascii.hexlify(data)
55 return b' '.join([line[i:i + chunksize]
56 for i
57 in range(0, len(line), chunksize)]).decode()
58
59 _base64_chunksize = 32
63 """Convert a binary string into its base64 encoding, broken up into chunks
64 of I{chunksize} characters separated by a space.
65
66 @param data: the binary string
67 @type data: string
68 @param chunksize: the chunk size. Default is
69 L{dns.rdata._base64_chunksize}
70 @rtype: string
71 """
72
73 line = base64.b64encode(data)
74 return b' '.join([line[i:i + chunksize]
75 for i
76 in range(0, len(line), chunksize)]).decode()
77
78 __escaped = {
79 '"': True,
80 '\\': True,
81 }
85 """Escape the characters in a quoted string which need it.
86
87 @param qstring: the string
88 @type qstring: string
89 @returns: the escaped string
90 @rtype: string
91 """
92
93 if isinstance(qstring, text_type):
94 qstring = qstring.encode()
95 if not isinstance(qstring, bytearray):
96 qstring = bytearray(qstring)
97
98 text = ''
99 for c in qstring:
100 packed = struct.pack('!B', c).decode()
101 if packed in __escaped:
102 text += '\\' + packed
103 elif c >= 0x20 and c < 0x7F:
104 text += packed
105 else:
106 text += '\\%03d' % c
107 return text
108
111 """Determine the index of greatest byte that isn't all zeros, and
112 return the bitmap that contains all the bytes less than that index.
113
114 @param what: a string of octets representing a bitmap.
115 @type what: string
116 @rtype: string
117 """
118
119 for i in xrange(len(what) - 1, -1, -1):
120 if what[i] != 0:
121 break
122 return what[0: i + 1]
123
126
127 """Base class for all DNS rdata types.
128 """
129
130 __slots__ = ['rdclass', 'rdtype']
131
133 """Initialize an rdata.
134 @param rdclass: The rdata class
135 @type rdclass: int
136 @param rdtype: The rdata type
137 @type rdtype: int
138 """
139
140 self.rdclass = rdclass
141 self.rdtype = rdtype
142
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).
149 @rtype: int
150 """
151
152 return dns.rdatatype.NONE
153
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.
158 @rtype: int
159 """
160
161 return self.covers() << 16 | self.rdtype
162
163 - def to_text(self, origin=None, relativize=True, **kw):
164 """Convert an rdata to text format.
165 @rtype: string
166 """
167 raise NotImplementedError
168
169 - def to_wire(self, file, compress=None, origin=None):
170 """Convert an rdata to wire format.
171 @rtype: string
172 """
173
174 raise NotImplementedError
175
177 """Convert rdata to a format suitable for digesting in hashes. This
178 is also the DNSSEC canonical form."""
179 f = BytesIO()
180 self.to_wire(f, None, origin)
181 return f.getvalue()
182
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
187 changes.
188 """
189 dns.rdata.from_text(self.rdclass, self.rdtype, self.to_text())
190
200
203
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.
208 """
209 our = self.to_digestable(dns.name.root)
210 their = other.to_digestable(dns.name.root)
211 if our == their:
212 return 0
213 if our > their:
214 return 1
215
216 return -1
217
224
231
238
244
250
256
259
260 @classmethod
261 - def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
262 """Build an rdata object from text format.
263
264 @param rdclass: The rdata class
265 @type rdclass: int
266 @param rdtype: The rdata type
267 @type rdtype: int
268 @param tok: The tokenizer
269 @type tok: dns.tokenizer.Tokenizer
270 @param origin: The origin to use for relative names
271 @type origin: dns.name.Name
272 @param relativize: should names be relativized?
273 @type relativize: bool
274 @rtype: dns.rdata.Rdata instance
275 """
276
277 raise NotImplementedError
278
279 @classmethod
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 offset 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
301 """Convert any domain names in the rdata to the specified
302 relativization.
303 """
304
305 pass
306
309
310 """Generate Rdata Class
311
312 This class is used for rdata types for which we have no better
313 implementation. It implements the DNS "unknown RRs" scheme.
314 """
315
316 __slots__ = ['data']
317
318 - def __init__(self, rdclass, rdtype, data):
321
322 - def to_text(self, origin=None, relativize=True, **kw):
323 return r'\# %d ' % len(self.data) + _hexify(self.data)
324
325 @classmethod
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(
330 r'generic rdata does not start with \#')
331 length = tok.get_int()
332 chunks = []
333 while 1:
334 token = tok.get()
335 if token.is_eol_or_eof():
336 break
337 chunks.append(token.value.encode())
338 hex = b''.join(chunks)
339 data = binascii.unhexlify(hex)
340 if len(data) != length:
341 raise dns.exception.SyntaxError(
342 'generic rdata hex data has wrong length')
343 return cls(rdclass, rdtype, data)
344
345 - def to_wire(self, file, compress=None, origin=None):
346 file.write(self.data)
347
348 @classmethod
349 - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
351
352 _rdata_modules = {}
353 _module_prefix = 'dns.rdtypes'
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
390 -def from_text(rdclass, rdtype, tok, origin=None, relativize=True):
391 """Build an rdata object from text format.
392
393 This function attempts to dynamically load a class which
394 implements the specified rdata class and type. If there is no
395 class-and-type-specific implementation, the GenericRdata class
396 is used.
397
398 Once a class is chosen, its from_text() class method is called
399 with the parameters to this function.
400
401 If I{tok} is a string, then a tokenizer is created and the string
402 is used as its input.
403
404 @param rdclass: The rdata class
405 @type rdclass: int
406 @param rdtype: The rdata type
407 @type rdtype: int
408 @param tok: The tokenizer or input text
409 @type tok: dns.tokenizer.Tokenizer or string
410 @param origin: The origin to use for relative names
411 @type origin: dns.name.Name
412 @param relativize: Should names be relativized?
413 @type relativize: bool
414 @rtype: dns.rdata.Rdata instance"""
415
416 if isinstance(tok, string_types):
417 tok = dns.tokenizer.Tokenizer(tok)
418 cls = get_rdata_class(rdclass, rdtype)
419 if cls != GenericRdata:
420
421 token = tok.get()
422 tok.unget(token)
423 if token.is_identifier() and \
424 token.value == r'\#':
425
426
427
428
429
430 rdata = GenericRdata.from_text(rdclass, rdtype, tok, origin,
431 relativize)
432 return from_wire(rdclass, rdtype, rdata.data, 0, len(rdata.data),
433 origin)
434 return cls.from_text(rdclass, rdtype, tok, origin, relativize)
435
436
437 -def from_wire(rdclass, rdtype, wire, current, rdlen, origin=None):
438 """Build an rdata object from wire format
439
440 This function attempts to dynamically load a class which
441 implements the specified rdata class and type. If there is no
442 class-and-type-specific implementation, the GenericRdata class
443 is used.
444
445 Once a class is chosen, its from_wire() class method is called
446 with the parameters to this function.
447
448 @param rdclass: The rdata class
449 @type rdclass: int
450 @param rdtype: The rdata type
451 @type rdtype: int
452 @param wire: The wire-format message
453 @type wire: string
454 @param current: The offset in wire of the beginning of the rdata.
455 @type current: int
456 @param rdlen: The length of the wire-format rdata
457 @type rdlen: int
458 @param origin: The origin to use for relative names
459 @type origin: dns.name.Name
460 @rtype: dns.rdata.Rdata instance"""
461
462 wire = dns.wiredata.maybe_wrap(wire)
463 cls = get_rdata_class(rdclass, rdtype)
464 return cls.from_wire(rdclass, rdtype, wire, current, rdlen, origin)
465