summaryrefslogtreecommitdiff
path: root/lib/dnspython
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dnspython')
-rw-r--r--lib/dnspython/.gitignore2
-rw-r--r--lib/dnspython/ChangeLog79
-rw-r--r--lib/dnspython/Makefile56
-rw-r--r--lib/dnspython/README59
-rw-r--r--lib/dnspython/dns/__init__.py1
-rw-r--r--lib/dnspython/dns/dnssec.py312
-rw-r--r--lib/dnspython/dns/hash.py67
-rw-r--r--lib/dnspython/dns/message.py12
-rw-r--r--lib/dnspython/dns/node.py14
-rw-r--r--lib/dnspython/dns/query.py84
-rw-r--r--lib/dnspython/dns/rdataset.py4
-rw-r--r--lib/dnspython/dns/resolver.py17
-rw-r--r--lib/dnspython/dns/rrset.py6
-rw-r--r--lib/dnspython/dns/tsig.py77
-rw-r--r--lib/dnspython/dns/update.py12
-rw-r--r--lib/dnspython/dns/version.py4
-rw-r--r--lib/dnspython/dns/zone.py20
-rwxr-xr-xlib/dnspython/examples/ddns.py2
-rwxr-xr-xlib/dnspython/examples/zonediff.py270
-rwxr-xr-xlib/dnspython/setup.py2
-rw-r--r--lib/dnspython/tests/dnssec.py146
-rw-r--r--lib/dnspython/tests/resolver.py24
22 files changed, 1175 insertions, 95 deletions
diff --git a/lib/dnspython/.gitignore b/lib/dnspython/.gitignore
index 2abcfc47d7..5592c971b0 100644
--- a/lib/dnspython/.gitignore
+++ b/lib/dnspython/.gitignore
@@ -2,4 +2,6 @@ build
dist
MANIFEST
html
+html.zip
+html.tar.gz
tests/*.out
diff --git a/lib/dnspython/ChangeLog b/lib/dnspython/ChangeLog
index 73a66edef6..91e69d3ea2 100644
--- a/lib/dnspython/ChangeLog
+++ b/lib/dnspython/ChangeLog
@@ -1,3 +1,82 @@
+2010-11-23 Bob Halley <halley@dnspython.org>
+
+ * (Version 1.9.2 released)
+
+2010-11-23 Bob Halley <halley@dnspython.org>
+
+ * dns/dnssec.py (_need_pycrypto): DSA and RSA are modules, not
+ functions, and I didn't notice because the test suite masked
+ the bug! *sigh*
+
+2010-11-22 Bob Halley <halley@dnspython.org>
+
+ * (Version 1.9.1 released)
+
+2010-11-22 Bob Halley <halley@dnspython.org>
+
+ * dns/dnssec.py: the "from" style import used to get DSA from
+ PyCrypto trashed a DSA constant. Now a normal import is used
+ to avoid namespace contamination.
+
+2010-11-20 Bob Halley <halley@dnspython.org>
+
+ * (Version 1.9.0 released)
+
+2010-11-07 Bob Halley <halley@dnspython.org>
+
+ * dns/dnssec.py: Added validate() to do basic DNSSEC validation
+ (requires PyCrypto). Thanks to Brian Wellington for the patch.
+
+ * dns/hash.py: Hash compatibility handling is now its own module.
+
+2010-10-31 Bob Halley <halley@dnspython.org>
+
+ * dns/resolver.py (zone_for_name): A query name resulting in a
+ CNAME or DNAME response to a node which had an SOA was incorrectly
+ treated as a zone origin. In these cases, we should just look
+ higher. Thanks to Gert Berger for reporting this problem.
+
+ * Added zonediff.py to examples. This program compares two zones
+ and shows the differences either in diff-like plain text, or
+ HTML. Thanks to Dennis Kaarsemaker for contributing this
+ useful program.
+
+2010-10-27 Bob Halley <halley@dnspython.org>
+
+ * Incorporate a patch to use poll() instead of select() by
+ default on platforms which support it. Thanks to
+ Peter Schüller and Spotify for the contribution.
+
+2010-10-17 Bob Halley <halley@dnspython.org>
+
+ * Python prior to 2.5.2 doesn't compute the correct values for
+ HMAC-SHA384 and HMAC-SHA512. We now detect attempts to use
+ them and raise NotImplemented if the Python version is too old.
+ Thanks to Kevin Chen for reporting the problem.
+
+ * Various routines that took the string forms of rdata types and
+ classes did not permit the strings to be Unicode strings.
+ Thanks to Ryan Workman for reporting the issue.
+
+ * dns/tsig.py: Added symbolic constants for the algorithm strings.
+ E.g. you can now say dns.tsig.HMAC_MD5 instead of
+ "HMAC-MD5.SIG-ALG.REG.INT". Thanks to Cillian Sharkey for
+ suggesting this improvement.
+
+ * dns/tsig.py (get_algorithm): fix hashlib compatibility; thanks to
+ Kevin Chen for the patch.
+
+ * dns/dnssec.py: Added key_id() and make_ds().
+
+ * dns/message.py: message.py needs to import dns.edns since it uses
+ it.
+
+2010-05-04 Bob Halley <halley@dnspython.org>
+
+ * dns/rrset.py (RRset.__init__): "covers" was not passed to the
+ superclass __init__(). Thanks to Shanmuga Rajan for reporting
+ the problem.
+
2010-03-10 Bob Halley <halley@dnspython.org>
* The TSIG algorithm value was passed to use_tsig() incorrectly
diff --git a/lib/dnspython/Makefile b/lib/dnspython/Makefile
new file mode 100644
index 0000000000..3dbfe95346
--- /dev/null
+++ b/lib/dnspython/Makefile
@@ -0,0 +1,56 @@
+# Copyright (C) 2003-2007, 2009 Nominum, Inc.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose with or without fee is hereby granted,
+# provided that the above copyright notice and this permission notice
+# appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# $Id: Makefile,v 1.16 2004/03/19 00:17:27 halley Exp $
+
+PYTHON=python
+
+all:
+ ${PYTHON} ./setup.py build
+
+install:
+ ${PYTHON} ./setup.py install
+
+clean:
+ ${PYTHON} ./setup.py clean --all
+ find . -name '*.pyc' -exec rm {} \;
+ find . -name '*.pyo' -exec rm {} \;
+ rm -f TAGS
+
+distclean: clean docclean
+ rm -rf build dist
+ rm -f MANIFEST
+
+doc:
+ epydoc -n dnspython -u http://www.dnspython.org \
+ dns/*.py dns/rdtypes/*.py dns/rdtypes/ANY/*.py \
+ dns/rdtypes/IN/*.py
+
+dockits: doc
+ mv html dnspython-html
+ tar czf html.tar.gz dnspython-html
+ zip -r html.zip dnspython-html
+ mv dnspython-html html
+
+docclean:
+ rm -rf html.tar.gz html.zip html
+
+kits:
+ ${PYTHON} ./setup.py sdist --formats=gztar,zip
+# ${PYTHON} ./setup.py bdist_wininst
+# ${PYTHON} ./setup.py bdist_rpm
+
+tags:
+ find . -name '*.py' -print | etags -
diff --git a/lib/dnspython/README b/lib/dnspython/README
index b313d1c132..d53dac61ab 100644
--- a/lib/dnspython/README
+++ b/lib/dnspython/README
@@ -22,7 +22,62 @@ development by continuing to employ the author :).
ABOUT THIS RELEASE
-This is dnspython 1.8.0
+This is dnspython 1.9.2
+
+New since 1.9.1:
+
+ Nothing.
+
+Bugs fixed since 1.9.1:
+
+ The dns.dnssec module didn't work at all due to missing
+ imports that escaped detection in testing because the test
+ suite also did the imports. The third time is the charm!
+
+New since 1.9.0:
+
+ Nothing.
+
+Bugs fixed since 1.9.0:
+
+ The dns.dnssec module didn't work with DSA due to namespace
+ contamination from a "from"-style import.
+
+New since 1.8.0:
+
+ dnspython now uses poll() instead of select() when available.
+
+ Basic DNSSEC validation can be done using dns.dnsec.validate()
+ and dns.dnssec.validate_rrsig() if you have PyCrypto 2.3 or
+ later installed. Complete secure resolution is not yet
+ available.
+
+ Added key_id() to the DNSSEC module, which computes the DNSSEC
+ key id of a DNSKEY rdata.
+
+ Added make_ds() to the DNSSEC module, which returns the DS RR
+ for a given DNSKEY rdata.
+
+ dnspython now raises an exception if HMAC-SHA284 or
+ HMAC-SHA512 are used with a Python older than 2.5.2. (Older
+ Pythons do not compute the correct value.)
+
+ Symbolic constants are now available for TSIG algorithm names.
+
+Bugs fixed since 1.8.0
+
+ dns.resolver.zone_for_name() didn't handle a query response
+ with a CNAME or DNAME correctly in some cases.
+
+ When specifying rdata types and classes as text, Unicode
+ strings may now be used.
+
+ Hashlib compatibility issues have been fixed.
+
+ dns.message now imports dns.edns.
+
+ The TSIG algorithm value was passed incorrectly to use_tsig()
+ in some cases.
New since 1.7.1:
@@ -310,7 +365,7 @@ the prior release.
REQUIREMENTS
-Python 2.2 or later.
+Python 2.4 or later.
INSTALLATION
diff --git a/lib/dnspython/dns/__init__.py b/lib/dnspython/dns/__init__.py
index 5ad5737cfa..56e1e8a2ea 100644
--- a/lib/dnspython/dns/__init__.py
+++ b/lib/dnspython/dns/__init__.py
@@ -22,6 +22,7 @@ __all__ = [
'entropy',
'exception',
'flags',
+ 'hash',
'inet',
'ipv4',
'ipv6',
diff --git a/lib/dnspython/dns/dnssec.py b/lib/dnspython/dns/dnssec.py
index 54fd78d9c9..a595fd4478 100644
--- a/lib/dnspython/dns/dnssec.py
+++ b/lib/dnspython/dns/dnssec.py
@@ -15,6 +15,27 @@
"""Common DNSSEC-related functions and constants."""
+import cStringIO
+import struct
+import time
+
+import dns.exception
+import dns.hash
+import dns.name
+import dns.node
+import dns.rdataset
+import dns.rdata
+import dns.rdatatype
+import dns.rdataclass
+
+class UnsupportedAlgorithm(dns.exception.DNSException):
+ """Raised if an algorithm is not supported."""
+ pass
+
+class ValidationFailure(dns.exception.DNSException):
+ """The DNSSEC signature is invalid."""
+ pass
+
RSAMD5 = 1
DH = 2
DSA = 3
@@ -49,14 +70,10 @@ _algorithm_by_text = {
_algorithm_by_value = dict([(y, x) for x, y in _algorithm_by_text.iteritems()])
-class UnknownAlgorithm(Exception):
- """Raised if an algorithm is unknown."""
- pass
-
def algorithm_from_text(text):
"""Convert text into a DNSSEC algorithm value
@rtype: int"""
-
+
value = _algorithm_by_text.get(text.upper())
if value is None:
value = int(text)
@@ -65,8 +82,291 @@ def algorithm_from_text(text):
def algorithm_to_text(value):
"""Convert a DNSSEC algorithm value to text
@rtype: string"""
-
+
text = _algorithm_by_value.get(value)
if text is None:
text = str(value)
return text
+
+def _to_rdata(record, origin):
+ s = cStringIO.StringIO()
+ record.to_wire(s, origin=origin)
+ return s.getvalue()
+
+def key_id(key, origin=None):
+ rdata = _to_rdata(key, origin)
+ if key.algorithm == RSAMD5:
+ return (ord(rdata[-3]) << 8) + ord(rdata[-2])
+ else:
+ total = 0
+ for i in range(len(rdata) / 2):
+ total += (ord(rdata[2 * i]) << 8) + ord(rdata[2 * i + 1])
+ if len(rdata) % 2 != 0:
+ total += ord(rdata[len(rdata) - 1]) << 8
+ total += ((total >> 16) & 0xffff);
+ return total & 0xffff
+
+def make_ds(name, key, algorithm, origin=None):
+ if algorithm.upper() == 'SHA1':
+ dsalg = 1
+ hash = dns.hash.get('SHA1')()
+ elif algorithm.upper() == 'SHA256':
+ dsalg = 2
+ hash = dns.hash.get('SHA256')()
+ else:
+ raise UnsupportedAlgorithm, 'unsupported algorithm "%s"' % algorithm
+
+ if isinstance(name, (str, unicode)):
+ name = dns.name.from_text(name, origin)
+ hash.update(name.canonicalize().to_wire())
+ hash.update(_to_rdata(key, origin))
+ digest = hash.digest()
+
+ dsrdata = struct.pack("!HBB", key_id(key), key.algorithm, dsalg) + digest
+ return dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0,
+ len(dsrdata))
+
+def _find_key(keys, rrsig):
+ value = keys.get(rrsig.signer)
+ if value is None:
+ return None
+ if isinstance(value, dns.node.Node):
+ try:
+ rdataset = node.find_rdataset(dns.rdataclass.IN,
+ dns.rdatatype.DNSKEY)
+ except KeyError:
+ return None
+ else:
+ rdataset = value
+ for rdata in rdataset:
+ if rdata.algorithm == rrsig.algorithm and \
+ key_id(rdata) == rrsig.key_tag:
+ return rdata
+ return None
+
+def _is_rsa(algorithm):
+ return algorithm in (RSAMD5, RSASHA1,
+ RSASHA1NSEC3SHA1, RSASHA256,
+ RSASHA512)
+
+def _is_dsa(algorithm):
+ return algorithm in (DSA, DSANSEC3SHA1)
+
+def _is_md5(algorithm):
+ return algorithm == RSAMD5
+
+def _is_sha1(algorithm):
+ return algorithm in (DSA, RSASHA1,
+ DSANSEC3SHA1, RSASHA1NSEC3SHA1)
+
+def _is_sha256(algorithm):
+ return algorithm == RSASHA256
+
+def _is_sha512(algorithm):
+ return algorithm == RSASHA512
+
+def _make_hash(algorithm):
+ if _is_md5(algorithm):
+ return dns.hash.get('MD5')()
+ if _is_sha1(algorithm):
+ return dns.hash.get('SHA1')()
+ if _is_sha256(algorithm):
+ return dns.hash.get('SHA256')()
+ if _is_sha512(algorithm):
+ return dns.hash.get('SHA512')()
+ raise ValidationFailure, 'unknown hash for algorithm %u' % algorithm
+
+def _make_algorithm_id(algorithm):
+ if _is_md5(algorithm):
+ oid = [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05]
+ elif _is_sha1(algorithm):
+ oid = [0x2b, 0x0e, 0x03, 0x02, 0x1a]
+ elif _is_sha256(algorithm):
+ oid = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01]
+ elif _is_sha512(algorithm):
+ oid = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03]
+ else:
+ raise ValidationFailure, 'unknown algorithm %u' % algorithm
+ olen = len(oid)
+ dlen = _make_hash(algorithm).digest_size
+ idbytes = [0x30] + [8 + olen + dlen] + \
+ [0x30, olen + 4] + [0x06, olen] + oid + \
+ [0x05, 0x00] + [0x04, dlen]
+ return ''.join(map(chr, idbytes))
+
+def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
+ """Validate an RRset against a single signature rdata
+
+ The owner name of the rrsig is assumed to be the same as the owner name
+ of the rrset.
+
+ @param rrset: The RRset to validate
+ @type rrset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset)
+ tuple
+ @param rrsig: The signature rdata
+ @type rrsig: dns.rrset.Rdata
+ @param keys: The key dictionary.
+ @type keys: a dictionary keyed by dns.name.Name with node or rdataset values
+ @param origin: The origin to use for relative names
+ @type origin: dns.name.Name or None
+ @param now: The time to use when validating the signatures. The default
+ is the current time.
+ @type now: int
+ """
+
+ if isinstance(origin, (str, unicode)):
+ origin = dns.name.from_text(origin, dns.name.root)
+
+ key = _find_key(keys, rrsig)
+ if not key:
+ raise ValidationFailure, 'unknown key'
+
+ # For convenience, allow the rrset to be specified as a (name, rdataset)
+ # tuple as well as a proper rrset
+ if isinstance(rrset, tuple):
+ rrname = rrset[0]
+ rdataset = rrset[1]
+ else:
+ rrname = rrset.name
+ rdataset = rrset
+
+ if now is None:
+ now = time.time()
+ if rrsig.expiration < now:
+ raise ValidationFailure, 'expired'
+ if rrsig.inception > now:
+ raise ValidationFailure, 'not yet valid'
+
+ hash = _make_hash(rrsig.algorithm)
+
+ if _is_rsa(rrsig.algorithm):
+ keyptr = key.key
+ (bytes,) = struct.unpack('!B', keyptr[0:1])
+ keyptr = keyptr[1:]
+ if bytes == 0:
+ (bytes,) = struct.unpack('!H', keyptr[0:2])
+ keyptr = keyptr[2:]
+ rsa_e = keyptr[0:bytes]
+ rsa_n = keyptr[bytes:]
+ keylen = len(rsa_n) * 8
+ pubkey = Crypto.PublicKey.RSA.construct(
+ (Crypto.Util.number.bytes_to_long(rsa_n),
+ Crypto.Util.number.bytes_to_long(rsa_e)))
+ sig = (Crypto.Util.number.bytes_to_long(rrsig.signature),)
+ elif _is_dsa(rrsig.algorithm):
+ keyptr = key.key
+ (t,) = struct.unpack('!B', keyptr[0:1])
+ keyptr = keyptr[1:]
+ octets = 64 + t * 8
+ dsa_q = keyptr[0:20]
+ keyptr = keyptr[20:]
+ dsa_p = keyptr[0:octets]
+ keyptr = keyptr[octets:]
+ dsa_g = keyptr[0:octets]
+ keyptr = keyptr[octets:]
+ dsa_y = keyptr[0:octets]
+ pubkey = Crypto.PublicKey.DSA.construct(
+ (Crypto.Util.number.bytes_to_long(dsa_y),
+ Crypto.Util.number.bytes_to_long(dsa_g),
+ Crypto.Util.number.bytes_to_long(dsa_p),
+ Crypto.Util.number.bytes_to_long(dsa_q)))
+ (dsa_r, dsa_s) = struct.unpack('!20s20s', rrsig.signature[1:])
+ sig = (Crypto.Util.number.bytes_to_long(dsa_r),
+ Crypto.Util.number.bytes_to_long(dsa_s))
+ else:
+ raise ValidationFailure, 'unknown algorithm %u' % rrsig.algorithm
+
+ hash.update(_to_rdata(rrsig, origin)[:18])
+ hash.update(rrsig.signer.to_digestable(origin))
+
+ if rrsig.labels < len(rrname) - 1:
+ suffix = rrname.split(rrsig.labels + 1)[1]
+ rrname = dns.name.from_text('*', suffix)
+ rrnamebuf = rrname.to_digestable(origin)
+ rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass,
+ rrsig.original_ttl)
+ rrlist = sorted(rdataset);
+ for rr in rrlist:
+ hash.update(rrnamebuf)
+ hash.update(rrfixed)
+ rrdata = rr.to_digestable(origin)
+ rrlen = struct.pack('!H', len(rrdata))
+ hash.update(rrlen)
+ hash.update(rrdata)
+
+ digest = hash.digest()
+
+ if _is_rsa(rrsig.algorithm):
+ # PKCS1 algorithm identifier goop
+ digest = _make_algorithm_id(rrsig.algorithm) + digest
+ padlen = keylen / 8 - len(digest) - 3
+ digest = chr(0) + chr(1) + chr(0xFF) * padlen + chr(0) + digest
+ elif _is_dsa(rrsig.algorithm):
+ pass
+ else:
+ # Raise here for code clarity; this won't actually ever happen
+ # since if the algorithm is really unknown we'd already have
+ # raised an exception above
+ raise ValidationFailure, 'unknown algorithm %u' % rrsig.algorithm
+
+ if not pubkey.verify(digest, sig):
+ raise ValidationFailure, 'verify failure'
+
+def _validate(rrset, rrsigset, keys, origin=None, now=None):
+ """Validate an RRset
+
+ @param rrset: The RRset to validate
+ @type rrset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset)
+ tuple
+ @param rrsigset: The signature RRset
+ @type rrsigset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset)
+ tuple
+ @param keys: The key dictionary.
+ @type keys: a dictionary keyed by dns.name.Name with node or rdataset values
+ @param origin: The origin to use for relative names
+ @type origin: dns.name.Name or None
+ @param now: The time to use when validating the signatures. The default
+ is the current time.
+ @type now: int
+ """
+
+ if isinstance(origin, (str, unicode)):
+ origin = dns.name.from_text(origin, dns.name.root)
+
+ if isinstance(rrset, tuple):
+ rrname = rrset[0]
+ else:
+ rrname = rrset.name
+
+ if isinstance(rrsigset, tuple):
+ rrsigname = rrsigset[0]
+ rrsigrdataset = rrsigset[1]
+ else:
+ rrsigname = rrsigset.name
+ rrsigrdataset = rrsigset
+
+ rrname = rrname.choose_relativity(origin)
+ rrsigname = rrname.choose_relativity(origin)
+ if rrname != rrsigname:
+ raise ValidationFailure, "owner names do not match"
+
+ for rrsig in rrsigrdataset:
+ try:
+ _validate_rrsig(rrset, rrsig, keys, origin, now)
+ return
+ except ValidationFailure, e:
+ pass
+ raise ValidationFailure, "no RRSIGs validated"
+
+def _need_pycrypto(*args, **kwargs):
+ raise NotImplementedError, "DNSSEC validation requires pycrypto"
+
+try:
+ import Crypto.PublicKey.RSA
+ import Crypto.PublicKey.DSA
+ import Crypto.Util.number
+ validate = _validate
+ validate_rrsig = _validate_rrsig
+except ImportError:
+ validate = _need_pycrypto
+ validate_rrsig = _need_pycrypto
diff --git a/lib/dnspython/dns/hash.py b/lib/dnspython/dns/hash.py
new file mode 100644
index 0000000000..7bd5ae5980
--- /dev/null
+++ b/lib/dnspython/dns/hash.py
@@ -0,0 +1,67 @@
+# Copyright (C) 2010 Nominum, Inc.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose with or without fee is hereby granted,
+# provided that the above copyright notice and this permission notice
+# appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""Hashing backwards compatibility wrapper"""
+
+import sys
+
+_hashes = None
+
+def _need_later_python(alg):
+ def func(*args, **kwargs):
+ raise NotImplementedError("TSIG algorithm " + alg +
+ " requires Python 2.5.2 or later")
+ return func
+
+def _setup():
+ global _hashes
+ _hashes = {}
+ try:
+ import hashlib
+ _hashes['MD5'] = hashlib.md5
+ _hashes['SHA1'] = hashlib.sha1
+ _hashes['SHA224'] = hashlib.sha224
+ _hashes['SHA256'] = hashlib.sha256
+ if sys.hexversion >= 0x02050200:
+ _hashes['SHA384'] = hashlib.sha384
+ _hashes['SHA512'] = hashlib.sha512
+ else:
+ _hashes['SHA384'] = _need_later_python('SHA384')
+ _hashes['SHA512'] = _need_later_python('SHA512')
+
+ if sys.hexversion < 0x02050000:
+ # hashlib doesn't conform to PEP 247: API for
+ # Cryptographic Hash Functions, which hmac before python
+ # 2.5 requires, so add the necessary items.
+ class HashlibWrapper:
+ def __init__(self, basehash):
+ self.basehash = basehash
+ self.digest_size = self.basehash().digest_size
+
+ def new(self, *args, **kwargs):
+ return self.basehash(*args, **kwargs)
+
+ for name in _hashes:
+ _hashes[name] = HashlibWrapper(_hashes[name])
+
+ except ImportError:
+ import md5, sha
+ _hashes['MD5'] = md5
+ _hashes['SHA1'] = sha
+
+def get(algorithm):
+ if _hashes is None:
+ _setup()
+ return _hashes[algorithm.upper()]
diff --git a/lib/dnspython/dns/message.py b/lib/dnspython/dns/message.py
index ba0ebf65f1..5ec711e1eb 100644
--- a/lib/dnspython/dns/message.py
+++ b/lib/dnspython/dns/message.py
@@ -21,6 +21,7 @@ import struct
import sys
import time
+import dns.edns
import dns.exception
import dns.flags
import dns.name
@@ -92,8 +93,11 @@ class Message(object):
@type keyring: dict
@ivar keyname: The TSIG keyname to use. The default is None.
@type keyname: dns.name.Name object
- @ivar keyalgorithm: The TSIG key algorithm to use. The default is
- dns.tsig.default_algorithm.
+ @ivar keyalgorithm: The TSIG algorithm to use; defaults to
+ dns.tsig.default_algorithm. Constants for TSIG algorithms are defined
+ in dns.tsig, and the currently implemented algorithms are
+ HMAC_MD5, HMAC_SHA1, HMAC_SHA224, HMAC_SHA256, HMAC_SHA384, and
+ HMAC_SHA512.
@type keyalgorithm: string
@ivar request_mac: The TSIG MAC of the request message associated with
this message; used when validating TSIG signatures. @see: RFC 2845 for
@@ -1035,9 +1039,9 @@ def make_query(qname, rdtype, rdclass = dns.rdataclass.IN, use_edns=None,
if isinstance(qname, (str, unicode)):
qname = dns.name.from_text(qname)
- if isinstance(rdtype, str):
+ if isinstance(rdtype, (str, unicode)):
rdtype = dns.rdatatype.from_text(rdtype)
- if isinstance(rdclass, str):
+ if isinstance(rdclass, (str, unicode)):
rdclass = dns.rdataclass.from_text(rdclass)
m = Message()
m.flags |= dns.flags.RD
diff --git a/lib/dnspython/dns/node.py b/lib/dnspython/dns/node.py
index 07fff9293c..785a245464 100644
--- a/lib/dnspython/dns/node.py
+++ b/lib/dnspython/dns/node.py
@@ -23,18 +23,18 @@ import dns.renderer
class Node(object):
"""A DNS node.
-
+
A node is a set of rdatasets
@ivar rdatasets: the node's rdatasets
@type rdatasets: list of dns.rdataset.Rdataset objects"""
__slots__ = ['rdatasets']
-
+
def __init__(self):
"""Initialize a DNS node.
"""
-
+
self.rdatasets = [];
def to_text(self, name, **kw):
@@ -46,7 +46,7 @@ class Node(object):
@type name: dns.name.Name object
@rtype: string
"""
-
+
s = StringIO.StringIO()
for rds in self.rdatasets:
print >> s, rds.to_text(name, **kw)
@@ -54,7 +54,7 @@ class Node(object):
def __repr__(self):
return '<DNS node ' + str(id(self)) + '>'
-
+
def __eq__(self, other):
"""Two nodes are equal if they have the same rdatasets.
@@ -73,7 +73,7 @@ class Node(object):
def __ne__(self, other):
return not self.__eq__(other)
-
+
def __len__(self):
return len(self.rdatasets)
@@ -159,7 +159,7 @@ class Node(object):
def replace_rdataset(self, replacement):
"""Replace an rdataset.
-
+
It is not an error if there is no rdataset matching I{replacement}.
Ownership of the I{replacement} object is transferred to the node;
diff --git a/lib/dnspython/dns/query.py b/lib/dnspython/dns/query.py
index c023b140af..9dc88a635c 100644
--- a/lib/dnspython/dns/query.py
+++ b/lib/dnspython/dns/query.py
@@ -45,7 +45,59 @@ def _compute_expiration(timeout):
else:
return time.time() + timeout
-def _wait_for(ir, iw, ix, expiration):
+def _poll_for(fd, readable, writable, error, timeout):
+ """
+ @param fd: File descriptor (int).
+ @param readable: Whether to wait for readability (bool).
+ @param writable: Whether to wait for writability (bool).
+ @param expiration: Deadline timeout (expiration time, in seconds (float)).
+
+ @return True on success, False on timeout
+ """
+ event_mask = 0
+ if readable:
+ event_mask |= select.POLLIN
+ if writable:
+ event_mask |= select.POLLOUT
+ if error:
+ event_mask |= select.POLLERR
+
+ pollable = select.poll()
+ pollable.register(fd, event_mask)
+
+ if timeout:
+ event_list = pollable.poll(long(timeout * 1000))
+ else:
+ event_list = pollable.poll()
+
+ return bool(event_list)
+
+def _select_for(fd, readable, writable, error, timeout):
+ """
+ @param fd: File descriptor (int).
+ @param readable: Whether to wait for readability (bool).
+ @param writable: Whether to wait for writability (bool).
+ @param expiration: Deadline timeout (expiration time, in seconds (float)).
+
+ @return True on success, False on timeout
+ """
+ rset, wset, xset = [], [], []
+
+ if readable:
+ rset = [fd]
+ if writable:
+ wset = [fd]
+ if error:
+ xset = [fd]
+
+ if timeout is None:
+ (rcount, wcount, xcount) = select.select(rset, wset, xset)
+ else:
+ (rcount, wcount, xcount) = select.select(rset, wset, xset, timeout)
+
+ return bool((rcount or wcount or xcount))
+
+def _wait_for(fd, readable, writable, error, expiration):
done = False
while not done:
if expiration is None:
@@ -55,22 +107,34 @@ def _wait_for(ir, iw, ix, expiration):
if timeout <= 0.0:
raise dns.exception.Timeout
try:
- if timeout is None:
- (r, w, x) = select.select(ir, iw, ix)
- else:
- (r, w, x) = select.select(ir, iw, ix, timeout)
+ if not _polling_backend(fd, readable, writable, error, timeout):
+ raise dns.exception.Timeout
except select.error, e:
if e.args[0] != errno.EINTR:
raise e
done = True
- if len(r) == 0 and len(w) == 0 and len(x) == 0:
- raise dns.exception.Timeout
+
+def _set_polling_backend(fn):
+ """
+ Internal API. Do not use.
+ """
+ global _polling_backend
+
+ _polling_backend = fn
+
+if hasattr(select, 'poll'):
+ # Prefer poll() on platforms that support it because it has no
+ # limits on the maximum value of a file descriptor (plus it will
+ # be more efficient for high values).
+ _polling_backend = _poll_for
+else:
+ _polling_backend = _select_for
def _wait_for_readable(s, expiration):
- _wait_for([s], [], [s], expiration)
+ _wait_for(s, True, False, True, expiration)
def _wait_for_writable(s, expiration):
- _wait_for([], [s], [s], expiration)
+ _wait_for(s, False, True, True, expiration)
def _addresses_equal(af, a1, a2):
# Convert the first value of the tuple, which is a textual format
@@ -310,7 +374,7 @@ def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN,
if isinstance(zone, (str, unicode)):
zone = dns.name.from_text(zone)
- if isinstance(rdtype, str):
+ if isinstance(rdtype, (str, unicode)):
rdtype = dns.rdatatype.from_text(rdtype)
q = dns.message.make_query(zone, rdtype, rdclass)
if rdtype == dns.rdatatype.IXFR:
diff --git a/lib/dnspython/dns/rdataset.py b/lib/dnspython/dns/rdataset.py
index 0af018bab5..f556d2288b 100644
--- a/lib/dnspython/dns/rdataset.py
+++ b/lib/dnspython/dns/rdataset.py
@@ -281,9 +281,9 @@ def from_text_list(rdclass, rdtype, ttl, text_rdatas):
@rtype: dns.rdataset.Rdataset object
"""
- if isinstance(rdclass, str):
+ if isinstance(rdclass, (str, unicode)):
rdclass = dns.rdataclass.from_text(rdclass)
- if isinstance(rdtype, str):
+ if isinstance(rdtype, (str, unicode)):
rdtype = dns.rdatatype.from_text(rdtype)
r = Rdataset(rdclass, rdtype)
r.update_ttl(ttl)
diff --git a/lib/dnspython/dns/resolver.py b/lib/dnspython/dns/resolver.py
index cd0e5f804b..f803eb6d20 100644
--- a/lib/dnspython/dns/resolver.py
+++ b/lib/dnspython/dns/resolver.py
@@ -569,9 +569,9 @@ class Resolver(object):
if isinstance(qname, (str, unicode)):
qname = dns.name.from_text(qname, None)
- if isinstance(rdtype, str):
+ if isinstance(rdtype, (str, unicode)):
rdtype = dns.rdatatype.from_text(rdtype)
- if isinstance(rdclass, str):
+ if isinstance(rdclass, (str, unicode)):
rdclass = dns.rdataclass.from_text(rdclass)
qnames_to_try = []
if qname.is_absolute():
@@ -754,9 +754,12 @@ def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, resolver=None):
while 1:
try:
answer = resolver.query(name, dns.rdatatype.SOA, rdclass, tcp)
- return name
+ if answer.rrset.name == name:
+ return name
+ # otherwise we were CNAMEd or DNAMEd and need to look higher
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
- try:
- name = name.parent()
- except dns.name.NoParent:
- raise NoRootSOA
+ pass
+ try:
+ name = name.parent()
+ except dns.name.NoParent:
+ raise NoRootSOA
diff --git a/lib/dnspython/dns/rrset.py b/lib/dnspython/dns/rrset.py
index 7f6c4afed4..21468174d4 100644
--- a/lib/dnspython/dns/rrset.py
+++ b/lib/dnspython/dns/rrset.py
@@ -36,7 +36,7 @@ class RRset(dns.rdataset.Rdataset):
deleting=None):
"""Create a new RRset."""
- super(RRset, self).__init__(rdclass, rdtype)
+ super(RRset, self).__init__(rdclass, rdtype, covers)
self.name = name
self.deleting = deleting
@@ -124,9 +124,9 @@ def from_text_list(name, ttl, rdclass, rdtype, text_rdatas):
if isinstance(name, (str, unicode)):
name = dns.name.from_text(name, None)
- if isinstance(rdclass, str):
+ if isinstance(rdclass, (str, unicode)):
rdclass = dns.rdataclass.from_text(rdclass)
- if isinstance(rdtype, str):
+ if isinstance(rdtype, (str, unicode)):
rdtype = dns.rdatatype.from_text(rdtype)
r = RRset(name, rdclass, rdtype)
r.update_ttl(ttl)
diff --git a/lib/dnspython/dns/tsig.py b/lib/dnspython/dns/tsig.py
index b4deeca859..5e58ea8841 100644
--- a/lib/dnspython/dns/tsig.py
+++ b/lib/dnspython/dns/tsig.py
@@ -17,8 +17,10 @@
import hmac
import struct
+import sys
import dns.exception
+import dns.hash
import dns.rdataclass
import dns.name
@@ -50,7 +52,16 @@ class PeerBadTruncation(PeerError):
"""Raised if the peer didn't like amount of truncation in the TSIG we sent"""
pass
-default_algorithm = "HMAC-MD5.SIG-ALG.REG.INT"
+# TSIG Algorithms
+
+HMAC_MD5 = dns.name.from_text("HMAC-MD5.SIG-ALG.REG.INT")
+HMAC_SHA1 = dns.name.from_text("hmac-sha1")
+HMAC_SHA224 = dns.name.from_text("hmac-sha224")
+HMAC_SHA256 = dns.name.from_text("hmac-sha256")
+HMAC_SHA384 = dns.name.from_text("hmac-sha384")
+HMAC_SHA512 = dns.name.from_text("hmac-sha512")
+
+default_algorithm = HMAC_MD5
BADSIG = 16
BADKEY = 17
@@ -167,6 +178,24 @@ def validate(wire, keyname, secret, now, request_mac, tsig_start, tsig_rdata,
raise BadSignature
return ctx
+_hashes = None
+
+def _maybe_add_hash(tsig_alg, hash_alg):
+ try:
+ _hashes[tsig_alg] = dns.hash.get(hash_alg)
+ except KeyError:
+ pass
+
+def _setup_hashes():
+ global _hashes
+ _hashes = {}
+ _maybe_add_hash(HMAC_SHA224, 'SHA224')
+ _maybe_add_hash(HMAC_SHA256, 'SHA256')
+ _maybe_add_hash(HMAC_SHA384, 'SHA384')
+ _maybe_add_hash(HMAC_SHA512, 'SHA512')
+ _maybe_add_hash(HMAC_SHA1, 'SHA1')
+ _maybe_add_hash(HMAC_MD5, 'MD5')
+
def get_algorithm(algorithm):
"""Returns the wire format string and the hash module to use for the
specified TSIG algorithm
@@ -175,42 +204,20 @@ def get_algorithm(algorithm):
@raises NotImplementedError: I{algorithm} is not supported
"""
- hashes = {}
- try:
- import hashlib
- hashes[dns.name.from_text('hmac-sha224')] = hashlib.sha224
- hashes[dns.name.from_text('hmac-sha256')] = hashlib.sha256
- hashes[dns.name.from_text('hmac-sha384')] = hashlib.sha384
- hashes[dns.name.from_text('hmac-sha512')] = hashlib.sha512
- hashes[dns.name.from_text('hmac-sha1')] = hashlib.sha1
- hashes[dns.name.from_text('HMAC-MD5.SIG-ALG.REG.INT')] = hashlib.md5
-
- import sys
- if sys.hexversion < 0x02050000:
- # hashlib doesn't conform to PEP 247: API for
- # Cryptographic Hash Functions, which hmac before python
- # 2.5 requires, so add the necessary items.
- class HashlibWrapper:
- def __init__(self, basehash):
- self.basehash = basehash
- self.digest_size = self.basehash().digest_size
-
- def new(self, *args, **kwargs):
- return self.basehash(*args, **kwargs)
-
- for name in hashes:
- hashes[name] = HashlibWrapper(hashes[name])
-
- except ImportError:
- import md5, sha
- hashes[dns.name.from_text('HMAC-MD5.SIG-ALG.REG.INT')] = md5.md5
- hashes[dns.name.from_text('hmac-sha1')] = sha.sha
+ global _hashes
+ if _hashes is None:
+ _setup_hashes()
if isinstance(algorithm, (str, unicode)):
algorithm = dns.name.from_text(algorithm)
- if algorithm in hashes:
- return (algorithm.to_digestable(), hashes[algorithm])
+ if sys.hexversion < 0x02050200 and \
+ (algorithm == HMAC_SHA384 or algorithm == HMAC_SHA512):
+ raise NotImplementedError("TSIG algorithm " + str(algorithm) +
+ " requires Python 2.5.2 or later")
- raise NotImplementedError("TSIG algorithm " + str(algorithm) +
- " is not supported")
+ try:
+ return (algorithm.to_digestable(), _hashes[algorithm])
+ except KeyError:
+ raise NotImplementedError("TSIG algorithm " + str(algorithm) +
+ " is not supported")
diff --git a/lib/dnspython/dns/update.py b/lib/dnspython/dns/update.py
index 97aea18fb9..e67acafec9 100644
--- a/lib/dnspython/dns/update.py
+++ b/lib/dnspython/dns/update.py
@@ -21,6 +21,7 @@ import dns.opcode
import dns.rdata
import dns.rdataclass
import dns.rdataset
+import dns.tsig
class Update(dns.message.Message):
def __init__(self, zone, rdclass=dns.rdataclass.IN, keyring=None,
@@ -42,7 +43,10 @@ class Update(dns.message.Message):
they know the keyring contains only one key.
@type keyname: dns.name.Name or string
@param keyalgorithm: The TSIG algorithm to use; defaults to
- dns.tsig.default_algorithm
+ dns.tsig.default_algorithm. Constants for TSIG algorithms are defined
+ in dns.tsig, and the currently implemented algorithms are
+ HMAC_MD5, HMAC_SHA1, HMAC_SHA224, HMAC_SHA256, HMAC_SHA384, and
+ HMAC_SHA512.
@type keyalgorithm: string
"""
super(Update, self).__init__()
@@ -148,7 +152,7 @@ class Update(dns.message.Message):
self._add_rr(name, 0, rd, dns.rdataclass.NONE)
else:
rdtype = args.pop(0)
- if isinstance(rdtype, str):
+ if isinstance(rdtype, (str, unicode)):
rdtype = dns.rdatatype.from_text(rdtype)
if len(args) == 0:
rrset = self.find_rrset(self.authority, name,
@@ -206,7 +210,7 @@ class Update(dns.message.Message):
self._add(False, self.answer, name, *args)
else:
rdtype = args[0]
- if isinstance(rdtype, str):
+ if isinstance(rdtype, (str, unicode)):
rdtype = dns.rdatatype.from_text(rdtype)
rrset = self.find_rrset(self.answer, name,
dns.rdataclass.ANY, rdtype,
@@ -225,7 +229,7 @@ class Update(dns.message.Message):
dns.rdatatype.NONE, None,
True, True)
else:
- if isinstance(rdtype, str):
+ if isinstance(rdtype, (str, unicode)):
rdtype = dns.rdatatype.from_text(rdtype)
rrset = self.find_rrset(self.answer, name,
dns.rdataclass.NONE, rdtype,
diff --git a/lib/dnspython/dns/version.py b/lib/dnspython/dns/version.py
index dd135a13e5..fe0e324217 100644
--- a/lib/dnspython/dns/version.py
+++ b/lib/dnspython/dns/version.py
@@ -16,8 +16,8 @@
"""dnspython release version information."""
MAJOR = 1
-MINOR = 8
-MICRO = 1
+MINOR = 9
+MICRO = 2
RELEASELEVEL = 0x0f
SERIAL = 0
diff --git a/lib/dnspython/dns/zone.py b/lib/dnspython/dns/zone.py
index 93c157d8f0..db5fd5df85 100644
--- a/lib/dnspython/dns/zone.py
+++ b/lib/dnspython/dns/zone.py
@@ -237,9 +237,9 @@ class Zone(object):
"""
name = self._validate_name(name)
- if isinstance(rdtype, str):
+ if isinstance(rdtype, (str, unicode)):
rdtype = dns.rdatatype.from_text(rdtype)
- if isinstance(covers, str):
+ if isinstance(covers, (str, unicode)):
covers = dns.rdatatype.from_text(covers)
node = self.find_node(name, create)
return node.find_rdataset(self.rdclass, rdtype, covers, create)
@@ -300,9 +300,9 @@ class Zone(object):
"""
name = self._validate_name(name)
- if isinstance(rdtype, str):
+ if isinstance(rdtype, (str, unicode)):
rdtype = dns.rdatatype.from_text(rdtype)
- if isinstance(covers, str):
+ if isinstance(covers, (str, unicode)):
covers = dns.rdatatype.from_text(covers)
node = self.get_node(name)
if not node is None:
@@ -363,9 +363,9 @@ class Zone(object):
"""
name = self._validate_name(name)
- if isinstance(rdtype, str):
+ if isinstance(rdtype, (str, unicode)):
rdtype = dns.rdatatype.from_text(rdtype)
- if isinstance(covers, str):
+ if isinstance(covers, (str, unicode)):
covers = dns.rdatatype.from_text(covers)
rdataset = self.nodes[name].find_rdataset(self.rdclass, rdtype, covers)
rrset = dns.rrset.RRset(name, self.rdclass, rdtype, covers)
@@ -419,9 +419,9 @@ class Zone(object):
@type covers: int or string
"""
- if isinstance(rdtype, str):
+ if isinstance(rdtype, (str, unicode)):
rdtype = dns.rdatatype.from_text(rdtype)
- if isinstance(covers, str):
+ if isinstance(covers, (str, unicode)):
covers = dns.rdatatype.from_text(covers)
for (name, node) in self.iteritems():
for rds in node:
@@ -442,9 +442,9 @@ class Zone(object):
@type covers: int or string
"""
- if isinstance(rdtype, str):
+ if isinstance(rdtype, (str, unicode)):
rdtype = dns.rdatatype.from_text(rdtype)
- if isinstance(covers, str):
+ if isinstance(covers, (str, unicode)):
covers = dns.rdatatype.from_text(covers)
for (name, node) in self.iteritems():
for rds in node:
diff --git a/lib/dnspython/examples/ddns.py b/lib/dnspython/examples/ddns.py
index 27a5b932f4..84814b73cf 100755
--- a/lib/dnspython/examples/ddns.py
+++ b/lib/dnspython/examples/ddns.py
@@ -16,7 +16,7 @@
#
# DEVICE=$1
#
-# if [ "X${DEVICE}" = "Xeth0" ]; then
+# if [ "X${DEVICE}" == "Xeth0" ]; then
# IPADDR=`LANG= LC_ALL= ifconfig ${DEVICE} | grep 'inet addr' |
# awk -F: '{ print $2 } ' | awk '{ print $1 }'`
# /usr/local/sbin/ddns.py $IPADDR
diff --git a/lib/dnspython/examples/zonediff.py b/lib/dnspython/examples/zonediff.py
new file mode 100755
index 0000000000..ad81fb1d2d
--- /dev/null
+++ b/lib/dnspython/examples/zonediff.py
@@ -0,0 +1,270 @@
+#!/usr/bin/env python
+#
+# Small library and commandline tool to do logical diffs of zonefiles
+# ./zonediff -h gives you help output
+#
+# Requires dnspython to do all the heavy lifting
+#
+# (c)2009 Dennis Kaarsemaker <dennis@kaarsemaker.net>
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose with or without fee is hereby granted,
+# provided that the above copyright notice and this permission notice
+# appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+"""See diff_zones.__doc__ for more information"""
+
+__all__ = ['diff_zones', 'format_changes_plain', 'format_changes_html']
+
+try:
+ import dns.zone
+except ImportError:
+ import sys
+ sys.stderr.write("Please install dnspython")
+ sys.exit(1)
+
+def diff_zones(zone1, zone2, ignore_ttl=False, ignore_soa=False):
+ """diff_zones(zone1, zone2, ignore_ttl=False, ignore_soa=False) -> changes
+ Compares two dns.zone.Zone objects and returns a list of all changes
+ in the format (name, oldnode, newnode).
+
+ If ignore_ttl is true, a node will not be added to this list if the
+ only change is its TTL.
+
+ If ignore_soa is true, a node will not be added to this list if the
+ only changes is a change in a SOA Rdata set.
+
+ The returned nodes do include all Rdata sets, including unchanged ones.
+ """
+
+ changes = []
+ for name in zone1:
+ name = str(name)
+ n1 = zone1.get_node(name)
+ n2 = zone2.get_node(name)
+ if not n2:
+ changes.append((str(name), n1, n2))
+ elif _nodes_differ(n1, n2, ignore_ttl, ignore_soa):
+ changes.append((str(name), n1, n2))
+
+ for name in zone2:
+ n1 = zone1.get_node(name)
+ if not n1:
+ n2 = zone2.get_node(name)
+ changes.append((str(name), n1, n2))
+ return changes
+
+def _nodes_differ(n1, n2, ignore_ttl, ignore_soa):
+ if ignore_soa or not ignore_ttl:
+ # Compare datasets directly
+ for r in n1.rdatasets:
+ if ignore_soa and r.rdtype == dns.rdatatype.SOA:
+ continue
+ if r not in n2.rdatasets:
+ return True
+ if not ignore_ttl:
+ return r.ttl != n2.find_rdataset(r.rdclass, r.rdtype).ttl
+
+ for r in n2.rdatasets:
+ if ignore_soa and r.rdtype == dns.rdatatype.SOA:
+ continue
+ if r not in n1.rdatasets:
+ return True
+ else:
+ return n1 != n2
+
+def format_changes_plain(oldf, newf, changes, ignore_ttl=False):
+ """format_changes(oldfile, newfile, changes, ignore_ttl=False) -> str
+ Given 2 filenames and a list of changes from diff_zones, produce diff-like
+ output. If ignore_ttl is True, TTL-only changes are not displayed"""
+
+ ret = "--- %s\n+++ %s\n" % (oldf, newf)
+ for name, old, new in changes:
+ ret += "@ %s\n" % name
+ if not old:
+ for r in new.rdatasets:
+ ret += "+ %s\n" % str(r).replace('\n','\n+ ')
+ elif not new:
+ for r in old.rdatasets:
+ ret += "- %s\n" % str(r).replace('\n','\n+ ')
+ else:
+ for r in old.rdatasets:
+ if r not in new.rdatasets or (r.ttl != new.find_rdataset(r.rdclass, r.rdtype).ttl and not ignore_ttl):
+ ret += "- %s\n" % str(r).replace('\n','\n+ ')
+ for r in new.rdatasets:
+ if r not in old.rdatasets or (r.ttl != old.find_rdataset(r.rdclass, r.rdtype).ttl and not ignore_ttl):
+ ret += "+ %s\n" % str(r).replace('\n','\n+ ')
+ return ret
+
+def format_changes_html(oldf, newf, changes, ignore_ttl=False):
+ """format_changes(oldfile, newfile, changes, ignore_ttl=False) -> str
+ Given 2 filenames and a list of changes from diff_zones, produce nice html
+ output. If ignore_ttl is True, TTL-only changes are not displayed"""
+
+ ret = '''<table class="zonediff">
+ <thead>
+ <tr>
+ <th>&nbsp;</th>
+ <th class="old">%s</th>
+ <th class="new">%s</th>
+ </tr>
+ </thead>
+ <tbody>\n''' % (oldf, newf)
+
+ for name, old, new in changes:
+ ret += ' <tr class="rdata">\n <td class="rdname">%s</td>\n' % name
+ if not old:
+ for r in new.rdatasets:
+ ret += ' <td class="old">&nbsp;</td>\n <td class="new">%s</td>\n' % str(r).replace('\n','<br />')
+ elif not new:
+ for r in old.rdatasets:
+ ret += ' <td class="old">%s</td>\n <td class="new">&nbsp;</td>\n' % str(r).replace('\n','<br />')
+ else:
+ ret += ' <td class="old">'
+ for r in old.rdatasets:
+ if r not in new.rdatasets or (r.ttl != new.find_rdataset(r.rdclass, r.rdtype).ttl and not ignore_ttl):
+ ret += str(r).replace('\n','<br />')
+ ret += '</td>\n'
+ ret += ' <td class="new">'
+ for r in new.rdatasets:
+ if r not in old.rdatasets or (r.ttl != old.find_rdataset(r.rdclass, r.rdtype).ttl and not ignore_ttl):
+ ret += str(r).replace('\n','<br />')
+ ret += '</td>\n'
+ ret += ' </tr>\n'
+ return ret + ' </tbody>\n</table>'
+
+# Make this module usable as a script too.
+if __name__ == '__main__':
+ import optparse
+ import subprocess
+ import sys
+ import traceback
+
+ usage = """%prog zonefile1 zonefile2 - Show differences between zones in a diff-like format
+%prog [--git|--bzr|--rcs] zonefile rev1 [rev2] - Show differences between two revisions of a zonefile
+
+The differences shown will be logical differences, not textual differences.
+"""
+ p = optparse.OptionParser(usage=usage)
+ p.add_option('-s', '--ignore-soa', action="store_true", default=False, dest="ignore_soa",
+ help="Ignore SOA-only changes to records")
+ p.add_option('-t', '--ignore-ttl', action="store_true", default=False, dest="ignore_ttl",
+ help="Ignore TTL-only changes to Rdata")
+ p.add_option('-T', '--traceback', action="store_true", default=False, dest="tracebacks",
+ help="Show python tracebacks when errors occur")
+ p.add_option('-H', '--html', action="store_true", default=False, dest="html",
+ help="Print HTML output")
+ p.add_option('-g', '--git', action="store_true", default=False, dest="use_git",
+ help="Use git revisions instead of real files")
+ p.add_option('-b', '--bzr', action="store_true", default=False, dest="use_bzr",
+ help="Use bzr revisions instead of real files")
+ p.add_option('-r', '--rcs', action="store_true", default=False, dest="use_rcs",
+ help="Use rcs revisions instead of real files")
+ opts, args = p.parse_args()
+ opts.use_vc = opts.use_git or opts.use_bzr or opts.use_rcs
+
+ def _open(what, err):
+ if isinstance(what, basestring):
+ # Open as normal file
+ try:
+ return open(what, 'rb')
+ except:
+ sys.stderr.write(err + "\n")
+ if opts.tracebacks:
+ traceback.print_exc()
+ else:
+ # Must be a list, open subprocess
+ try:
+ proc = subprocess.Popen(what, stdout=subprocess.PIPE)
+ proc.wait()
+ if proc.returncode == 0:
+ return proc.stdout
+ sys.stderr.write(err + "\n")
+ except:
+ sys.stderr.write(err + "\n")
+ if opts.tracebacks:
+ traceback.print_exc()
+
+ if not opts.use_vc and len(args) != 2:
+ p.print_help()
+ sys.exit(64)
+ if opts.use_vc and len(args) not in (2,3):
+ p.print_help()
+ sys.exit(64)
+
+ # Open file desriptors
+ if not opts.use_vc:
+ oldn, newn = args
+ else:
+ if len(args) == 3:
+ filename, oldr, newr = args
+ oldn = "%s:%s" % (oldr, filename)
+ newn = "%s:%s" % (newr, filename)
+ else:
+ filename, oldr = args
+ newr = None
+ oldn = "%s:%s" % (oldr, filename)
+ newn = filename
+
+
+ old, new = None, None
+ oldz, newz = None, None
+ if opts.use_bzr:
+ old = _open(["bzr", "cat", "-r" + oldr, filename],
+ "Unable to retrieve revision %s of %s" % (oldr, filename))
+ if newr != None:
+ new = _open(["bzr", "cat", "-r" + newr, filename],
+ "Unable to retrieve revision %s of %s" % (newr, filename))
+ elif opts.use_git:
+ old = _open(["git", "show", oldn],
+ "Unable to retrieve revision %s of %s" % (oldr, filename))
+ if newr != None:
+ new = _open(["git", "show", newn],
+ "Unable to retrieve revision %s of %s" % (newr, filename))
+ elif opts.use_rcs:
+ old = _open(["co", "-q", "-p", "-r" + oldr, filename],
+ "Unable to retrieve revision %s of %s" % (oldr, filename))
+ if newr != None:
+ new = _open(["co", "-q", "-p", "-r" + newr, filename],
+ "Unable to retrieve revision %s of %s" % (newr, filename))
+ if not opts.use_vc:
+ old = _open(oldn, "Unable to open %s" % oldn)
+ if not opts.use_vc or newr == None:
+ new = _open(newn, "Unable to open %s" % newn)
+
+ if not old or not new:
+ sys.exit(65)
+
+ # Parse the zones
+ try:
+ oldz = dns.zone.from_file(old, origin = '.', check_origin=False)
+ except dns.exception.DNSException:
+ sys.stderr.write("Incorrect zonefile: %s\n", old)
+ if opts.tracebacks:
+ traceback.print_exc()
+ try:
+ newz = dns.zone.from_file(new, origin = '.', check_origin=False)
+ except dns.exception.DNSException:
+ sys.stderr.write("Incorrect zonefile: %s\n" % new)
+ if opts.tracebacks:
+ traceback.print_exc()
+ if not oldz or not newz:
+ sys.exit(65)
+
+ changes = diff_zones(oldz, newz, opts.ignore_ttl, opts.ignore_soa)
+ changes.sort()
+
+ if not changes:
+ sys.exit(0)
+ if opts.html:
+ print format_changes_html(oldn, newn, changes, opts.ignore_ttl)
+ else:
+ print format_changes_plain(oldn, newn, changes, opts.ignore_ttl)
+ sys.exit(1)
diff --git a/lib/dnspython/setup.py b/lib/dnspython/setup.py
index 21ebddfb59..59bd0ebc95 100755
--- a/lib/dnspython/setup.py
+++ b/lib/dnspython/setup.py
@@ -18,7 +18,7 @@
import sys
from distutils.core import setup
-version = '1.8.1'
+version = '1.9.2'
kwargs = {
'name' : 'dnspython',
diff --git a/lib/dnspython/tests/dnssec.py b/lib/dnspython/tests/dnssec.py
new file mode 100644
index 0000000000..b30e847fba
--- /dev/null
+++ b/lib/dnspython/tests/dnssec.py
@@ -0,0 +1,146 @@
+# Copyright (C) 2010 Nominum, Inc.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose with or without fee is hereby granted,
+# provided that the above copyright notice and this permission notice
+# appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import unittest
+
+import dns.dnssec
+import dns.name
+import dns.rdata
+import dns.rdataclass
+import dns.rdatatype
+import dns.rrset
+
+abs_dnspython_org = dns.name.from_text('dnspython.org')
+
+abs_keys = { abs_dnspython_org :
+ dns.rrset.from_text('dnspython.org.', 3600, 'IN', 'DNSKEY',
+ '257 3 5 AwEAAenVTr9L1OMlL1/N2ta0Qj9LLLnnmFWIr1dJoAsWM9BQfsbV7kFZ XbAkER/FY9Ji2o7cELxBwAsVBuWn6IUUAJXLH74YbC1anY0lifjgt29z SwDzuB7zmC7yVYZzUunBulVW4zT0tg1aePbpVL2EtTL8VzREqbJbE25R KuQYHZtFwG8S4iBxJUmT2Bbd0921LLxSQgVoFXlQx/gFV2+UERXcJ5ce iX6A6wc02M/pdg/YbJd2rBa0MYL3/Fz/Xltre0tqsImZGxzi6YtYDs45 NC8gH+44egz82e2DATCVM1ICPmRDjXYTLldQiWA2ZXIWnK0iitl5ue24 7EsWJefrIhE=',
+ '256 3 5 AwEAAdSSghOGjU33IQZgwZM2Hh771VGXX05olJK49FxpSyuEAjDBXY58 LGU9R2Zgeecnk/b9EAhFu/vCV9oECtiTCvwuVAkt9YEweqYDluQInmgP NGMJCKdSLlnX93DkjDw8rMYv5dqXCuSGPlKChfTJOLQxIAxGloS7lL+c 0CTZydAF')
+ }
+
+rel_keys = { dns.name.empty :
+ dns.rrset.from_text('@', 3600, 'IN', 'DNSKEY',
+ '257 3 5 AwEAAenVTr9L1OMlL1/N2ta0Qj9LLLnnmFWIr1dJoAsWM9BQfsbV7kFZ XbAkER/FY9Ji2o7cELxBwAsVBuWn6IUUAJXLH74YbC1anY0lifjgt29z SwDzuB7zmC7yVYZzUunBulVW4zT0tg1aePbpVL2EtTL8VzREqbJbE25R KuQYHZtFwG8S4iBxJUmT2Bbd0921LLxSQgVoFXlQx/gFV2+UERXcJ5ce iX6A6wc02M/pdg/YbJd2rBa0MYL3/Fz/Xltre0tqsImZGxzi6YtYDs45 NC8gH+44egz82e2DATCVM1ICPmRDjXYTLldQiWA2ZXIWnK0iitl5ue24 7EsWJefrIhE=',
+ '256 3 5 AwEAAdSSghOGjU33IQZgwZM2Hh771VGXX05olJK49FxpSyuEAjDBXY58 LGU9R2Zgeecnk/b9EAhFu/vCV9oECtiTCvwuVAkt9YEweqYDluQInmgP NGMJCKdSLlnX93DkjDw8rMYv5dqXCuSGPlKChfTJOLQxIAxGloS7lL+c 0CTZydAF')
+ }
+
+when = 1290250287
+
+abs_soa = dns.rrset.from_text('dnspython.org.', 3600, 'IN', 'SOA',
+ 'howl.dnspython.org. hostmaster.dnspython.org. 2010020047 3600 1800 604800 3600')
+
+abs_other_soa = dns.rrset.from_text('dnspython.org.', 3600, 'IN', 'SOA',
+ 'foo.dnspython.org. hostmaster.dnspython.org. 2010020047 3600 1800 604800 3600')
+
+abs_soa_rrsig = dns.rrset.from_text('dnspython.org.', 3600, 'IN', 'RRSIG',
+ 'SOA 5 2 3600 20101127004331 20101119213831 61695 dnspython.org. sDUlltRlFTQw5ITFxOXW3TgmrHeMeNpdqcZ4EXxM9FHhIlte6V9YCnDw t6dvM9jAXdIEi03l9H/RAd9xNNW6gvGMHsBGzpvvqFQxIBR2PoiZA1mX /SWHZFdbt4xjYTtXqpyYvrMK0Dt7bUYPadyhPFCJ1B+I8Zi7B5WJEOd0 8vs=')
+
+rel_soa = dns.rrset.from_text('@', 3600, 'IN', 'SOA',
+ 'howl hostmaster 2010020047 3600 1800 604800 3600')
+
+rel_other_soa = dns.rrset.from_text('@', 3600, 'IN', 'SOA',
+ 'foo hostmaster 2010020047 3600 1800 604800 3600')
+
+rel_soa_rrsig = dns.rrset.from_text('@', 3600, 'IN', 'RRSIG',
+ 'SOA 5 2 3600 20101127004331 20101119213831 61695 @ sDUlltRlFTQw5ITFxOXW3TgmrHeMeNpdqcZ4EXxM9FHhIlte6V9YCnDw t6dvM9jAXdIEi03l9H/RAd9xNNW6gvGMHsBGzpvvqFQxIBR2PoiZA1mX /SWHZFdbt4xjYTtXqpyYvrMK0Dt7bUYPadyhPFCJ1B+I8Zi7B5WJEOd0 8vs=')
+
+sep_key = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DNSKEY,
+ '257 3 5 AwEAAenVTr9L1OMlL1/N2ta0Qj9LLLnnmFWIr1dJoAsWM9BQfsbV7kFZ XbAkER/FY9Ji2o7cELxBwAsVBuWn6IUUAJXLH74YbC1anY0lifjgt29z SwDzuB7zmC7yVYZzUunBulVW4zT0tg1aePbpVL2EtTL8VzREqbJbE25R KuQYHZtFwG8S4iBxJUmT2Bbd0921LLxSQgVoFXlQx/gFV2+UERXcJ5ce iX6A6wc02M/pdg/YbJd2rBa0MYL3/Fz/Xltre0tqsImZGxzi6YtYDs45 NC8gH+44egz82e2DATCVM1ICPmRDjXYTLldQiWA2ZXIWnK0iitl5ue24 7EsWJefrIhE=')
+
+good_ds = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DS,
+ '57349 5 2 53A79A3E7488AB44FFC56B2D1109F0699D1796DD977E72108B841F96 E47D7013')
+
+when2 = 1290425644
+
+abs_example = dns.name.from_text('example')
+
+abs_dsa_keys = { abs_example :
+ dns.rrset.from_text('example.', 86400, 'IN', 'DNSKEY',
+ '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',
+ '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')
+ }
+
+abs_dsa_soa = dns.rrset.from_text('example.', 86400, 'IN', 'SOA',
+ 'ns1.example. hostmaster.example. 2 10800 3600 604800 86400')
+
+abs_other_dsa_soa = dns.rrset.from_text('example.', 86400, 'IN', 'SOA',
+ 'ns1.example. hostmaster.example. 2 10800 3600 604800 86401')
+
+abs_dsa_soa_rrsig = dns.rrset.from_text('example.', 86400, 'IN', 'RRSIG',
+ 'SOA 3 1 86400 20101129143231 20101122112731 42088 example. CGul9SuBofsktunV8cJs4eRs6u+3NCS3yaPKvBbD+pB2C76OUXDZq9U=')
+
+example_sep_key = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DNSKEY,
+ '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')
+
+example_ds_sha1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DS,
+ '18673 3 1 71b71d4f3e11bbd71b4eff12cde69f7f9215bbe7')
+
+example_ds_sha256 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DS,
+ '18673 3 2 eb8344cbbf07c9d3d3d6c81d10c76653e28d8611a65e639ef8f716e4e4e5d913')
+
+class DNSSECValidatorTestCase(unittest.TestCase):
+
+ def testAbsoluteRSAGood(self):
+ dns.dnssec.validate(abs_soa, abs_soa_rrsig, abs_keys, None, when)
+
+ def testAbsoluteRSABad(self):
+ def bad():
+ dns.dnssec.validate(abs_other_soa, abs_soa_rrsig, abs_keys, None,
+ when)
+ self.failUnlessRaises(dns.dnssec.ValidationFailure, bad)
+
+ def testRelativeRSAGood(self):
+ dns.dnssec.validate(rel_soa, rel_soa_rrsig, rel_keys,
+ abs_dnspython_org, when)
+
+ def testRelativeRSABad(self):
+ def bad():
+ dns.dnssec.validate(rel_other_soa, rel_soa_rrsig, rel_keys,
+ abs_dnspython_org, when)
+ self.failUnlessRaises(dns.dnssec.ValidationFailure, bad)
+
+ def testMakeSHA256DS(self):
+ ds = dns.dnssec.make_ds(abs_dnspython_org, sep_key, 'SHA256')
+ self.failUnless(ds == good_ds)
+
+ def testAbsoluteDSAGood(self):
+ dns.dnssec.validate(abs_dsa_soa, abs_dsa_soa_rrsig, abs_dsa_keys, None,
+ when2)
+
+ def testAbsoluteDSABad(self):
+ def bad():
+ dns.dnssec.validate(abs_other_dsa_soa, abs_dsa_soa_rrsig,
+ abs_dsa_keys, None, when2)
+ self.failUnlessRaises(dns.dnssec.ValidationFailure, bad)
+
+ def testMakeExampleSHA1DS(self):
+ ds = dns.dnssec.make_ds(abs_example, example_sep_key, 'SHA1')
+ self.failUnless(ds == example_ds_sha1)
+
+ def testMakeExampleSHA256DS(self):
+ ds = dns.dnssec.make_ds(abs_example, example_sep_key, 'SHA256')
+ self.failUnless(ds == example_ds_sha256)
+
+if __name__ == '__main__':
+ import_ok = False
+ try:
+ import Crypto.Util.number
+ import_ok = True
+ except:
+ pass
+ if import_ok:
+ unittest.main()
+ else:
+ print 'skipping DNSSEC tests because pycrypto is not installed'
diff --git a/lib/dnspython/tests/resolver.py b/lib/dnspython/tests/resolver.py
index 4cacbdc79d..bd6dc5fbc2 100644
--- a/lib/dnspython/tests/resolver.py
+++ b/lib/dnspython/tests/resolver.py
@@ -14,6 +14,7 @@
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import cStringIO
+import select
import sys
import time
import unittest
@@ -46,7 +47,7 @@ example. 1 IN A 10.0.0.1
;ADDITIONAL
"""
-class ResolverTestCase(unittest.TestCase):
+class BaseResolverTests(object):
if sys.platform != 'win32':
def testRead(self):
@@ -101,5 +102,26 @@ class ResolverTestCase(unittest.TestCase):
zname = dns.resolver.zone_for_name(name)
self.failUnlessRaises(dns.resolver.NotAbsolute, bad)
+class PollingMonkeyPatchMixin(object):
+ def setUp(self):
+ self.__native_polling_backend = dns.query._polling_backend
+ dns.query._set_polling_backend(self.polling_backend())
+
+ unittest.TestCase.setUp(self)
+
+ def tearDown(self):
+ dns.query._set_polling_backend(self.__native_polling_backend)
+
+ unittest.TestCase.tearDown(self)
+
+class SelectResolverTestCase(PollingMonkeyPatchMixin, BaseResolverTests, unittest.TestCase):
+ def polling_backend(self):
+ return dns.query._select_for
+
+if hasattr(select, 'poll'):
+ class PollResolverTestCase(PollingMonkeyPatchMixin, BaseResolverTests, unittest.TestCase):
+ def polling_backend(self):
+ return dns.query._poll_for
+
if __name__ == '__main__':
unittest.main()