1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 from io import BytesIO
25 import struct
26 import sys
27 import copy
28 import encodings.idna
29
30 import dns.exception
31 import dns.wiredata
32
33 from ._compat import long, binary_type, text_type, unichr
34
35 try:
36 maxint = sys.maxint
37 except:
38 maxint = (1 << (8 * struct.calcsize("P"))) / 2 - 1
39
40 NAMERELN_NONE = 0
41 NAMERELN_SUPERDOMAIN = 1
42 NAMERELN_SUBDOMAIN = 2
43 NAMERELN_EQUAL = 3
44 NAMERELN_COMMONANCESTOR = 4
45
46
48
49 """A DNS label is empty."""
50
51
53
54 """An escaped code in a text format of DNS name is invalid."""
55
56
58
59 """A DNS compression pointer points forward instead of backward."""
60
61
63
64 """The label type in DNS name wire format is unknown."""
65
66
68
69 """An attempt was made to convert a non-absolute name to
70 wire when there was also a non-absolute (or missing) origin."""
71
72
76
77
81
82
84
85 """An attempt was made to append anything other than the
86 empty name to an absolute DNS name."""
87
88
89 -class NoParent(dns.exception.DNSException):
90
91 """An attempt was made to get the parent of the root name
92 or the empty name."""
93
94 _escaped = bytearray(b'"().;\\@$')
95
96
98 """Escape the characters in label which need it.
99 @param unicode_mode: escapify only special and whitespace (<= 0x20)
100 characters
101 @returns: the escaped string
102 @rtype: string"""
103 if not unicode_mode:
104 text = ''
105 if isinstance(label, text_type):
106 label = label.encode()
107 for c in bytearray(label):
108 packed = struct.pack('!B', c).decode()
109 if c in _escaped:
110 text += '\\' + packed
111 elif c > 0x20 and c < 0x7F:
112 text += packed
113 else:
114 text += '\\%03d' % c
115 return text.encode()
116
117 text = u''
118 if isinstance(label, binary_type):
119 label = label.decode()
120 for c in label:
121 if c > u'\x20' and c < u'\x7f':
122 text += c
123 else:
124 if c >= u'\x7f':
125 text += c
126 else:
127 text += u'\\%03d' % c
128 return text
129
130
132 """Check for empty labels in the middle of a label sequence,
133 labels that are too long, and for too many labels.
134 @raises NameTooLong: the name as a whole is too long
135 @raises EmptyLabel: a label is empty (i.e. the root label) and appears
136 in a position other than the end of the label sequence"""
137
138 l = len(labels)
139 total = 0
140 i = -1
141 j = 0
142 for label in labels:
143 ll = len(label)
144 total += ll + 1
145 if ll > 63:
146 raise LabelTooLong
147 if i < 0 and label == b'':
148 i = j
149 j += 1
150 if total > 255:
151 raise NameTooLong
152 if i >= 0 and i != l - 1:
153 raise EmptyLabel
154
155
157 if isinstance(label, binary_type):
158 return label
159 if isinstance(label, text_type):
160 return label.encode()
161 raise ValueError
162
163
165
166 """A DNS name.
167
168 The dns.name.Name class represents a DNS name as a tuple of labels.
169 Instances of the class are immutable.
170
171 @ivar labels: The tuple of labels in the name. Each label is a string of
172 up to 63 octets."""
173
174 __slots__ = ['labels']
175
177 """Initialize a domain name from a list of labels.
178 @param labels: the labels
179 @type labels: any iterable whose values are strings
180 """
181 labels = [_ensure_bytes(x) for x in labels]
182 super(Name, self).__setattr__('labels', tuple(labels))
183 _validate_labels(self.labels)
184
186 raise TypeError("object doesn't support attribute assignment")
187
190
193
195 return {'labels': self.labels}
196
200
202 """Is the most significant label of this name the root label?
203 @rtype: bool
204 """
205
206 return len(self.labels) > 0 and self.labels[-1] == b''
207
209 """Is this name wild? (I.e. Is the least significant label '*'?)
210 @rtype: bool
211 """
212
213 return len(self.labels) > 0 and self.labels[0] == b'*'
214
216 """Return a case-insensitive hash of the name.
217 @rtype: int
218 """
219
220 h = long(0)
221 for label in self.labels:
222 for c in bytearray(label.lower()):
223 h += (h << 3) + c
224 return int(h % maxint)
225
227 """Compare two names, returning a 3-tuple (relation, order, nlabels).
228
229 I{relation} describes the relation ship between the names,
230 and is one of: dns.name.NAMERELN_NONE,
231 dns.name.NAMERELN_SUPERDOMAIN, dns.name.NAMERELN_SUBDOMAIN,
232 dns.name.NAMERELN_EQUAL, or dns.name.NAMERELN_COMMONANCESTOR
233
234 I{order} is < 0 if self < other, > 0 if self > other, and ==
235 0 if self == other. A relative name is always less than an
236 absolute name. If both names have the same relativity, then
237 the DNSSEC order relation is used to order them.
238
239 I{nlabels} is the number of significant labels that the two names
240 have in common.
241 """
242
243 sabs = self.is_absolute()
244 oabs = other.is_absolute()
245 if sabs != oabs:
246 if sabs:
247 return (NAMERELN_NONE, 1, 0)
248 else:
249 return (NAMERELN_NONE, -1, 0)
250 l1 = len(self.labels)
251 l2 = len(other.labels)
252 ldiff = l1 - l2
253 if ldiff < 0:
254 l = l1
255 else:
256 l = l2
257
258 order = 0
259 nlabels = 0
260 namereln = NAMERELN_NONE
261 while l > 0:
262 l -= 1
263 l1 -= 1
264 l2 -= 1
265 label1 = self.labels[l1].lower()
266 label2 = other.labels[l2].lower()
267 if label1 < label2:
268 order = -1
269 if nlabels > 0:
270 namereln = NAMERELN_COMMONANCESTOR
271 return (namereln, order, nlabels)
272 elif label1 > label2:
273 order = 1
274 if nlabels > 0:
275 namereln = NAMERELN_COMMONANCESTOR
276 return (namereln, order, nlabels)
277 nlabels += 1
278 order = ldiff
279 if ldiff < 0:
280 namereln = NAMERELN_SUPERDOMAIN
281 elif ldiff > 0:
282 namereln = NAMERELN_SUBDOMAIN
283 else:
284 namereln = NAMERELN_EQUAL
285 return (namereln, order, nlabels)
286
287 - def is_subdomain(self, other):
288 """Is self a subdomain of other?
289
290 The notion of subdomain includes equality.
291 @rtype: bool
292 """
293
294 (nr, o, nl) = self.fullcompare(other)
295 if nr == NAMERELN_SUBDOMAIN or nr == NAMERELN_EQUAL:
296 return True
297 return False
298
299 - def is_superdomain(self, other):
300 """Is self a superdomain of other?
301
302 The notion of subdomain includes equality.
303 @rtype: bool
304 """
305
306 (nr, o, nl) = self.fullcompare(other)
307 if nr == NAMERELN_SUPERDOMAIN or nr == NAMERELN_EQUAL:
308 return True
309 return False
310
312 """Return a name which is equal to the current name, but is in
313 DNSSEC canonical form.
314 @rtype: dns.name.Name object
315 """
316
317 return Name([x.lower() for x in self.labels])
318
320 if isinstance(other, Name):
321 return self.fullcompare(other)[1] == 0
322 else:
323 return False
324
326 if isinstance(other, Name):
327 return self.fullcompare(other)[1] != 0
328 else:
329 return True
330
332 if isinstance(other, Name):
333 return self.fullcompare(other)[1] < 0
334 else:
335 return NotImplemented
336
338 if isinstance(other, Name):
339 return self.fullcompare(other)[1] <= 0
340 else:
341 return NotImplemented
342
344 if isinstance(other, Name):
345 return self.fullcompare(other)[1] >= 0
346 else:
347 return NotImplemented
348
350 if isinstance(other, Name):
351 return self.fullcompare(other)[1] > 0
352 else:
353 return NotImplemented
354
356 return '<DNS name ' + self.__str__() + '>'
357
359 return self.to_text(False).decode()
360
361 - def to_text(self, omit_final_dot=False):
362 """Convert name to text format.
363 @param omit_final_dot: If True, don't emit the final dot (denoting the
364 root label) for absolute names. The default is False.
365 @rtype: string
366 """
367
368 if len(self.labels) == 0:
369 return b'@'
370 if len(self.labels) == 1 and self.labels[0] == b'':
371 return b'.'
372 if omit_final_dot and self.is_absolute():
373 l = self.labels[:-1]
374 else:
375 l = self.labels
376 s = b'.'.join(map(_escapify, l))
377 return s
378
380 """Convert name to Unicode text format.
381
382 IDN ACE labels are converted to Unicode.
383
384 @param omit_final_dot: If True, don't emit the final dot (denoting the
385 root label) for absolute names. The default is False.
386 @rtype: string
387 """
388
389 if len(self.labels) == 0:
390 return u'@'
391 if len(self.labels) == 1 and self.labels[0] == '':
392 return u'.'
393 if omit_final_dot and self.is_absolute():
394 l = self.labels[:-1]
395 else:
396 l = self.labels
397 s = u'.'.join([_escapify(encodings.idna.ToUnicode(x), True)
398 for x in l])
399 return s
400
402 """Convert name to a format suitable for digesting in hashes.
403
404 The name is canonicalized and converted to uncompressed wire format.
405
406 @param origin: If the name is relative and origin is not None, then
407 origin will be appended to it.
408 @type origin: dns.name.Name object
409 @raises NeedAbsoluteNameOrOrigin: All names in wire format are
410 absolute. If self is a relative name, then an origin must be supplied;
411 if it is missing, then this exception is raised
412 @rtype: string
413 """
414
415 if not self.is_absolute():
416 if origin is None or not origin.is_absolute():
417 raise NeedAbsoluteNameOrOrigin
418 labels = list(self.labels)
419 labels.extend(list(origin.labels))
420 else:
421 labels = self.labels
422 dlabels = [struct.pack('!B%ds' % len(x), len(x), x.lower())
423 for x in labels]
424 return b''.join(dlabels)
425
426 - def to_wire(self, file=None, compress=None, origin=None):
427 """Convert name to wire format, possibly compressing it.
428
429 @param file: the file where the name is emitted (typically
430 a BytesIO file). If None, a string containing the wire name
431 will be returned.
432 @type file: file or None
433 @param compress: The compression table. If None (the default) names
434 will not be compressed.
435 @type compress: dict
436 @param origin: If the name is relative and origin is not None, then
437 origin will be appended to it.
438 @type origin: dns.name.Name object
439 @raises NeedAbsoluteNameOrOrigin: All names in wire format are
440 absolute. If self is a relative name, then an origin must be supplied;
441 if it is missing, then this exception is raised
442 """
443
444 if file is None:
445 file = BytesIO()
446 want_return = True
447 else:
448 want_return = False
449
450 if not self.is_absolute():
451 if origin is None or not origin.is_absolute():
452 raise NeedAbsoluteNameOrOrigin
453 labels = list(self.labels)
454 labels.extend(list(origin.labels))
455 else:
456 labels = self.labels
457 i = 0
458 for label in labels:
459 n = Name(labels[i:])
460 i += 1
461 if compress is not None:
462 pos = compress.get(n)
463 else:
464 pos = None
465 if pos is not None:
466 value = 0xc000 + pos
467 s = struct.pack('!H', value)
468 file.write(s)
469 break
470 else:
471 if compress is not None and len(n) > 1:
472 pos = file.tell()
473 if pos <= 0x3fff:
474 compress[n] = pos
475 l = len(label)
476 file.write(struct.pack('!B', l))
477 if l > 0:
478 file.write(label)
479 if want_return:
480 return file.getvalue()
481
483 """The length of the name (in labels).
484 @rtype: int
485 """
486
487 return len(self.labels)
488
491
493 return self.labels[start:stop]
494
497
500
502 """Split a name into a prefix and suffix at depth.
503
504 @param depth: the number of labels in the suffix
505 @type depth: int
506 @raises ValueError: the depth was not >= 0 and <= the length of the
507 name.
508 @returns: the tuple (prefix, suffix)
509 @rtype: tuple
510 """
511
512 l = len(self.labels)
513 if depth == 0:
514 return (self, dns.name.empty)
515 elif depth == l:
516 return (dns.name.empty, self)
517 elif depth < 0 or depth > l:
518 raise ValueError(
519 'depth must be >= 0 and <= the length of the name')
520 return (Name(self[: -depth]), Name(self[-depth:]))
521
523 """Return a new name which is the concatenation of self and other.
524 @rtype: dns.name.Name object
525 @raises AbsoluteConcatenation: self is absolute and other is
526 not the empty name
527 """
528
529 if self.is_absolute() and len(other) > 0:
530 raise AbsoluteConcatenation
531 labels = list(self.labels)
532 labels.extend(list(other.labels))
533 return Name(labels)
534
536 """If self is a subdomain of origin, return a new name which is self
537 relative to origin. Otherwise return self.
538 @rtype: dns.name.Name object
539 """
540
541 if origin is not None and self.is_subdomain(origin):
542 return Name(self[: -len(origin)])
543 else:
544 return self
545
547 """If self is a relative name, return a new name which is the
548 concatenation of self and origin. Otherwise return self.
549 @rtype: dns.name.Name object
550 """
551
552 if not self.is_absolute():
553 return self.concatenate(origin)
554 else:
555 return self
556
558 """Return a name with the relativity desired by the caller. If
559 origin is None, then self is returned. Otherwise, if
560 relativize is true the name is relativized, and if relativize is
561 false the name is derelativized.
562 @rtype: dns.name.Name object
563 """
564
565 if origin:
566 if relativize:
567 return self.relativize(origin)
568 else:
569 return self.derelativize(origin)
570 else:
571 return self
572
574 """Return the parent of the name.
575 @rtype: dns.name.Name object
576 @raises NoParent: the name is either the root name or the empty name,
577 and thus has no parent.
578 """
579 if self == root or self == empty:
580 raise NoParent
581 return Name(self.labels[1:])
582
583 root = Name([b''])
584 empty = Name([])
585
586
588 """Convert unicode text into a Name object.
589
590 Labels are encoded in IDN ACE form.
591
592 @rtype: dns.name.Name object
593 """
594
595 if not isinstance(text, text_type):
596 raise ValueError("input to from_unicode() must be a unicode string")
597 if not (origin is None or isinstance(origin, Name)):
598 raise ValueError("origin must be a Name or None")
599 labels = []
600 label = u''
601 escaping = False
602 edigits = 0
603 total = 0
604 if text == u'@':
605 text = u''
606 if text:
607 if text == u'.':
608 return Name([b''])
609 for c in text:
610 if escaping:
611 if edigits == 0:
612 if c.isdigit():
613 total = int(c)
614 edigits += 1
615 else:
616 label += c
617 escaping = False
618 else:
619 if not c.isdigit():
620 raise BadEscape
621 total *= 10
622 total += int(c)
623 edigits += 1
624 if edigits == 3:
625 escaping = False
626 label += unichr(total)
627 elif c in [u'.', u'\u3002', u'\uff0e', u'\uff61']:
628 if len(label) == 0:
629 raise EmptyLabel
630 try:
631 labels.append(encodings.idna.ToASCII(label))
632 except UnicodeError:
633 raise LabelTooLong
634 label = u''
635 elif c == u'\\':
636 escaping = True
637 edigits = 0
638 total = 0
639 else:
640 label += c
641 if escaping:
642 raise BadEscape
643 if len(label) > 0:
644 try:
645 labels.append(encodings.idna.ToASCII(label))
646 except UnicodeError:
647 raise LabelTooLong
648 else:
649 labels.append(b'')
650
651 if (len(labels) == 0 or labels[-1] != b'') and origin is not None:
652 labels.extend(list(origin.labels))
653 return Name(labels)
654
655
656 -def from_text(text, origin=root):
657 """Convert text into a Name object.
658 @rtype: dns.name.Name object
659 """
660
661 if isinstance(text, text_type):
662 return from_unicode(text, origin)
663 if not isinstance(text, binary_type):
664 raise ValueError("input to from_text() must be a string")
665 if not (origin is None or isinstance(origin, Name)):
666 raise ValueError("origin must be a Name or None")
667 labels = []
668 label = b''
669 escaping = False
670 edigits = 0
671 total = 0
672 if text == b'@':
673 text = b''
674 if text:
675 if text == b'.':
676 return Name([b''])
677 for c in bytearray(text):
678 byte_ = struct.pack('!B', c)
679 if escaping:
680 if edigits == 0:
681 if byte_.isdigit():
682 total = int(byte_)
683 edigits += 1
684 else:
685 label += byte_
686 escaping = False
687 else:
688 if not byte_.isdigit():
689 raise BadEscape
690 total *= 10
691 total += int(byte_)
692 edigits += 1
693 if edigits == 3:
694 escaping = False
695 label += struct.pack('!B', total)
696 elif byte_ == b'.':
697 if len(label) == 0:
698 raise EmptyLabel
699 labels.append(label)
700 label = b''
701 elif byte_ == b'\\':
702 escaping = True
703 edigits = 0
704 total = 0
705 else:
706 label += byte_
707 if escaping:
708 raise BadEscape
709 if len(label) > 0:
710 labels.append(label)
711 else:
712 labels.append(b'')
713 if (len(labels) == 0 or labels[-1] != b'') and origin is not None:
714 labels.extend(list(origin.labels))
715 return Name(labels)
716
717
719 """Convert possibly compressed wire format into a Name.
720 @param message: the entire DNS message
721 @type message: string
722 @param current: the offset of the beginning of the name from the start
723 of the message
724 @type current: int
725 @raises dns.name.BadPointer: a compression pointer did not point backwards
726 in the message
727 @raises dns.name.BadLabelType: an invalid label type was encountered.
728 @returns: a tuple consisting of the name that was read and the number
729 of bytes of the wire format message which were consumed reading it
730 @rtype: (dns.name.Name object, int) tuple
731 """
732
733 if not isinstance(message, binary_type):
734 raise ValueError("input to from_wire() must be a byte string")
735 message = dns.wiredata.maybe_wrap(message)
736 labels = []
737 biggest_pointer = current
738 hops = 0
739 count = message[current]
740 current += 1
741 cused = 1
742 while count != 0:
743 if count < 64:
744 labels.append(message[current: current + count].unwrap())
745 current += count
746 if hops == 0:
747 cused += count
748 elif count >= 192:
749 current = (count & 0x3f) * 256 + message[current]
750 if hops == 0:
751 cused += 1
752 if current >= biggest_pointer:
753 raise BadPointer
754 biggest_pointer = current
755 hops += 1
756 else:
757 raise BadLabelType
758 count = message[current]
759 current += 1
760 if hops == 0:
761 cused += 1
762 labels.append('')
763 return (Name(labels), cused)
764