~lefteris-nikoltsios/+junk/samba-lp1016895

« back to all changes in this revision

Viewing changes to lib/dnspython/dns/message.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2011-12-21 13:18:04 UTC
  • mfrom: (0.39.21 sid)
  • Revision ID: package-import@ubuntu.com-20111221131804-xtlr39wx6njehxxr
Tags: 2:3.6.1-3ubuntu1
* Merge from Debian testing.  Remaining changes:
  + debian/patches/VERSION.patch:
    - set SAMBA_VERSION_SUFFIX to Ubuntu.
  + debian/patches/error-trans.fix-276472:
    - Add the translation of Unix Error code -ENOTSUP to NT Error Code
    - NT_STATUS_NOT_SUPPORTED to prevent the Permission denied error.
  + debian/smb.conf:
    - add "(Samba, Ubuntu)" to server string.
    - comment out the default [homes] share, and add a comment about
      "valid users = %S" to show users how to restrict access to
      \\server\username to only username.
    - Set 'usershare allow guests', so that usershare admins are 
      allowed to create public shares in addition to authenticated
      ones.
    - add map to guest = Bad user, maps bad username to guest access.
  + debian/samba-common.config:
    - Do not change priority to high if dhclient3 is installed.
    - Use priority medium instead of high for the workgroup question.
  + debian/control:
    - Don't build against or suggest ctdb.
    - Add dependency on samba-common-bin to samba.
  + Add ufw integration:
    - Created debian/samba.ufw.profile
    - debian/rules, debian/samba.dirs, debian/samba.files: install
      profile
    - debian/control: have samba suggest ufw
  + Add apport hook:
    - Created debian/source_samba.py.
    - debian/rules, debian/samba.dirs, debian/samba-common-bin.files: install
  + Switch to upstart:
    - Add debian/samba.{nmbd,smbd}.upstart.
  + debian/samba.logrotate, debian/samba-common.dhcp, debian/samba.if-up:
    - Make them upstart compatible
  + debian/samba.postinst: 
    - Avoid scary pdbedit warnings on first import.
  + debian/samba-common.postinst: Add more informative error message for
    the case where smb.conf was manually deleted
  + debian/patches/fix-debuglevel-name-conflict.patch: don't use 'debug_level'
    as a global variable name in an NSS module 
  + Dropped:
    - debian/patches/error-trans.fix-276472
    - debian/patches/fix-debuglevel-name-conflict.patch

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.edns
 
25
import dns.exception
 
26
import dns.flags
 
27
import dns.name
 
28
import dns.opcode
 
29
import dns.entropy
 
30
import dns.rcode
 
31
import dns.rdata
 
32
import dns.rdataclass
 
33
import dns.rdatatype
 
34
import dns.rrset
 
35
import dns.renderer
 
36
import dns.tsig
 
37
 
 
38
class ShortHeader(dns.exception.FormError):
 
39
    """Raised if the DNS packet passed to from_wire() is too short."""
 
40
    pass
 
41
 
 
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
 
46
 
 
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
 
51
 
 
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
 
56
 
 
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
 
61
 
 
62
class UnknownTSIGKey(dns.exception.DNSException):
 
63
    """Raised if we got a TSIG but don't know the key."""
 
64
    pass
 
65
 
 
66
class Message(object):
 
67
    """A DNS message.
 
68
 
 
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
 
140
    """
 
141
 
 
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 = {}
 
173
 
 
174
    def __repr__(self):
 
175
        return '<DNS message, ID ' + `self.id` + '>'
 
176
 
 
177
    def __str__(self):
 
178
        return self.to_text()
 
179
 
 
180
    def to_text(self,  origin=None, relativize=True, **kw):
 
181
        """Convert the message to text.
 
182
 
 
183
        The I{origin}, I{relativize}, and any other keyword
 
184
        arguments are passed to the rrset to_wire() method.
 
185
 
 
186
        @rtype: string
 
187
        """
 
188
 
 
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)
 
224
        #
 
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
 
228
        #
 
229
        return s.getvalue()[:-1]
 
230
 
 
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
 
260
 
 
261
    def __ne__(self, other):
 
262
        """Are two messages not equal?
 
263
        @rtype: bool"""
 
264
        return not self.__eq__(other)
 
265
 
 
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
 
286
 
 
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')
 
298
 
 
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.
 
303
 
 
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"""
 
325
 
 
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
 
344
 
 
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.
 
349
 
 
350
        If the RRset is not found, None is returned.
 
351
 
 
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"""
 
372
 
 
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
 
379
 
 
380
    def to_wire(self, origin=None, max_size=0, **kw):
 
381
        """Return a string containing the message in DNS compressed wire
 
382
        format.
 
383
 
 
384
        Additional keyword arguments are passed to the rrset to_wire()
 
385
        method.
 
386
 
 
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
 
395
        """
 
396
 
 
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()
 
425
 
 
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.
 
431
 
 
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
 
451
        """
 
452
 
 
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
 
468
 
 
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
 
487
        """
 
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
 
510
 
 
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
 
517
        """
 
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
 
524
 
 
525
    def rcode(self):
 
526
        """Return the rcode.
 
527
        @rtype: int
 
528
        """
 
529
        return dns.rcode.from_flags(self.flags, self.ednsflags)
 
530
 
 
531
    def set_rcode(self, rcode):
 
532
        """Set the rcode.
 
533
        @param rcode: the rcode
 
534
        @type rcode: int
 
535
        """
 
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
 
543
 
 
544
    def opcode(self):
 
545
        """Return the opcode.
 
546
        @rtype: int
 
547
        """
 
548
        return dns.opcode.from_flags(self.flags)
 
549
 
 
550
    def set_opcode(self, opcode):
 
551
        """Set the opcode.
 
552
        @param opcode: the opcode
 
553
        @type opcode: int
 
554
        """
 
555
        self.flags &= 0x87FF
 
556
        self.flags |= dns.opcode.to_flags(opcode)
 
557
 
 
558
class _WireReader(object):
 
559
    """Wire format reader.
 
560
 
 
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
 
576
    """
 
577
 
 
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
 
587
 
 
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"""
 
593
 
 
594
        if self.updating and qcount > 1:
 
595
            raise dns.exception.FormError
 
596
 
 
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
 
611
 
 
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"""
 
619
 
 
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
 
705
 
 
706
    def read(self):
 
707
        """Read a wire format DNS message and build a dns.message.Message
 
708
        object."""
 
709
 
 
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)
 
729
 
 
730
 
 
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.
 
736
 
 
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"""
 
767
 
 
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
 
776
 
 
777
    reader = _WireReader(wire, m, question_only, one_rr_per_rrset)
 
778
    reader.read()
 
779
 
 
780
    return m
 
781
 
 
782
 
 
783
class _TextReader(object):
 
784
    """Text format reader.
 
785
 
 
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
 
798
    """
 
799
 
 
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
 
806
 
 
807
    def _header_line(self, section):
 
808
        """Process one line from the text format header section."""
 
809
 
 
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()
 
852
 
 
853
    def _question_line(self, section):
 
854
        """Process one line from the text format question section."""
 
855
 
 
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()
 
881
 
 
882
    def _rr_line(self, section):
 
883
        """Process one line from the text format answer, authority, or
 
884
        additional data sections.
 
885
        """
 
886
 
 
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)
 
934
 
 
935
    def read(self):
 
936
        """Read a text format DNS message and build a dns.message.Message
 
937
        object."""
 
938
 
 
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)
 
965
 
 
966
 
 
967
def from_text(text):
 
968
    """Convert the text format message into a message object.
 
969
 
 
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"""
 
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
def from_file(f):
 
988
    """Read the next text format message from the specified file.
 
989
 
 
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"""
 
995
 
 
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
 
1008
 
 
1009
    try:
 
1010
        m = from_text(f)
 
1011
    finally:
 
1012
        if want_close:
 
1013
            f.close()
 
1014
    return m
 
1015
 
 
1016
def make_query(qname, rdtype, rdclass = dns.rdataclass.IN, use_edns=None,
 
1017
               want_dnssec=False):
 
1018
    """Make a query message.
 
1019
 
 
1020
    The query name, type, and class may all be specified either
 
1021
    as objects of the appropriate type, or as strings.
 
1022
 
 
1023
    The query will have a randomly choosen query id, and its DNS flags
 
1024
    will be set to dns.flags.RD.
 
1025
 
 
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"""
 
1039
 
 
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
 
1053
 
 
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.
 
1059
 
 
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.
 
1063
 
 
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"""
 
1072
 
 
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