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

# Source Code for Module dns.message

       1  # Copyright (C) 2001-2007, 2009, 2010 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 Messages"""
      17
      18  import cStringIO
      19  import random
      20  import struct
      21  import sys
      22  import time
      23
      24  import dns.edns
      25  import dns.exception
      26  import dns.flags
      27  import dns.name
      28  import dns.opcode
      29  import dns.entropy
      30  import dns.rcode
      31  import dns.rdata
      32  import dns.rdataclass
      33  import dns.rdatatype
      34  import dns.rrset
      35  import dns.renderer
      36  import dns.tsig
      37
      38 -class ShortHeader(dns.exception.FormError):
      39      """Raised if the DNS packet passed to from_wire() is too short."""
      40      pass
      41
      42 -class TrailingJunk(dns.exception.FormError):
      43      """Raised if the DNS packet passed to from_wire() has extra junk
      44      at the end of it."""
      45      pass
      46
      47 -class UnknownHeaderField(dns.exception.DNSException):
      48      """Raised if a header field name is not recognized when converting from
      49      text into a message."""
      50      pass
      51
      52 -class BadEDNS(dns.exception.FormError):
      53      """Raised if an OPT record occurs somewhere other than the start of
      54      the additional data section."""
      55      pass
      56
      57 -class BadTSIG(dns.exception.FormError):
      58      """Raised if a TSIG record occurs somewhere other than the end of
      59      the additional data section."""
      60      pass
      61
      62 -class UnknownTSIGKey(dns.exception.DNSException):
      63      """Raised if we got a TSIG but don't know the key."""
      64      pass
      65
      66 -class Message(object):
      67      """A DNS message.
      68
      69      @ivar id: The query id; the default is a randomly chosen id.
      70      @type id: int
      71      @ivar flags: The DNS flags of the message.  @see: RFC 1035 for an
      72      explanation of these flags.
      73      @type flags: int
      74      @ivar question: The question section.
      75      @type question: list of dns.rrset.RRset objects
      76      @ivar answer: The answer section.
      77      @type answer: list of dns.rrset.RRset objects
      78      @ivar authority: The authority section.
      79      @type authority: list of dns.rrset.RRset objects
      80      @ivar additional: The additional data section.
      81      @type additional: list of dns.rrset.RRset objects
      82      @ivar edns: The EDNS level to use.  The default is -1, no Edns.
      83      @type edns: int
      84      @ivar ednsflags: The EDNS flags
      85      @type ednsflags: long
      86      @ivar payload: The EDNS payload size.  The default is 0.
      87      @type payload: int
      88      @ivar options: The EDNS options
      89      @type options: list of dns.edns.Option objects
      90      @ivar request_payload: The associated request's EDNS payload size.
      91      @type request_payload: int
      92      @ivar keyring: The TSIG keyring to use.  The default is None.
      93      @type keyring: dict
      94      @ivar keyname: The TSIG keyname to use.  The default is None.
      95      @type keyname: dns.name.Name object
      96      @ivar keyalgorithm: The TSIG algorithm to use; defaults to
      97      dns.tsig.default_algorithm.  Constants for TSIG algorithms are defined
      98      in dns.tsig, and the currently implemented algorithms are
      99      HMAC_MD5, HMAC_SHA1, HMAC_SHA224, HMAC_SHA256, HMAC_SHA384, and
     100      HMAC_SHA512.
     101      @type keyalgorithm: string
     102      @ivar request_mac: The TSIG MAC of the request message associated with
     103      this message; used when validating TSIG signatures.   @see: RFC 2845 for
     104      more information on TSIG fields.
     105      @type request_mac: string
     106      @ivar fudge: TSIG time fudge; default is 300 seconds.
     107      @type fudge: int
     108      @ivar original_id: TSIG original id; defaults to the message's id
     109      @type original_id: int
     110      @ivar tsig_error: TSIG error code; default is 0.
     111      @type tsig_error: int
     112      @ivar other_data: TSIG other data.
     113      @type other_data: string
     114      @ivar mac: The TSIG MAC for this message.
     115      @type mac: string
     116      @ivar xfr: Is the message being used to contain the results of a DNS
     117      zone transfer?  The default is False.
     118      @type xfr: bool
     119      @ivar origin: The origin of the zone in messages which are used for
     120      zone transfers or for DNS dynamic updates.  The default is None.
     121      @type origin: dns.name.Name object
     122      @ivar tsig_ctx: The TSIG signature context associated with this
     123      message.  The default is None.
     124      @type tsig_ctx: hmac.HMAC object
     125      @ivar had_tsig: Did the message decoded from wire format have a TSIG
     126      signature?
     127      @type had_tsig: bool
     128      @ivar multi: Is this message part of a multi-message sequence?  The
     129      default is false.  This variable is used when validating TSIG signatures
     130      on messages which are part of a zone transfer.
     131      @type multi: bool
     132      @ivar first: Is this message standalone, or the first of a multi
     133      message sequence?  This variable is used when validating TSIG signatures
     134      on messages which are part of a zone transfer.
     135      @type first: bool
     136      @ivar index: An index of rrsets in the message.  The index key is
     137      (section, name, rdclass, rdtype, covers, deleting).  Indexing can be
     138      disabled by setting the index to None.
     139      @type index: dict
     140      """
     141
     142 -    def __init__(self, id=None):
     143          if id is None:
     144              self.id = dns.entropy.random_16()
     145          else:
     146              self.id = id
     147          self.flags = 0
     148          self.question = []
     149          self.answer = []
     150          self.authority = []
     151          self.additional = []
     152          self.edns = -1
     153          self.ednsflags = 0
     154          self.payload = 0
     155          self.options = []
     156          self.request_payload = 0
     157          self.keyring = None
     158          self.keyname = None
     159          self.keyalgorithm = dns.tsig.default_algorithm
     160          self.request_mac = ''
     161          self.other_data = ''
     162          self.tsig_error = 0
     163          self.fudge = 300
     164          self.original_id = self.id
     165          self.mac = ''
     166          self.xfr = False
     167          self.origin = None
     168          self.tsig_ctx = None
     169          self.had_tsig = False
     170          self.multi = False
     171          self.first = True
     172          self.index = {}
     173
     174 -    def __repr__(self):
     175          return '<DNS message, ID ' + `self.id` + '>'
     176
     177 -    def __str__(self):
     178          return self.to_text()
     179
     180 -    def to_text(self,  origin=None, relativize=True, **kw):
     181          """Convert the message to text.
     182
     183          The I{origin}, I{relativize}, and any other keyword
     184          arguments are passed to the rrset to_wire() method.
     185
     186          @rtype: string
     187          """
     188
     189          s = cStringIO.StringIO()
     190          print >> s, 'id %d' % self.id
     191          print >> s, 'opcode %s' % \
     192                dns.opcode.to_text(dns.opcode.from_flags(self.flags))
     193          rc = dns.rcode.from_flags(self.flags, self.ednsflags)
     194          print >> s, 'rcode %s' % dns.rcode.to_text(rc)
     195          print >> s, 'flags %s' % dns.flags.to_text(self.flags)
     196          if self.edns >= 0:
     197              print >> s, 'edns %s' % self.edns
     198              if self.ednsflags != 0:
     199                  print >> s, 'eflags %s' % \
     200                        dns.flags.edns_to_text(self.ednsflags)
     201              print >> s, 'payload', self.payload
     202          is_update = dns.opcode.is_update(self.flags)
     203          if is_update:
     204              print >> s, ';ZONE'
     205          else:
     206              print >> s, ';QUESTION'
     207          for rrset in self.question:
     208              print >> s, rrset.to_text(origin, relativize, **kw)
     209          if is_update:
     210              print >> s, ';PREREQ'
     211          else:
     212              print >> s, ';ANSWER'
     213          for rrset in self.answer:
     214              print >> s, rrset.to_text(origin, relativize, **kw)
     215          if is_update:
     216              print >> s, ';UPDATE'
     217          else:
     218              print >> s, ';AUTHORITY'
     219          for rrset in self.authority:
     220              print >> s, rrset.to_text(origin, relativize, **kw)
     221          print >> s, ';ADDITIONAL'
     222          for rrset in self.additional:
     223              print >> s, rrset.to_text(origin, relativize, **kw)
     224          #
     225          # We strip off the final \n so the caller can print the result without
     226          # doing weird things to get around eccentricities in Python print
     227          # formatting
     228          #
     229          return s.getvalue()[:-1]
     230
     231 -    def __eq__(self, other):
     232          """Two messages are equal if they have the same content in the
     233          header, question, answer, and authority sections.
     234          @rtype: bool"""
     235          if not isinstance(other, Message):
     236              return False
     237          if self.id != other.id:
     238              return False
     239          if self.flags != other.flags:
     240              return False
     241          for n in self.question:
     242              if n not in other.question:
     243                  return False
     244          for n in other.question:
     245              if n not in self.question:
     246                  return False
     247          for n in self.answer:
     248              if n not in other.answer:
     249                  return False
     250          for n in other.answer:
     251              if n not in self.answer:
     252                  return False
     253          for n in self.authority:
     254              if n not in other.authority:
     255                  return False
     256          for n in other.authority:
     257              if n not in self.authority:
     258                  return False
     259          return True
     260
     261 -    def __ne__(self, other):
     262          """Are two messages not equal?
     263          @rtype: bool"""
     264          return not self.__eq__(other)
     265
     266 -    def is_response(self, other):
     267          """Is other a response to self?
     268          @rtype: bool"""
     269          if other.flags & dns.flags.QR == 0 or \
     270             self.id != other.id or \
     271             dns.opcode.from_flags(self.flags) != \
     272             dns.opcode.from_flags(other.flags):
     273              return False
     274          if dns.rcode.from_flags(other.flags, other.ednsflags) != \
     275                 dns.rcode.NOERROR:
     276              return True
     277          if dns.opcode.is_update(self.flags):
     278              return True
     279          for n in self.question:
     280              if n not in other.question:
     281                  return False
     282          for n in other.question:
     283              if n not in self.question:
     284                  return False
     285          return True
     286
     287 -    def section_number(self, section):
     288          if section is self.question:
     289              return 0
     290          elif section is self.answer:
     291              return 1
     292          elif section is self.authority:
     293              return 2
     294          elif section is self.additional:
     295              return 3
     296          else:
     297              raise ValueError('unknown section')
     298
     299 -    def find_rrset(self, section, name, rdclass, rdtype,
     300                     covers=dns.rdatatype.NONE, deleting=None, create=False,
     301                     force_unique=False):
     302          """Find the RRset with the given attributes in the specified section.
     303
     304          @param section: the section of the message to look in, e.g.
     305          self.answer.
     306          @type section: list of dns.rrset.RRset objects
     307          @param name: the name of the RRset
     308          @type name: dns.name.Name object
     309          @param rdclass: the class of the RRset
     310          @type rdclass: int
     311          @param rdtype: the type of the RRset
     312          @type rdtype: int
     313          @param covers: the covers value of the RRset
     314          @type covers: int
     315          @param deleting: the deleting value of the RRset
     316          @type deleting: int
     317          @param create: If True, create the RRset if it is not found.
     318          The created RRset is appended to I{section}.
     319          @type create: bool
     320          @param force_unique: If True and create is also True, create a
     321          new RRset regardless of whether a matching RRset exists already.
     322          @type force_unique: bool
     323          @raises KeyError: the RRset was not found and create was False
     324          @rtype: dns.rrset.RRset object"""
     325
     326          key = (self.section_number(section),
     327                 name, rdclass, rdtype, covers, deleting)
     328          if not force_unique:
     329              if not self.index is None:
     330                  rrset = self.index.get(key)
     331                  if not rrset is None:
     332                      return rrset
     333              else:
     334                  for rrset in section:
     335                      if rrset.match(name, rdclass, rdtype, covers, deleting):
     336                          return rrset
     337          if not create:
     338              raise KeyError
     339          rrset = dns.rrset.RRset(name, rdclass, rdtype, covers, deleting)
     340          section.append(rrset)
     341          if not self.index is None:
     342              self.index[key] = rrset
     343          return rrset
     344
     345 -    def get_rrset(self, section, name, rdclass, rdtype,
     346                    covers=dns.rdatatype.NONE, deleting=None, create=False,
     347                    force_unique=False):
     348          """Get the RRset with the given attributes in the specified section.
     349
     350          If the RRset is not found, None is returned.
     351
     352          @param section: the section of the message to look in, e.g.
     353          self.answer.
     354          @type section: list of dns.rrset.RRset objects
     355          @param name: the name of the RRset
     356          @type name: dns.name.Name object
     357          @param rdclass: the class of the RRset
     358          @type rdclass: int
     359          @param rdtype: the type of the RRset
     360          @type rdtype: int
     361          @param covers: the covers value of the RRset
     362          @type covers: int
     363          @param deleting: the deleting value of the RRset
     364          @type deleting: int
     365          @param create: If True, create the RRset if it is not found.
     366          The created RRset is appended to I{section}.
     367          @type create: bool
     368          @param force_unique: If True and create is also True, create a
     369          new RRset regardless of whether a matching RRset exists already.
     370          @type force_unique: bool
     371          @rtype: dns.rrset.RRset object or None"""
     372
     373          try:
     374              rrset = self.find_rrset(section, name, rdclass, rdtype, covers,
     375                                      deleting, create, force_unique)
     376          except KeyError:
     377              rrset = None
     378          return rrset
     379
     380 -    def to_wire(self, origin=None, max_size=0, **kw):
     381          """Return a string containing the message in DNS compressed wire
     382          format.
     383
     384          Additional keyword arguments are passed to the rrset to_wire()
     385          method.
     386
     387          @param origin: The origin to be appended to any relative names.
     388          @type origin: dns.name.Name object
     389          @param max_size: The maximum size of the wire format output; default
     390          is 0, which means 'the message's request payload, if nonzero, or
     391          65536'.
     392          @type max_size: int
     393          @raises dns.exception.TooBig: max_size was exceeded
     394          @rtype: string
     395          """
     396
     397          if max_size == 0:
     398              if self.request_payload != 0:
     399                  max_size = self.request_payload
     400              else:
     401                  max_size = 65535
     402          if max_size < 512:
     403              max_size = 512
     404          elif max_size > 65535:
     405              max_size = 65535
     406          r = dns.renderer.Renderer(self.id, self.flags, max_size, origin)
     407          for rrset in self.question:
     408              r.add_question(rrset.name, rrset.rdtype, rrset.rdclass)
     409          for rrset in self.answer:
     410              r.add_rrset(dns.renderer.ANSWER, rrset, **kw)
     411          for rrset in self.authority:
     412              r.add_rrset(dns.renderer.AUTHORITY, rrset, **kw)
     413          if self.edns >= 0:
     414              r.add_edns(self.edns, self.ednsflags, self.payload, self.options)
     415          for rrset in self.additional:
     416              r.add_rrset(dns.renderer.ADDITIONAL, rrset, **kw)
     417          r.write_header()
     418          if not self.keyname is None:
     419              r.add_tsig(self.keyname, self.keyring[self.keyname],
     420                         self.fudge, self.original_id, self.tsig_error,
     421                         self.other_data, self.request_mac,
     422                         self.keyalgorithm)
     423              self.mac = r.mac
     424          return r.get_wire()
     425
     426 -    def use_tsig(self, keyring, keyname=None, fudge=300,
     427                   original_id=None, tsig_error=0, other_data='',
     428                   algorithm=dns.tsig.default_algorithm):
     429          """When sending, a TSIG signature using the specified keyring
     430          and keyname should be added.
     431
     432          @param keyring: The TSIG keyring to use; defaults to None.
     433          @type keyring: dict
     434          @param keyname: The name of the TSIG key to use; defaults to None.
     435          The key must be defined in the keyring.  If a keyring is specified
     436          but a keyname is not, then the key used will be the first key in the
     437          keyring.  Note that the order of keys in a dictionary is not defined,
     438          so applications should supply a keyname when a keyring is used, unless
     439          they know the keyring contains only one key.
     440          @type keyname: dns.name.Name or string
     441          @param fudge: TSIG time fudge; default is 300 seconds.
     442          @type fudge: int
     443          @param original_id: TSIG original id; defaults to the message's id
     444          @type original_id: int
     445          @param tsig_error: TSIG error code; default is 0.
     446          @type tsig_error: int
     447          @param other_data: TSIG other data.
     448          @type other_data: string
     449          @param algorithm: The TSIG algorithm to use; defaults to
     450          dns.tsig.default_algorithm
     451          """
     452
     453          self.keyring = keyring
     454          if keyname is None:
     455              self.keyname = self.keyring.keys()[0]
     456          else:
     457              if isinstance(keyname, (str, unicode)):
     458                  keyname = dns.name.from_text(keyname)
     459              self.keyname = keyname
     460          self.keyalgorithm = algorithm
     461          self.fudge = fudge
     462          if original_id is None:
     463              self.original_id = self.id
     464          else:
     465              self.original_id = original_id
     466          self.tsig_error = tsig_error
     467          self.other_data = other_data
     468
     469 -    def use_edns(self, edns=0, ednsflags=0, payload=1280, request_payload=None, options=None):
     470          """Configure EDNS behavior.
     471          @param edns: The EDNS level to use.  Specifying None, False, or -1
     472          means 'do not use EDNS', and in this case the other parameters are
     473          ignored.  Specifying True is equivalent to specifying 0, i.e. 'use
     474          EDNS0'.
     475          @type edns: int or bool or None
     476          @param ednsflags: EDNS flag values.
     477          @type ednsflags: int
     478          @param payload: The EDNS sender's payload field, which is the maximum
     479          size of UDP datagram the sender can handle.
     480          @type payload: int
     481          @param request_payload: The EDNS payload size to use when sending
     482          this message.  If not specified, defaults to the value of payload.
     483          @type request_payload: int or None
     484          @param options: The EDNS options
     485          @type options: None or list of dns.edns.Option objects
     486          @see: RFC 2671
     487          """
     488          if edns is None or edns is False:
     489              edns = -1
     490          if edns is True:
     491              edns = 0
     492          if request_payload is None:
     493              request_payload = payload
     494          if edns < 0:
     495              ednsflags = 0
     496              payload = 0
     497              request_payload = 0
     498              options = []
     499          else:
     500              # make sure the EDNS version in ednsflags agrees with edns
     501              ednsflags &= 0xFF00FFFFL
     502              ednsflags |= (edns << 16)
     503              if options is None:
     504                  options = []
     505          self.edns = edns
     506          self.ednsflags = ednsflags
     507          self.payload = payload
     508          self.options = options
     509          self.request_payload = request_payload
     510
     511 -    def want_dnssec(self, wanted=True):
     512          """Enable or disable 'DNSSEC desired' flag in requests.
     513          @param wanted: Is DNSSEC desired?  If True, EDNS is enabled if
     514          required, and then the DO bit is set.  If False, the DO bit is
     515          cleared if EDNS is enabled.
     516          @type wanted: bool
     517          """
     518          if wanted:
     519              if self.edns < 0:
     520                  self.use_edns()
     521              self.ednsflags |= dns.flags.DO
     522          elif self.edns >= 0:
     523              self.ednsflags &= ~dns.flags.DO
     524
     525 -    def rcode(self):
     526          """Return the rcode.
     527          @rtype: int
     528          """
     529          return dns.rcode.from_flags(self.flags, self.ednsflags)
     530
     531 -    def set_rcode(self, rcode):
     532          """Set the rcode.
     533          @param rcode: the rcode
     534          @type rcode: int
     535          """
     536          (value, evalue) = dns.rcode.to_flags(rcode)
     537          self.flags &= 0xFFF0
     538          self.flags |= value
     539          self.ednsflags &= 0x00FFFFFFL
     540          self.ednsflags |= evalue
     541          if self.ednsflags != 0 and self.edns < 0:
     542              self.edns = 0
     543
     544 -    def opcode(self):
     545          """Return the opcode.
     546          @rtype: int
     547          """
     548          return dns.opcode.from_flags(self.flags)
     549
     550 -    def set_opcode(self, opcode):
     551          """Set the opcode.
     552          @param opcode: the opcode
     553          @type opcode: int
     554          """
     555          self.flags &= 0x87FF
     556          self.flags |= dns.opcode.to_flags(opcode)
     557
     558 -class _WireReader(object):
     559      """Wire format reader.
     560
     561      @ivar wire: the wire-format message.
     562      @type wire: string
     563      @ivar message: The message object being built
     564      @type message: dns.message.Message object
     565      @ivar current: When building a message object from wire format, this
     566      variable contains the offset from the beginning of wire of the next octet
     567      to be read.
     568      @type current: int
     569      @ivar updating: Is the message a dynamic update?
     570      @type updating: bool
     571      @ivar one_rr_per_rrset: Put each RR into its own RRset?
     572      @type one_rr_per_rrset: bool
     573      @ivar zone_rdclass: The class of the zone in messages which are
     574      DNS dynamic updates.
     575      @type zone_rdclass: int
     576      """
     577
     578 -    def __init__(self, wire, message, question_only=False,
     579                   one_rr_per_rrset=False):
     580          self.wire = wire
     581          self.message = message
     582          self.current = 0
     583          self.updating = False
     584          self.zone_rdclass = dns.rdataclass.IN
     585          self.question_only = question_only
     586          self.one_rr_per_rrset = one_rr_per_rrset
     587
     588 -    def _get_question(self, qcount):
     589          """Read the next I{qcount} records from the wire data and add them to
     590          the question section.
     591          @param qcount: the number of questions in the message
     592          @type qcount: int"""
     593
     594          if self.updating and qcount > 1:
     595              raise dns.exception.FormError
     596
     597          for i in xrange(0, qcount):
     598              (qname, used) = dns.name.from_wire(self.wire, self.current)
     599              if not self.message.origin is None:
     600                  qname = qname.relativize(self.message.origin)
     601              self.current = self.current + used
     602              (rdtype, rdclass) = \
     603                       struct.unpack('!HH',
     604                                     self.wire[self.current:self.current + 4])
     605              self.current = self.current + 4
     606              self.message.find_rrset(self.message.question, qname,
     607                                      rdclass, rdtype, create=True,
     608                                      force_unique=True)
     609              if self.updating:
     610                  self.zone_rdclass = rdclass
     611
     612 -    def _get_section(self, section, count):
     613          """Read the next I{count} records from the wire data and add them to
     614          the specified section.
     615          @param section: the section of the message to which to add records
     616          @type section: list of dns.rrset.RRset objects
     617          @param count: the number of records to read
     618          @type count: int"""
     619
     620          if self.updating or self.one_rr_per_rrset:
     621              force_unique = True
     622          else:
     623              force_unique = False
     624          seen_opt = False
     625          for i in xrange(0, count):
     626              rr_start = self.current
     627              (name, used) = dns.name.from_wire(self.wire, self.current)
     628              absolute_name = name
     629              if not self.message.origin is None:
     630                  name = name.relativize(self.message.origin)
     631              self.current = self.current + used
     632              (rdtype, rdclass, ttl, rdlen) = \
     633                       struct.unpack('!HHIH',
     634                                     self.wire[self.current:self.current + 10])
     635              self.current = self.current + 10
     636              if rdtype == dns.rdatatype.OPT:
     637                  if not section is self.message.additional or seen_opt:
     638                      raise BadEDNS
     639                  self.message.payload = rdclass
     640                  self.message.ednsflags = ttl
     641                  self.message.edns = (ttl & 0xff0000) >> 16
     642                  self.message.options = []
     643                  current = self.current
     644                  optslen = rdlen
     645                  while optslen > 0:
     646                      (otype, olen) = \
     647                              struct.unpack('!HH',
     648                                            self.wire[current:current + 4])
     649                      current = current + 4
     650                      opt = dns.edns.option_from_wire(otype, self.wire, current, olen)
     651                      self.message.options.append(opt)
     652                      current = current + olen
     653                      optslen = optslen - 4 - olen
     654                  seen_opt = True
     655              elif rdtype == dns.rdatatype.TSIG:
     656                  if not (section is self.message.additional and
     657                          i == (count - 1)):
     658                      raise BadTSIG
     659                  if self.message.keyring is None:
     660                      raise UnknownTSIGKey('got signed message without keyring')
     661                  secret = self.message.keyring.get(absolute_name)
     662                  if secret is None:
     663                      raise UnknownTSIGKey("key '%s' unknown" % name)
     664                  self.message.tsig_ctx = \
     665                                        dns.tsig.validate(self.wire,
     666                                            absolute_name,
     667                                            secret,
     668                                            int(time.time()),
     669                                            self.message.request_mac,
     670                                            rr_start,
     671                                            self.current,
     672                                            rdlen,
     673                                            self.message.tsig_ctx,
     674                                            self.message.multi,
     675                                            self.message.first)
     676                  self.message.had_tsig = True
     677              else:
     678                  if ttl < 0:
     679                      ttl = 0
     680                  if self.updating and \
     681                     (rdclass == dns.rdataclass.ANY or
     682                      rdclass == dns.rdataclass.NONE):
     683                      deleting = rdclass
     684                      rdclass = self.zone_rdclass
     685                  else:
     686                      deleting = None
     687                  if deleting == dns.rdataclass.ANY or \
     688                     (deleting == dns.rdataclass.NONE and \
     689                      section is self.message.answer):
     690                      covers = dns.rdatatype.NONE
     691                      rd = None
     692                  else:
     693                      rd = dns.rdata.from_wire(rdclass, rdtype, self.wire,
     694                                               self.current, rdlen,
     695                                               self.message.origin)
     696                      covers = rd.covers()
     697                  if self.message.xfr and rdtype == dns.rdatatype.SOA:
     698                      force_unique = True
     699                  rrset = self.message.find_rrset(section, name,
     700                                                  rdclass, rdtype, covers,
     701                                                  deleting, True, force_unique)
     702                  if not rd is None:
     703                      rrset.add(rd, ttl)
     704              self.current = self.current + rdlen
     705
     706 -    def read(self):
     707          """Read a wire format DNS message and build a dns.message.Message
     708          object."""
     709
     710          l = len(self.wire)
     711          if l < 12:
     712              raise ShortHeader
     713          (self.message.id, self.message.flags, qcount, ancount,
     714           aucount, adcount) = struct.unpack('!HHHHHH', self.wire[:12])
     715          self.current = 12
     716          if dns.opcode.is_update(self.message.flags):
     717              self.updating = True
     718          self._get_question(qcount)
     719          if self.question_only:
     720              return
     721          self._get_section(self.message.answer, ancount)
     722          self._get_section(self.message.authority, aucount)
     723          self._get_section(self.message.additional, adcount)
     724          if self.current != l:
     725              raise TrailingJunk
     726          if self.message.multi and self.message.tsig_ctx and \
     727                 not self.message.had_tsig:
     728              self.message.tsig_ctx.update(self.wire)
     729
     730
     731 -def from_wire(wire, keyring=None, request_mac='', xfr=False, origin=None,
     732                tsig_ctx = None, multi = False, first = True,
     733                question_only = False, one_rr_per_rrset = False):
     734      """Convert a DNS wire format message into a message
     735      object.
     736
     737      @param keyring: The keyring to use if the message is signed.
     738      @type keyring: dict
     739      @param request_mac: If the message is a response to a TSIG-signed request,
     740      I{request_mac} should be set to the MAC of that request.
     741      @type request_mac: string
     742      @param xfr: Is this message part of a zone transfer?
     743      @type xfr: bool
     744      @param origin: If the message is part of a zone transfer, I{origin}
     745      should be the origin name of the zone.
     746      @type origin: dns.name.Name object
     747      @param tsig_ctx: The ongoing TSIG context, used when validating zone
     748      transfers.
     749      @type tsig_ctx: hmac.HMAC object
     750      @param multi: Is this message part of a multiple message sequence?
     751      @type multi: bool
     752      @param first: Is this message standalone, or the first of a multi
     753      message sequence?
     754      @type first: bool
     755      @param question_only: Read only up to the end of the question section?
     756      @type question_only: bool
     757      @param one_rr_per_rrset: Put each RR into its own RRset
     758      @type one_rr_per_rrset: bool
     759      @raises ShortHeader: The message is less than 12 octets long.
     760      @raises TrailingJunk: There were octets in the message past the end
     761      of the proper DNS message.
     762      @raises BadEDNS: An OPT record was in the wrong section, or occurred more
     763      than once.
     764      @raises BadTSIG: A TSIG record was not the last record of the additional
     765      data section.
     766      @rtype: dns.message.Message object"""
     767
     768      m = Message(id=0)
     769      m.keyring = keyring
     770      m.request_mac = request_mac
     771      m.xfr = xfr
     772      m.origin = origin
     773      m.tsig_ctx = tsig_ctx
     774      m.multi = multi
     775      m.first = first
     776
     777      reader = _WireReader(wire, m, question_only, one_rr_per_rrset)
     778      reader.read()
     779
     780      return m
     781
     782
     783 -class _TextReader(object):
     784      """Text format reader.
     785
     786      @ivar tok: the tokenizer
     787      @type tok: dns.tokenizer.Tokenizer object
     788      @ivar message: The message object being built
     789      @type message: dns.message.Message object
     790      @ivar updating: Is the message a dynamic update?
     791      @type updating: bool
     792      @ivar zone_rdclass: The class of the zone in messages which are
     793      DNS dynamic updates.
     794      @type zone_rdclass: int
     795      @ivar last_name: The most recently read name when building a message object
     796      from text format.
     797      @type last_name: dns.name.Name object
     798      """
     799
     800 -    def __init__(self, text, message):
     801          self.message = message
     802          self.tok = dns.tokenizer.Tokenizer(text)
     803          self.last_name = None
     804          self.zone_rdclass = dns.rdataclass.IN
     805          self.updating = False
     806
     807 -    def _header_line(self, section):
     808          """Process one line from the text format header section."""
     809
     810          token = self.tok.get()
     811          what = token.value
     812          if what == 'id':
     813              self.message.id = self.tok.get_int()
     814          elif what == 'flags':
     815              while True:
     816                  token = self.tok.get()
     817                  if not token.is_identifier():
     818                      self.tok.unget(token)
     819                      break
     820                  self.message.flags = self.message.flags | \
     821                                       dns.flags.from_text(token.value)
     822              if dns.opcode.is_update(self.message.flags):
     823                  self.updating = True
     824          elif what == 'edns':
     825              self.message.edns = self.tok.get_int()
     826              self.message.ednsflags = self.message.ednsflags | \
     827                                       (self.message.edns << 16)
     828          elif what == 'eflags':
     829              if self.message.edns < 0:
     830                  self.message.edns = 0
     831              while True:
     832                  token = self.tok.get()
     833                  if not token.is_identifier():
     834                      self.tok.unget(token)
     835                      break
     836                  self.message.ednsflags = self.message.ednsflags | \
     837                                dns.flags.edns_from_text(token.value)
     838          elif what == 'payload':
     839              self.message.payload = self.tok.get_int()
     840              if self.message.edns < 0:
     841                  self.message.edns = 0
     842          elif what == 'opcode':
     843              text = self.tok.get_string()
     844              self.message.flags = self.message.flags | \
     845                        dns.opcode.to_flags(dns.opcode.from_text(text))
     846          elif what == 'rcode':
     847              text = self.tok.get_string()
     848              self.message.set_rcode(dns.rcode.from_text(text))
     849          else:
     850              raise UnknownHeaderField
     851          self.tok.get_eol()
     852
     853 -    def _question_line(self, section):
     854          """Process one line from the text format question section."""
     855
     856          token = self.tok.get(want_leading = True)
     857          if not token.is_whitespace():
     858              self.last_name = dns.name.from_text(token.value, None)
     859          name = self.last_name
     860          token = self.tok.get()
     861          if not token.is_identifier():
     862              raise dns.exception.SyntaxError
     863          # Class
     864          try:
     865              rdclass = dns.rdataclass.from_text(token.value)
     866              token = self.tok.get()
     867              if not token.is_identifier():
     868                  raise dns.exception.SyntaxError
     869          except dns.exception.SyntaxError:
     870              raise dns.exception.SyntaxError
     871          except:
     872              rdclass = dns.rdataclass.IN
     873          # Type
     874          rdtype = dns.rdatatype.from_text(token.value)
     875          self.message.find_rrset(self.message.question, name,
     876                                  rdclass, rdtype, create=True,
     877                                  force_unique=True)
     878          if self.updating:
     879              self.zone_rdclass = rdclass
     880          self.tok.get_eol()
     881
     882 -    def _rr_line(self, section):
     883          """Process one line from the text format answer, authority, or
     884          additional data sections.
     885          """
     886
     887          deleting = None
     888          # Name
     889          token = self.tok.get(want_leading = True)
     890          if not token.is_whitespace():
     891              self.last_name = dns.name.from_text(token.value, None)
     892          name = self.last_name
     893          token = self.tok.get()
     894          if not token.is_identifier():
     895              raise dns.exception.SyntaxError
     896          # TTL
     897          try:
     898              ttl = int(token.value, 0)
     899              token = self.tok.get()
     900              if not token.is_identifier():
     901                  raise dns.exception.SyntaxError
     902          except dns.exception.SyntaxError:
     903              raise dns.exception.SyntaxError
     904          except:
     905              ttl = 0
     906          # Class
     907          try:
     908              rdclass = dns.rdataclass.from_text(token.value)
     909              token = self.tok.get()
     910              if not token.is_identifier():
     911                  raise dns.exception.SyntaxError
     912              if rdclass == dns.rdataclass.ANY or rdclass == dns.rdataclass.NONE:
     913                  deleting = rdclass
     914                  rdclass = self.zone_rdclass
     915          except dns.exception.SyntaxError:
     916              raise dns.exception.SyntaxError
     917          except:
     918              rdclass = dns.rdataclass.IN
     919          # Type
     920          rdtype = dns.rdatatype.from_text(token.value)
     921          token = self.tok.get()
     922          if not token.is_eol_or_eof():
     923              self.tok.unget(token)
     924              rd = dns.rdata.from_text(rdclass, rdtype, self.tok, None)
     925              covers = rd.covers()
     926          else:
     927              rd = None
     928              covers = dns.rdatatype.NONE
     929          rrset = self.message.find_rrset(section, name,
     930                                          rdclass, rdtype, covers,
     931                                          deleting, True, self.updating)
     932          if not rd is None:
     933              rrset.add(rd, ttl)
     934
     935 -    def read(self):
     936          """Read a text format DNS message and build a dns.message.Message
     937          object."""
     938
     939          line_method = self._header_line
     940          section = None
     941          while 1:
     942              token = self.tok.get(True, True)
     943              if token.is_eol_or_eof():
     944                  break
     945              if token.is_comment():
     946                  u = token.value.upper()
     947                  if u == 'HEADER':
     948                      line_method = self._header_line
     949                  elif u == 'QUESTION' or u == 'ZONE':
     950                      line_method = self._question_line
     951                      section = self.message.question
     952                  elif u == 'ANSWER' or u == 'PREREQ':
     953                      line_method = self._rr_line
     954                      section = self.message.answer
     955                  elif u == 'AUTHORITY' or u == 'UPDATE':
     956                      line_method = self._rr_line
     957                      section = self.message.authority
     958                  elif u == 'ADDITIONAL':
     959                      line_method = self._rr_line
     960                      section = self.message.additional
     961                  self.tok.get_eol()
     962                  continue
     963              self.tok.unget(token)
     964              line_method(section)
     965
     966
     967 -def from_text(text):
     968      """Convert the text format message into a message object.
     969
     970      @param text: The text format message.
     971      @type text: string
     972      @raises UnknownHeaderField:
     973      @raises dns.exception.SyntaxError:
     974      @rtype: dns.message.Message object"""
     975
     976      # 'text' can also be a file, but we don't publish that fact
     977      # since it's an implementation detail.  The official file
     978      # interface is from_file().
     979
     980      m = Message()
     981
     982      reader = _TextReader(text, m)
     983      reader.read()
     984
     985      return m
     986
     987 -def from_file(f):
     988      """Read the next text format message from the specified file.
     989
     990      @param f: file or string.  If I{f} is a string, it is treated
     991      as the name of a file to open.
     992      @raises UnknownHeaderField:
     993      @raises dns.exception.SyntaxError:
     994      @rtype: dns.message.Message object"""
     995
     996      if sys.hexversion >= 0x02030000:
     997          # allow Unicode filenames; turn on universal newline support
     998          str_type = basestring
     999          opts = 'rU'
    1000      else:
    1001          str_type = str
    1002          opts = 'r'
    1003      if isinstance(f, str_type):
    1004          f = file(f, opts)
    1005          want_close = True
    1006      else:
    1007          want_close = False
    1008
    1009      try:
    1010          m = from_text(f)
    1011      finally:
    1012          if want_close:
    1013              f.close()
    1014      return m
    1015
    1016 -def make_query(qname, rdtype, rdclass = dns.rdataclass.IN, use_edns=None,
    1017                 want_dnssec=False):
    1018      """Make a query message.
    1019
    1020      The query name, type, and class may all be specified either
    1021      as objects of the appropriate type, or as strings.
    1022
    1023      The query will have a randomly choosen query id, and its DNS flags
    1024      will be set to dns.flags.RD.
    1025
    1026      @param qname: The query name.
    1027      @type qname: dns.name.Name object or string
    1028      @param rdtype: The desired rdata type.
    1029      @type rdtype: int
    1030      @param rdclass: The desired rdata class; the default is class IN.
    1031      @type rdclass: int
    1032      @param use_edns: The EDNS level to use; the default is None (no EDNS).
    1033      See the description of dns.message.Message.use_edns() for the possible
    1034      values for use_edns and their meanings.
    1035      @type use_edns: int or bool or None
    1036      @param want_dnssec: Should the query indicate that DNSSEC is desired?
    1037      @type want_dnssec: bool
    1038      @rtype: dns.message.Message object"""
    1039
    1040      if isinstance(qname, (str, unicode)):
    1041          qname = dns.name.from_text(qname)
    1042      if isinstance(rdtype, (str, unicode)):
    1043          rdtype = dns.rdatatype.from_text(rdtype)
    1044      if isinstance(rdclass, (str, unicode)):
    1045          rdclass = dns.rdataclass.from_text(rdclass)
    1046      m = Message()
    1047      m.flags |= dns.flags.RD
    1048      m.find_rrset(m.question, qname, rdclass, rdtype, create=True,
    1049                   force_unique=True)
    1050      m.use_edns(use_edns)
    1051      m.want_dnssec(want_dnssec)
    1052      return m
    1053
    1054 -def make_response(query, recursion_available=False, our_payload=8192):
    1055      """Make a message which is a response for the specified query.
    1056      The message returned is really a response skeleton; it has all
    1057      of the infrastructure required of a response, but none of the
    1058      content.
    1059
    1060      The response's question section is a shallow copy of the query's
    1061      question section, so the query's question RRsets should not be
    1062      changed.
    1063
    1064      @param query: the query to respond to
    1065      @type query: dns.message.Message object
    1066      @param recursion_available: should RA be set in the response?
    1067      @type recursion_available: bool
    1068      @param our_payload: payload size to advertise in EDNS responses; default
    1069      is 8192.
    1070      @type our_payload: int
    1071      @rtype: dns.message.Message object"""
    1072
    1073      if query.flags & dns.flags.QR:
    1074          raise dns.exception.FormError('specified query message is not a query')
    1075      response = dns.message.Message(query.id)
    1076      response.flags = dns.flags.QR | (query.flags & dns.flags.RD)
    1077      if recursion_available:
    1078          response.flags |= dns.flags.RA
    1079      response.set_opcode(query.opcode())
    1080      response.question = list(query.question)
    1081      if query.edns >= 0:
    1082          response.use_edns(0, 0, our_payload, query.payload)
    1083      if not query.keyname is None:
    1084          response.keyname = query.keyname
    1085          response.keyring = query.keyring
    1086          response.request_mac = query.mac
    1087      return response
    1088