Package dns :: Module name |
[hide private]
[frames] | no frames]

# Source Code for Module dns.name

      1  # Copyright (C) 2001-2007, 2009-2011 Nominum, Inc.
      2  #
      3  # Permission to use, copy, modify, and distribute this software and its
      4  # documentation for any purpose with or without fee is hereby granted,
      5  # provided that the above copyright notice and this permission notice
      6  # appear in all copies.
      7  #
      8  # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
      9  # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
     11  # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
     14  # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     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
     41 -class EmptyLabel(dns.exception.SyntaxError):
     42      """Raised if a label is empty."""
     43      pass
     44
     45 -class BadEscape(dns.exception.SyntaxError):
     46      """Raised if an escaped code in a text format name is invalid."""
     47      pass
     48
     49 -class BadPointer(dns.exception.FormError):
     50      """Raised if a compression pointer points forward instead of backward."""
     51      pass
     52
     53 -class BadLabelType(dns.exception.FormError):
     54      """Raised if the label type of a wire format name is unknown."""
     55      pass
     56
     57 -class NeedAbsoluteNameOrOrigin(dns.exception.DNSException):
     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
     62 -class NameTooLong(dns.exception.FormError):
     63      """Raised if a name is > 255 octets long."""
     64      pass
     65
     66 -class LabelTooLong(dns.exception.SyntaxError):
     67      """Raised if a label is > 63 octets long."""
     68      pass
     69
     70 -class AbsoluteConcatenation(dns.exception.DNSException):
     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
     91 -def _escapify(label, unicode_mode=False):
     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
    110 -def _validate_labels(labels):
    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
    135 -class Name(object):
    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
    146 -    def __init__(self, labels):
    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
    155 -    def __setattr__(self, name, value):
    156          raise TypeError("object doesn't support attribute assignment")
    157
    158 -    def __copy__(self):
    159          return Name(self.labels)
    160
    161 -    def __deepcopy__(self, memo):
    162          return Name(copy.deepcopy(self.labels, memo))
    163
    164 -    def is_absolute(self):
    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
    171 -    def is_wild(self):
    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
    178 -    def __hash__(self):
    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
    189 -    def fullcompare(self, other):
    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
    274 -    def canonicalize(self):
    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
    282 -    def __eq__(self, other):
    283          if isinstance(other, Name):
    284              return self.fullcompare(other)[1] == 0
    285          else:
    286              return False
    287
    288 -    def __ne__(self, other):
    289          if isinstance(other, Name):
    290              return self.fullcompare(other)[1] != 0
    291          else:
    292              return True
    293
    294 -    def __lt__(self, other):
    295          if isinstance(other, Name):
    296              return self.fullcompare(other)[1] < 0
    297          else:
    298              return NotImplemented
    299
    300 -    def __le__(self, other):
    301          if isinstance(other, Name):
    302              return self.fullcompare(other)[1] <= 0
    303          else:
    304              return NotImplemented
    305
    306 -    def __ge__(self, other):
    307          if isinstance(other, Name):
    308              return self.fullcompare(other)[1] >= 0
    309          else:
    310              return NotImplemented
    311
    312 -    def __gt__(self, other):
    313          if isinstance(other, Name):
    314              return self.fullcompare(other)[1] > 0
    315          else:
    316              return NotImplemented
    317
    318 -    def __repr__(self):
    319          return '<DNS name ' + self.__str__() + '>'
    320
    321 -    def __str__(self):
    322          return self.to_text(False)
    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
    342 -    def to_unicode(self, omit_final_dot = False):
    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
    363 -    def to_digestable(self, origin=None):
    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
    443 -    def __len__(self):
    444          """The length of the name (in labels).
    445          @rtype: int
    446          """
    447
    448          return len(self.labels)
    449
    450 -    def __getitem__(self, index):
    451          return self.labels[index]
    452
    453 -    def __getslice__(self, start, stop):
    454          return self.labels[start:stop]
    455
    456 -    def __add__(self, other):
    457          return self.concatenate(other)
    458
    459 -    def __sub__(self, other):
    460          return self.relativize(other)
    461
    462 -    def split(self, depth):
    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
    482 -    def concatenate(self, other):
    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
    495 -    def relativize(self, origin):
    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
    506 -    def derelativize(self, origin):
    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
    517 -    def choose_relativity(self, origin=None, relativize=True):
    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
    533 -    def parent(self):
    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
    546 -def from_unicode(text, origin = root):
    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([''])   # no Unicode "u" on this constant!
    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
    669 -def from_wire(message, current):
    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