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