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