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