1# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license 2 3# Copyright (C) 2001-2017 Nominum, Inc. 4# 5# Permission to use, copy, modify, and distribute this software and its 6# documentation for any purpose with or without fee is hereby granted, 7# provided that the above copyright notice and this permission notice 8# appear in all copies. 9# 10# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES 11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR 13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 16# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 18"""DNS rdata.""" 19 20from importlib import import_module 21import base64 22import binascii 23import io 24import inspect 25import itertools 26 27import dns.wire 28import dns.exception 29import dns.name 30import dns.rdataclass 31import dns.rdatatype 32import dns.tokenizer 33 34_chunksize = 32 35 36 37def _wordbreak(data, chunksize=_chunksize): 38 """Break a binary string into chunks of chunksize characters separated by 39 a space. 40 """ 41 42 if not chunksize: 43 return data.decode() 44 return b' '.join([data[i:i + chunksize] 45 for i 46 in range(0, len(data), chunksize)]).decode() 47 48 49def _hexify(data, chunksize=_chunksize): 50 """Convert a binary string into its hex encoding, broken up into chunks 51 of chunksize characters separated by a space. 52 """ 53 54 return _wordbreak(binascii.hexlify(data), chunksize) 55 56 57def _base64ify(data, chunksize=_chunksize): 58 """Convert a binary string into its base64 encoding, broken up into chunks 59 of chunksize characters separated by a space. 60 """ 61 62 return _wordbreak(base64.b64encode(data), chunksize) 63 64__escaped = b'"\\' 65 66def _escapify(qstring): 67 """Escape the characters in a quoted string which need it.""" 68 69 if isinstance(qstring, str): 70 qstring = qstring.encode() 71 if not isinstance(qstring, bytearray): 72 qstring = bytearray(qstring) 73 74 text = '' 75 for c in qstring: 76 if c in __escaped: 77 text += '\\' + chr(c) 78 elif c >= 0x20 and c < 0x7F: 79 text += chr(c) 80 else: 81 text += '\\%03d' % c 82 return text 83 84 85def _truncate_bitmap(what): 86 """Determine the index of greatest byte that isn't all zeros, and 87 return the bitmap that contains all the bytes less than that index. 88 """ 89 90 for i in range(len(what) - 1, -1, -1): 91 if what[i] != 0: 92 return what[0: i + 1] 93 return what[0:1] 94 95def _constify(o): 96 """ 97 Convert mutable types to immutable types. 98 """ 99 if isinstance(o, bytearray): 100 return bytes(o) 101 if isinstance(o, tuple): 102 try: 103 hash(o) 104 return o 105 except Exception: 106 return tuple(_constify(elt) for elt in o) 107 if isinstance(o, list): 108 return tuple(_constify(elt) for elt in o) 109 return o 110 111class Rdata: 112 """Base class for all DNS rdata types.""" 113 114 __slots__ = ['rdclass', 'rdtype'] 115 116 def __init__(self, rdclass, rdtype): 117 """Initialize an rdata. 118 119 *rdclass*, an ``int`` is the rdataclass of the Rdata. 120 121 *rdtype*, an ``int`` is the rdatatype of the Rdata. 122 """ 123 124 object.__setattr__(self, 'rdclass', rdclass) 125 object.__setattr__(self, 'rdtype', rdtype) 126 127 def __setattr__(self, name, value): 128 # Rdatas are immutable 129 raise TypeError("object doesn't support attribute assignment") 130 131 def __delattr__(self, name): 132 # Rdatas are immutable 133 raise TypeError("object doesn't support attribute deletion") 134 135 def _get_all_slots(self): 136 return itertools.chain.from_iterable(getattr(cls, '__slots__', []) 137 for cls in self.__class__.__mro__) 138 139 def __getstate__(self): 140 # We used to try to do a tuple of all slots here, but it 141 # doesn't work as self._all_slots isn't available at 142 # __setstate__() time. Before that we tried to store a tuple 143 # of __slots__, but that didn't work as it didn't store the 144 # slots defined by ancestors. This older way didn't fail 145 # outright, but ended up with partially broken objects, e.g. 146 # if you unpickled an A RR it wouldn't have rdclass and rdtype 147 # attributes, and would compare badly. 148 state = {} 149 for slot in self._get_all_slots(): 150 state[slot] = getattr(self, slot) 151 return state 152 153 def __setstate__(self, state): 154 for slot, val in state.items(): 155 object.__setattr__(self, slot, val) 156 157 def covers(self): 158 """Return the type a Rdata covers. 159 160 DNS SIG/RRSIG rdatas apply to a specific type; this type is 161 returned by the covers() function. If the rdata type is not 162 SIG or RRSIG, dns.rdatatype.NONE is returned. This is useful when 163 creating rdatasets, allowing the rdataset to contain only RRSIGs 164 of a particular type, e.g. RRSIG(NS). 165 166 Returns an ``int``. 167 """ 168 169 return dns.rdatatype.NONE 170 171 def extended_rdatatype(self): 172 """Return a 32-bit type value, the least significant 16 bits of 173 which are the ordinary DNS type, and the upper 16 bits of which are 174 the "covered" type, if any. 175 176 Returns an ``int``. 177 """ 178 179 return self.covers() << 16 | self.rdtype 180 181 def to_text(self, origin=None, relativize=True, **kw): 182 """Convert an rdata to text format. 183 184 Returns a ``str``. 185 """ 186 187 raise NotImplementedError 188 189 def _to_wire(self, file, compress=None, origin=None, canonicalize=False): 190 raise NotImplementedError 191 192 def to_wire(self, file=None, compress=None, origin=None, 193 canonicalize=False): 194 """Convert an rdata to wire format. 195 196 Returns a ``bytes`` or ``None``. 197 """ 198 199 if file: 200 return self._to_wire(file, compress, origin, canonicalize) 201 else: 202 f = io.BytesIO() 203 self._to_wire(f, compress, origin, canonicalize) 204 return f.getvalue() 205 206 def to_generic(self, origin=None): 207 """Creates a dns.rdata.GenericRdata equivalent of this rdata. 208 209 Returns a ``dns.rdata.GenericRdata``. 210 """ 211 return dns.rdata.GenericRdata(self.rdclass, self.rdtype, 212 self.to_wire(origin=origin)) 213 214 def to_digestable(self, origin=None): 215 """Convert rdata to a format suitable for digesting in hashes. This 216 is also the DNSSEC canonical form. 217 218 Returns a ``bytes``. 219 """ 220 221 return self.to_wire(origin=origin, canonicalize=True) 222 223 def __repr__(self): 224 covers = self.covers() 225 if covers == dns.rdatatype.NONE: 226 ctext = '' 227 else: 228 ctext = '(' + dns.rdatatype.to_text(covers) + ')' 229 return '<DNS ' + dns.rdataclass.to_text(self.rdclass) + ' ' + \ 230 dns.rdatatype.to_text(self.rdtype) + ctext + ' rdata: ' + \ 231 str(self) + '>' 232 233 def __str__(self): 234 return self.to_text() 235 236 def _cmp(self, other): 237 """Compare an rdata with another rdata of the same rdtype and 238 rdclass. 239 240 Return < 0 if self < other in the DNSSEC ordering, 0 if self 241 == other, and > 0 if self > other. 242 243 """ 244 our = self.to_digestable(dns.name.root) 245 their = other.to_digestable(dns.name.root) 246 if our == their: 247 return 0 248 elif our > their: 249 return 1 250 else: 251 return -1 252 253 def __eq__(self, other): 254 if not isinstance(other, Rdata): 255 return False 256 if self.rdclass != other.rdclass or self.rdtype != other.rdtype: 257 return False 258 return self._cmp(other) == 0 259 260 def __ne__(self, other): 261 if not isinstance(other, Rdata): 262 return True 263 if self.rdclass != other.rdclass or self.rdtype != other.rdtype: 264 return True 265 return self._cmp(other) != 0 266 267 def __lt__(self, other): 268 if not isinstance(other, Rdata) or \ 269 self.rdclass != other.rdclass or self.rdtype != other.rdtype: 270 271 return NotImplemented 272 return self._cmp(other) < 0 273 274 def __le__(self, other): 275 if not isinstance(other, Rdata) or \ 276 self.rdclass != other.rdclass or self.rdtype != other.rdtype: 277 return NotImplemented 278 return self._cmp(other) <= 0 279 280 def __ge__(self, other): 281 if not isinstance(other, Rdata) or \ 282 self.rdclass != other.rdclass or self.rdtype != other.rdtype: 283 return NotImplemented 284 return self._cmp(other) >= 0 285 286 def __gt__(self, other): 287 if not isinstance(other, Rdata) or \ 288 self.rdclass != other.rdclass or self.rdtype != other.rdtype: 289 return NotImplemented 290 return self._cmp(other) > 0 291 292 def __hash__(self): 293 return hash(self.to_digestable(dns.name.root)) 294 295 @classmethod 296 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, 297 relativize_to=None): 298 raise NotImplementedError 299 300 @classmethod 301 def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): 302 raise NotImplementedError 303 304 def replace(self, **kwargs): 305 """ 306 Create a new Rdata instance based on the instance replace was 307 invoked on. It is possible to pass different parameters to 308 override the corresponding properties of the base Rdata. 309 310 Any field specific to the Rdata type can be replaced, but the 311 *rdtype* and *rdclass* fields cannot. 312 313 Returns an instance of the same Rdata subclass as *self*. 314 """ 315 316 # Get the constructor parameters. 317 parameters = inspect.signature(self.__init__).parameters 318 319 # Ensure that all of the arguments correspond to valid fields. 320 # Don't allow rdclass or rdtype to be changed, though. 321 for key in kwargs: 322 if key not in parameters: 323 raise AttributeError("'{}' object has no attribute '{}'" 324 .format(self.__class__.__name__, key)) 325 if key in ('rdclass', 'rdtype'): 326 raise AttributeError("Cannot overwrite '{}' attribute '{}'" 327 .format(self.__class__.__name__, key)) 328 329 # Construct the parameter list. For each field, use the value in 330 # kwargs if present, and the current value otherwise. 331 args = (kwargs.get(key, getattr(self, key)) for key in parameters) 332 333 # Create, validate, and return the new object. 334 # 335 # Note that if we make constructors do validation in the future, 336 # this validation can go away. 337 rd = self.__class__(*args) 338 dns.rdata.from_text(rd.rdclass, rd.rdtype, rd.to_text()) 339 return rd 340 341 342class GenericRdata(Rdata): 343 344 """Generic Rdata Class 345 346 This class is used for rdata types for which we have no better 347 implementation. It implements the DNS "unknown RRs" scheme. 348 """ 349 350 __slots__ = ['data'] 351 352 def __init__(self, rdclass, rdtype, data): 353 super().__init__(rdclass, rdtype) 354 object.__setattr__(self, 'data', data) 355 356 def to_text(self, origin=None, relativize=True, **kw): 357 return r'\# %d ' % len(self.data) + _hexify(self.data) 358 359 @classmethod 360 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, 361 relativize_to=None): 362 token = tok.get() 363 if not token.is_identifier() or token.value != r'\#': 364 raise dns.exception.SyntaxError( 365 r'generic rdata does not start with \#') 366 length = tok.get_int() 367 chunks = [] 368 while 1: 369 token = tok.get() 370 if token.is_eol_or_eof(): 371 break 372 chunks.append(token.value.encode()) 373 hex = b''.join(chunks) 374 data = binascii.unhexlify(hex) 375 if len(data) != length: 376 raise dns.exception.SyntaxError( 377 'generic rdata hex data has wrong length') 378 return cls(rdclass, rdtype, data) 379 380 def _to_wire(self, file, compress=None, origin=None, canonicalize=False): 381 file.write(self.data) 382 383 @classmethod 384 def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): 385 return cls(rdclass, rdtype, parser.get_remaining()) 386 387_rdata_classes = {} 388_module_prefix = 'dns.rdtypes' 389 390def get_rdata_class(rdclass, rdtype): 391 cls = _rdata_classes.get((rdclass, rdtype)) 392 if not cls: 393 cls = _rdata_classes.get((dns.rdatatype.ANY, rdtype)) 394 if not cls: 395 rdclass_text = dns.rdataclass.to_text(rdclass) 396 rdtype_text = dns.rdatatype.to_text(rdtype) 397 rdtype_text = rdtype_text.replace('-', '_') 398 try: 399 mod = import_module('.'.join([_module_prefix, 400 rdclass_text, rdtype_text])) 401 cls = getattr(mod, rdtype_text) 402 _rdata_classes[(rdclass, rdtype)] = cls 403 except ImportError: 404 try: 405 mod = import_module('.'.join([_module_prefix, 406 'ANY', rdtype_text])) 407 cls = getattr(mod, rdtype_text) 408 _rdata_classes[(dns.rdataclass.ANY, rdtype)] = cls 409 _rdata_classes[(rdclass, rdtype)] = cls 410 except ImportError: 411 pass 412 if not cls: 413 cls = GenericRdata 414 _rdata_classes[(rdclass, rdtype)] = cls 415 return cls 416 417 418def from_text(rdclass, rdtype, tok, origin=None, relativize=True, 419 relativize_to=None, idna_codec=None): 420 """Build an rdata object from text format. 421 422 This function attempts to dynamically load a class which 423 implements the specified rdata class and type. If there is no 424 class-and-type-specific implementation, the GenericRdata class 425 is used. 426 427 Once a class is chosen, its from_text() class method is called 428 with the parameters to this function. 429 430 If *tok* is a ``str``, then a tokenizer is created and the string 431 is used as its input. 432 433 *rdclass*, an ``int``, the rdataclass. 434 435 *rdtype*, an ``int``, the rdatatype. 436 437 *tok*, a ``dns.tokenizer.Tokenizer`` or a ``str``. 438 439 *origin*, a ``dns.name.Name`` (or ``None``), the 440 origin to use for relative names. 441 442 *relativize*, a ``bool``. If true, name will be relativized. 443 444 *relativize_to*, a ``dns.name.Name`` (or ``None``), the origin to use 445 when relativizing names. If not set, the *origin* value will be used. 446 447 *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA 448 encoder/decoder to use if a tokenizer needs to be created. If 449 ``None``, the default IDNA 2003 encoder/decoder is used. If a 450 tokenizer is not created, then the codec associated with the tokenizer 451 is the one that is used. 452 453 Returns an instance of the chosen Rdata subclass. 454 455 """ 456 457 if isinstance(tok, str): 458 tok = dns.tokenizer.Tokenizer(tok, idna_codec=idna_codec) 459 rdclass = dns.rdataclass.RdataClass.make(rdclass) 460 rdtype = dns.rdatatype.RdataType.make(rdtype) 461 cls = get_rdata_class(rdclass, rdtype) 462 if cls != GenericRdata: 463 # peek at first token 464 token = tok.get() 465 tok.unget(token) 466 if token.is_identifier() and \ 467 token.value == r'\#': 468 # 469 # Known type using the generic syntax. Extract the 470 # wire form from the generic syntax, and then run 471 # from_wire on it. 472 # 473 rdata = GenericRdata.from_text(rdclass, rdtype, tok, origin, 474 relativize, relativize_to) 475 return from_wire(rdclass, rdtype, rdata.data, 0, len(rdata.data), 476 origin) 477 return cls.from_text(rdclass, rdtype, tok, origin, relativize, 478 relativize_to) 479 480 481def from_wire_parser(rdclass, rdtype, parser, origin=None): 482 """Build an rdata object from wire format 483 484 This function attempts to dynamically load a class which 485 implements the specified rdata class and type. If there is no 486 class-and-type-specific implementation, the GenericRdata class 487 is used. 488 489 Once a class is chosen, its from_wire() class method is called 490 with the parameters to this function. 491 492 *rdclass*, an ``int``, the rdataclass. 493 494 *rdtype*, an ``int``, the rdatatype. 495 496 *parser*, a ``dns.wire.Parser``, the parser, which should be 497 restricted to the rdata length. 498 499 *origin*, a ``dns.name.Name`` (or ``None``). If not ``None``, 500 then names will be relativized to this origin. 501 502 Returns an instance of the chosen Rdata subclass. 503 """ 504 505 rdclass = dns.rdataclass.RdataClass.make(rdclass) 506 rdtype = dns.rdatatype.RdataType.make(rdtype) 507 cls = get_rdata_class(rdclass, rdtype) 508 return cls.from_wire_parser(rdclass, rdtype, parser, origin) 509 510 511def from_wire(rdclass, rdtype, wire, current, rdlen, origin=None): 512 """Build an rdata object from wire format 513 514 This function attempts to dynamically load a class which 515 implements the specified rdata class and type. If there is no 516 class-and-type-specific implementation, the GenericRdata class 517 is used. 518 519 Once a class is chosen, its from_wire() class method is called 520 with the parameters to this function. 521 522 *rdclass*, an ``int``, the rdataclass. 523 524 *rdtype*, an ``int``, the rdatatype. 525 526 *wire*, a ``bytes``, the wire-format message. 527 528 *current*, an ``int``, the offset in wire of the beginning of 529 the rdata. 530 531 *rdlen*, an ``int``, the length of the wire-format rdata 532 533 *origin*, a ``dns.name.Name`` (or ``None``). If not ``None``, 534 then names will be relativized to this origin. 535 536 Returns an instance of the chosen Rdata subclass. 537 """ 538 parser = dns.wire.Parser(wire, current) 539 with parser.restrict_to(rdlen): 540 return from_wire_parser(rdclass, rdtype, parser, origin) 541 542 543class RdatatypeExists(dns.exception.DNSException): 544 """DNS rdatatype already exists.""" 545 supp_kwargs = {'rdclass', 'rdtype'} 546 fmt = "The rdata type with class {rdclass} and rdtype {rdtype} " + \ 547 "already exists." 548 549 550def register_type(implementation, rdtype, rdtype_text, is_singleton=False, 551 rdclass=dns.rdataclass.IN): 552 """Dynamically register a module to handle an rdatatype. 553 554 *implementation*, a module implementing the type in the usual dnspython 555 way. 556 557 *rdtype*, an ``int``, the rdatatype to register. 558 559 *rdtype_text*, a ``str``, the textual form of the rdatatype. 560 561 *is_singleton*, a ``bool``, indicating if the type is a singleton (i.e. 562 RRsets of the type can have only one member.) 563 564 *rdclass*, the rdataclass of the type, or ``dns.rdataclass.ANY`` if 565 it applies to all classes. 566 """ 567 568 existing_cls = get_rdata_class(rdclass, rdtype) 569 if existing_cls != GenericRdata or dns.rdatatype.is_metatype(rdtype): 570 raise RdatatypeExists(rdclass=rdclass, rdtype=rdtype) 571 try: 572 if dns.rdatatype.RdataType(rdtype).name != rdtype_text: 573 raise RdatatypeExists(rdclass=rdclass, rdtype=rdtype) 574 except ValueError: 575 pass 576 _rdata_classes[(rdclass, rdtype)] = getattr(implementation, 577 rdtype_text.replace('-', '_')) 578 dns.rdatatype.register_type(rdtype, rdtype_text, is_singleton) 579