summaryrefslogtreecommitdiff
path: root/lib/dnspython/dns
diff options
context:
space:
mode:
authorJelmer Vernooij <jelmer@samba.org>2010-12-09 14:53:45 +0100
committerJelmer Vernooij <jelmer@samba.org>2010-12-10 03:04:06 +0100
commit75ef8f1dd27f4985b3d705e7681a9218ad513c84 (patch)
treec28f415ff0e15bc40aa07f191afe0238d45ae598 /lib/dnspython/dns
parent91438920b465ec7455dd1cd700bbe8ec5050b3f9 (diff)
downloadsamba-75ef8f1dd27f4985b3d705e7681a9218ad513c84.tar.gz
samba-75ef8f1dd27f4985b3d705e7681a9218ad513c84.tar.bz2
samba-75ef8f1dd27f4985b3d705e7681a9218ad513c84.zip
dnspython: Update to newer upstream snapshot.
Diffstat (limited to 'lib/dnspython/dns')
-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
13 files changed, 540 insertions, 90 deletions
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: