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