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