| OLD | NEW |
| (Empty) | |
| 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 cStringIO |
| 29 |
| 30 import dns.exception |
| 31 import dns.rdataclass |
| 32 import dns.rdatatype |
| 33 import dns.tokenizer |
| 34 |
| 35 _hex_chunksize = 32 |
| 36 |
| 37 def _hexify(data, chunksize=None): |
| 38 """Convert a binary string into its hex encoding, broken up into chunks |
| 39 of I{chunksize} characters separated by a space. |
| 40 |
| 41 @param data: the binary string |
| 42 @type data: string |
| 43 @param chunksize: the chunk size. Default is L{dns.rdata._hex_chunksize} |
| 44 @rtype: string |
| 45 """ |
| 46 |
| 47 if chunksize is None: |
| 48 chunksize = _hex_chunksize |
| 49 hex = data.encode('hex_codec') |
| 50 l = len(hex) |
| 51 if l > chunksize: |
| 52 chunks = [] |
| 53 i = 0 |
| 54 while i < l: |
| 55 chunks.append(hex[i : i + chunksize]) |
| 56 i += chunksize |
| 57 hex = ' '.join(chunks) |
| 58 return hex |
| 59 |
| 60 _base64_chunksize = 32 |
| 61 |
| 62 def _base64ify(data, chunksize=None): |
| 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 if chunksize is None: |
| 74 chunksize = _base64_chunksize |
| 75 b64 = data.encode('base64_codec') |
| 76 b64 = b64.replace('\n', '') |
| 77 l = len(b64) |
| 78 if l > chunksize: |
| 79 chunks = [] |
| 80 i = 0 |
| 81 while i < l: |
| 82 chunks.append(b64[i : i + chunksize]) |
| 83 i += chunksize |
| 84 b64 = ' '.join(chunks) |
| 85 return b64 |
| 86 |
| 87 __escaped = { |
| 88 '"' : True, |
| 89 '\\' : True, |
| 90 } |
| 91 |
| 92 def _escapify(qstring): |
| 93 """Escape the characters in a quoted string which need it. |
| 94 |
| 95 @param qstring: the string |
| 96 @type qstring: string |
| 97 @returns: the escaped string |
| 98 @rtype: string |
| 99 """ |
| 100 |
| 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 xrange(len(what) - 1, -1, -1): |
| 121 if what[i] != '\x00': |
| 122 break |
| 123 return ''.join(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 = cStringIO.StringIO() |
| 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) or \ |
| 229 self.rdclass != other.rdclass or \ |
| 230 self.rdtype != other.rdtype: |
| 231 return NotImplemented |
| 232 return self._cmp(other) < 0 |
| 233 |
| 234 def __le__(self, other): |
| 235 if not isinstance(other, Rdata) or \ |
| 236 self.rdclass != other.rdclass or \ |
| 237 self.rdtype != other.rdtype: |
| 238 return NotImplemented |
| 239 return self._cmp(other) <= 0 |
| 240 |
| 241 def __ge__(self, other): |
| 242 if not isinstance(other, Rdata) or \ |
| 243 self.rdclass != other.rdclass or \ |
| 244 self.rdtype != other.rdtype: |
| 245 return NotImplemented |
| 246 return self._cmp(other) >= 0 |
| 247 |
| 248 def __gt__(self, other): |
| 249 if not isinstance(other, Rdata) or \ |
| 250 self.rdclass != other.rdclass or \ |
| 251 self.rdtype != other.rdtype: |
| 252 return NotImplemented |
| 253 return self._cmp(other) > 0 |
| 254 |
| 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 from_text = classmethod(from_text) |
| 274 |
| 275 def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): |
| 276 """Build an rdata object from wire format |
| 277 |
| 278 @param rdclass: The rdata class |
| 279 @type rdclass: int |
| 280 @param rdtype: The rdata type |
| 281 @type rdtype: int |
| 282 @param wire: The wire-format message |
| 283 @type wire: string |
| 284 @param current: The offet in wire of the beginning of the rdata. |
| 285 @type current: int |
| 286 @param rdlen: The length of the wire-format rdata |
| 287 @type rdlen: int |
| 288 @param origin: The origin to use for relative names |
| 289 @type origin: dns.name.Name |
| 290 @rtype: dns.rdata.Rdata instance |
| 291 """ |
| 292 |
| 293 raise NotImplementedError |
| 294 |
| 295 from_wire = classmethod(from_wire) |
| 296 |
| 297 def choose_relativity(self, origin = None, relativize = True): |
| 298 """Convert any domain names in the rdata to the specified |
| 299 relativization. |
| 300 """ |
| 301 |
| 302 pass |
| 303 |
| 304 |
| 305 class GenericRdata(Rdata): |
| 306 """Generate Rdata Class |
| 307 |
| 308 This class is used for rdata types for which we have no better |
| 309 implementation. It implements the DNS "unknown RRs" scheme. |
| 310 """ |
| 311 |
| 312 __slots__ = ['data'] |
| 313 |
| 314 def __init__(self, rdclass, rdtype, data): |
| 315 super(GenericRdata, self).__init__(rdclass, rdtype) |
| 316 self.data = data |
| 317 |
| 318 def to_text(self, origin=None, relativize=True, **kw): |
| 319 return r'\# %d ' % len(self.data) + _hexify(self.data) |
| 320 |
| 321 def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): |
| 322 token = tok.get() |
| 323 if not token.is_identifier() or token.value != '\#': |
| 324 raise dns.exception.SyntaxError(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) |
| 332 hex = ''.join(chunks) |
| 333 data = hex.decode('hex_codec') |
| 334 if len(data) != length: |
| 335 raise dns.exception.SyntaxError('generic rdata hex data has wrong le
ngth') |
| 336 return cls(rdclass, rdtype, data) |
| 337 |
| 338 from_text = classmethod(from_text) |
| 339 |
| 340 def to_wire(self, file, compress = None, origin = None): |
| 341 file.write(self.data) |
| 342 |
| 343 def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): |
| 344 return cls(rdclass, rdtype, wire[current : current + rdlen]) |
| 345 |
| 346 from_wire = classmethod(from_wire) |
| 347 |
| 348 def _cmp(self, other): |
| 349 return 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) |
| OLD | NEW |