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

Source Code for Module dns.name

  1  # Copyright (C) 2001-2007, 2009-2011 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 Names. 
 17   
 18  @var root: The DNS root name. 
 19  @type root: dns.name.Name object 
 20  @var empty: The empty DNS name. 
 21  @type empty: dns.name.Name object 
 22  """ 
 23   
 24  import encodings.idna 
 25  import io 
 26  import struct 
 27  import sys 
 28   
 29  import dns.exception 
 30  import dns.util 
 31  import dns.wiredata 
 32   
 33  NAMERELN_NONE = 0 
 34  NAMERELN_SUPERDOMAIN = 1 
 35  NAMERELN_SUBDOMAIN = 2 
 36  NAMERELN_EQUAL = 3 
 37  NAMERELN_COMMONANCESTOR = 4 
 38   
39 -class EmptyLabel(dns.exception.SyntaxError):
40 """Raised if a label is empty.""" 41 pass
42
43 -class BadEscape(dns.exception.SyntaxError):
44 """Raised if an escaped code in a text format name is invalid.""" 45 pass
46
47 -class BadPointer(dns.exception.FormError):
48 """Raised if a compression pointer points forward instead of backward.""" 49 pass
50
51 -class BadLabelType(dns.exception.FormError):
52 """Raised if the label type of a wire format name is unknown.""" 53 pass
54
55 -class NeedAbsoluteNameOrOrigin(dns.exception.DNSException):
56 """Raised if an attempt is made to convert a non-absolute name to 57 wire when there is also a non-absolute (or missing) origin.""" 58 pass
59
60 -class NameTooLong(dns.exception.FormError):
61 """Raised if a name is > 255 octets long.""" 62 pass
63
64 -class LabelTooLong(dns.exception.SyntaxError):
65 """Raised if a label is > 63 octets long.""" 66 pass
67
68 -class AbsoluteConcatenation(dns.exception.DNSException):
69 """Raised if an attempt is made to append anything other than the 70 empty name to an absolute name.""" 71 pass
72
73 -class NoParent(dns.exception.DNSException):
74 """Raised if an attempt is made to get the parent of the root name 75 or the empty name.""" 76 pass
77
78 -class LabelMixesUnicodeAndASCII(dns.exception.SyntaxError):
79 """Raised if a label mixes Unicode characters and ASCII escapes.""" 80 pass
81 82 _escaped = frozenset([ord(c) for c in '"().;\\@$']) 83
84 -def _escapify(label):
85 """Escape the characters in label which need it. 86 @returns: the escaped string 87 @rtype: string""" 88 text = '' 89 for c in label: 90 if c in _escaped: 91 text += '\\' + chr(c) 92 elif c > 0x20 and c < 0x7F: 93 text += chr(c) 94 else: 95 text += '\\%03d' % c 96 return text
97
98 -def _bytesify(label):
99 if isinstance(label, str): 100 return label.encode('latin_1') 101 elif not isinstance(label, bytes): 102 raise ValueError('label is not a bytes or a string') 103 else: 104 return label
105
106 -def _validate_labels(labels):
107 """Check for empty labels in the middle of a label sequence, 108 labels that are too long, and for too many labels. 109 @raises NameTooLong: the name as a whole is too long 110 @raises LabelTooLong: an individual label is too long 111 @raises EmptyLabel: a label is empty (i.e. the root label) and appears 112 in a position other than the end of the label sequence""" 113 114 l = len(labels) 115 total = 0 116 i = -1 117 j = 0 118 for label in labels: 119 if not isinstance(label, bytes): 120 raise ValueError("label is not a bytes object or a string") 121 ll = len(label) 122 total += ll + 1 123 if ll > 63: 124 raise LabelTooLong 125 if i < 0 and label == b'': 126 i = j 127 j += 1 128 if total > 255: 129 raise NameTooLong 130 if i >= 0 and i != l - 1: 131 raise EmptyLabel
132
133 -class Name(object):
134 """A DNS name. 135 136 The dns.name.Name class represents a DNS name as a tuple of labels. 137 Instances of the class are immutable. 138 139 @ivar labels: The tuple of labels in the name. Each label is a string of 140 up to 63 octets.""" 141 142 __slots__ = ['labels'] 143
144 - def __init__(self, labels):
145 """Initialize a domain name from a list of labels. 146 @param labels: the labels 147 @type labels: any iterable whose values are bytes objects or strings 148 containing only ISO Latin 1 characters (i.e. characters whose unicode 149 code points have values <= 255). 150 """ 151 152 labels = tuple([_bytesify(l) for l in labels]) 153 _validate_labels(labels) 154 super(Name, self).__setattr__('labels', labels)
155
156 - def __setattr__(self, name, value):
157 raise TypeError("object doesn't support attribute assignment")
158
159 - def is_absolute(self):
160 """Is the most significant label of this name the root label? 161 @rtype: bool 162 """ 163 164 return len(self.labels) > 0 and self.labels[-1] == b''
165
166 - def is_wild(self):
167 """Is this name wild? (I.e. Is the least significant label '*'?) 168 @rtype: bool 169 """ 170 171 return len(self.labels) > 0 and self.labels[0] == b'*'
172
173 - def __hash__(self):
174 """Return a case-insensitive hash of the name. 175 @rtype: int 176 """ 177 178 h = 0 179 for label in self.labels: 180 label = label.lower() 181 for c in label: 182 h += ( h << 3 ) + c 183 return int(h % 18446744073709551616)
184
185 - def fullcompare(self, other):
186 """Compare two names, returning a 3-tuple (relation, order, nlabels). 187 188 I{relation} describes the relation ship beween the names, 189 and is one of: dns.name.NAMERELN_NONE, 190 dns.name.NAMERELN_SUPERDOMAIN, dns.name.NAMERELN_SUBDOMAIN, 191 dns.name.NAMERELN_EQUAL, or dns.name.NAMERELN_COMMONANCESTOR 192 193 I{order} is < 0 if self < other, > 0 if self > other, and == 194 0 if self == other. A relative name is always less than an 195 absolute name. If both names have the same relativity, then 196 the DNSSEC order relation is used to order them. 197 198 I{nlabels} is the number of significant labels that the two names 199 have in common. 200 """ 201 202 sabs = self.is_absolute() 203 oabs = other.is_absolute() 204 if sabs != oabs: 205 if sabs: 206 return (NAMERELN_NONE, 1, 0) 207 else: 208 return (NAMERELN_NONE, -1, 0) 209 l1 = len(self.labels) 210 l2 = len(other.labels) 211 ldiff = l1 - l2 212 if ldiff < 0: 213 l = l1 214 else: 215 l = l2 216 217 order = 0 218 nlabels = 0 219 namereln = NAMERELN_NONE 220 while l > 0: 221 l -= 1 222 l1 -= 1 223 l2 -= 1 224 label1 = self.labels[l1].lower() 225 label2 = other.labels[l2].lower() 226 if label1 < label2: 227 order = -1 228 if nlabels > 0: 229 namereln = NAMERELN_COMMONANCESTOR 230 return (namereln, order, nlabels) 231 elif label1 > label2: 232 order = 1 233 if nlabels > 0: 234 namereln = NAMERELN_COMMONANCESTOR 235 return (namereln, order, nlabels) 236 nlabels += 1 237 order = ldiff 238 if ldiff < 0: 239 namereln = NAMERELN_SUPERDOMAIN 240 elif ldiff > 0: 241 namereln = NAMERELN_SUBDOMAIN 242 else: 243 namereln = NAMERELN_EQUAL 244 return (namereln, order, nlabels)
245
246 - def is_subdomain(self, other):
247 """Is self a subdomain of other? 248 249 The notion of subdomain includes equality. 250 @rtype: bool 251 """ 252 253 (nr, o, nl) = self.fullcompare(other) 254 if nr == NAMERELN_SUBDOMAIN or nr == NAMERELN_EQUAL: 255 return True 256 return False
257
258 - def is_superdomain(self, other):
259 """Is self a superdomain of other? 260 261 The notion of subdomain includes equality. 262 @rtype: bool 263 """ 264 265 (nr, o, nl) = self.fullcompare(other) 266 if nr == NAMERELN_SUPERDOMAIN or nr == NAMERELN_EQUAL: 267 return True 268 return False
269
270 - def canonicalize(self):
271 """Return a name which is equal to the current name, but is in 272 DNSSEC canonical form. 273 @rtype: dns.name.Name object 274 """ 275 276 return Name([x.lower() for x in self.labels])
277
278 - def __eq__(self, other):
279 if isinstance(other, Name): 280 return self.fullcompare(other)[1] == 0 281 else: 282 return False
283
284 - def __ne__(self, other):
285 if isinstance(other, Name): 286 return self.fullcompare(other)[1] != 0 287 else: 288 return True
289
290 - def __lt__(self, other):
291 if isinstance(other, Name): 292 return self.fullcompare(other)[1] < 0 293 else: 294 return NotImplemented
295
296 - def __le__(self, other):
297 if isinstance(other, Name): 298 return self.fullcompare(other)[1] <= 0 299 else: 300 return NotImplemented
301
302 - def __ge__(self, other):
303 if isinstance(other, Name): 304 return self.fullcompare(other)[1] >= 0 305 else: 306 return NotImplemented
307
308 - def __gt__(self, other):
309 if isinstance(other, Name): 310 return self.fullcompare(other)[1] > 0 311 else: 312 return NotImplemented
313
314 - def __repr__(self):
315 return '<DNS name ' + self.__str__() + '>'
316
317 - def __str__(self):
318 return self.to_text(False)
319
320 - def to_text(self, omit_final_dot = False):
321 """Convert name to text format. 322 @param omit_final_dot: If True, don't emit the final dot (denoting the 323 root label) for absolute names. The default is False. 324 @rtype: string 325 """ 326 327 if len(self.labels) == 0: 328 return '@' 329 if len(self.labels) == 1 and self.labels[0] == b'': 330 return '.' 331 if omit_final_dot and self.is_absolute(): 332 l = self.labels[:-1] 333 else: 334 l = self.labels 335 s = '.'.join(map(_escapify, l)) 336 return s
337
338 - def to_unicode(self, omit_final_dot = False):
339 """Convert name to Unicode text format. 340 341 IDN ACE lables are converted to Unicode. 342 343 @param omit_final_dot: If True, don't emit the final dot (denoting the 344 root label) for absolute names. The default is False. 345 @rtype: string 346 """ 347 348 if len(self.labels) == 0: 349 return '@' 350 if len(self.labels) == 1 and self.labels[0] == b'': 351 return '.' 352 if omit_final_dot and self.is_absolute(): 353 l = self.labels[:-1] 354 else: 355 l = self.labels 356 s = '.'.join([encodings.idna.ToUnicode(_escapify(x)) for x in l]) 357 return s
358
359 - def to_digestable(self, origin=None):
360 """Convert name to a format suitable for digesting in hashes. 361 362 The name is canonicalized and converted to uncompressed wire format. 363 364 @param origin: If the name is relative and origin is not None, then 365 origin will be appended to it. 366 @type origin: dns.name.Name object 367 @raises NeedAbsoluteNameOrOrigin: All names in wire format are 368 absolute. If self is a relative name, then an origin must be supplied; 369 if it is missing, then this exception is raised 370 @rtype: bytes 371 """ 372 373 if not self.is_absolute(): 374 if origin is None or not origin.is_absolute(): 375 raise NeedAbsoluteNameOrOrigin 376 labels = list(self.labels) 377 labels.extend(list(origin.labels)) 378 else: 379 labels = self.labels 380 ba = bytearray() 381 for label in labels: 382 ba.append(len(label)) 383 ba.extend(label.lower()) 384 return bytes(ba)
385
386 - def to_wire(self, file = None, compress = None, origin = None):
387 """Convert name to wire format, possibly compressing it. 388 389 @param file: the file where the name is emitted (typically 390 a io.BytesIO file). If None, a string containing the wire name 391 will be returned. 392 @type file: bytearray or None 393 @param compress: The compression table. If None (the default) names 394 will not be compressed. 395 @type compress: dict 396 @param origin: If the name is relative and origin is not None, then 397 origin will be appended to it. 398 @type origin: dns.name.Name object 399 @raises NeedAbsoluteNameOrOrigin: All names in wire format are 400 absolute. If self is a relative name, then an origin must be supplied; 401 if it is missing, then this exception is raised 402 """ 403 404 if file is None: 405 file = io.BytesIO() 406 want_return = True 407 else: 408 want_return = False 409 410 if not self.is_absolute(): 411 if origin is None or not origin.is_absolute(): 412 raise NeedAbsoluteNameOrOrigin 413 labels = list(self.labels) 414 labels.extend(list(origin.labels)) 415 else: 416 labels = self.labels 417 i = 0 418 for label in labels: 419 n = Name(labels[i:]) 420 i += 1 421 if not compress is None: 422 pos = compress.get(n) 423 else: 424 pos = None 425 if not pos is None: 426 value = 0xc000 + pos 427 dns.util.write_uint16(file, value) 428 break 429 else: 430 if not compress is None and len(n) > 1: 431 pos = file.tell() 432 if pos < 0xc000: 433 compress[n] = pos 434 l = len(label) 435 dns.util.write_uint8(file, l) 436 if l > 0: 437 file.write(label) 438 if want_return: 439 return file.getvalue()
440
441 - def __len__(self):
442 """The length of the name (in labels). 443 @rtype: int 444 """ 445 446 return len(self.labels)
447
448 - def __getitem__(self, index):
449 return self.labels[index]
450
451 - def __getslice__(self, start, stop):
452 return self.labels[start:stop]
453
454 - def __add__(self, other):
455 return self.concatenate(other)
456
457 - def __sub__(self, other):
458 return self.relativize(other)
459
460 - def split(self, depth):
461 """Split a name into a prefix and suffix at depth. 462 463 @param depth: the number of labels in the suffix 464 @type depth: int 465 @raises ValueError: the depth was not >= 0 and <= the length of the 466 name. 467 @returns: the tuple (prefix, suffix) 468 @rtype: tuple 469 """ 470 471 l = len(self.labels) 472 if depth == 0: 473 return (self, dns.name.empty) 474 elif depth == l: 475 return (dns.name.empty, self) 476 elif depth < 0 or depth > l: 477 raise ValueError('depth must be >= 0 and <= the length of the name') 478 return (Name(self[: -depth]), Name(self[-depth :]))
479
480 - def concatenate(self, other):
481 """Return a new name which is the concatenation of self and other. 482 @rtype: dns.name.Name object 483 @raises AbsoluteConcatenation: self is absolute and other is 484 not the empty name 485 """ 486 487 if self.is_absolute() and len(other) > 0: 488 raise AbsoluteConcatenation 489 labels = list(self.labels) 490 labels.extend(list(other.labels)) 491 return Name(labels)
492
493 - def relativize(self, origin):
494 """If self is a subdomain of origin, return a new name which is self 495 relative to origin. Otherwise return self. 496 @rtype: dns.name.Name object 497 """ 498 499 if not origin is None and self.is_subdomain(origin): 500 return Name(self[: -len(origin)]) 501 else: 502 return self
503
504 - def derelativize(self, origin):
505 """If self is a relative name, return a new name which is the 506 concatenation of self and origin. Otherwise return self. 507 @rtype: dns.name.Name object 508 """ 509 510 if not self.is_absolute(): 511 return self.concatenate(origin) 512 else: 513 return self
514
515 - def choose_relativity(self, origin=None, relativize=True):
516 """Return a name with the relativity desired by the caller. If 517 origin is None, then self is returned. Otherwise, if 518 relativize is true the name is relativized, and if relativize is 519 false the name is derelativized. 520 @rtype: dns.name.Name object 521 """ 522 523 if origin: 524 if relativize: 525 return self.relativize(origin) 526 else: 527 return self.derelativize(origin) 528 else: 529 return self
530
531 - def parent(self):
532 """Return the parent of the name. 533 @rtype: dns.name.Name object 534 @raises NoParent: the name is either the root name or the empty name, 535 and thus has no parent. 536 """ 537 if self == root or self == empty: 538 raise NoParent 539 return Name(self.labels[1:])
540 541 root = Name([b'']) 542 empty = Name([]) 543
544 -def from_text(text, origin = root):
545 """Convert unicode text into a Name object. 546 547 Lables are encoded in IDN ACE form. 548 549 @rtype: dns.name.Name object 550 """ 551 552 if not (origin is None or isinstance(origin, Name)): 553 raise ValueError("origin must be a Name or None") 554 labels = [] 555 label = '' 556 escaping = False 557 seen_non_ascii = False 558 seen_non_ascii_escape = False 559 edigits = 0 560 total = 0 561 if text == '@': 562 text = '' 563 if text: 564 if text == '.': 565 return Name([b'']) 566 for c in text: 567 if escaping: 568 if edigits == 0: 569 if c.isdigit(): 570 total = int(c) 571 edigits += 1 572 else: 573 label += c 574 escaping = False 575 if ord(c) > 127: 576 seen_non_ascii = True 577 else: 578 if not c.isdigit(): 579 raise BadEscape 580 total *= 10 581 total += int(c) 582 edigits += 1 583 if edigits == 3: 584 escaping = False 585 label += chr(total) 586 if total > 127: 587 seen_non_ascii_escape = True 588 elif c == '.' or c == '\u3002' or \ 589 c == '\uff0e' or c == '\uff61': 590 if len(label) == 0: 591 raise EmptyLabel 592 if seen_non_ascii: 593 if seen_non_ascii_escape: 594 raise LabelMixesUnicodeAndASCII 595 labels.append(encodings.idna.ToASCII(label)) 596 else: 597 labels.append(label.encode('latin_1')) 598 label = '' 599 seen_non_ascii = False 600 seen_non_ascii_escape = False 601 elif c == '\\': 602 escaping = True 603 edigits = 0 604 total = 0 605 else: 606 label += c 607 if ord(c) > 127: 608 seen_non_ascii = True 609 if escaping: 610 raise BadEscape 611 if len(label) > 0: 612 if seen_non_ascii: 613 if seen_non_ascii_escape: 614 raise LabelMixesUnicodeAndASCII 615 labels.append(encodings.idna.ToASCII(label)) 616 else: 617 labels.append(label.encode('latin_1')) 618 else: 619 labels.append(b'') 620 if (len(labels) == 0 or labels[-1] != b'') and not origin is None: 621 labels.extend(list(origin.labels)) 622 return Name(labels)
623
624 -def from_wire(message, current):
625 """Convert possibly compressed wire format into a Name. 626 @param message: the entire DNS message 627 @type message: bytes 628 @param current: the offset of the beginning of the name from the start 629 of the message 630 @type current: int 631 @raises dns.name.BadPointer: a compression pointer did not point backwards 632 in the message 633 @raises dns.name.BadLabelType: an invalid label type was encountered. 634 @returns: a tuple consisting of the name that was read and the number 635 of bytes of the wire format message which were consumed reading it 636 @rtype: (dns.name.Name object, int) tuple 637 """ 638 639 if not isinstance(message, bytes): 640 raise ValueError("input to from_wire() must be a byte string") 641 message = dns.wiredata.maybe_wrap(message) 642 labels = [] 643 biggest_pointer = current 644 hops = 0 645 count = message[current] 646 current += 1 647 cused = 1 648 while count != 0: 649 if count < 64: 650 labels.append(message[current : current + count].unwrap()) 651 current += count 652 if hops == 0: 653 cused += count 654 elif count >= 192: 655 current = (count & 0x3f) * 256 + message[current] 656 if hops == 0: 657 cused += 1 658 if current >= biggest_pointer: 659 raise BadPointer 660 biggest_pointer = current 661 hops += 1 662 else: 663 raise BadLabelType 664 count = message[current] 665 current += 1 666 if hops == 0: 667 cused += 1 668 labels.append(b'') 669 return (Name(labels), cused)
670