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

# Source Code for Module dns.message

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