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