diff options
Diffstat (limited to 'lib/dnspython/dns')
-rw-r--r-- | lib/dnspython/dns/__init__.py | 1 | ||||
-rw-r--r-- | lib/dnspython/dns/dnssec.py | 312 | ||||
-rw-r--r-- | lib/dnspython/dns/hash.py | 67 | ||||
-rw-r--r-- | lib/dnspython/dns/message.py | 12 | ||||
-rw-r--r-- | lib/dnspython/dns/node.py | 14 | ||||
-rw-r--r-- | lib/dnspython/dns/query.py | 84 | ||||
-rw-r--r-- | lib/dnspython/dns/rdataset.py | 4 | ||||
-rw-r--r-- | lib/dnspython/dns/resolver.py | 17 | ||||
-rw-r--r-- | lib/dnspython/dns/rrset.py | 6 | ||||
-rw-r--r-- | lib/dnspython/dns/tsig.py | 77 | ||||
-rw-r--r-- | lib/dnspython/dns/update.py | 12 | ||||
-rw-r--r-- | lib/dnspython/dns/version.py | 4 | ||||
-rw-r--r-- | lib/dnspython/dns/zone.py | 20 |
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: |