Package dns :: Module rdata
[hide private]
[frames] | no frames]

Source Code for Module dns.rdata

  1  # Copyright (C) 2001-2007, 2009, 2010 Nominum, Inc. 
  2  # 
  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. 
  7  # 
  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. 
 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
63 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. 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('"\\')
89 90 -def _escapify(qstring):
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
111 -def _truncate_bitmap(what):
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
125 -class Rdata(object):
126 """Base class for all DNS rdata types. 127 """ 128 129 __slots__ = ['rdclass', 'rdtype'] 130
131 - def __init__(self, rdclass, rdtype):
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
142 - def covers(self):
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
153 - def extended_rdatatype(self):
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
175 - def to_digestable(self, origin = None):
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
182 - def validate(self):
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
190 - def __repr__(self):
191 covers = self.covers() 192 if covers == dns.rdatatype.NONE: 193 ctext = '' 194 else: 195 ctext = '(' + dns.rdatatype.to_text(covers) + ')' 196 return '<DNS ' + dns.rdataclass.to_text(self.rdclass) + ' ' + \ 197 dns.rdatatype.to_text(self.rdtype) + ctext + ' rdata: ' + \ 198 str(self) + '>'
199
200 - def __str__(self):
201 return self.to_text()
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
211 - def __eq__(self, other):
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
219 - def __ne__(self, other):
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
227 - def __lt__(self, other):
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
235 - def __le__(self, other):
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
243 - def __ge__(self, other):
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
251 - def __gt__(self, other):
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
299 - def choose_relativity(self, origin = None, relativize = True):
300 """Convert any domain names in the rdata to the specified 301 relativization. 302 """ 303 304 pass
305
306 307 -class GenericRdata(Rdata):
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):
317 super(GenericRdata, self).__init__(rdclass, rdtype) 318 self.data = 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):
349 return dns.util.cmp(self.data, other.data)
350 351 _rdata_modules = {} 352 _module_prefix = 'dns.rdtypes'
353 354 -def get_rdata_class(rdclass, rdtype):
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 # peek at first token 415 token = tok.get() 416 tok.unget(token) 417 if token.is_identifier() and \ 418 token.value == r'\#': 419 # 420 # Known type using the generic syntax. Extract the 421 # wire form from the generic syntax, and then run 422 # from_wire on it. 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