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