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 """A DNS label is empty."""
43
45 """An escaped code in a text format of DNS name is invalid."""
46
48 """A DNS compression pointer points forward instead of backward."""
49
51 """The label type in DNS name wire format is unknown."""
52
54 """An attempt was made to convert a non-absolute name to
55 wire when there was also a non-absolute (or missing) origin."""
56
59
62
64 """An attempt was made to append anything other than the
65 empty name to an absolute DNS name."""
66
67 -class NoParent(dns.exception.DNSException):
68 """An attempt was made to get the parent of the root name
69 or the empty name."""
70
71 _escaped = {
72 '"' : True,
73 '(' : True,
74 ')' : True,
75 '.' : True,
76 ';' : True,
77 '\\' : True,
78 '@' : True,
79 '$' : True
80 }
81
83 """Escape the characters in label which need it.
84 @param unicode_mode: escapify only special and whitespace (<= 0x20)
85 characters
86 @returns: the escaped string
87 @rtype: string"""
88 text = ''
89 for c in label:
90 if c in _escaped:
91 text += '\\' + c
92 elif ord(c) > 0x20 and ord(c) < 0x7F:
93 text += c
94 else:
95 if unicode_mode and ord(c) >= 0x7F:
96 text += c
97 else:
98 text += '\\%03d' % ord(c)
99 return text
100
102 """Check for empty labels in the middle of a label sequence,
103 labels that are too long, and for too many labels.
104 @raises NameTooLong: the name as a whole is too long
105 @raises LabelTooLong: an individual label is too long
106 @raises EmptyLabel: a label is empty (i.e. the root label) and appears
107 in a position other than the end of the label sequence"""
108
109 l = len(labels)
110 total = 0
111 i = -1
112 j = 0
113 for label in labels:
114 ll = len(label)
115 total += ll + 1
116 if ll > 63:
117 raise LabelTooLong
118 if i < 0 and label == '':
119 i = j
120 j += 1
121 if total > 255:
122 raise NameTooLong
123 if i >= 0 and i != l - 1:
124 raise EmptyLabel
125
127 """A DNS name.
128
129 The dns.name.Name class represents a DNS name as a tuple of labels.
130 Instances of the class are immutable.
131
132 @ivar labels: The tuple of labels in the name. Each label is a string of
133 up to 63 octets."""
134
135 __slots__ = ['labels']
136
138 """Initialize a domain name from a list of labels.
139 @param labels: the labels
140 @type labels: any iterable whose values are strings
141 """
142
143 super(Name, self).__setattr__('labels', tuple(labels))
144 _validate_labels(self.labels)
145
147 raise TypeError("object doesn't support attribute assignment")
148
151
154
156 return { 'labels' : self.labels }
157
161
163 """Is the most significant label of this name the root label?
164 @rtype: bool
165 """
166
167 return len(self.labels) > 0 and self.labels[-1] == ''
168
170 """Is this name wild? (I.e. Is the least significant label '*'?)
171 @rtype: bool
172 """
173
174 return len(self.labels) > 0 and self.labels[0] == '*'
175
177 """Return a case-insensitive hash of the name.
178 @rtype: int
179 """
180
181 h = 0L
182 for label in self.labels:
183 for c in label:
184 h += ( h << 3 ) + ord(c.lower())
185 return int(h % sys.maxint)
186
188 """Compare two names, returning a 3-tuple (relation, order, nlabels).
189
190 I{relation} describes the relation ship beween the names,
191 and is one of: dns.name.NAMERELN_NONE,
192 dns.name.NAMERELN_SUPERDOMAIN, dns.name.NAMERELN_SUBDOMAIN,
193 dns.name.NAMERELN_EQUAL, or dns.name.NAMERELN_COMMONANCESTOR
194
195 I{order} is < 0 if self < other, > 0 if self > other, and ==
196 0 if self == other. A relative name is always less than an
197 absolute name. If both names have the same relativity, then
198 the DNSSEC order relation is used to order them.
199
200 I{nlabels} is the number of significant labels that the two names
201 have in common.
202 """
203
204 sabs = self.is_absolute()
205 oabs = other.is_absolute()
206 if sabs != oabs:
207 if sabs:
208 return (NAMERELN_NONE, 1, 0)
209 else:
210 return (NAMERELN_NONE, -1, 0)
211 l1 = len(self.labels)
212 l2 = len(other.labels)
213 ldiff = l1 - l2
214 if ldiff < 0:
215 l = l1
216 else:
217 l = l2
218
219 order = 0
220 nlabels = 0
221 namereln = NAMERELN_NONE
222 while l > 0:
223 l -= 1
224 l1 -= 1
225 l2 -= 1
226 label1 = self.labels[l1].lower()
227 label2 = other.labels[l2].lower()
228 if label1 < label2:
229 order = -1
230 if nlabels > 0:
231 namereln = NAMERELN_COMMONANCESTOR
232 return (namereln, order, nlabels)
233 elif label1 > label2:
234 order = 1
235 if nlabels > 0:
236 namereln = NAMERELN_COMMONANCESTOR
237 return (namereln, order, nlabels)
238 nlabels += 1
239 order = ldiff
240 if ldiff < 0:
241 namereln = NAMERELN_SUPERDOMAIN
242 elif ldiff > 0:
243 namereln = NAMERELN_SUBDOMAIN
244 else:
245 namereln = NAMERELN_EQUAL
246 return (namereln, order, nlabels)
247
248 - def is_subdomain(self, other):
249 """Is self a subdomain of other?
250
251 The notion of subdomain includes equality.
252 @rtype: bool
253 """
254
255 (nr, o, nl) = self.fullcompare(other)
256 if nr == NAMERELN_SUBDOMAIN or nr == NAMERELN_EQUAL:
257 return True
258 return False
259
260 - def is_superdomain(self, other):
261 """Is self a superdomain of other?
262
263 The notion of subdomain includes equality.
264 @rtype: bool
265 """
266
267 (nr, o, nl) = self.fullcompare(other)
268 if nr == NAMERELN_SUPERDOMAIN or nr == NAMERELN_EQUAL:
269 return True
270 return False
271
273 """Return a name which is equal to the current name, but is in
274 DNSSEC canonical form.
275 @rtype: dns.name.Name object
276 """
277
278 return Name([x.lower() for x in self.labels])
279
281 if isinstance(other, Name):
282 return self.fullcompare(other)[1] == 0
283 else:
284 return False
285
287 if isinstance(other, Name):
288 return self.fullcompare(other)[1] != 0
289 else:
290 return True
291
293 if isinstance(other, Name):
294 return self.fullcompare(other)[1] < 0
295 else:
296 return NotImplemented
297
299 if isinstance(other, Name):
300 return self.fullcompare(other)[1] <= 0
301 else:
302 return NotImplemented
303
305 if isinstance(other, Name):
306 return self.fullcompare(other)[1] >= 0
307 else:
308 return NotImplemented
309
311 if isinstance(other, Name):
312 return self.fullcompare(other)[1] > 0
313 else:
314 return NotImplemented
315
317 return '<DNS name ' + self.__str__() + '>'
318
321
322 - def to_text(self, omit_final_dot = False):
323 """Convert name to text format.
324 @param omit_final_dot: If True, don't emit the final dot (denoting the
325 root label) for absolute names. The default is False.
326 @rtype: string
327 """
328
329 if len(self.labels) == 0:
330 return '@'
331 if len(self.labels) == 1 and self.labels[0] == '':
332 return '.'
333 if omit_final_dot and self.is_absolute():
334 l = self.labels[:-1]
335 else:
336 l = self.labels
337 s = '.'.join(map(_escapify, l))
338 return s
339
341 """Convert name to Unicode text format.
342
343 IDN ACE lables are converted to Unicode.
344
345 @param omit_final_dot: If True, don't emit the final dot (denoting the
346 root label) for absolute names. The default is False.
347 @rtype: string
348 """
349
350 if len(self.labels) == 0:
351 return u'@'
352 if len(self.labels) == 1 and self.labels[0] == '':
353 return u'.'
354 if omit_final_dot and self.is_absolute():
355 l = self.labels[:-1]
356 else:
357 l = self.labels
358 s = u'.'.join([_escapify(encodings.idna.ToUnicode(x), True) for x in l])
359 return s
360
362 """Convert name to a format suitable for digesting in hashes.
363
364 The name is canonicalized and converted to uncompressed wire format.
365
366 @param origin: If the name is relative and origin is not None, then
367 origin will be appended to it.
368 @type origin: dns.name.Name object
369 @raises NeedAbsoluteNameOrOrigin: All names in wire format are
370 absolute. If self is a relative name, then an origin must be supplied;
371 if it is missing, then this exception is raised
372 @rtype: string
373 """
374
375 if not self.is_absolute():
376 if origin is None or not origin.is_absolute():
377 raise NeedAbsoluteNameOrOrigin
378 labels = list(self.labels)
379 labels.extend(list(origin.labels))
380 else:
381 labels = self.labels
382 dlabels = ["%s%s" % (chr(len(x)), x.lower()) for x in labels]
383 return ''.join(dlabels)
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 cStringIO file). If None, a string containing the wire name
390 will be returned.
391 @type file: file 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 = cStringIO.StringIO()
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 s = struct.pack('!H', value)
427 file.write(s)
428 break
429 else:
430 if not compress is None and len(n) > 1:
431 pos = file.tell()
432 if pos <= 0x3fff:
433 compress[n] = pos
434 l = len(label)
435 file.write(chr(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([''])
542 empty = Name([])
543
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 isinstance(text, unicode):
553 raise ValueError("input to from_unicode() must be a unicode string")
554 if not (origin is None or isinstance(origin, Name)):
555 raise ValueError("origin must be a Name or None")
556 labels = []
557 label = u''
558 escaping = False
559 edigits = 0
560 total = 0
561 if text == u'@':
562 text = u''
563 if text:
564 if text == u'.':
565 return Name([''])
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 else:
576 if not c.isdigit():
577 raise BadEscape
578 total *= 10
579 total += int(c)
580 edigits += 1
581 if edigits == 3:
582 escaping = False
583 label += chr(total)
584 elif c == u'.' or c == u'\u3002' or \
585 c == u'\uff0e' or c == u'\uff61':
586 if len(label) == 0:
587 raise EmptyLabel
588 if len(label) > 63:
589
590 raise LabelTooLong
591 labels.append(encodings.idna.ToASCII(label))
592 label = u''
593 elif c == u'\\':
594 escaping = True
595 edigits = 0
596 total = 0
597 else:
598 label += c
599 if escaping:
600 raise BadEscape
601 if len(label) > 63:
602
603 raise LabelTooLong
604 if len(label) > 0:
605 labels.append(encodings.idna.ToASCII(label))
606 else:
607 labels.append('')
608 if (len(labels) == 0 or labels[-1] != '') and not origin is None:
609 labels.extend(list(origin.labels))
610 return Name(labels)
611
612 -def from_text(text, origin = root):
613 """Convert text into a Name object.
614 @rtype: dns.name.Name object
615 """
616
617 if not isinstance(text, str):
618 if isinstance(text, unicode) and sys.hexversion >= 0x02030000:
619 return from_unicode(text, origin)
620 else:
621 raise ValueError("input to from_text() must be a string")
622 if not (origin is None or isinstance(origin, Name)):
623 raise ValueError("origin must be a Name or None")
624 labels = []
625 label = ''
626 escaping = False
627 edigits = 0
628 total = 0
629 if text == '@':
630 text = ''
631 if text:
632 if text == '.':
633 return Name([''])
634 for c in text:
635 if escaping:
636 if edigits == 0:
637 if c.isdigit():
638 total = int(c)
639 edigits += 1
640 else:
641 label += c
642 escaping = False
643 else:
644 if not c.isdigit():
645 raise BadEscape
646 total *= 10
647 total += int(c)
648 edigits += 1
649 if edigits == 3:
650 escaping = False
651 label += chr(total)
652 elif c == '.':
653 if len(label) == 0:
654 raise EmptyLabel
655 labels.append(label)
656 label = ''
657 elif c == '\\':
658 escaping = True
659 edigits = 0
660 total = 0
661 else:
662 label += c
663 if escaping:
664 raise BadEscape
665 if len(label) > 0:
666 labels.append(label)
667 else:
668 labels.append('')
669 if (len(labels) == 0 or labels[-1] != '') and not origin is None:
670 labels.extend(list(origin.labels))
671 return Name(labels)
672
674 """Convert possibly compressed wire format into a Name.
675 @param message: the entire DNS message
676 @type message: string
677 @param current: the offset of the beginning of the name from the start
678 of the message
679 @type current: int
680 @raises dns.name.BadPointer: a compression pointer did not point backwards
681 in the message
682 @raises dns.name.BadLabelType: an invalid label type was encountered.
683 @returns: a tuple consisting of the name that was read and the number
684 of bytes of the wire format message which were consumed reading it
685 @rtype: (dns.name.Name object, int) tuple
686 """
687
688 if not isinstance(message, str):
689 raise ValueError("input to from_wire() must be a byte string")
690 message = dns.wiredata.maybe_wrap(message)
691 labels = []
692 biggest_pointer = current
693 hops = 0
694 count = ord(message[current])
695 current += 1
696 cused = 1
697 while count != 0:
698 if count < 64:
699 labels.append(message[current : current + count].unwrap())
700 current += count
701 if hops == 0:
702 cused += count
703 elif count >= 192:
704 current = (count & 0x3f) * 256 + ord(message[current])
705 if hops == 0:
706 cused += 1
707 if current >= biggest_pointer:
708 raise BadPointer
709 biggest_pointer = current
710 hops += 1
711 else:
712 raise BadLabelType
713 count = ord(message[current])
714 current += 1
715 if hops == 0:
716 cused += 1
717 labels.append('')
718 return (Name(labels), cused)
719