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 base64
29 import io
30
31 import dns.exception
32 import dns.rdataclass
33 import dns.rdatatype
34 import dns.tokenizer
35 import dns.util
36
37 _hex_chunksize = 32
38
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.
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 = base64.b16encode(data).decode('ascii').lower()
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
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 = base64.b64encode(data).decode('ascii')
78 l = len(b64)
79 if l > chunksize:
80 chunks = []
81 i = 0
82 while i < l:
83 chunks.append(b64[i : i + chunksize])
84 i += chunksize
85 b64 = ' '.join(chunks)
86 return b64
87
88 _escaped = frozenset('"\\')
91 """Escape the characters in a quoted string which need it.
92
93 @param qstring: the string
94 @type qstring: string
95 @returns: the escaped string
96 @rtype: string
97 """
98
99 if isinstance(qstring, bytes):
100 qstring = qstring.decode('latin_1')
101 text = ''
102 for c in qstring:
103 if c in _escaped:
104 text += '\\' + c
105 elif ord(c) >= 0x20 and ord(c) < 0x7F:
106 text += c
107 else:
108 text += '\\%03d' % ord(c)
109 return text
110
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.
114
115 @param what: a string of octets representing a bitmap.
116 @type what: string
117 @rtype: string
118 """
119
120 for i in range(len(what) - 1, -1, -1):
121 if what[i] != 0:
122 break
123 return bytes(what[0 : i + 1])
124
126 """Base class for all DNS rdata types.
127 """
128
129 __slots__ = ['rdclass', 'rdtype']
130
132 """Initialize an rdata.
133 @param rdclass: The rdata class
134 @type rdclass: int
135 @param rdtype: The rdata type
136 @type rdtype: int
137 """
138
139 self.rdclass = rdclass
140 self.rdtype = rdtype
141
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).
148 @rtype: int
149 """
150
151 return dns.rdatatype.NONE
152
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.
157 @rtype: int
158 """
159
160 return self.covers() << 16 | self.rdtype
161
162 - def to_text(self, origin=None, relativize=True, **kw):
163 """Convert an rdata to text format.
164 @rtype: string
165 """
166 raise NotImplementedError
167
168 - def to_wire(self, file, compress = None, origin = None):
169 """Convert an rdata to wire format.
170 @rtype: string
171 """
172
173 raise NotImplementedError
174
176 """Convert rdata to a format suitable for digesting in hashes. This
177 is also the DNSSEC canonical form."""
178 f = io.BytesIO()
179 self.to_wire(f, None, origin)
180 return f.getvalue()
181
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
186 changes.
187 """
188 dns.rdata.from_text(self.rdclass, self.rdtype, self.to_text())
189
199
202
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.
207 """
208
209 raise NotImplementedError
210
212 if not isinstance(other, Rdata):
213 return False
214 if self.rdclass != other.rdclass or \
215 self.rdtype != other.rdtype:
216 return False
217 return self._cmp(other) == 0
218
220 if not isinstance(other, Rdata):
221 return True
222 if self.rdclass != other.rdclass or \
223 self.rdtype != other.rdtype:
224 return True
225 return self._cmp(other) != 0
226
228 if not isinstance(other, Rdata):
229 return NotImplemented
230 if self.rdclass != other.rdclass or \
231 self.rdtype != other.rdtype:
232 return dns.util.cmp((self.rdclass, self.rdtype), (other.rdclass, other.rdtype))
233 return self._cmp(other) < 0
234
236 if not isinstance(other, Rdata):
237 return NotImplemented
238 if self.rdclass != other.rdclass or \
239 self.rdtype != other.rdtype:
240 return dns.util.cmp((self.rdclass, self.rdtype), (other.rdclass, other.rdtype))
241 return self._cmp(other) <= 0
242
244 if not isinstance(other, Rdata):
245 return NotImplemented
246 if self.rdclass != other.rdclass or \
247 self.rdtype != other.rdtype:
248 return dns.util.cmp((self.rdclass, self.rdtype), (other.rdclass, other.rdtype))
249 return self._cmp(other) >= 0
250
252 if not isinstance(other, Rdata):
253 return NotImplemented
254 if self.rdclass != other.rdclass or \
255 self.rdtype != other.rdtype:
256 return dns.util.cmp((self.rdclass, self.rdtype), (other.rdclass, other.rdtype))
257 return self._cmp(other) > 0
258
259 @classmethod
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 @classmethod
279 - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
280 """Build an rdata object from wire format
281
282 @param rdclass: The rdata class
283 @type rdclass: int
284 @param rdtype: The rdata type
285 @type rdtype: int
286 @param wire: The wire-format message
287 @type wire: string
288 @param current: The offet in wire of the beginning of the rdata.
289 @type current: int
290 @param rdlen: The length of the wire-format rdata
291 @type rdlen: int
292 @param origin: The origin to use for relative names
293 @type origin: dns.name.Name
294 @rtype: dns.rdata.Rdata instance
295 """
296
297 raise NotImplementedError
298
300 """Convert any domain names in the rdata to the specified
301 relativization.
302 """
303
304 pass
305
308 """Generate Rdata Class
309
310 This class is used for rdata types for which we have no better
311 implementation. It implements the DNS "unknown RRs" scheme.
312 """
313
314 __slots__ = ['data']
315
316 - def __init__(self, rdclass, rdtype, data):
319
320 - def to_text(self, origin=None, relativize=True, **kw):
321 return r'\# %d ' % len(self.data) + _hexify(self.data)
322
323 @classmethod
324 - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
325 token = tok.get()
326 if not token.is_identifier() or token.value != '\#':
327 print('XXX %u %u' % (rdclass, rdtype))
328 raise dns.exception.SyntaxError(r'generic rdata does not start with \#')
329 length = tok.get_int()
330 chunks = []
331 while 1:
332 token = tok.get()
333 if token.is_eol_or_eof():
334 break
335 chunks.append(token.value)
336 data = bytes.fromhex(''.join(chunks))
337 if len(data) != length:
338 raise dns.exception.SyntaxError('generic rdata hex data has wrong length')
339 return cls(rdclass, rdtype, data)
340
341 - def to_wire(self, file, compress = None, origin = None):
342 file.write(self.data)
343
344 @classmethod
345 - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
346 return cls(rdclass, rdtype, wire[current : current + rdlen])
347
348 - def _cmp(self, other):
350
351 _rdata_modules = {}
352 _module_prefix = 'dns.rdtypes'
355
356 def import_module(name):
357 mod = __import__(name)
358 components = name.split('.')
359 for comp in components[1:]:
360 mod = getattr(mod, comp)
361 return mod
362
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('-', '_')
367 if not mod:
368 mod = _rdata_modules.get((dns.rdatatype.ANY, rdtype))
369 if not mod:
370 try:
371 mod = import_module('.'.join([_module_prefix,
372 rdclass_text, rdtype_text]))
373 _rdata_modules[(rdclass, rdtype)] = mod
374 except ImportError:
375 try:
376 mod = import_module('.'.join([_module_prefix,
377 'ANY', rdtype_text]))
378 _rdata_modules[(dns.rdataclass.ANY, rdtype)] = mod
379 except ImportError:
380 mod = None
381 if mod:
382 cls = getattr(mod, rdtype_text)
383 else:
384 cls = GenericRdata
385 return cls
386
387 -def from_text(rdclass, rdtype, tok, origin = None, relativize = True):
388 """Build an rdata object from text format.
389
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
393 is used.
394
395 Once a class is chosen, its from_text() class method is called
396 with the parameters to this function.
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
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"""
409
410 if isinstance(tok, str):
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 -def from_wire(rdclass, rdtype, wire, current, rdlen, origin = None):
431 """Build an rdata object from wire format
432
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
436 is used.
437
438 Once a class is chosen, its from_wire() class method is called
439 with the parameters to this function.
440
441 @param rdclass: The rdata class
442 @type rdclass: int
443 @param rdtype: The rdata type
444 @type rdtype: int
445 @param wire: The wire-format message
446 @type wire: string
447 @param current: The offet in wire of the beginning of the rdata.
448 @type current: int
449 @param rdlen: The length of the wire-format rdata
450 @type rdlen: int
451 @param origin: The origin to use for relative names
452 @type origin: dns.name.Name
453 @rtype: dns.rdata.Rdata instance"""
454
455 cls = get_rdata_class(rdclass, rdtype)
456 return cls.from_wire(rdclass, rdtype, wire, current, rdlen, origin)
457