1   
   2   
   3   
   4   
   5   
   6   
   7   
   8   
   9   
  10   
  11   
  12   
  13   
  14   
  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   
  43   
  44      """The DNS packet passed to from_wire() is too short.""" 
   45   
  46   
  48   
  49      """The DNS packet passed to from_wire() has extra junk at the end of it.""" 
   50   
  51   
  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   
  71   
  72      """A TSIG with an unknown key was received.""" 
   73   
  74   
  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   
 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   
 185          return '<DNS message, ID ' + repr(self.id) + '>' 
  186   
 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           
 240           
 241           
 242           
 243          return s.getvalue()[:-1] 
  244   
 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   
 276          """Are two messages not equal? 
 277          @rtype: bool""" 
 278          return not self.__eq__(other) 
  279   
 300   
 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   
 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               
 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   
 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   
 545   
 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   
 564   
  572   
 573   
 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   
 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   
 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   
  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   
 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           
 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           
 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           
 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           
 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           
 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           
 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   
 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       
1007       
1008       
1009   
1010      m = Message() 
1011   
1012      reader = _TextReader(text, m) 
1013      reader.read() 
1014   
1015      return m 
 1016   
1017   
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       
1091       
1092       
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