Imported Upstream version 1.9.4
SVN-Git Migration
8 years ago
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 | ||
0 | 130 | 2010-01-13 Bob Halley <halley@dnspython.org> |
1 | 131 | |
2 | 132 | * dns/dnssec.py: Added RSASHA256 and RSASHA512 codepoints; added |
0 | 0 | Metadata-Version: 1.1 |
1 | 1 | Name: dnspython |
2 | Version: 1.8.0 | |
2 | Version: 1.9.4 | |
3 | 3 | Summary: DNS toolkit |
4 | 4 | Home-page: http://www.dnspython.org |
5 | 5 | Author: Bob Halley |
6 | 6 | Author-email: halley@dnspython.org |
7 | 7 | 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 | |
9 | 9 | Description: dnspython is a DNS toolkit for Python. It supports almost all |
10 | 10 | record types. It can be used for queries, zone transfers, and dynamic |
11 | 11 | updates. It supports TSIG authenticated messages and EDNS0. |
21 | 21 | |
22 | 22 | ABOUT THIS RELEASE |
23 | 23 | |
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. | |
25 | 112 | |
26 | 113 | New since 1.7.1: |
27 | 114 | |
309 | 396 | |
310 | 397 | REQUIREMENTS |
311 | 398 | |
312 | Python 2.2 or later. | |
399 | Python 2.4 or later. | |
313 | 400 | |
314 | 401 | |
315 | 402 | INSTALLATION |
14 | 14 | |
15 | 15 | """Common DNSSEC-related functions and constants.""" |
16 | 16 | |
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 | ||
17 | 38 | RSAMD5 = 1 |
18 | 39 | DH = 2 |
19 | 40 | DSA = 3 |
48 | 69 | |
49 | 70 | _algorithm_by_value = dict([(y, x) for x, y in _algorithm_by_text.iteritems()]) |
50 | 71 | |
51 | class UnknownAlgorithm(Exception): | |
52 | """Raised if an algorithm is unknown.""" | |
53 | pass | |
54 | ||
55 | 72 | def algorithm_from_text(text): |
56 | 73 | """Convert text into a DNSSEC algorithm value |
57 | 74 | @rtype: int""" |
58 | ||
75 | ||
59 | 76 | value = _algorithm_by_text.get(text.upper()) |
60 | 77 | if value is None: |
61 | 78 | value = int(text) |
64 | 81 | def algorithm_to_text(value): |
65 | 82 | """Convert a DNSSEC algorithm value to text |
66 | 83 | @rtype: string""" |
67 | ||
84 | ||
68 | 85 | text = _algorithm_by_value.get(value) |
69 | 86 | if text is None: |
70 | 87 | text = str(value) |
71 | 88 | 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()] |
20 | 20 | import sys |
21 | 21 | import time |
22 | 22 | |
23 | import dns.edns | |
23 | 24 | import dns.exception |
24 | 25 | import dns.flags |
25 | 26 | import dns.name |
91 | 92 | @type keyring: dict |
92 | 93 | @ivar keyname: The TSIG keyname to use. The default is None. |
93 | 94 | @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. | |
96 | 100 | @type keyalgorithm: string |
97 | 101 | @ivar request_mac: The TSIG MAC of the request message associated with |
98 | 102 | this message; used when validating TSIG signatures. @see: RFC 2845 for |
681 | 685 | deleting = None |
682 | 686 | if deleting == dns.rdataclass.ANY or \ |
683 | 687 | (deleting == dns.rdataclass.NONE and \ |
684 | section == self.message.answer): | |
688 | section is self.message.answer): | |
685 | 689 | covers = dns.rdatatype.NONE |
686 | 690 | rd = None |
687 | 691 | else: |
1034 | 1038 | |
1035 | 1039 | if isinstance(qname, (str, unicode)): |
1036 | 1040 | qname = dns.name.from_text(qname) |
1037 | if isinstance(rdtype, str): | |
1041 | if isinstance(rdtype, (str, unicode)): | |
1038 | 1042 | rdtype = dns.rdatatype.from_text(rdtype) |
1039 | if isinstance(rdclass, str): | |
1043 | if isinstance(rdclass, (str, unicode)): | |
1040 | 1044 | rdclass = dns.rdataclass.from_text(rdclass) |
1041 | 1045 | m = Message() |
1042 | 1046 | m.flags |= dns.flags.RD |
22 | 22 | |
23 | 23 | class Node(object): |
24 | 24 | """A DNS node. |
25 | ||
25 | ||
26 | 26 | A node is a set of rdatasets |
27 | 27 | |
28 | 28 | @ivar rdatasets: the node's rdatasets |
29 | 29 | @type rdatasets: list of dns.rdataset.Rdataset objects""" |
30 | 30 | |
31 | 31 | __slots__ = ['rdatasets'] |
32 | ||
32 | ||
33 | 33 | def __init__(self): |
34 | 34 | """Initialize a DNS node. |
35 | 35 | """ |
36 | ||
36 | ||
37 | 37 | self.rdatasets = []; |
38 | 38 | |
39 | 39 | def to_text(self, name, **kw): |
45 | 45 | @type name: dns.name.Name object |
46 | 46 | @rtype: string |
47 | 47 | """ |
48 | ||
48 | ||
49 | 49 | s = StringIO.StringIO() |
50 | 50 | for rds in self.rdatasets: |
51 | 51 | print >> s, rds.to_text(name, **kw) |
53 | 53 | |
54 | 54 | def __repr__(self): |
55 | 55 | return '<DNS node ' + str(id(self)) + '>' |
56 | ||
56 | ||
57 | 57 | def __eq__(self, other): |
58 | 58 | """Two nodes are equal if they have the same rdatasets. |
59 | 59 | |
72 | 72 | |
73 | 73 | def __ne__(self, other): |
74 | 74 | return not self.__eq__(other) |
75 | ||
75 | ||
76 | 76 | def __len__(self): |
77 | 77 | return len(self.rdatasets) |
78 | 78 | |
158 | 158 | |
159 | 159 | def replace_rdataset(self, replacement): |
160 | 160 | """Replace an rdataset. |
161 | ||
161 | ||
162 | 162 | It is not an error if there is no rdataset matching I{replacement}. |
163 | 163 | |
164 | 164 | Ownership of the I{replacement} object is transferred to the node; |
44 | 44 | else: |
45 | 45 | return time.time() + timeout |
46 | 46 | |
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): | |
48 | 100 | done = False |
49 | 101 | while not done: |
50 | 102 | if expiration is None: |
54 | 106 | if timeout <= 0.0: |
55 | 107 | raise dns.exception.Timeout |
56 | 108 | 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 | |
61 | 111 | except select.error, e: |
62 | 112 | if e.args[0] != errno.EINTR: |
63 | 113 | raise e |
64 | 114 | 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 | |
67 | 131 | |
68 | 132 | def _wait_for_readable(s, expiration): |
69 | _wait_for([s], [], [s], expiration) | |
133 | _wait_for(s, True, False, True, expiration) | |
70 | 134 | |
71 | 135 | def _wait_for_writable(s, expiration): |
72 | _wait_for([], [s], [s], expiration) | |
136 | _wait_for(s, False, True, True, expiration) | |
73 | 137 | |
74 | 138 | def _addresses_equal(af, a1, a2): |
75 | 139 | # Convert the first value of the tuple, which is a textual format |
309 | 373 | |
310 | 374 | if isinstance(zone, (str, unicode)): |
311 | 375 | zone = dns.name.from_text(zone) |
312 | if isinstance(rdtype, str): | |
376 | if isinstance(rdtype, (str, unicode)): | |
313 | 377 | rdtype = dns.rdatatype.from_text(rdtype) |
314 | 378 | q = dns.message.make_query(zone, rdtype, rdclass) |
315 | 379 | if rdtype == dns.rdatatype.IXFR: |
27 | 27 | import cStringIO |
28 | 28 | |
29 | 29 | import dns.exception |
30 | import dns.name | |
30 | 31 | import dns.rdataclass |
31 | 32 | import dns.rdatatype |
32 | 33 | import dns.tokenizer |
250 | 251 | self.rdtype != other.rdtype: |
251 | 252 | return NotImplemented |
252 | 253 | 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()) | |
253 | 270 | |
254 | 271 | def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): |
255 | 272 | """Build an rdata object from text format. |
280 | 280 | @rtype: dns.rdataset.Rdataset object |
281 | 281 | """ |
282 | 282 | |
283 | if isinstance(rdclass, str): | |
283 | if isinstance(rdclass, (str, unicode)): | |
284 | 284 | rdclass = dns.rdataclass.from_text(rdclass) |
285 | if isinstance(rdtype, str): | |
285 | if isinstance(rdtype, (str, unicode)): | |
286 | 286 | rdtype = dns.rdatatype.from_text(rdtype) |
287 | 287 | r = Rdataset(rdclass, rdtype) |
288 | 288 | r.update_ttl(ttl) |
124 | 124 | self.next = self.next.choose_relativity(origin, relativize) |
125 | 125 | |
126 | 126 | 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) |
12 | 12 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT |
13 | 13 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
14 | 14 | |
15 | import struct | |
16 | ||
15 | 17 | import dns.rdtypes.sigbase |
16 | 18 | |
17 | 19 | class SIG(dns.rdtypes.sigbase.SIGBase): |
23 | 25 | self.inception, self.key_tag) + \ |
24 | 26 | self.signer.to_digestable(origin) + \ |
25 | 27 | 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 |
151 | 151 | self.signer = self.signer.choose_relativity(origin, relativize) |
152 | 152 | |
153 | 153 | 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) |
60 | 60 | This should never happen!""" |
61 | 61 | pass |
62 | 62 | |
63 | class NoMetaqueries(dns.exception.DNSException): | |
64 | """Metaqueries are not allowed.""" | |
65 | pass | |
66 | ||
63 | 67 | |
64 | 68 | class Answer(object): |
65 | 69 | """DNS stub resolver answer |
88 | 92 | @type rrset: dns.rrset.RRset object |
89 | 93 | @ivar expiration: The time when the answer expires |
90 | 94 | @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 | |
91 | 97 | """ |
92 | def __init__(self, qname, rdtype, rdclass, response): | |
98 | def __init__(self, qname, rdtype, rdclass, response, | |
99 | raise_on_no_answer=True): | |
93 | 100 | self.qname = qname |
94 | 101 | self.rdtype = rdtype |
95 | 102 | self.rdclass = rdclass |
117 | 124 | break |
118 | 125 | continue |
119 | 126 | 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 | |
122 | 135 | 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 | |
125 | 152 | self.expiration = time.time() + min_ttl |
126 | 153 | |
127 | 154 | def __getattr__(self, attr): |
541 | 568 | return min(self.lifetime - duration, self.timeout) |
542 | 569 | |
543 | 570 | 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): | |
545 | 572 | """Query nameservers to find the answer to the question. |
546 | 573 | |
547 | 574 | The I{qname}, I{rdtype}, and I{rdclass} parameters may be objects |
559 | 586 | @type tcp: bool |
560 | 587 | @param source: bind to this IP address (defaults to machine default IP). |
561 | 588 | @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 | |
562 | 592 | @rtype: dns.resolver.Answer instance |
563 | 593 | @raises Timeout: no answers could be found in the specified lifetime |
564 | 594 | @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. | |
566 | 597 | @raises NoNameservers: no non-broken nameservers are available to |
567 | 598 | answer the question.""" |
568 | 599 | |
569 | 600 | if isinstance(qname, (str, unicode)): |
570 | 601 | qname = dns.name.from_text(qname, None) |
571 | if isinstance(rdtype, str): | |
602 | if isinstance(rdtype, (str, unicode)): | |
572 | 603 | 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)): | |
574 | 607 | rdclass = dns.rdataclass.from_text(rdclass) |
608 | if dns.rdataclass.is_metaclass(rdclass): | |
609 | raise NoMetaqueries | |
575 | 610 | qnames_to_try = [] |
576 | 611 | if qname.is_absolute(): |
577 | 612 | qnames_to_try.append(qname) |
592 | 627 | return answer |
593 | 628 | request = dns.message.make_query(qname, rdtype, rdclass) |
594 | 629 | 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) | |
596 | 632 | request.use_edns(self.edns, self.ednsflags, self.payload) |
597 | 633 | response = None |
598 | 634 | # |
668 | 704 | break |
669 | 705 | if all_nxdomain: |
670 | 706 | raise NXDOMAIN |
671 | answer = Answer(qname, rdtype, rdclass, response) | |
707 | answer = Answer(qname, rdtype, rdclass, response, | |
708 | raise_on_no_answer) | |
672 | 709 | if self.cache: |
673 | 710 | self.cache.put((qname, rdtype, rdclass), answer) |
674 | 711 | return answer |
721 | 758 | return default_resolver |
722 | 759 | |
723 | 760 | 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): | |
725 | 762 | """Query nameservers to find the answer to the question. |
726 | 763 | |
727 | 764 | This is a convenience function that uses the default resolver |
728 | 765 | object to make the query. |
729 | 766 | @see: L{dns.resolver.Resolver.query} for more information on the |
730 | 767 | 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) | |
732 | 770 | |
733 | 771 | def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, resolver=None): |
734 | 772 | """Find the name of the zone which contains the specified name. |
752 | 790 | while 1: |
753 | 791 | try: |
754 | 792 | 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 | |
756 | 796 | 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 |
35 | 35 | deleting=None): |
36 | 36 | """Create a new RRset.""" |
37 | 37 | |
38 | super(RRset, self).__init__(rdclass, rdtype) | |
38 | super(RRset, self).__init__(rdclass, rdtype, covers) | |
39 | 39 | self.name = name |
40 | 40 | self.deleting = deleting |
41 | 41 | |
123 | 123 | |
124 | 124 | if isinstance(name, (str, unicode)): |
125 | 125 | name = dns.name.from_text(name, None) |
126 | if isinstance(rdclass, str): | |
126 | if isinstance(rdclass, (str, unicode)): | |
127 | 127 | rdclass = dns.rdataclass.from_text(rdclass) |
128 | if isinstance(rdtype, str): | |
128 | if isinstance(rdtype, (str, unicode)): | |
129 | 129 | rdtype = dns.rdatatype.from_text(rdtype) |
130 | 130 | r = RRset(name, rdclass, rdtype) |
131 | 131 | r.update_ttl(ttl) |
16 | 16 | |
17 | 17 | import hmac |
18 | 18 | import struct |
19 | import sys | |
19 | 20 | |
20 | 21 | import dns.exception |
22 | import dns.hash | |
21 | 23 | import dns.rdataclass |
22 | 24 | import dns.name |
23 | 25 | |
49 | 51 | """Raised if the peer didn't like amount of truncation in the TSIG we sent""" |
50 | 52 | pass |
51 | 53 | |
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 | |
53 | 64 | |
54 | 65 | BADSIG = 16 |
55 | 66 | BADKEY = 17 |
166 | 177 | raise BadSignature |
167 | 178 | return ctx |
168 | 179 | |
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 | ||
169 | 198 | def get_algorithm(algorithm): |
170 | 199 | """Returns the wire format string and the hash module to use for the |
171 | 200 | specified TSIG algorithm |
174 | 203 | @raises NotImplementedError: I{algorithm} is not supported |
175 | 204 | """ |
176 | 205 | |
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() | |
207 | 209 | |
208 | 210 | if isinstance(algorithm, (str, unicode)): |
209 | 211 | algorithm = dns.name.from_text(algorithm) |
210 | 212 | |
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") |
20 | 20 | import dns.rdata |
21 | 21 | import dns.rdataclass |
22 | 22 | import dns.rdataset |
23 | import dns.tsig | |
23 | 24 | |
24 | 25 | class Update(dns.message.Message): |
25 | 26 | def __init__(self, zone, rdclass=dns.rdataclass.IN, keyring=None, |
41 | 42 | they know the keyring contains only one key. |
42 | 43 | @type keyname: dns.name.Name or string |
43 | 44 | @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. | |
45 | 49 | @type keyalgorithm: string |
46 | 50 | """ |
47 | 51 | super(Update, self).__init__() |
55 | 59 | self.find_rrset(self.question, self.origin, rdclass, dns.rdatatype.SOA, |
56 | 60 | create=True, force_unique=True) |
57 | 61 | if not keyring is None: |
58 | self.use_tsig(keyring, keyname, keyalgorithm) | |
62 | self.use_tsig(keyring, keyname, algorithm=keyalgorithm) | |
59 | 63 | |
60 | 64 | def _add_rr(self, name, ttl, rd, deleting=None, section=None): |
61 | 65 | """Add a single RR to the update section.""" |
147 | 151 | self._add_rr(name, 0, rd, dns.rdataclass.NONE) |
148 | 152 | else: |
149 | 153 | rdtype = args.pop(0) |
150 | if isinstance(rdtype, str): | |
154 | if isinstance(rdtype, (str, unicode)): | |
151 | 155 | rdtype = dns.rdatatype.from_text(rdtype) |
152 | 156 | if len(args) == 0: |
153 | 157 | rrset = self.find_rrset(self.authority, name, |
205 | 209 | self._add(False, self.answer, name, *args) |
206 | 210 | else: |
207 | 211 | rdtype = args[0] |
208 | if isinstance(rdtype, str): | |
212 | if isinstance(rdtype, (str, unicode)): | |
209 | 213 | rdtype = dns.rdatatype.from_text(rdtype) |
210 | 214 | rrset = self.find_rrset(self.answer, name, |
211 | 215 | dns.rdataclass.ANY, rdtype, |
224 | 228 | dns.rdatatype.NONE, None, |
225 | 229 | True, True) |
226 | 230 | else: |
227 | if isinstance(rdtype, str): | |
231 | if isinstance(rdtype, (str, unicode)): | |
228 | 232 | rdtype = dns.rdatatype.from_text(rdtype) |
229 | 233 | rrset = self.find_rrset(self.answer, name, |
230 | 234 | dns.rdataclass.NONE, rdtype, |
15 | 15 | """dnspython release version information.""" |
16 | 16 | |
17 | 17 | MAJOR = 1 |
18 | MINOR = 8 | |
19 | MICRO = 0 | |
18 | MINOR = 9 | |
19 | MICRO = 4 | |
20 | 20 | RELEASELEVEL = 0x0f |
21 | 21 | SERIAL = 0 |
22 | 22 |
236 | 236 | """ |
237 | 237 | |
238 | 238 | name = self._validate_name(name) |
239 | if isinstance(rdtype, str): | |
239 | if isinstance(rdtype, (str, unicode)): | |
240 | 240 | rdtype = dns.rdatatype.from_text(rdtype) |
241 | if isinstance(covers, str): | |
241 | if isinstance(covers, (str, unicode)): | |
242 | 242 | covers = dns.rdatatype.from_text(covers) |
243 | 243 | node = self.find_node(name, create) |
244 | 244 | return node.find_rdataset(self.rdclass, rdtype, covers, create) |
299 | 299 | """ |
300 | 300 | |
301 | 301 | name = self._validate_name(name) |
302 | if isinstance(rdtype, str): | |
302 | if isinstance(rdtype, (str, unicode)): | |
303 | 303 | rdtype = dns.rdatatype.from_text(rdtype) |
304 | if isinstance(covers, str): | |
304 | if isinstance(covers, (str, unicode)): | |
305 | 305 | covers = dns.rdatatype.from_text(covers) |
306 | 306 | node = self.get_node(name) |
307 | 307 | if not node is None: |
362 | 362 | """ |
363 | 363 | |
364 | 364 | name = self._validate_name(name) |
365 | if isinstance(rdtype, str): | |
365 | if isinstance(rdtype, (str, unicode)): | |
366 | 366 | rdtype = dns.rdatatype.from_text(rdtype) |
367 | if isinstance(covers, str): | |
367 | if isinstance(covers, (str, unicode)): | |
368 | 368 | covers = dns.rdatatype.from_text(covers) |
369 | 369 | rdataset = self.nodes[name].find_rdataset(self.rdclass, rdtype, covers) |
370 | 370 | rrset = dns.rrset.RRset(name, self.rdclass, rdtype, covers) |
418 | 418 | @type covers: int or string |
419 | 419 | """ |
420 | 420 | |
421 | if isinstance(rdtype, str): | |
421 | if isinstance(rdtype, (str, unicode)): | |
422 | 422 | rdtype = dns.rdatatype.from_text(rdtype) |
423 | if isinstance(covers, str): | |
423 | if isinstance(covers, (str, unicode)): | |
424 | 424 | covers = dns.rdatatype.from_text(covers) |
425 | 425 | for (name, node) in self.iteritems(): |
426 | 426 | for rds in node: |
441 | 441 | @type covers: int or string |
442 | 442 | """ |
443 | 443 | |
444 | if isinstance(rdtype, str): | |
444 | if isinstance(rdtype, (str, unicode)): | |
445 | 445 | rdtype = dns.rdatatype.from_text(rdtype) |
446 | if isinstance(covers, str): | |
446 | if isinstance(covers, (str, unicode)): | |
447 | 447 | covers = dns.rdatatype.from_text(covers) |
448 | 448 | for (name, node) in self.iteritems(): |
449 | 449 | 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> </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"> </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"> </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) |
17 | 17 | import sys |
18 | 18 | from distutils.core import setup |
19 | 19 | |
20 | version = '1.8.0' | |
20 | version = '1.9.4' | |
21 | 21 | |
22 | 22 | kwargs = { |
23 | 23 | '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' |
13 | 13 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
14 | 14 | |
15 | 15 | import cStringIO |
16 | import select | |
16 | 17 | import sys |
17 | 18 | import time |
18 | 19 | import unittest |
45 | 46 | ;ADDITIONAL |
46 | 47 | """ |
47 | 48 | |
48 | class ResolverTestCase(unittest.TestCase): | |
49 | class BaseResolverTests(object): | |
49 | 50 | |
50 | 51 | if sys.platform != 'win32': |
51 | 52 | def testRead(self): |
100 | 101 | zname = dns.resolver.zone_for_name(name) |
101 | 102 | self.failUnlessRaises(dns.resolver.NotAbsolute, bad) |
102 | 103 | |
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 | ||
103 | 125 | if __name__ == '__main__': |
104 | 126 | unittest.main() |