Codebase list dnspython / 5c5788a
Imported Upstream version 1.9.4 SVN-Git Migration 8 years ago
24 changed file(s) with 1289 addition(s) and 138 deletion(s). Raw diff Collapse all Expand all
0 2011-03-24 Bob Halley <halley@dnspython.org>
1
2 * dns/rdata.py (Rdata._wire_cmp): We need to specify no
3 compression and an origin to _wire_cmp() in case names in the
4 rdata are relative names.
5
6 * dns/rdtypes/ANY/SIG.py (SIG._cmp): Add missing 'import struct'.
7 Thanks to Arfrever Frehtes Taifersar Arahesis for reporting the
8 problem.
9
10 2011-03-24 Bob Halley <halley@dnspython.org>
11
12 * (Version 1.9.3 released)
13
14 2011-03-22 Bob Halley <halley@dnspython.org>
15
16 * dns/resolver.py: a boolean parameter, 'raise_on_no_answer', has
17 been added to the query() methods. In no-error, no-data
18 situations, this parameter determines whether NoAnswer should be
19 raised or not. If True, NoAnswer is raised. If False, then an
20 Answer() object with a None rrset will be returned.
21
22 * dns/resolver.py: Answer() objects now have a canonical_name field.
23
24 2011-01-11 Bob Halley <halley@dnspython.org>
25
26 * Dnspython was erroneously doing case-insensitive comparisons
27 of the names in NSEC and RRSIG RRs. Thanks to Casey Deccio for
28 reporting this bug.
29
30 2010-12-17 Bob Halley <halley@dnspython.org>
31
32 * dns/message.py (_WireReader._get_section): use "is" and not "=="
33 when testing what section an RR is in. Thanks to James Raftery
34 for reporting this bug.
35
36 2010-12-10 Bob Halley <halley@dnspython.org>
37
38 * dns/resolver.py (Resolver.query): disallow metaqueries.
39
40 * dns/rdata.py (Rdata.__hash__): Added a __hash__ method for rdata.
41
42 2010-11-23 Bob Halley <halley@dnspython.org>
43
44 * (Version 1.9.2 released)
45
46 2010-11-23 Bob Halley <halley@dnspython.org>
47
48 * dns/dnssec.py (_need_pycrypto): DSA and RSA are modules, not
49 functions, and I didn't notice because the test suite masked
50 the bug! *sigh*
51
52 2010-11-22 Bob Halley <halley@dnspython.org>
53
54 * (Version 1.9.1 released)
55
56 2010-11-22 Bob Halley <halley@dnspython.org>
57
58 * dns/dnssec.py: the "from" style import used to get DSA from
59 PyCrypto trashed a DSA constant. Now a normal import is used
60 to avoid namespace contamination.
61
62 2010-11-20 Bob Halley <halley@dnspython.org>
63
64 * (Version 1.9.0 released)
65
66 2010-11-07 Bob Halley <halley@dnspython.org>
67
68 * dns/dnssec.py: Added validate() to do basic DNSSEC validation
69 (requires PyCrypto). Thanks to Brian Wellington for the patch.
70
71 * dns/hash.py: Hash compatibility handling is now its own module.
72
73 2010-10-31 Bob Halley <halley@dnspython.org>
74
75 * dns/resolver.py (zone_for_name): A query name resulting in a
76 CNAME or DNAME response to a node which had an SOA was incorrectly
77 treated as a zone origin. In these cases, we should just look
78 higher. Thanks to Gert Berger for reporting this problem.
79
80 * Added zonediff.py to examples. This program compares two zones
81 and shows the differences either in diff-like plain text, or
82 HTML. Thanks to Dennis Kaarsemaker for contributing this
83 useful program.
84
85 2010-10-27 Bob Halley <halley@dnspython.org>
86
87 * Incorporate a patch to use poll() instead of select() by
88 default on platforms which support it. Thanks to
89 Peter Schüller and Spotify for the contribution.
90
91 2010-10-17 Bob Halley <halley@dnspython.org>
92
93 * Python prior to 2.5.2 doesn't compute the correct values for
94 HMAC-SHA384 and HMAC-SHA512. We now detect attempts to use
95 them and raise NotImplemented if the Python version is too old.
96 Thanks to Kevin Chen for reporting the problem.
97
98 * Various routines that took the string forms of rdata types and
99 classes did not permit the strings to be Unicode strings.
100 Thanks to Ryan Workman for reporting the issue.
101
102 * dns/tsig.py: Added symbolic constants for the algorithm strings.
103 E.g. you can now say dns.tsig.HMAC_MD5 instead of
104 "HMAC-MD5.SIG-ALG.REG.INT". Thanks to Cillian Sharkey for
105 suggesting this improvement.
106
107 * dns/tsig.py (get_algorithm): fix hashlib compatibility; thanks to
108 Kevin Chen for the patch.
109
110 * dns/dnssec.py: Added key_id() and make_ds().
111
112 * dns/message.py: message.py needs to import dns.edns since it uses
113 it.
114
115 2010-05-04 Bob Halley <halley@dnspython.org>
116
117 * dns/rrset.py (RRset.__init__): "covers" was not passed to the
118 superclass __init__(). Thanks to Shanmuga Rajan for reporting
119 the problem.
120
121 2010-03-10 Bob Halley <halley@dnspython.org>
122
123 * The TSIG algorithm value was passed to use_tsig() incorrectly
124 in some cases. Thanks to 'ducciovigolo' for reporting the problem.
125
126 2010-01-26 Bob Halley <halley@dnspython.org>
127
128 * (Version 1.8.0 released)
129
0130 2010-01-13 Bob Halley <halley@dnspython.org>
1131
2132 * dns/dnssec.py: Added RSASHA256 and RSASHA512 codepoints; added
00 Metadata-Version: 1.1
11 Name: dnspython
2 Version: 1.8.0
2 Version: 1.9.4
33 Summary: DNS toolkit
44 Home-page: http://www.dnspython.org
55 Author: Bob Halley
66 Author-email: halley@dnspython.org
77 License: BSD-like
8 Download-URL: http://www.dnspython.org/kits/1.8.0/dnspython-1.8.0.tar.gz
8 Download-URL: http://www.dnspython.org/kits/1.9.4/dnspython-1.9.4.tar.gz
99 Description: dnspython is a DNS toolkit for Python. It supports almost all
1010 record types. It can be used for queries, zone transfers, and dynamic
1111 updates. It supports TSIG authenticated messages and EDNS0.
2121
2222 ABOUT THIS RELEASE
2323
24 This is dnspython 1.8.0
24 This is dnspython 1.9.4
25
26 New since 1.9.3:
27
28 Nothing.
29
30 Bugs fixed since 1.9.3:
31
32 The rdata _wire_cmp() routine now handles relative names.
33
34 The SIG RR implementation was missing 'import struct'.
35
36 New since 1.9.2:
37
38 A boolean parameter, 'raise_on_no_answer', has been added to
39 the query() methods. In no-error, no-data situations, this
40 parameter determines whether NoAnswer should be raised or not.
41 If True, NoAnswer is raised. If False, then an Answer()
42 object with a None rrset will be returned.
43
44 Resolver Answer() objects now have a canonical_name field.
45
46 Rdata now have a __hash__ method.
47
48 Bugs fixed since 1.9.2:
49
50 Dnspython was erroneously doing case-insensitive comparisons
51 of the names in NSEC and RRSIG RRs.
52
53 We now use "is" and not "==" when testing what section an RR
54 is in.
55
56 The resolver now disallows metaqueries.
57
58 New since 1.9.1:
59
60 Nothing.
61
62 Bugs fixed since 1.9.1:
63
64 The dns.dnssec module didn't work at all due to missing
65 imports that escaped detection in testing because the test
66 suite also did the imports. The third time is the charm!
67
68 New since 1.9.0:
69
70 Nothing.
71
72 Bugs fixed since 1.9.0:
73
74 The dns.dnssec module didn't work with DSA due to namespace
75 contamination from a "from"-style import.
76
77 New since 1.8.0:
78
79 dnspython now uses poll() instead of select() when available.
80
81 Basic DNSSEC validation can be done using dns.dnsec.validate()
82 and dns.dnssec.validate_rrsig() if you have PyCrypto 2.3 or
83 later installed. Complete secure resolution is not yet
84 available.
85
86 Added key_id() to the DNSSEC module, which computes the DNSSEC
87 key id of a DNSKEY rdata.
88
89 Added make_ds() to the DNSSEC module, which returns the DS RR
90 for a given DNSKEY rdata.
91
92 dnspython now raises an exception if HMAC-SHA284 or
93 HMAC-SHA512 are used with a Python older than 2.5.2. (Older
94 Pythons do not compute the correct value.)
95
96 Symbolic constants are now available for TSIG algorithm names.
97
98 Bugs fixed since 1.8.0
99
100 dns.resolver.zone_for_name() didn't handle a query response
101 with a CNAME or DNAME correctly in some cases.
102
103 When specifying rdata types and classes as text, Unicode
104 strings may now be used.
105
106 Hashlib compatibility issues have been fixed.
107
108 dns.message now imports dns.edns.
109
110 The TSIG algorithm value was passed incorrectly to use_tsig()
111 in some cases.
25112
26113 New since 1.7.1:
27114
309396
310397 REQUIREMENTS
311398
312 Python 2.2 or later.
399 Python 2.4 or later.
313400
314401
315402 INSTALLATION
2121 'entropy',
2222 'exception',
2323 'flags',
24 'hash',
2425 'inet',
2526 'ipv4',
2627 'ipv6',
1414
1515 """Common DNSSEC-related functions and constants."""
1616
17 import cStringIO
18 import struct
19 import time
20
21 import dns.exception
22 import dns.hash
23 import dns.name
24 import dns.node
25 import dns.rdataset
26 import dns.rdata
27 import dns.rdatatype
28 import dns.rdataclass
29
30 class UnsupportedAlgorithm(dns.exception.DNSException):
31 """Raised if an algorithm is not supported."""
32 pass
33
34 class ValidationFailure(dns.exception.DNSException):
35 """The DNSSEC signature is invalid."""
36 pass
37
1738 RSAMD5 = 1
1839 DH = 2
1940 DSA = 3
4869
4970 _algorithm_by_value = dict([(y, x) for x, y in _algorithm_by_text.iteritems()])
5071
51 class UnknownAlgorithm(Exception):
52 """Raised if an algorithm is unknown."""
53 pass
54
5572 def algorithm_from_text(text):
5673 """Convert text into a DNSSEC algorithm value
5774 @rtype: int"""
58
75
5976 value = _algorithm_by_text.get(text.upper())
6077 if value is None:
6178 value = int(text)
6481 def algorithm_to_text(value):
6582 """Convert a DNSSEC algorithm value to text
6683 @rtype: string"""
67
84
6885 text = _algorithm_by_value.get(value)
6986 if text is None:
7087 text = str(value)
7188 return text
89
90 def _to_rdata(record, origin):
91 s = cStringIO.StringIO()
92 record.to_wire(s, origin=origin)
93 return s.getvalue()
94
95 def key_id(key, origin=None):
96 rdata = _to_rdata(key, origin)
97 if key.algorithm == RSAMD5:
98 return (ord(rdata[-3]) << 8) + ord(rdata[-2])
99 else:
100 total = 0
101 for i in range(len(rdata) / 2):
102 total += (ord(rdata[2 * i]) << 8) + ord(rdata[2 * i + 1])
103 if len(rdata) % 2 != 0:
104 total += ord(rdata[len(rdata) - 1]) << 8
105 total += ((total >> 16) & 0xffff);
106 return total & 0xffff
107
108 def make_ds(name, key, algorithm, origin=None):
109 if algorithm.upper() == 'SHA1':
110 dsalg = 1
111 hash = dns.hash.get('SHA1')()
112 elif algorithm.upper() == 'SHA256':
113 dsalg = 2
114 hash = dns.hash.get('SHA256')()
115 else:
116 raise UnsupportedAlgorithm, 'unsupported algorithm "%s"' % algorithm
117
118 if isinstance(name, (str, unicode)):
119 name = dns.name.from_text(name, origin)
120 hash.update(name.canonicalize().to_wire())
121 hash.update(_to_rdata(key, origin))
122 digest = hash.digest()
123
124 dsrdata = struct.pack("!HBB", key_id(key), key.algorithm, dsalg) + digest
125 return dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0,
126 len(dsrdata))
127
128 def _find_key(keys, rrsig):
129 value = keys.get(rrsig.signer)
130 if value is None:
131 return None
132 if isinstance(value, dns.node.Node):
133 try:
134 rdataset = node.find_rdataset(dns.rdataclass.IN,
135 dns.rdatatype.DNSKEY)
136 except KeyError:
137 return None
138 else:
139 rdataset = value
140 for rdata in rdataset:
141 if rdata.algorithm == rrsig.algorithm and \
142 key_id(rdata) == rrsig.key_tag:
143 return rdata
144 return None
145
146 def _is_rsa(algorithm):
147 return algorithm in (RSAMD5, RSASHA1,
148 RSASHA1NSEC3SHA1, RSASHA256,
149 RSASHA512)
150
151 def _is_dsa(algorithm):
152 return algorithm in (DSA, DSANSEC3SHA1)
153
154 def _is_md5(algorithm):
155 return algorithm == RSAMD5
156
157 def _is_sha1(algorithm):
158 return algorithm in (DSA, RSASHA1,
159 DSANSEC3SHA1, RSASHA1NSEC3SHA1)
160
161 def _is_sha256(algorithm):
162 return algorithm == RSASHA256
163
164 def _is_sha512(algorithm):
165 return algorithm == RSASHA512
166
167 def _make_hash(algorithm):
168 if _is_md5(algorithm):
169 return dns.hash.get('MD5')()
170 if _is_sha1(algorithm):
171 return dns.hash.get('SHA1')()
172 if _is_sha256(algorithm):
173 return dns.hash.get('SHA256')()
174 if _is_sha512(algorithm):
175 return dns.hash.get('SHA512')()
176 raise ValidationFailure, 'unknown hash for algorithm %u' % algorithm
177
178 def _make_algorithm_id(algorithm):
179 if _is_md5(algorithm):
180 oid = [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05]
181 elif _is_sha1(algorithm):
182 oid = [0x2b, 0x0e, 0x03, 0x02, 0x1a]
183 elif _is_sha256(algorithm):
184 oid = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01]
185 elif _is_sha512(algorithm):
186 oid = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03]
187 else:
188 raise ValidationFailure, 'unknown algorithm %u' % algorithm
189 olen = len(oid)
190 dlen = _make_hash(algorithm).digest_size
191 idbytes = [0x30] + [8 + olen + dlen] + \
192 [0x30, olen + 4] + [0x06, olen] + oid + \
193 [0x05, 0x00] + [0x04, dlen]
194 return ''.join(map(chr, idbytes))
195
196 def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
197 """Validate an RRset against a single signature rdata
198
199 The owner name of the rrsig is assumed to be the same as the owner name
200 of the rrset.
201
202 @param rrset: The RRset to validate
203 @type rrset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset)
204 tuple
205 @param rrsig: The signature rdata
206 @type rrsig: dns.rrset.Rdata
207 @param keys: The key dictionary.
208 @type keys: a dictionary keyed by dns.name.Name with node or rdataset values
209 @param origin: The origin to use for relative names
210 @type origin: dns.name.Name or None
211 @param now: The time to use when validating the signatures. The default
212 is the current time.
213 @type now: int
214 """
215
216 if isinstance(origin, (str, unicode)):
217 origin = dns.name.from_text(origin, dns.name.root)
218
219 key = _find_key(keys, rrsig)
220 if not key:
221 raise ValidationFailure, 'unknown key'
222
223 # For convenience, allow the rrset to be specified as a (name, rdataset)
224 # tuple as well as a proper rrset
225 if isinstance(rrset, tuple):
226 rrname = rrset[0]
227 rdataset = rrset[1]
228 else:
229 rrname = rrset.name
230 rdataset = rrset
231
232 if now is None:
233 now = time.time()
234 if rrsig.expiration < now:
235 raise ValidationFailure, 'expired'
236 if rrsig.inception > now:
237 raise ValidationFailure, 'not yet valid'
238
239 hash = _make_hash(rrsig.algorithm)
240
241 if _is_rsa(rrsig.algorithm):
242 keyptr = key.key
243 (bytes,) = struct.unpack('!B', keyptr[0:1])
244 keyptr = keyptr[1:]
245 if bytes == 0:
246 (bytes,) = struct.unpack('!H', keyptr[0:2])
247 keyptr = keyptr[2:]
248 rsa_e = keyptr[0:bytes]
249 rsa_n = keyptr[bytes:]
250 keylen = len(rsa_n) * 8
251 pubkey = Crypto.PublicKey.RSA.construct(
252 (Crypto.Util.number.bytes_to_long(rsa_n),
253 Crypto.Util.number.bytes_to_long(rsa_e)))
254 sig = (Crypto.Util.number.bytes_to_long(rrsig.signature),)
255 elif _is_dsa(rrsig.algorithm):
256 keyptr = key.key
257 (t,) = struct.unpack('!B', keyptr[0:1])
258 keyptr = keyptr[1:]
259 octets = 64 + t * 8
260 dsa_q = keyptr[0:20]
261 keyptr = keyptr[20:]
262 dsa_p = keyptr[0:octets]
263 keyptr = keyptr[octets:]
264 dsa_g = keyptr[0:octets]
265 keyptr = keyptr[octets:]
266 dsa_y = keyptr[0:octets]
267 pubkey = Crypto.PublicKey.DSA.construct(
268 (Crypto.Util.number.bytes_to_long(dsa_y),
269 Crypto.Util.number.bytes_to_long(dsa_g),
270 Crypto.Util.number.bytes_to_long(dsa_p),
271 Crypto.Util.number.bytes_to_long(dsa_q)))
272 (dsa_r, dsa_s) = struct.unpack('!20s20s', rrsig.signature[1:])
273 sig = (Crypto.Util.number.bytes_to_long(dsa_r),
274 Crypto.Util.number.bytes_to_long(dsa_s))
275 else:
276 raise ValidationFailure, 'unknown algorithm %u' % rrsig.algorithm
277
278 hash.update(_to_rdata(rrsig, origin)[:18])
279 hash.update(rrsig.signer.to_digestable(origin))
280
281 if rrsig.labels < len(rrname) - 1:
282 suffix = rrname.split(rrsig.labels + 1)[1]
283 rrname = dns.name.from_text('*', suffix)
284 rrnamebuf = rrname.to_digestable(origin)
285 rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass,
286 rrsig.original_ttl)
287 rrlist = sorted(rdataset);
288 for rr in rrlist:
289 hash.update(rrnamebuf)
290 hash.update(rrfixed)
291 rrdata = rr.to_digestable(origin)
292 rrlen = struct.pack('!H', len(rrdata))
293 hash.update(rrlen)
294 hash.update(rrdata)
295
296 digest = hash.digest()
297
298 if _is_rsa(rrsig.algorithm):
299 # PKCS1 algorithm identifier goop
300 digest = _make_algorithm_id(rrsig.algorithm) + digest
301 padlen = keylen / 8 - len(digest) - 3
302 digest = chr(0) + chr(1) + chr(0xFF) * padlen + chr(0) + digest
303 elif _is_dsa(rrsig.algorithm):
304 pass
305 else:
306 # Raise here for code clarity; this won't actually ever happen
307 # since if the algorithm is really unknown we'd already have
308 # raised an exception above
309 raise ValidationFailure, 'unknown algorithm %u' % rrsig.algorithm
310
311 if not pubkey.verify(digest, sig):
312 raise ValidationFailure, 'verify failure'
313
314 def _validate(rrset, rrsigset, keys, origin=None, now=None):
315 """Validate an RRset
316
317 @param rrset: The RRset to validate
318 @type rrset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset)
319 tuple
320 @param rrsigset: The signature RRset
321 @type rrsigset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset)
322 tuple
323 @param keys: The key dictionary.
324 @type keys: a dictionary keyed by dns.name.Name with node or rdataset values
325 @param origin: The origin to use for relative names
326 @type origin: dns.name.Name or None
327 @param now: The time to use when validating the signatures. The default
328 is the current time.
329 @type now: int
330 """
331
332 if isinstance(origin, (str, unicode)):
333 origin = dns.name.from_text(origin, dns.name.root)
334
335 if isinstance(rrset, tuple):
336 rrname = rrset[0]
337 else:
338 rrname = rrset.name
339
340 if isinstance(rrsigset, tuple):
341 rrsigname = rrsigset[0]
342 rrsigrdataset = rrsigset[1]
343 else:
344 rrsigname = rrsigset.name
345 rrsigrdataset = rrsigset
346
347 rrname = rrname.choose_relativity(origin)
348 rrsigname = rrname.choose_relativity(origin)
349 if rrname != rrsigname:
350 raise ValidationFailure, "owner names do not match"
351
352 for rrsig in rrsigrdataset:
353 try:
354 _validate_rrsig(rrset, rrsig, keys, origin, now)
355 return
356 except ValidationFailure, e:
357 pass
358 raise ValidationFailure, "no RRSIGs validated"
359
360 def _need_pycrypto(*args, **kwargs):
361 raise NotImplementedError, "DNSSEC validation requires pycrypto"
362
363 try:
364 import Crypto.PublicKey.RSA
365 import Crypto.PublicKey.DSA
366 import Crypto.Util.number
367 validate = _validate
368 validate_rrsig = _validate_rrsig
369 except ImportError:
370 validate = _need_pycrypto
371 validate_rrsig = _need_pycrypto
0 # Copyright (C) 2010 Nominum, Inc.
1 #
2 # Permission to use, copy, modify, and distribute this software and its
3 # documentation for any purpose with or without fee is hereby granted,
4 # provided that the above copyright notice and this permission notice
5 # appear in all copies.
6 #
7 # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
8 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
10 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
13 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15 """Hashing backwards compatibility wrapper"""
16
17 import sys
18
19 _hashes = None
20
21 def _need_later_python(alg):
22 def func(*args, **kwargs):
23 raise NotImplementedError("TSIG algorithm " + alg +
24 " requires Python 2.5.2 or later")
25 return func
26
27 def _setup():
28 global _hashes
29 _hashes = {}
30 try:
31 import hashlib
32 _hashes['MD5'] = hashlib.md5
33 _hashes['SHA1'] = hashlib.sha1
34 _hashes['SHA224'] = hashlib.sha224
35 _hashes['SHA256'] = hashlib.sha256
36 if sys.hexversion >= 0x02050200:
37 _hashes['SHA384'] = hashlib.sha384
38 _hashes['SHA512'] = hashlib.sha512
39 else:
40 _hashes['SHA384'] = _need_later_python('SHA384')
41 _hashes['SHA512'] = _need_later_python('SHA512')
42
43 if sys.hexversion < 0x02050000:
44 # hashlib doesn't conform to PEP 247: API for
45 # Cryptographic Hash Functions, which hmac before python
46 # 2.5 requires, so add the necessary items.
47 class HashlibWrapper:
48 def __init__(self, basehash):
49 self.basehash = basehash
50 self.digest_size = self.basehash().digest_size
51
52 def new(self, *args, **kwargs):
53 return self.basehash(*args, **kwargs)
54
55 for name in _hashes:
56 _hashes[name] = HashlibWrapper(_hashes[name])
57
58 except ImportError:
59 import md5, sha
60 _hashes['MD5'] = md5
61 _hashes['SHA1'] = sha
62
63 def get(algorithm):
64 if _hashes is None:
65 _setup()
66 return _hashes[algorithm.upper()]
2020 import sys
2121 import time
2222
23 import dns.edns
2324 import dns.exception
2425 import dns.flags
2526 import dns.name
9192 @type keyring: dict
9293 @ivar keyname: The TSIG keyname to use. The default is None.
9394 @type keyname: dns.name.Name object
94 @ivar keyalgorithm: The TSIG key algorithm to use. The default is
95 dns.tsig.default_algorithm.
95 @ivar keyalgorithm: The TSIG algorithm to use; defaults to
96 dns.tsig.default_algorithm. Constants for TSIG algorithms are defined
97 in dns.tsig, and the currently implemented algorithms are
98 HMAC_MD5, HMAC_SHA1, HMAC_SHA224, HMAC_SHA256, HMAC_SHA384, and
99 HMAC_SHA512.
96100 @type keyalgorithm: string
97101 @ivar request_mac: The TSIG MAC of the request message associated with
98102 this message; used when validating TSIG signatures. @see: RFC 2845 for
681685 deleting = None
682686 if deleting == dns.rdataclass.ANY or \
683687 (deleting == dns.rdataclass.NONE and \
684 section == self.message.answer):
688 section is self.message.answer):
685689 covers = dns.rdatatype.NONE
686690 rd = None
687691 else:
10341038
10351039 if isinstance(qname, (str, unicode)):
10361040 qname = dns.name.from_text(qname)
1037 if isinstance(rdtype, str):
1041 if isinstance(rdtype, (str, unicode)):
10381042 rdtype = dns.rdatatype.from_text(rdtype)
1039 if isinstance(rdclass, str):
1043 if isinstance(rdclass, (str, unicode)):
10401044 rdclass = dns.rdataclass.from_text(rdclass)
10411045 m = Message()
10421046 m.flags |= dns.flags.RD
2222
2323 class Node(object):
2424 """A DNS node.
25
25
2626 A node is a set of rdatasets
2727
2828 @ivar rdatasets: the node's rdatasets
2929 @type rdatasets: list of dns.rdataset.Rdataset objects"""
3030
3131 __slots__ = ['rdatasets']
32
32
3333 def __init__(self):
3434 """Initialize a DNS node.
3535 """
36
36
3737 self.rdatasets = [];
3838
3939 def to_text(self, name, **kw):
4545 @type name: dns.name.Name object
4646 @rtype: string
4747 """
48
48
4949 s = StringIO.StringIO()
5050 for rds in self.rdatasets:
5151 print >> s, rds.to_text(name, **kw)
5353
5454 def __repr__(self):
5555 return '<DNS node ' + str(id(self)) + '>'
56
56
5757 def __eq__(self, other):
5858 """Two nodes are equal if they have the same rdatasets.
5959
7272
7373 def __ne__(self, other):
7474 return not self.__eq__(other)
75
75
7676 def __len__(self):
7777 return len(self.rdatasets)
7878
158158
159159 def replace_rdataset(self, replacement):
160160 """Replace an rdataset.
161
161
162162 It is not an error if there is no rdataset matching I{replacement}.
163163
164164 Ownership of the I{replacement} object is transferred to the node;
4444 else:
4545 return time.time() + timeout
4646
47 def _wait_for(ir, iw, ix, expiration):
47 def _poll_for(fd, readable, writable, error, timeout):
48 """
49 @param fd: File descriptor (int).
50 @param readable: Whether to wait for readability (bool).
51 @param writable: Whether to wait for writability (bool).
52 @param expiration: Deadline timeout (expiration time, in seconds (float)).
53
54 @return True on success, False on timeout
55 """
56 event_mask = 0
57 if readable:
58 event_mask |= select.POLLIN
59 if writable:
60 event_mask |= select.POLLOUT
61 if error:
62 event_mask |= select.POLLERR
63
64 pollable = select.poll()
65 pollable.register(fd, event_mask)
66
67 if timeout:
68 event_list = pollable.poll(long(timeout * 1000))
69 else:
70 event_list = pollable.poll()
71
72 return bool(event_list)
73
74 def _select_for(fd, readable, writable, error, timeout):
75 """
76 @param fd: File descriptor (int).
77 @param readable: Whether to wait for readability (bool).
78 @param writable: Whether to wait for writability (bool).
79 @param expiration: Deadline timeout (expiration time, in seconds (float)).
80
81 @return True on success, False on timeout
82 """
83 rset, wset, xset = [], [], []
84
85 if readable:
86 rset = [fd]
87 if writable:
88 wset = [fd]
89 if error:
90 xset = [fd]
91
92 if timeout is None:
93 (rcount, wcount, xcount) = select.select(rset, wset, xset)
94 else:
95 (rcount, wcount, xcount) = select.select(rset, wset, xset, timeout)
96
97 return bool((rcount or wcount or xcount))
98
99 def _wait_for(fd, readable, writable, error, expiration):
48100 done = False
49101 while not done:
50102 if expiration is None:
54106 if timeout <= 0.0:
55107 raise dns.exception.Timeout
56108 try:
57 if timeout is None:
58 (r, w, x) = select.select(ir, iw, ix)
59 else:
60 (r, w, x) = select.select(ir, iw, ix, timeout)
109 if not _polling_backend(fd, readable, writable, error, timeout):
110 raise dns.exception.Timeout
61111 except select.error, e:
62112 if e.args[0] != errno.EINTR:
63113 raise e
64114 done = True
65 if len(r) == 0 and len(w) == 0 and len(x) == 0:
66 raise dns.exception.Timeout
115
116 def _set_polling_backend(fn):
117 """
118 Internal API. Do not use.
119 """
120 global _polling_backend
121
122 _polling_backend = fn
123
124 if hasattr(select, 'poll'):
125 # Prefer poll() on platforms that support it because it has no
126 # limits on the maximum value of a file descriptor (plus it will
127 # be more efficient for high values).
128 _polling_backend = _poll_for
129 else:
130 _polling_backend = _select_for
67131
68132 def _wait_for_readable(s, expiration):
69 _wait_for([s], [], [s], expiration)
133 _wait_for(s, True, False, True, expiration)
70134
71135 def _wait_for_writable(s, expiration):
72 _wait_for([], [s], [s], expiration)
136 _wait_for(s, False, True, True, expiration)
73137
74138 def _addresses_equal(af, a1, a2):
75139 # Convert the first value of the tuple, which is a textual format
309373
310374 if isinstance(zone, (str, unicode)):
311375 zone = dns.name.from_text(zone)
312 if isinstance(rdtype, str):
376 if isinstance(rdtype, (str, unicode)):
313377 rdtype = dns.rdatatype.from_text(rdtype)
314378 q = dns.message.make_query(zone, rdtype, rdclass)
315379 if rdtype == dns.rdatatype.IXFR:
2727 import cStringIO
2828
2929 import dns.exception
30 import dns.name
3031 import dns.rdataclass
3132 import dns.rdatatype
3233 import dns.tokenizer
250251 self.rdtype != other.rdtype:
251252 return NotImplemented
252253 return self._cmp(other) > 0
254
255 def __hash__(self):
256 return hash(self.to_digestable(dns.name.root))
257
258 def _wire_cmp(self, other):
259 # A number of types compare rdata in wire form, so we provide
260 # the method here instead of duplicating it.
261 #
262 # We specifiy an arbitrary origin of '.' when doing the
263 # comparison, since the rdata may have relative names and we
264 # can't convert a relative name to wire without an origin.
265 b1 = cStringIO.StringIO()
266 self.to_wire(b1, None, dns.name.root)
267 b2 = cStringIO.StringIO()
268 other.to_wire(b2, None, dns.name.root)
269 return cmp(b1.getvalue(), b2.getvalue())
253270
254271 def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
255272 """Build an rdata object from text format.
280280 @rtype: dns.rdataset.Rdataset object
281281 """
282282
283 if isinstance(rdclass, str):
283 if isinstance(rdclass, (str, unicode)):
284284 rdclass = dns.rdataclass.from_text(rdclass)
285 if isinstance(rdtype, str):
285 if isinstance(rdtype, (str, unicode)):
286286 rdtype = dns.rdatatype.from_text(rdtype)
287287 r = Rdataset(rdclass, rdtype)
288288 r.update_ttl(ttl)
124124 self.next = self.next.choose_relativity(origin, relativize)
125125
126126 def _cmp(self, other):
127 v = cmp(self.next, other.next)
128 if v == 0:
129 b1 = cStringIO.StringIO()
130 for (window, bitmap) in self.windows:
131 b1.write(chr(window))
132 b1.write(chr(len(bitmap)))
133 b1.write(bitmap)
134 b2 = cStringIO.StringIO()
135 for (window, bitmap) in other.windows:
136 b2.write(chr(window))
137 b2.write(chr(len(bitmap)))
138 b2.write(bitmap)
139 v = cmp(b1.getvalue(), b2.getvalue())
140 return v
127 return self._wire_cmp(other)
1212 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
1313 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1414
15 import struct
16
1517 import dns.rdtypes.sigbase
1618
1719 class SIG(dns.rdtypes.sigbase.SIGBase):
2325 self.inception, self.key_tag) + \
2426 self.signer.to_digestable(origin) + \
2527 self.signature
28 def _cmp(self, other):
29 hs = struct.pack('!HBBIIIH', self.type_covered,
30 self.algorithm, self.labels,
31 self.original_ttl, self.expiration,
32 self.inception, self.key_tag)
33 ho = struct.pack('!HBBIIIH', other.type_covered,
34 other.algorithm, other.labels,
35 other.original_ttl, other.expiration,
36 other.inception, other.key_tag)
37 v = cmp(hs, ho)
38 if v == 0:
39 v = cmp(self.signer, other.signer)
40 if v == 0:
41 v = cmp(self.signature, other.signature)
42 return v
151151 self.signer = self.signer.choose_relativity(origin, relativize)
152152
153153 def _cmp(self, other):
154 hs = struct.pack('!HBBIIIH', self.type_covered,
155 self.algorithm, self.labels,
156 self.original_ttl, self.expiration,
157 self.inception, self.key_tag)
158 ho = struct.pack('!HBBIIIH', other.type_covered,
159 other.algorithm, other.labels,
160 other.original_ttl, other.expiration,
161 other.inception, other.key_tag)
162 v = cmp(hs, ho)
163 if v == 0:
164 v = cmp(self.signer, other.signer)
165 if v == 0:
166 v = cmp(self.signature, other.signature)
167 return v
154 return self._wire_cmp(other)
6060 This should never happen!"""
6161 pass
6262
63 class NoMetaqueries(dns.exception.DNSException):
64 """Metaqueries are not allowed."""
65 pass
66
6367
6468 class Answer(object):
6569 """DNS stub resolver answer
8892 @type rrset: dns.rrset.RRset object
8993 @ivar expiration: The time when the answer expires
9094 @type expiration: float (seconds since the epoch)
95 @ivar canonical_name: The canonical name of the query name
96 @type canonical_name: dns.name.Name object
9197 """
92 def __init__(self, qname, rdtype, rdclass, response):
98 def __init__(self, qname, rdtype, rdclass, response,
99 raise_on_no_answer=True):
93100 self.qname = qname
94101 self.rdtype = rdtype
95102 self.rdclass = rdclass
117124 break
118125 continue
119126 except KeyError:
120 raise NoAnswer
121 raise NoAnswer
127 if raise_on_no_answer:
128 raise NoAnswer
129 if raise_on_no_answer:
130 raise NoAnswer
131 if rrset is None and raise_on_no_answer:
132 raise NoAnswer
133 self.canonical_name = qname
134 self.rrset = rrset
122135 if rrset is None:
123 raise NoAnswer
124 self.rrset = rrset
136 while 1:
137 # Look for a SOA RR whose owner name is a superdomain
138 # of qname.
139 try:
140 srrset = response.find_rrset(response.authority, qname,
141 rdclass, dns.rdatatype.SOA)
142 if min_ttl == -1 or srrset.ttl < min_ttl:
143 min_ttl = srrset.ttl
144 if srrset[0].minimum < min_ttl:
145 min_ttl = srrset[0].minimum
146 break
147 except KeyError:
148 try:
149 qname = qname.parent()
150 except dns.name.NoParent:
151 break
125152 self.expiration = time.time() + min_ttl
126153
127154 def __getattr__(self, attr):
541568 return min(self.lifetime - duration, self.timeout)
542569
543570 def query(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN,
544 tcp=False, source=None):
571 tcp=False, source=None, raise_on_no_answer=True):
545572 """Query nameservers to find the answer to the question.
546573
547574 The I{qname}, I{rdtype}, and I{rdclass} parameters may be objects
559586 @type tcp: bool
560587 @param source: bind to this IP address (defaults to machine default IP).
561588 @type source: IP address in dotted quad notation
589 @param raise_on_no_answer: raise NoAnswer if there's no answer
590 (defaults is True).
591 @type raise_on_no_answer: bool
562592 @rtype: dns.resolver.Answer instance
563593 @raises Timeout: no answers could be found in the specified lifetime
564594 @raises NXDOMAIN: the query name does not exist
565 @raises NoAnswer: the response did not contain an answer
595 @raises NoAnswer: the response did not contain an answer and
596 raise_on_no_answer is True.
566597 @raises NoNameservers: no non-broken nameservers are available to
567598 answer the question."""
568599
569600 if isinstance(qname, (str, unicode)):
570601 qname = dns.name.from_text(qname, None)
571 if isinstance(rdtype, str):
602 if isinstance(rdtype, (str, unicode)):
572603 rdtype = dns.rdatatype.from_text(rdtype)
573 if isinstance(rdclass, str):
604 if dns.rdatatype.is_metatype(rdtype):
605 raise NoMetaqueries
606 if isinstance(rdclass, (str, unicode)):
574607 rdclass = dns.rdataclass.from_text(rdclass)
608 if dns.rdataclass.is_metaclass(rdclass):
609 raise NoMetaqueries
575610 qnames_to_try = []
576611 if qname.is_absolute():
577612 qnames_to_try.append(qname)
592627 return answer
593628 request = dns.message.make_query(qname, rdtype, rdclass)
594629 if not self.keyname is None:
595 request.use_tsig(self.keyring, self.keyname, self.keyalgorithm)
630 request.use_tsig(self.keyring, self.keyname,
631 algorithm=self.keyalgorithm)
596632 request.use_edns(self.edns, self.ednsflags, self.payload)
597633 response = None
598634 #
668704 break
669705 if all_nxdomain:
670706 raise NXDOMAIN
671 answer = Answer(qname, rdtype, rdclass, response)
707 answer = Answer(qname, rdtype, rdclass, response,
708 raise_on_no_answer)
672709 if self.cache:
673710 self.cache.put((qname, rdtype, rdclass), answer)
674711 return answer
721758 return default_resolver
722759
723760 def query(qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN,
724 tcp=False, source=None):
761 tcp=False, source=None, raise_on_no_answer=True):
725762 """Query nameservers to find the answer to the question.
726763
727764 This is a convenience function that uses the default resolver
728765 object to make the query.
729766 @see: L{dns.resolver.Resolver.query} for more information on the
730767 parameters."""
731 return get_default_resolver().query(qname, rdtype, rdclass, tcp, source)
768 return get_default_resolver().query(qname, rdtype, rdclass, tcp, source,
769 raise_on_no_answer)
732770
733771 def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, resolver=None):
734772 """Find the name of the zone which contains the specified name.
752790 while 1:
753791 try:
754792 answer = resolver.query(name, dns.rdatatype.SOA, rdclass, tcp)
755 return name
793 if answer.rrset.name == name:
794 return name
795 # otherwise we were CNAMEd or DNAMEd and need to look higher
756796 except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
757 try:
758 name = name.parent()
759 except dns.name.NoParent:
760 raise NoRootSOA
797 pass
798 try:
799 name = name.parent()
800 except dns.name.NoParent:
801 raise NoRootSOA
3535 deleting=None):
3636 """Create a new RRset."""
3737
38 super(RRset, self).__init__(rdclass, rdtype)
38 super(RRset, self).__init__(rdclass, rdtype, covers)
3939 self.name = name
4040 self.deleting = deleting
4141
123123
124124 if isinstance(name, (str, unicode)):
125125 name = dns.name.from_text(name, None)
126 if isinstance(rdclass, str):
126 if isinstance(rdclass, (str, unicode)):
127127 rdclass = dns.rdataclass.from_text(rdclass)
128 if isinstance(rdtype, str):
128 if isinstance(rdtype, (str, unicode)):
129129 rdtype = dns.rdatatype.from_text(rdtype)
130130 r = RRset(name, rdclass, rdtype)
131131 r.update_ttl(ttl)
1616
1717 import hmac
1818 import struct
19 import sys
1920
2021 import dns.exception
22 import dns.hash
2123 import dns.rdataclass
2224 import dns.name
2325
4951 """Raised if the peer didn't like amount of truncation in the TSIG we sent"""
5052 pass
5153
52 default_algorithm = "HMAC-MD5.SIG-ALG.REG.INT"
54 # TSIG Algorithms
55
56 HMAC_MD5 = dns.name.from_text("HMAC-MD5.SIG-ALG.REG.INT")
57 HMAC_SHA1 = dns.name.from_text("hmac-sha1")
58 HMAC_SHA224 = dns.name.from_text("hmac-sha224")
59 HMAC_SHA256 = dns.name.from_text("hmac-sha256")
60 HMAC_SHA384 = dns.name.from_text("hmac-sha384")
61 HMAC_SHA512 = dns.name.from_text("hmac-sha512")
62
63 default_algorithm = HMAC_MD5
5364
5465 BADSIG = 16
5566 BADKEY = 17
166177 raise BadSignature
167178 return ctx
168179
180 _hashes = None
181
182 def _maybe_add_hash(tsig_alg, hash_alg):
183 try:
184 _hashes[tsig_alg] = dns.hash.get(hash_alg)
185 except KeyError:
186 pass
187
188 def _setup_hashes():
189 global _hashes
190 _hashes = {}
191 _maybe_add_hash(HMAC_SHA224, 'SHA224')
192 _maybe_add_hash(HMAC_SHA256, 'SHA256')
193 _maybe_add_hash(HMAC_SHA384, 'SHA384')
194 _maybe_add_hash(HMAC_SHA512, 'SHA512')
195 _maybe_add_hash(HMAC_SHA1, 'SHA1')
196 _maybe_add_hash(HMAC_MD5, 'MD5')
197
169198 def get_algorithm(algorithm):
170199 """Returns the wire format string and the hash module to use for the
171200 specified TSIG algorithm
174203 @raises NotImplementedError: I{algorithm} is not supported
175204 """
176205
177 hashes = {}
178 try:
179 import hashlib
180 hashes[dns.name.from_text('hmac-sha224')] = hashlib.sha224
181 hashes[dns.name.from_text('hmac-sha256')] = hashlib.sha256
182 hashes[dns.name.from_text('hmac-sha384')] = hashlib.sha384
183 hashes[dns.name.from_text('hmac-sha512')] = hashlib.sha512
184 hashes[dns.name.from_text('hmac-sha1')] = hashlib.sha1
185 hashes[dns.name.from_text('HMAC-MD5.SIG-ALG.REG.INT')] = hashlib.md5
186
187 import sys
188 if sys.hexversion < 0x02050000:
189 # hashlib doesn't conform to PEP 247: API for
190 # Cryptographic Hash Functions, which hmac before python
191 # 2.5 requires, so add the necessary items.
192 class HashlibWrapper:
193 def __init__(self, basehash):
194 self.basehash = basehash
195 self.digest_size = self.basehash().digest_size
196
197 def new(self, *args, **kwargs):
198 return self.basehash(*args, **kwargs)
199
200 for name in hashes:
201 hashes[name] = HashlibWrapper(hashes[name])
202
203 except ImportError:
204 import md5, sha
205 hashes[dns.name.from_text('HMAC-MD5.SIG-ALG.REG.INT')] = md5.md5
206 hashes[dns.name.from_text('hmac-sha1')] = sha.sha
206 global _hashes
207 if _hashes is None:
208 _setup_hashes()
207209
208210 if isinstance(algorithm, (str, unicode)):
209211 algorithm = dns.name.from_text(algorithm)
210212
211 if algorithm in hashes:
212 return (algorithm.to_digestable(), hashes[algorithm])
213
214 raise NotImplementedError("TSIG algorithm " + str(algorithm) +
215 " is not supported")
213 if sys.hexversion < 0x02050200 and \
214 (algorithm == HMAC_SHA384 or algorithm == HMAC_SHA512):
215 raise NotImplementedError("TSIG algorithm " + str(algorithm) +
216 " requires Python 2.5.2 or later")
217
218 try:
219 return (algorithm.to_digestable(), _hashes[algorithm])
220 except KeyError:
221 raise NotImplementedError("TSIG algorithm " + str(algorithm) +
222 " is not supported")
2020 import dns.rdata
2121 import dns.rdataclass
2222 import dns.rdataset
23 import dns.tsig
2324
2425 class Update(dns.message.Message):
2526 def __init__(self, zone, rdclass=dns.rdataclass.IN, keyring=None,
4142 they know the keyring contains only one key.
4243 @type keyname: dns.name.Name or string
4344 @param keyalgorithm: The TSIG algorithm to use; defaults to
44 dns.tsig.default_algorithm
45 dns.tsig.default_algorithm. Constants for TSIG algorithms are defined
46 in dns.tsig, and the currently implemented algorithms are
47 HMAC_MD5, HMAC_SHA1, HMAC_SHA224, HMAC_SHA256, HMAC_SHA384, and
48 HMAC_SHA512.
4549 @type keyalgorithm: string
4650 """
4751 super(Update, self).__init__()
5559 self.find_rrset(self.question, self.origin, rdclass, dns.rdatatype.SOA,
5660 create=True, force_unique=True)
5761 if not keyring is None:
58 self.use_tsig(keyring, keyname, keyalgorithm)
62 self.use_tsig(keyring, keyname, algorithm=keyalgorithm)
5963
6064 def _add_rr(self, name, ttl, rd, deleting=None, section=None):
6165 """Add a single RR to the update section."""
147151 self._add_rr(name, 0, rd, dns.rdataclass.NONE)
148152 else:
149153 rdtype = args.pop(0)
150 if isinstance(rdtype, str):
154 if isinstance(rdtype, (str, unicode)):
151155 rdtype = dns.rdatatype.from_text(rdtype)
152156 if len(args) == 0:
153157 rrset = self.find_rrset(self.authority, name,
205209 self._add(False, self.answer, name, *args)
206210 else:
207211 rdtype = args[0]
208 if isinstance(rdtype, str):
212 if isinstance(rdtype, (str, unicode)):
209213 rdtype = dns.rdatatype.from_text(rdtype)
210214 rrset = self.find_rrset(self.answer, name,
211215 dns.rdataclass.ANY, rdtype,
224228 dns.rdatatype.NONE, None,
225229 True, True)
226230 else:
227 if isinstance(rdtype, str):
231 if isinstance(rdtype, (str, unicode)):
228232 rdtype = dns.rdatatype.from_text(rdtype)
229233 rrset = self.find_rrset(self.answer, name,
230234 dns.rdataclass.NONE, rdtype,
1515 """dnspython release version information."""
1616
1717 MAJOR = 1
18 MINOR = 8
19 MICRO = 0
18 MINOR = 9
19 MICRO = 4
2020 RELEASELEVEL = 0x0f
2121 SERIAL = 0
2222
236236 """
237237
238238 name = self._validate_name(name)
239 if isinstance(rdtype, str):
239 if isinstance(rdtype, (str, unicode)):
240240 rdtype = dns.rdatatype.from_text(rdtype)
241 if isinstance(covers, str):
241 if isinstance(covers, (str, unicode)):
242242 covers = dns.rdatatype.from_text(covers)
243243 node = self.find_node(name, create)
244244 return node.find_rdataset(self.rdclass, rdtype, covers, create)
299299 """
300300
301301 name = self._validate_name(name)
302 if isinstance(rdtype, str):
302 if isinstance(rdtype, (str, unicode)):
303303 rdtype = dns.rdatatype.from_text(rdtype)
304 if isinstance(covers, str):
304 if isinstance(covers, (str, unicode)):
305305 covers = dns.rdatatype.from_text(covers)
306306 node = self.get_node(name)
307307 if not node is None:
362362 """
363363
364364 name = self._validate_name(name)
365 if isinstance(rdtype, str):
365 if isinstance(rdtype, (str, unicode)):
366366 rdtype = dns.rdatatype.from_text(rdtype)
367 if isinstance(covers, str):
367 if isinstance(covers, (str, unicode)):
368368 covers = dns.rdatatype.from_text(covers)
369369 rdataset = self.nodes[name].find_rdataset(self.rdclass, rdtype, covers)
370370 rrset = dns.rrset.RRset(name, self.rdclass, rdtype, covers)
418418 @type covers: int or string
419419 """
420420
421 if isinstance(rdtype, str):
421 if isinstance(rdtype, (str, unicode)):
422422 rdtype = dns.rdatatype.from_text(rdtype)
423 if isinstance(covers, str):
423 if isinstance(covers, (str, unicode)):
424424 covers = dns.rdatatype.from_text(covers)
425425 for (name, node) in self.iteritems():
426426 for rds in node:
441441 @type covers: int or string
442442 """
443443
444 if isinstance(rdtype, str):
444 if isinstance(rdtype, (str, unicode)):
445445 rdtype = dns.rdatatype.from_text(rdtype)
446 if isinstance(covers, str):
446 if isinstance(covers, (str, unicode)):
447447 covers = dns.rdatatype.from_text(covers)
448448 for (name, node) in self.iteritems():
449449 for rds in node:
0 #!/usr/bin/env python
1 #
2 # Small library and commandline tool to do logical diffs of zonefiles
3 # ./zonediff -h gives you help output
4 #
5 # Requires dnspython to do all the heavy lifting
6 #
7 # (c)2009 Dennis Kaarsemaker <dennis@kaarsemaker.net>
8 #
9 # Permission to use, copy, modify, and distribute this software and its
10 # documentation for any purpose with or without fee is hereby granted,
11 # provided that the above copyright notice and this permission notice
12 # appear in all copies.
13 #
14 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
20 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 """See diff_zones.__doc__ for more information"""
22
23 __all__ = ['diff_zones', 'format_changes_plain', 'format_changes_html']
24
25 try:
26 import dns.zone
27 except ImportError:
28 import sys
29 sys.stderr.write("Please install dnspython")
30 sys.exit(1)
31
32 def diff_zones(zone1, zone2, ignore_ttl=False, ignore_soa=False):
33 """diff_zones(zone1, zone2, ignore_ttl=False, ignore_soa=False) -> changes
34 Compares two dns.zone.Zone objects and returns a list of all changes
35 in the format (name, oldnode, newnode).
36
37 If ignore_ttl is true, a node will not be added to this list if the
38 only change is its TTL.
39
40 If ignore_soa is true, a node will not be added to this list if the
41 only changes is a change in a SOA Rdata set.
42
43 The returned nodes do include all Rdata sets, including unchanged ones.
44 """
45
46 changes = []
47 for name in zone1:
48 name = str(name)
49 n1 = zone1.get_node(name)
50 n2 = zone2.get_node(name)
51 if not n2:
52 changes.append((str(name), n1, n2))
53 elif _nodes_differ(n1, n2, ignore_ttl, ignore_soa):
54 changes.append((str(name), n1, n2))
55
56 for name in zone2:
57 n1 = zone1.get_node(name)
58 if not n1:
59 n2 = zone2.get_node(name)
60 changes.append((str(name), n1, n2))
61 return changes
62
63 def _nodes_differ(n1, n2, ignore_ttl, ignore_soa):
64 if ignore_soa or not ignore_ttl:
65 # Compare datasets directly
66 for r in n1.rdatasets:
67 if ignore_soa and r.rdtype == dns.rdatatype.SOA:
68 continue
69 if r not in n2.rdatasets:
70 return True
71 if not ignore_ttl:
72 return r.ttl != n2.find_rdataset(r.rdclass, r.rdtype).ttl
73
74 for r in n2.rdatasets:
75 if ignore_soa and r.rdtype == dns.rdatatype.SOA:
76 continue
77 if r not in n1.rdatasets:
78 return True
79 else:
80 return n1 != n2
81
82 def format_changes_plain(oldf, newf, changes, ignore_ttl=False):
83 """format_changes(oldfile, newfile, changes, ignore_ttl=False) -> str
84 Given 2 filenames and a list of changes from diff_zones, produce diff-like
85 output. If ignore_ttl is True, TTL-only changes are not displayed"""
86
87 ret = "--- %s\n+++ %s\n" % (oldf, newf)
88 for name, old, new in changes:
89 ret += "@ %s\n" % name
90 if not old:
91 for r in new.rdatasets:
92 ret += "+ %s\n" % str(r).replace('\n','\n+ ')
93 elif not new:
94 for r in old.rdatasets:
95 ret += "- %s\n" % str(r).replace('\n','\n+ ')
96 else:
97 for r in old.rdatasets:
98 if r not in new.rdatasets or (r.ttl != new.find_rdataset(r.rdclass, r.rdtype).ttl and not ignore_ttl):
99 ret += "- %s\n" % str(r).replace('\n','\n+ ')
100 for r in new.rdatasets:
101 if r not in old.rdatasets or (r.ttl != old.find_rdataset(r.rdclass, r.rdtype).ttl and not ignore_ttl):
102 ret += "+ %s\n" % str(r).replace('\n','\n+ ')
103 return ret
104
105 def format_changes_html(oldf, newf, changes, ignore_ttl=False):
106 """format_changes(oldfile, newfile, changes, ignore_ttl=False) -> str
107 Given 2 filenames and a list of changes from diff_zones, produce nice html
108 output. If ignore_ttl is True, TTL-only changes are not displayed"""
109
110 ret = '''<table class="zonediff">
111 <thead>
112 <tr>
113 <th>&nbsp;</th>
114 <th class="old">%s</th>
115 <th class="new">%s</th>
116 </tr>
117 </thead>
118 <tbody>\n''' % (oldf, newf)
119
120 for name, old, new in changes:
121 ret += ' <tr class="rdata">\n <td class="rdname">%s</td>\n' % name
122 if not old:
123 for r in new.rdatasets:
124 ret += ' <td class="old">&nbsp;</td>\n <td class="new">%s</td>\n' % str(r).replace('\n','<br />')
125 elif not new:
126 for r in old.rdatasets:
127 ret += ' <td class="old">%s</td>\n <td class="new">&nbsp;</td>\n' % str(r).replace('\n','<br />')
128 else:
129 ret += ' <td class="old">'
130 for r in old.rdatasets:
131 if r not in new.rdatasets or (r.ttl != new.find_rdataset(r.rdclass, r.rdtype).ttl and not ignore_ttl):
132 ret += str(r).replace('\n','<br />')
133 ret += '</td>\n'
134 ret += ' <td class="new">'
135 for r in new.rdatasets:
136 if r not in old.rdatasets or (r.ttl != old.find_rdataset(r.rdclass, r.rdtype).ttl and not ignore_ttl):
137 ret += str(r).replace('\n','<br />')
138 ret += '</td>\n'
139 ret += ' </tr>\n'
140 return ret + ' </tbody>\n</table>'
141
142 # Make this module usable as a script too.
143 if __name__ == '__main__':
144 import optparse
145 import subprocess
146 import sys
147 import traceback
148
149 usage = """%prog zonefile1 zonefile2 - Show differences between zones in a diff-like format
150 %prog [--git|--bzr|--rcs] zonefile rev1 [rev2] - Show differences between two revisions of a zonefile
151
152 The differences shown will be logical differences, not textual differences.
153 """
154 p = optparse.OptionParser(usage=usage)
155 p.add_option('-s', '--ignore-soa', action="store_true", default=False, dest="ignore_soa",
156 help="Ignore SOA-only changes to records")
157 p.add_option('-t', '--ignore-ttl', action="store_true", default=False, dest="ignore_ttl",
158 help="Ignore TTL-only changes to Rdata")
159 p.add_option('-T', '--traceback', action="store_true", default=False, dest="tracebacks",
160 help="Show python tracebacks when errors occur")
161 p.add_option('-H', '--html', action="store_true", default=False, dest="html",
162 help="Print HTML output")
163 p.add_option('-g', '--git', action="store_true", default=False, dest="use_git",
164 help="Use git revisions instead of real files")
165 p.add_option('-b', '--bzr', action="store_true", default=False, dest="use_bzr",
166 help="Use bzr revisions instead of real files")
167 p.add_option('-r', '--rcs', action="store_true", default=False, dest="use_rcs",
168 help="Use rcs revisions instead of real files")
169 opts, args = p.parse_args()
170 opts.use_vc = opts.use_git or opts.use_bzr or opts.use_rcs
171
172 def _open(what, err):
173 if isinstance(what, basestring):
174 # Open as normal file
175 try:
176 return open(what, 'rb')
177 except:
178 sys.stderr.write(err + "\n")
179 if opts.tracebacks:
180 traceback.print_exc()
181 else:
182 # Must be a list, open subprocess
183 try:
184 proc = subprocess.Popen(what, stdout=subprocess.PIPE)
185 proc.wait()
186 if proc.returncode == 0:
187 return proc.stdout
188 sys.stderr.write(err + "\n")
189 except:
190 sys.stderr.write(err + "\n")
191 if opts.tracebacks:
192 traceback.print_exc()
193
194 if not opts.use_vc and len(args) != 2:
195 p.print_help()
196 sys.exit(64)
197 if opts.use_vc and len(args) not in (2,3):
198 p.print_help()
199 sys.exit(64)
200
201 # Open file desriptors
202 if not opts.use_vc:
203 oldn, newn = args
204 else:
205 if len(args) == 3:
206 filename, oldr, newr = args
207 oldn = "%s:%s" % (oldr, filename)
208 newn = "%s:%s" % (newr, filename)
209 else:
210 filename, oldr = args
211 newr = None
212 oldn = "%s:%s" % (oldr, filename)
213 newn = filename
214
215
216 old, new = None, None
217 oldz, newz = None, None
218 if opts.use_bzr:
219 old = _open(["bzr", "cat", "-r" + oldr, filename],
220 "Unable to retrieve revision %s of %s" % (oldr, filename))
221 if newr != None:
222 new = _open(["bzr", "cat", "-r" + newr, filename],
223 "Unable to retrieve revision %s of %s" % (newr, filename))
224 elif opts.use_git:
225 old = _open(["git", "show", oldn],
226 "Unable to retrieve revision %s of %s" % (oldr, filename))
227 if newr != None:
228 new = _open(["git", "show", newn],
229 "Unable to retrieve revision %s of %s" % (newr, filename))
230 elif opts.use_rcs:
231 old = _open(["co", "-q", "-p", "-r" + oldr, filename],
232 "Unable to retrieve revision %s of %s" % (oldr, filename))
233 if newr != None:
234 new = _open(["co", "-q", "-p", "-r" + newr, filename],
235 "Unable to retrieve revision %s of %s" % (newr, filename))
236 if not opts.use_vc:
237 old = _open(oldn, "Unable to open %s" % oldn)
238 if not opts.use_vc or newr == None:
239 new = _open(newn, "Unable to open %s" % newn)
240
241 if not old or not new:
242 sys.exit(65)
243
244 # Parse the zones
245 try:
246 oldz = dns.zone.from_file(old, origin = '.', check_origin=False)
247 except dns.exception.DNSException:
248 sys.stderr.write("Incorrect zonefile: %s\n", old)
249 if opts.tracebacks:
250 traceback.print_exc()
251 try:
252 newz = dns.zone.from_file(new, origin = '.', check_origin=False)
253 except dns.exception.DNSException:
254 sys.stderr.write("Incorrect zonefile: %s\n" % new)
255 if opts.tracebacks:
256 traceback.print_exc()
257 if not oldz or not newz:
258 sys.exit(65)
259
260 changes = diff_zones(oldz, newz, opts.ignore_ttl, opts.ignore_soa)
261 changes.sort()
262
263 if not changes:
264 sys.exit(0)
265 if opts.html:
266 print format_changes_html(oldn, newn, changes, opts.ignore_ttl)
267 else:
268 print format_changes_plain(oldn, newn, changes, opts.ignore_ttl)
269 sys.exit(1)
1717 import sys
1818 from distutils.core import setup
1919
20 version = '1.8.0'
20 version = '1.9.4'
2121
2222 kwargs = {
2323 'name' : 'dnspython',
0 # Copyright (C) 2010 Nominum, Inc.
1 #
2 # Permission to use, copy, modify, and distribute this software and its
3 # documentation for any purpose with or without fee is hereby granted,
4 # provided that the above copyright notice and this permission notice
5 # appear in all copies.
6 #
7 # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
8 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
10 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
13 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15 import unittest
16
17 import dns.dnssec
18 import dns.name
19 import dns.rdata
20 import dns.rdataclass
21 import dns.rdatatype
22 import dns.rrset
23
24 abs_dnspython_org = dns.name.from_text('dnspython.org')
25
26 abs_keys = { abs_dnspython_org :
27 dns.rrset.from_text('dnspython.org.', 3600, 'IN', 'DNSKEY',
28 '257 3 5 AwEAAenVTr9L1OMlL1/N2ta0Qj9LLLnnmFWIr1dJoAsWM9BQfsbV7kFZ XbAkER/FY9Ji2o7cELxBwAsVBuWn6IUUAJXLH74YbC1anY0lifjgt29z SwDzuB7zmC7yVYZzUunBulVW4zT0tg1aePbpVL2EtTL8VzREqbJbE25R KuQYHZtFwG8S4iBxJUmT2Bbd0921LLxSQgVoFXlQx/gFV2+UERXcJ5ce iX6A6wc02M/pdg/YbJd2rBa0MYL3/Fz/Xltre0tqsImZGxzi6YtYDs45 NC8gH+44egz82e2DATCVM1ICPmRDjXYTLldQiWA2ZXIWnK0iitl5ue24 7EsWJefrIhE=',
29 '256 3 5 AwEAAdSSghOGjU33IQZgwZM2Hh771VGXX05olJK49FxpSyuEAjDBXY58 LGU9R2Zgeecnk/b9EAhFu/vCV9oECtiTCvwuVAkt9YEweqYDluQInmgP NGMJCKdSLlnX93DkjDw8rMYv5dqXCuSGPlKChfTJOLQxIAxGloS7lL+c 0CTZydAF')
30 }
31
32 rel_keys = { dns.name.empty :
33 dns.rrset.from_text('@', 3600, 'IN', 'DNSKEY',
34 '257 3 5 AwEAAenVTr9L1OMlL1/N2ta0Qj9LLLnnmFWIr1dJoAsWM9BQfsbV7kFZ XbAkER/FY9Ji2o7cELxBwAsVBuWn6IUUAJXLH74YbC1anY0lifjgt29z SwDzuB7zmC7yVYZzUunBulVW4zT0tg1aePbpVL2EtTL8VzREqbJbE25R KuQYHZtFwG8S4iBxJUmT2Bbd0921LLxSQgVoFXlQx/gFV2+UERXcJ5ce iX6A6wc02M/pdg/YbJd2rBa0MYL3/Fz/Xltre0tqsImZGxzi6YtYDs45 NC8gH+44egz82e2DATCVM1ICPmRDjXYTLldQiWA2ZXIWnK0iitl5ue24 7EsWJefrIhE=',
35 '256 3 5 AwEAAdSSghOGjU33IQZgwZM2Hh771VGXX05olJK49FxpSyuEAjDBXY58 LGU9R2Zgeecnk/b9EAhFu/vCV9oECtiTCvwuVAkt9YEweqYDluQInmgP NGMJCKdSLlnX93DkjDw8rMYv5dqXCuSGPlKChfTJOLQxIAxGloS7lL+c 0CTZydAF')
36 }
37
38 when = 1290250287
39
40 abs_soa = dns.rrset.from_text('dnspython.org.', 3600, 'IN', 'SOA',
41 'howl.dnspython.org. hostmaster.dnspython.org. 2010020047 3600 1800 604800 3600')
42
43 abs_other_soa = dns.rrset.from_text('dnspython.org.', 3600, 'IN', 'SOA',
44 'foo.dnspython.org. hostmaster.dnspython.org. 2010020047 3600 1800 604800 3600')
45
46 abs_soa_rrsig = dns.rrset.from_text('dnspython.org.', 3600, 'IN', 'RRSIG',
47 'SOA 5 2 3600 20101127004331 20101119213831 61695 dnspython.org. sDUlltRlFTQw5ITFxOXW3TgmrHeMeNpdqcZ4EXxM9FHhIlte6V9YCnDw t6dvM9jAXdIEi03l9H/RAd9xNNW6gvGMHsBGzpvvqFQxIBR2PoiZA1mX /SWHZFdbt4xjYTtXqpyYvrMK0Dt7bUYPadyhPFCJ1B+I8Zi7B5WJEOd0 8vs=')
48
49 rel_soa = dns.rrset.from_text('@', 3600, 'IN', 'SOA',
50 'howl hostmaster 2010020047 3600 1800 604800 3600')
51
52 rel_other_soa = dns.rrset.from_text('@', 3600, 'IN', 'SOA',
53 'foo hostmaster 2010020047 3600 1800 604800 3600')
54
55 rel_soa_rrsig = dns.rrset.from_text('@', 3600, 'IN', 'RRSIG',
56 'SOA 5 2 3600 20101127004331 20101119213831 61695 @ sDUlltRlFTQw5ITFxOXW3TgmrHeMeNpdqcZ4EXxM9FHhIlte6V9YCnDw t6dvM9jAXdIEi03l9H/RAd9xNNW6gvGMHsBGzpvvqFQxIBR2PoiZA1mX /SWHZFdbt4xjYTtXqpyYvrMK0Dt7bUYPadyhPFCJ1B+I8Zi7B5WJEOd0 8vs=')
57
58 sep_key = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DNSKEY,
59 '257 3 5 AwEAAenVTr9L1OMlL1/N2ta0Qj9LLLnnmFWIr1dJoAsWM9BQfsbV7kFZ XbAkER/FY9Ji2o7cELxBwAsVBuWn6IUUAJXLH74YbC1anY0lifjgt29z SwDzuB7zmC7yVYZzUunBulVW4zT0tg1aePbpVL2EtTL8VzREqbJbE25R KuQYHZtFwG8S4iBxJUmT2Bbd0921LLxSQgVoFXlQx/gFV2+UERXcJ5ce iX6A6wc02M/pdg/YbJd2rBa0MYL3/Fz/Xltre0tqsImZGxzi6YtYDs45 NC8gH+44egz82e2DATCVM1ICPmRDjXYTLldQiWA2ZXIWnK0iitl5ue24 7EsWJefrIhE=')
60
61 good_ds = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DS,
62 '57349 5 2 53A79A3E7488AB44FFC56B2D1109F0699D1796DD977E72108B841F96 E47D7013')
63
64 when2 = 1290425644
65
66 abs_example = dns.name.from_text('example')
67
68 abs_dsa_keys = { abs_example :
69 dns.rrset.from_text('example.', 86400, 'IN', 'DNSKEY',
70 '257 3 3 CI3nCqyJsiCJHTjrNsJOT4RaszetzcJPYuoH3F9ZTVt3KJXncCVR3bwn 1w0iavKljb9hDlAYSfHbFCp4ic/rvg4p1L8vh5s8ToMjqDNl40A0hUGQ Ybx5hsECyK+qHoajilUX1phYSAD8d9WAGO3fDWzUPBuzR7o85NiZCDxz yXuNVfni0uhj9n1KYhEO5yAbbruDGN89wIZcxMKuQsdUY2GYD93ssnBv a55W6XRABYWayKZ90WkRVODLVYLSn53Pj/wwxGH+XdhIAZJXimrZL4yl My7rtBsLMqq8Ihs4Tows7LqYwY7cp6y/50tw6pj8tFqMYcPUjKZV36l1 M/2t5BVg3i7IK61Aidt6aoC3TDJtzAxg3ZxfjZWJfhHjMJqzQIfbW5b9 q1mjFsW5EUv39RaNnX+3JWPRLyDqD4pIwDyqfutMsdk/Py3paHn82FGp CaOg+nicqZ9TiMZURN/XXy5JoXUNQ3RNvbHCUiPUe18KUkY6mTfnyHld 1l9YCWmzXQVClkx/hOYxjJ4j8Ife58+Obu5X',
71 '256 3 3 CJE1yb9YRQiw5d2xZrMUMR+cGCTt1bp1KDCefmYKmS+Z1+q9f42ETVhx JRiQwXclYwmxborzIkSZegTNYIV6mrYwbNB27Q44c3UGcspb3PiOw5TC jNPRYEcdwGvDZ2wWy+vkSV/S9tHXY8O6ODiE6abZJDDg/RnITyi+eoDL R3KZ5n/V1f1T1b90rrV6EewhBGQJpQGDogaXb2oHww9Tm6NfXyo7SoMM pbwbzOckXv+GxRPJIQNSF4D4A9E8XCksuzVVdE/0lr37+uoiAiPia38U 5W2QWe/FJAEPLjIp2eTzf0TrADc1pKP1wrA2ASpdzpm/aX3IB5RPp8Ew S9U72eBFZJAUwg635HxJVxH1maG6atzorR566E+e0OZSaxXS9o1o6QqN 3oPlYLGPORDiExilKfez3C/x/yioOupW9K5eKF0gmtaqrHX0oq9s67f/ RIM2xVaKHgG9Vf2cgJIZkhv7sntujr+E4htnRmy9P9BxyFxsItYxPI6Z bzygHAZpGhlI/7ltEGlIwKxyTK3ZKBm67q7B')
72 }
73
74 abs_dsa_soa = dns.rrset.from_text('example.', 86400, 'IN', 'SOA',
75 'ns1.example. hostmaster.example. 2 10800 3600 604800 86400')
76
77 abs_other_dsa_soa = dns.rrset.from_text('example.', 86400, 'IN', 'SOA',
78 'ns1.example. hostmaster.example. 2 10800 3600 604800 86401')
79
80 abs_dsa_soa_rrsig = dns.rrset.from_text('example.', 86400, 'IN', 'RRSIG',
81 'SOA 3 1 86400 20101129143231 20101122112731 42088 example. CGul9SuBofsktunV8cJs4eRs6u+3NCS3yaPKvBbD+pB2C76OUXDZq9U=')
82
83 example_sep_key = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DNSKEY,
84 '257 3 3 CI3nCqyJsiCJHTjrNsJOT4RaszetzcJPYuoH3F9ZTVt3KJXncCVR3bwn 1w0iavKljb9hDlAYSfHbFCp4ic/rvg4p1L8vh5s8ToMjqDNl40A0hUGQ Ybx5hsECyK+qHoajilUX1phYSAD8d9WAGO3fDWzUPBuzR7o85NiZCDxz yXuNVfni0uhj9n1KYhEO5yAbbruDGN89wIZcxMKuQsdUY2GYD93ssnBv a55W6XRABYWayKZ90WkRVODLVYLSn53Pj/wwxGH+XdhIAZJXimrZL4yl My7rtBsLMqq8Ihs4Tows7LqYwY7cp6y/50tw6pj8tFqMYcPUjKZV36l1 M/2t5BVg3i7IK61Aidt6aoC3TDJtzAxg3ZxfjZWJfhHjMJqzQIfbW5b9 q1mjFsW5EUv39RaNnX+3JWPRLyDqD4pIwDyqfutMsdk/Py3paHn82FGp CaOg+nicqZ9TiMZURN/XXy5JoXUNQ3RNvbHCUiPUe18KUkY6mTfnyHld 1l9YCWmzXQVClkx/hOYxjJ4j8Ife58+Obu5X')
85
86 example_ds_sha1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DS,
87 '18673 3 1 71b71d4f3e11bbd71b4eff12cde69f7f9215bbe7')
88
89 example_ds_sha256 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DS,
90 '18673 3 2 eb8344cbbf07c9d3d3d6c81d10c76653e28d8611a65e639ef8f716e4e4e5d913')
91
92 class DNSSECValidatorTestCase(unittest.TestCase):
93
94 def testAbsoluteRSAGood(self):
95 dns.dnssec.validate(abs_soa, abs_soa_rrsig, abs_keys, None, when)
96
97 def testAbsoluteRSABad(self):
98 def bad():
99 dns.dnssec.validate(abs_other_soa, abs_soa_rrsig, abs_keys, None,
100 when)
101 self.failUnlessRaises(dns.dnssec.ValidationFailure, bad)
102
103 def testRelativeRSAGood(self):
104 dns.dnssec.validate(rel_soa, rel_soa_rrsig, rel_keys,
105 abs_dnspython_org, when)
106
107 def testRelativeRSABad(self):
108 def bad():
109 dns.dnssec.validate(rel_other_soa, rel_soa_rrsig, rel_keys,
110 abs_dnspython_org, when)
111 self.failUnlessRaises(dns.dnssec.ValidationFailure, bad)
112
113 def testMakeSHA256DS(self):
114 ds = dns.dnssec.make_ds(abs_dnspython_org, sep_key, 'SHA256')
115 self.failUnless(ds == good_ds)
116
117 def testAbsoluteDSAGood(self):
118 dns.dnssec.validate(abs_dsa_soa, abs_dsa_soa_rrsig, abs_dsa_keys, None,
119 when2)
120
121 def testAbsoluteDSABad(self):
122 def bad():
123 dns.dnssec.validate(abs_other_dsa_soa, abs_dsa_soa_rrsig,
124 abs_dsa_keys, None, when2)
125 self.failUnlessRaises(dns.dnssec.ValidationFailure, bad)
126
127 def testMakeExampleSHA1DS(self):
128 ds = dns.dnssec.make_ds(abs_example, example_sep_key, 'SHA1')
129 self.failUnless(ds == example_ds_sha1)
130
131 def testMakeExampleSHA256DS(self):
132 ds = dns.dnssec.make_ds(abs_example, example_sep_key, 'SHA256')
133 self.failUnless(ds == example_ds_sha256)
134
135 if __name__ == '__main__':
136 import_ok = False
137 try:
138 import Crypto.Util.number
139 import_ok = True
140 except:
141 pass
142 if import_ok:
143 unittest.main()
144 else:
145 print 'skipping DNSSEC tests because pycrypto is not installed'
1313 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1414
1515 import cStringIO
16 import select
1617 import sys
1718 import time
1819 import unittest
4546 ;ADDITIONAL
4647 """
4748
48 class ResolverTestCase(unittest.TestCase):
49 class BaseResolverTests(object):
4950
5051 if sys.platform != 'win32':
5152 def testRead(self):
100101 zname = dns.resolver.zone_for_name(name)
101102 self.failUnlessRaises(dns.resolver.NotAbsolute, bad)
102103
104 class PollingMonkeyPatchMixin(object):
105 def setUp(self):
106 self.__native_polling_backend = dns.query._polling_backend
107 dns.query._set_polling_backend(self.polling_backend())
108
109 unittest.TestCase.setUp(self)
110
111 def tearDown(self):
112 dns.query._set_polling_backend(self.__native_polling_backend)
113
114 unittest.TestCase.tearDown(self)
115
116 class SelectResolverTestCase(PollingMonkeyPatchMixin, BaseResolverTests, unittest.TestCase):
117 def polling_backend(self):
118 return dns.query._select_for
119
120 if hasattr(select, 'poll'):
121 class PollResolverTestCase(PollingMonkeyPatchMixin, BaseResolverTests, unittest.TestCase):
122 def polling_backend(self):
123 return dns.query._poll_for
124
103125 if __name__ == '__main__':
104126 unittest.main()