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 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
40 """Raised if a label is empty."""
41 pass
42
44 """Raised if an escaped code in a text format name is invalid."""
45 pass
46
48 """Raised if a compression pointer points forward instead of backward."""
49 pass
50
52 """Raised if the label type of a wire format name is unknown."""
53 pass
54
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
63
67
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
79 """Raised if a label mixes Unicode characters and ASCII escapes."""
80 pass
81
82 _escaped = frozenset([ord(c) for c in '"().;\\@$'])
83
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
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
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
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
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
157 raise TypeError("object doesn't support attribute assignment")
158
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
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
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
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
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
279 if isinstance(other, Name):
280 return self.fullcompare(other)[1] == 0
281 else:
282 return False
283
285 if isinstance(other, Name):
286 return self.fullcompare(other)[1] != 0
287 else:
288 return True
289
291 if isinstance(other, Name):
292 return self.fullcompare(other)[1] < 0
293 else:
294 return NotImplemented
295
297 if isinstance(other, Name):
298 return self.fullcompare(other)[1] <= 0
299 else:
300 return NotImplemented
301
303 if isinstance(other, Name):
304 return self.fullcompare(other)[1] >= 0
305 else:
306 return NotImplemented
307
309 if isinstance(other, Name):
310 return self.fullcompare(other)[1] > 0
311 else:
312 return NotImplemented
313
315 return '<DNS name ' + self.__str__() + '>'
316
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
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
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
442 """The length of the name (in labels).
443 @rtype: int
444 """
445
446 return len(self.labels)
447
450
452 return self.labels[start:stop]
453
456
459
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
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
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
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
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
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
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