3P library updates

This commit is contained in:
Jay Lee
2019-02-14 14:11:35 -05:00
parent ef86508bbb
commit 37e0e8942e
249 changed files with 14912 additions and 3134 deletions

View File

@@ -16,7 +16,7 @@ __all__ = (
'cached', 'cachedmethod' 'cached', 'cachedmethod'
) )
__version__ = '2.1.0' __version__ = '3.1.0'
if hasattr(functools.update_wrapper(lambda f: f(), lambda: 42), '__wrapped__'): if hasattr(functools.update_wrapper(lambda f: f(), lambda: 42), '__wrapped__'):
_update_wrapper = functools.update_wrapper _update_wrapper = functools.update_wrapper
@@ -79,7 +79,7 @@ def cachedmethod(cache, key=keys.hashkey, lock=None):
c = cache(self) c = cache(self)
if c is None: if c is None:
return method(self, *args, **kwargs) return method(self, *args, **kwargs)
k = key(self, *args, **kwargs) k = key(*args, **kwargs)
try: try:
return c[k] return c[k]
except KeyError: except KeyError:
@@ -95,7 +95,7 @@ def cachedmethod(cache, key=keys.hashkey, lock=None):
c = cache(self) c = cache(self)
if c is None: if c is None:
return method(self, *args, **kwargs) return method(self, *args, **kwargs)
k = key(self, *args, **kwargs) k = key(*args, **kwargs)
try: try:
with lock(self): with lock(self):
return c[k] return c[k]

View File

@@ -1,10 +1,14 @@
from __future__ import absolute_import from __future__ import absolute_import
import collections
from abc import abstractmethod from abc import abstractmethod
try:
from collections.abc import MutableMapping
except ImportError:
from collections import MutableMapping
class DefaultMapping(collections.MutableMapping):
class DefaultMapping(MutableMapping):
__slots__ = () __slots__ = ()

View File

@@ -1,7 +1,5 @@
from __future__ import absolute_import from __future__ import absolute_import
from warnings import warn
from .abc import DefaultMapping from .abc import DefaultMapping
@@ -16,20 +14,12 @@ class _DefaultSize(object):
return 1 return 1
_deprecated = object()
class Cache(DefaultMapping): class Cache(DefaultMapping):
"""Mutable mapping to serve as a simple cache or cache base class.""" """Mutable mapping to serve as a simple cache or cache base class."""
__size = _DefaultSize() __size = _DefaultSize()
def __init__(self, maxsize, missing=_deprecated, getsizeof=None): def __init__(self, maxsize, getsizeof=None):
if missing is not _deprecated:
warn("Cache constructor parameter 'missing' is deprecated",
DeprecationWarning, 3)
if missing:
self.__missing = missing
if getsizeof: if getsizeof:
self.getsizeof = getsizeof self.getsizeof = getsizeof
if self.getsizeof is not Cache.getsizeof: if self.getsizeof is not Cache.getsizeof:
@@ -77,12 +67,7 @@ class Cache(DefaultMapping):
return key in self.__data return key in self.__data
def __missing__(self, key): def __missing__(self, key):
value = self.__missing(key) raise KeyError(key)
try:
self.__setitem__(key, value)
except ValueError:
pass # value too large
return value
def __iter__(self): def __iter__(self):
return iter(self.__data) return iter(self.__data)
@@ -104,7 +89,3 @@ class Cache(DefaultMapping):
def getsizeof(value): def getsizeof(value):
"""Return the size of a cache element's value.""" """Return the size of a cache element's value."""
return 1 return 1
@staticmethod
def __missing(key):
raise KeyError(key)

View File

@@ -5,11 +5,15 @@ from __future__ import absolute_import
import collections import collections
import functools import functools
import random import random
import time
try:
from time import monotonic as default_timer
except ImportError:
from time import time as default_timer
try: try:
from threading import RLock from threading import RLock
except ImportError: except ImportError: # pragma: no cover
from dummy_threading import RLock from dummy_threading import RLock
from . import keys from . import keys
@@ -26,6 +30,24 @@ _CacheInfo = collections.namedtuple('CacheInfo', [
]) ])
class _UnboundCache(dict):
maxsize = None
@property
def currsize(self):
return len(self)
class _UnboundTTLCache(TTLCache):
def __init__(self, ttl, timer):
TTLCache.__init__(self, float('inf'), ttl, timer)
@property
def maxsize(self):
return None
def _cache(cache, typed=False): def _cache(cache, typed=False):
def decorator(func): def decorator(func):
key = keys.typedkey if typed else keys.hashkey key = keys.typedkey if typed else keys.hashkey
@@ -77,6 +99,9 @@ def lfu_cache(maxsize=128, typed=False):
algorithm. algorithm.
""" """
if maxsize is None:
return _cache(_UnboundCache(), typed)
else:
return _cache(LFUCache(maxsize), typed) return _cache(LFUCache(maxsize), typed)
@@ -86,6 +111,9 @@ def lru_cache(maxsize=128, typed=False):
algorithm. algorithm.
""" """
if maxsize is None:
return _cache(_UnboundCache(), typed)
else:
return _cache(LRUCache(maxsize), typed) return _cache(LRUCache(maxsize), typed)
@@ -95,12 +123,18 @@ def rr_cache(maxsize=128, choice=random.choice, typed=False):
algorithm. algorithm.
""" """
if maxsize is None:
return _cache(_UnboundCache(), typed)
else:
return _cache(RRCache(maxsize, choice), typed) return _cache(RRCache(maxsize, choice), typed)
def ttl_cache(maxsize=128, ttl=600, timer=time.time, typed=False): def ttl_cache(maxsize=128, ttl=600, timer=default_timer, typed=False):
"""Decorator to wrap a function with a memoizing callable that saves """Decorator to wrap a function with a memoizing callable that saves
up to `maxsize` results based on a Least Recently Used (LRU) up to `maxsize` results based on a Least Recently Used (LRU)
algorithm with a per-item time-to-live (TTL) value. algorithm with a per-item time-to-live (TTL) value.
""" """
if maxsize is None:
return _cache(_UnboundTTLCache(ttl, timer), typed)
else:
return _cache(TTLCache(maxsize, ttl, timer), typed) return _cache(TTLCache(maxsize, ttl, timer), typed)

View File

@@ -2,14 +2,14 @@ from __future__ import absolute_import
import collections import collections
from .cache import Cache, _deprecated from .cache import Cache
class LFUCache(Cache): class LFUCache(Cache):
"""Least Frequently Used (LFU) cache implementation.""" """Least Frequently Used (LFU) cache implementation."""
def __init__(self, maxsize, missing=_deprecated, getsizeof=None): def __init__(self, maxsize, getsizeof=None):
Cache.__init__(self, maxsize, missing, getsizeof) Cache.__init__(self, maxsize, getsizeof)
self.__counter = collections.Counter() self.__counter = collections.Counter()
def __getitem__(self, key, cache_getitem=Cache.__getitem__): def __getitem__(self, key, cache_getitem=Cache.__getitem__):

View File

@@ -2,14 +2,14 @@ from __future__ import absolute_import
import collections import collections
from .cache import Cache, _deprecated from .cache import Cache
class LRUCache(Cache): class LRUCache(Cache):
"""Least Recently Used (LRU) cache implementation.""" """Least Recently Used (LRU) cache implementation."""
def __init__(self, maxsize, missing=_deprecated, getsizeof=None): def __init__(self, maxsize, getsizeof=None):
Cache.__init__(self, maxsize, missing, getsizeof) Cache.__init__(self, maxsize, getsizeof)
self.__order = collections.OrderedDict() self.__order = collections.OrderedDict()
def __getitem__(self, key, cache_getitem=Cache.__getitem__): def __getitem__(self, key, cache_getitem=Cache.__getitem__):

View File

@@ -2,7 +2,7 @@ from __future__ import absolute_import
import random import random
from .cache import Cache, _deprecated from .cache import Cache
# random.choice cannot be pickled in Python 2.7 # random.choice cannot be pickled in Python 2.7
@@ -13,9 +13,8 @@ def _choice(seq):
class RRCache(Cache): class RRCache(Cache):
"""Random Replacement (RR) cache implementation.""" """Random Replacement (RR) cache implementation."""
def __init__(self, maxsize, choice=random.choice, missing=_deprecated, def __init__(self, maxsize, choice=random.choice, getsizeof=None):
getsizeof=None): Cache.__init__(self, maxsize, getsizeof)
Cache.__init__(self, maxsize, missing, getsizeof)
# TODO: use None as default, assing to self.choice directly? # TODO: use None as default, assing to self.choice directly?
if choice is random.choice: if choice is random.choice:
self.__choice = _choice self.__choice = _choice

View File

@@ -1,9 +1,13 @@
from __future__ import absolute_import from __future__ import absolute_import
import collections import collections
import time
from .cache import Cache, _deprecated try:
from time import monotonic as default_timer
except ImportError:
from time import time as default_timer
from .cache import Cache
class _Link(object): class _Link(object):
@@ -57,9 +61,8 @@ class _Timer(object):
class TTLCache(Cache): class TTLCache(Cache):
"""LRU Cache implementation with per-item time-to-live (TTL) value.""" """LRU Cache implementation with per-item time-to-live (TTL) value."""
def __init__(self, maxsize, ttl, timer=time.time, missing=_deprecated, def __init__(self, maxsize, ttl, timer=default_timer, getsizeof=None):
getsizeof=None): Cache.__init__(self, maxsize, getsizeof)
Cache.__init__(self, maxsize, missing, getsizeof)
self.__root = root = _Link() self.__root = root = _Link()
root.prev = root.next = root root.prev = root.next = root
self.__links = collections.OrderedDict() self.__links = collections.OrderedDict()

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009, 2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009, 2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -2,7 +2,11 @@ import sys
import decimal import decimal
from decimal import Context from decimal import Context
if sys.version_info > (3,): PY3 = sys.version_info[0] == 3
PY2 = sys.version_info[0] == 2
if PY3:
long = int long = int
xrange = range xrange = range
else: else:
@@ -10,7 +14,7 @@ else:
xrange = xrange # pylint: disable=xrange-builtin xrange = xrange # pylint: disable=xrange-builtin
# unicode / binary types # unicode / binary types
if sys.version_info > (3,): if PY3:
text_type = str text_type = str
binary_type = bytes binary_type = bytes
string_types = (str,) string_types = (str,)
@@ -19,6 +23,10 @@ if sys.version_info > (3,):
return x.decode() return x.decode()
def maybe_encode(x): def maybe_encode(x):
return x.encode() return x.encode()
def maybe_chr(x):
return x
def maybe_ord(x):
return x
else: else:
text_type = unicode # pylint: disable=unicode-builtin, undefined-variable text_type = unicode # pylint: disable=unicode-builtin, undefined-variable
binary_type = str binary_type = str
@@ -30,6 +38,10 @@ else:
return x return x
def maybe_encode(x): def maybe_encode(x):
return x return x
def maybe_chr(x):
return chr(x)
def maybe_ord(x):
return ord(x)
def round_py2_compat(what): def round_py2_compat(what):

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2003-2007, 2009, 2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -20,7 +22,6 @@ import struct
import time import time
import dns.exception import dns.exception
import dns.hash
import dns.name import dns.name
import dns.node import dns.node
import dns.rdataset import dns.rdataset
@@ -31,27 +32,40 @@ from ._compat import string_types
class UnsupportedAlgorithm(dns.exception.DNSException): class UnsupportedAlgorithm(dns.exception.DNSException):
"""The DNSSEC algorithm is not supported.""" """The DNSSEC algorithm is not supported."""
class ValidationFailure(dns.exception.DNSException): class ValidationFailure(dns.exception.DNSException):
"""The DNSSEC signature is invalid.""" """The DNSSEC signature is invalid."""
#: RSAMD5
RSAMD5 = 1 RSAMD5 = 1
#: DH
DH = 2 DH = 2
#: DSA
DSA = 3 DSA = 3
#: ECC
ECC = 4 ECC = 4
#: RSASHA1
RSASHA1 = 5 RSASHA1 = 5
#: DSANSEC3SHA1
DSANSEC3SHA1 = 6 DSANSEC3SHA1 = 6
#: RSASHA1NSEC3SHA1
RSASHA1NSEC3SHA1 = 7 RSASHA1NSEC3SHA1 = 7
#: RSASHA256
RSASHA256 = 8 RSASHA256 = 8
#: RSASHA512
RSASHA512 = 10 RSASHA512 = 10
#: ECDSAP256SHA256
ECDSAP256SHA256 = 13 ECDSAP256SHA256 = 13
#: ECDSAP384SHA384
ECDSAP384SHA384 = 14 ECDSAP384SHA384 = 14
#: INDIRECT
INDIRECT = 252 INDIRECT = 252
#: PRIVATEDNS
PRIVATEDNS = 253 PRIVATEDNS = 253
#: PRIVATEOID
PRIVATEOID = 254 PRIVATEOID = 254
_algorithm_by_text = { _algorithm_by_text = {
@@ -75,12 +89,14 @@ _algorithm_by_text = {
# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that # cannot make any mistakes (e.g. omissions, cut-and-paste errors) that
# would cause the mapping not to be true inverse. # would cause the mapping not to be true inverse.
_algorithm_by_value = dict((y, x) for x, y in _algorithm_by_text.items()) _algorithm_by_value = {y: x for x, y in _algorithm_by_text.items()}
def algorithm_from_text(text): def algorithm_from_text(text):
"""Convert text into a DNSSEC algorithm value """Convert text into a DNSSEC algorithm value.
@rtype: int"""
Returns an ``int``.
"""
value = _algorithm_by_text.get(text.upper()) value = _algorithm_by_text.get(text.upper())
if value is None: if value is None:
@@ -90,7 +106,9 @@ def algorithm_from_text(text):
def algorithm_to_text(value): def algorithm_to_text(value):
"""Convert a DNSSEC algorithm value to text """Convert a DNSSEC algorithm value to text
@rtype: string"""
Returns a ``str``.
"""
text = _algorithm_by_value.get(value) text = _algorithm_by_value.get(value)
if text is None: if text is None:
@@ -105,6 +123,14 @@ def _to_rdata(record, origin):
def key_id(key, origin=None): def key_id(key, origin=None):
"""Return the key id (a 16-bit number) for the specified key.
Note the *origin* parameter of this function is historical and
is not needed.
Returns an ``int`` between 0 and 65535.
"""
rdata = _to_rdata(key, origin) rdata = _to_rdata(key, origin)
rdata = bytearray(rdata) rdata = bytearray(rdata)
if key.algorithm == RSAMD5: if key.algorithm == RSAMD5:
@@ -121,12 +147,28 @@ def key_id(key, origin=None):
def make_ds(name, key, algorithm, origin=None): def make_ds(name, key, algorithm, origin=None):
"""Create a DS record for a DNSSEC key.
*name* is the owner name of the DS record.
*key* is a ``dns.rdtypes.ANY.DNSKEY``.
*algorithm* is a string describing which hash algorithm to use. The
currently supported hashes are "SHA1" and "SHA256". Case does not
matter for these strings.
*origin* is a ``dns.name.Name`` and will be used as the origin
if *key* is a relative name.
Returns a ``dns.rdtypes.ANY.DS``.
"""
if algorithm.upper() == 'SHA1': if algorithm.upper() == 'SHA1':
dsalg = 1 dsalg = 1
hash = dns.hash.hashes['SHA1']() hash = SHA1.new()
elif algorithm.upper() == 'SHA256': elif algorithm.upper() == 'SHA256':
dsalg = 2 dsalg = 2
hash = dns.hash.hashes['SHA256']() hash = SHA256.new()
else: else:
raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm) raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm)
@@ -198,15 +240,15 @@ def _is_sha512(algorithm):
def _make_hash(algorithm): def _make_hash(algorithm):
if _is_md5(algorithm): if _is_md5(algorithm):
return dns.hash.hashes['MD5']() return MD5.new()
if _is_sha1(algorithm): if _is_sha1(algorithm):
return dns.hash.hashes['SHA1']() return SHA1.new()
if _is_sha256(algorithm): if _is_sha256(algorithm):
return dns.hash.hashes['SHA256']() return SHA256.new()
if _is_sha384(algorithm): if _is_sha384(algorithm):
return dns.hash.hashes['SHA384']() return SHA384.new()
if _is_sha512(algorithm): if _is_sha512(algorithm):
return dns.hash.hashes['SHA512']() return SHA512.new()
raise ValidationFailure('unknown hash for algorithm %u' % algorithm) raise ValidationFailure('unknown hash for algorithm %u' % algorithm)
@@ -232,31 +274,32 @@ def _make_algorithm_id(algorithm):
def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
"""Validate an RRset against a single signature rdata """Validate an RRset against a single signature rdata
The owner name of the rrsig is assumed to be the same as the owner name The owner name of *rrsig* is assumed to be the same as the owner name
of the rrset. of *rrset*.
@param rrset: The RRset to validate *rrset* is the RRset to validate. It can be a ``dns.rrset.RRset`` or
@type rrset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset) a ``(dns.name.Name, dns.rdataset.Rdataset)`` tuple.
tuple
@param rrsig: The signature rdata *rrsig* is a ``dns.rdata.Rdata``, the signature to validate.
@type rrsig: dns.rrset.Rdata
@param keys: The key dictionary. *keys* is the key dictionary, used to find the DNSKEY associated with
@type keys: a dictionary keyed by dns.name.Name with node or rdataset a given name. The dictionary is keyed by a ``dns.name.Name``, and has
values ``dns.node.Node`` or ``dns.rdataset.Rdataset`` values.
@param origin: The origin to use for relative names
@type origin: dns.name.Name or None *origin* is a ``dns.name.Name``, the origin to use for relative names.
@param now: The time to use when validating the signatures. The default
is the current time. *now* is an ``int``, the time to use when validating the signatures,
@type now: int in seconds since the UNIX epoch. The default is the current time.
""" """
if isinstance(origin, string_types): if isinstance(origin, string_types):
origin = dns.name.from_text(origin, dns.name.root) origin = dns.name.from_text(origin, dns.name.root)
for candidate_key in _find_candidate_keys(keys, rrsig): candidate_keys = _find_candidate_keys(keys, rrsig)
if not candidate_key: if candidate_keys is None:
raise ValidationFailure('unknown key') raise ValidationFailure('unknown key')
for candidate_key in candidate_keys:
# For convenience, allow the rrset to be specified as a (name, # For convenience, allow the rrset to be specified as a (name,
# rdataset) tuple as well as a proper rrset # rdataset) tuple as well as a proper rrset
if isinstance(rrset, tuple): if isinstance(rrset, tuple):
@@ -284,11 +327,13 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
keyptr = keyptr[2:] keyptr = keyptr[2:]
rsa_e = keyptr[0:bytes_] rsa_e = keyptr[0:bytes_]
rsa_n = keyptr[bytes_:] rsa_n = keyptr[bytes_:]
keylen = len(rsa_n) * 8 try:
pubkey = Crypto.PublicKey.RSA.construct( pubkey = CryptoRSA.construct(
(Crypto.Util.number.bytes_to_long(rsa_n), (number.bytes_to_long(rsa_n),
Crypto.Util.number.bytes_to_long(rsa_e))) number.bytes_to_long(rsa_e)))
sig = (Crypto.Util.number.bytes_to_long(rrsig.signature),) except ValueError:
raise ValidationFailure('invalid public key')
sig = rrsig.signature
elif _is_dsa(rrsig.algorithm): elif _is_dsa(rrsig.algorithm):
keyptr = candidate_key.key keyptr = candidate_key.key
(t,) = struct.unpack('!B', keyptr[0:1]) (t,) = struct.unpack('!B', keyptr[0:1])
@@ -301,36 +346,37 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
dsa_g = keyptr[0:octets] dsa_g = keyptr[0:octets]
keyptr = keyptr[octets:] keyptr = keyptr[octets:]
dsa_y = keyptr[0:octets] dsa_y = keyptr[0:octets]
pubkey = Crypto.PublicKey.DSA.construct( pubkey = CryptoDSA.construct(
(Crypto.Util.number.bytes_to_long(dsa_y), (number.bytes_to_long(dsa_y),
Crypto.Util.number.bytes_to_long(dsa_g), number.bytes_to_long(dsa_g),
Crypto.Util.number.bytes_to_long(dsa_p), number.bytes_to_long(dsa_p),
Crypto.Util.number.bytes_to_long(dsa_q))) number.bytes_to_long(dsa_q)))
(dsa_r, dsa_s) = struct.unpack('!20s20s', rrsig.signature[1:]) sig = rrsig.signature[1:]
sig = (Crypto.Util.number.bytes_to_long(dsa_r),
Crypto.Util.number.bytes_to_long(dsa_s))
elif _is_ecdsa(rrsig.algorithm): elif _is_ecdsa(rrsig.algorithm):
# use ecdsa for NIST-384p -- not currently supported by pycryptodome
keyptr = candidate_key.key
if rrsig.algorithm == ECDSAP256SHA256: if rrsig.algorithm == ECDSAP256SHA256:
curve = ecdsa.curves.NIST256p curve = ecdsa.curves.NIST256p
key_len = 32 key_len = 32
elif rrsig.algorithm == ECDSAP384SHA384: elif rrsig.algorithm == ECDSAP384SHA384:
curve = ecdsa.curves.NIST384p curve = ecdsa.curves.NIST384p
key_len = 48 key_len = 48
else:
# shouldn't happen x = number.bytes_to_long(keyptr[0:key_len])
raise ValidationFailure('unknown ECDSA curve') y = number.bytes_to_long(keyptr[key_len:key_len * 2])
keyptr = candidate_key.key if not ecdsa.ecdsa.point_is_valid(curve.generator, x, y):
x = Crypto.Util.number.bytes_to_long(keyptr[0:key_len]) raise ValidationFailure('invalid ECDSA key')
y = Crypto.Util.number.bytes_to_long(keyptr[key_len:key_len * 2])
assert ecdsa.ecdsa.point_is_valid(curve.generator, x, y)
point = ecdsa.ellipticcurve.Point(curve.curve, x, y, curve.order) point = ecdsa.ellipticcurve.Point(curve.curve, x, y, curve.order)
verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point, verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point,
curve) curve)
pubkey = ECKeyWrapper(verifying_key, key_len) pubkey = ECKeyWrapper(verifying_key, key_len)
r = rrsig.signature[:key_len] r = rrsig.signature[:key_len]
s = rrsig.signature[key_len:] s = rrsig.signature[key_len:]
sig = ecdsa.ecdsa.Signature(Crypto.Util.number.bytes_to_long(r), sig = ecdsa.ecdsa.Signature(number.bytes_to_long(r),
Crypto.Util.number.bytes_to_long(s)) number.bytes_to_long(s))
else: else:
raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm) raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm)
@@ -352,44 +398,49 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
hash.update(rrlen) hash.update(rrlen)
hash.update(rrdata) hash.update(rrdata)
digest = hash.digest() try:
if _is_rsa(rrsig.algorithm): if _is_rsa(rrsig.algorithm):
# PKCS1 algorithm identifier goop verifier = pkcs1_15.new(pubkey)
digest = _make_algorithm_id(rrsig.algorithm) + digest # will raise ValueError if verify fails:
padlen = keylen // 8 - len(digest) - 3 verifier.verify(hash, sig)
digest = struct.pack('!%dB' % (2 + padlen + 1), elif _is_dsa(rrsig.algorithm):
*([0, 1] + [0xFF] * padlen + [0])) + digest verifier = DSS.new(pubkey, 'fips-186-3')
elif _is_dsa(rrsig.algorithm) or _is_ecdsa(rrsig.algorithm): verifier.verify(hash, sig)
pass elif _is_ecdsa(rrsig.algorithm):
digest = hash.digest()
if not pubkey.verify(digest, sig):
raise ValueError
else: else:
# Raise here for code clarity; this won't actually ever happen # Raise here for code clarity; this won't actually ever happen
# since if the algorithm is really unknown we'd already have # since if the algorithm is really unknown we'd already have
# raised an exception above # raised an exception above
raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm) raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm)
# If we got here, we successfully verified so we can return without error
if pubkey.verify(digest, sig):
return return
except ValueError:
# this happens on an individual validation failure
continue
# nothing verified -- raise failure:
raise ValidationFailure('verify failure') raise ValidationFailure('verify failure')
def _validate(rrset, rrsigset, keys, origin=None, now=None): def _validate(rrset, rrsigset, keys, origin=None, now=None):
"""Validate an RRset """Validate an RRset.
@param rrset: The RRset to validate *rrset* is the RRset to validate. It can be a ``dns.rrset.RRset`` or
@type rrset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset) a ``(dns.name.Name, dns.rdataset.Rdataset)`` tuple.
tuple
@param rrsigset: The signature RRset *rrsigset* is the signature RRset to be validated. It can be a
@type rrsigset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset) ``dns.rrset.RRset`` or a ``(dns.name.Name, dns.rdataset.Rdataset)`` tuple.
tuple
@param keys: The key dictionary. *keys* is the key dictionary, used to find the DNSKEY associated with
@type keys: a dictionary keyed by dns.name.Name with node or rdataset a given name. The dictionary is keyed by a ``dns.name.Name``, and has
values ``dns.node.Node`` or ``dns.rdataset.Rdataset`` values.
@param origin: The origin to use for relative names
@type origin: dns.name.Name or None *origin* is a ``dns.name.Name``, the origin to use for relative names.
@param now: The time to use when validating the signatures. The default
is the current time. *now* is an ``int``, the time to use when validating the signatures,
@type now: int in seconds since the UNIX epoch. The default is the current time.
""" """
if isinstance(origin, string_types): if isinstance(origin, string_types):
@@ -408,7 +459,7 @@ def _validate(rrset, rrsigset, keys, origin=None, now=None):
rrsigrdataset = rrsigset rrsigrdataset = rrsigset
rrname = rrname.choose_relativity(origin) rrname = rrname.choose_relativity(origin)
rrsigname = rrname.choose_relativity(origin) rrsigname = rrsigname.choose_relativity(origin)
if rrname != rrsigname: if rrname != rrsigname:
raise ValidationFailure("owner names do not match") raise ValidationFailure("owner names do not match")
@@ -422,25 +473,39 @@ def _validate(rrset, rrsigset, keys, origin=None, now=None):
def _need_pycrypto(*args, **kwargs): def _need_pycrypto(*args, **kwargs):
raise NotImplementedError("DNSSEC validation requires pycrypto") raise NotImplementedError("DNSSEC validation requires pycryptodome/pycryptodomex")
try: try:
import Crypto.PublicKey.RSA try:
import Crypto.PublicKey.DSA # test we're using pycryptodome, not pycrypto (which misses SHA1 for example)
import Crypto.Util.number from Crypto.Hash import MD5, SHA1, SHA256, SHA384, SHA512
validate = _validate from Crypto.PublicKey import RSA as CryptoRSA, DSA as CryptoDSA
validate_rrsig = _validate_rrsig from Crypto.Signature import pkcs1_15, DSS
_have_pycrypto = True from Crypto.Util import number
except ImportError:
from Cryptodome.Hash import MD5, SHA1, SHA256, SHA384, SHA512
from Cryptodome.PublicKey import RSA as CryptoRSA, DSA as CryptoDSA
from Cryptodome.Signature import pkcs1_15, DSS
from Cryptodome.Util import number
except ImportError: except ImportError:
validate = _need_pycrypto validate = _need_pycrypto
validate_rrsig = _need_pycrypto validate_rrsig = _need_pycrypto
_have_pycrypto = False _have_pycrypto = False
_have_ecdsa = False
else:
validate = _validate
validate_rrsig = _validate_rrsig
_have_pycrypto = True
try: try:
import ecdsa import ecdsa
import ecdsa.ecdsa import ecdsa.ecdsa
import ecdsa.ellipticcurve import ecdsa.ellipticcurve
import ecdsa.keys import ecdsa.keys
except ImportError:
_have_ecdsa = False
else:
_have_ecdsa = True _have_ecdsa = True
class ECKeyWrapper(object): class ECKeyWrapper(object):
@@ -450,8 +515,5 @@ try:
self.key_len = key_len self.key_len = key_len
def verify(self, digest, sig): def verify(self, digest, sig):
diglong = Crypto.Util.number.bytes_to_long(digest) diglong = number.bytes_to_long(digest)
return self.key.pubkey.verifies(diglong, sig) return self.key.pubkey.verifies(diglong, sig)
except ImportError:
_have_ecdsa = False

19
src/dns/dnssec.pyi Normal file
View File

@@ -0,0 +1,19 @@
from typing import Union, Dict, Tuple, Optional
from . import rdataset, rrset, exception, name, rdtypes, rdata, node
import dns.rdtypes.ANY.DS as DS
import dns.rdtypes.ANY.DNSKEY as DNSKEY
_have_ecdsa : bool
_have_pycrypto : bool
def validate_rrsig(rrset : Union[Tuple[name.Name, rdataset.Rdataset], rrset.RRset], rrsig : rdata.Rdata, keys : Dict[name.Name, Union[node.Node, rdataset.Rdataset]], origin : Optional[name.Name] = None, now : Optional[int] = None) -> None:
...
def validate(rrset: Union[Tuple[name.Name, rdataset.Rdataset], rrset.RRset], rrsigset : Union[Tuple[name.Name, rdataset.Rdataset], rrset.RRset], keys : Dict[name.Name, Union[node.Node, rdataset.Rdataset]], origin=None, now=None) -> None:
...
class ValidationFailure(exception.DNSException):
...
def make_ds(name : name.Name, key : DNSKEY.DNSKEY, algorithm : str, origin : Optional[name.Name] = None) -> DS.DS:
...

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2006, 2007, 2009, 2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2006-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -13,31 +15,32 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS E.164 helpers """DNS E.164 helpers."""
@var public_enum_domain: The DNS public ENUM domain, e164.arpa.
@type public_enum_domain: dns.name.Name object
"""
import dns.exception import dns.exception
import dns.name import dns.name
import dns.resolver import dns.resolver
from ._compat import string_types from ._compat import string_types, maybe_decode
#: The public E.164 domain.
public_enum_domain = dns.name.from_text('e164.arpa.') public_enum_domain = dns.name.from_text('e164.arpa.')
def from_e164(text, origin=public_enum_domain): def from_e164(text, origin=public_enum_domain):
"""Convert an E.164 number in textual form into a Name object whose """Convert an E.164 number in textual form into a Name object whose
value is the ENUM domain name for that number. value is the ENUM domain name for that number.
@param text: an E.164 number in textual form.
@type text: str Non-digits in the text are ignored, i.e. "16505551212",
@param origin: The domain in which the number should be constructed. "+1.650.555.1212" and "1 (650) 555-1212" are all the same.
The default is e164.arpa.
@type origin: dns.name.Name object or None *text*, a ``text``, is an E.164 number in textual form.
@rtype: dns.name.Name object
*origin*, a ``dns.name.Name``, the domain in which the number
should be constructed. The default is ``e164.arpa.``.
Returns a ``dns.name.Name``.
""" """
parts = [d for d in text if d.isdigit()] parts = [d for d in text if d.isdigit()]
parts.reverse() parts.reverse()
return dns.name.from_text('.'.join(parts), origin=origin) return dns.name.from_text('.'.join(parts), origin=origin)
@@ -45,14 +48,23 @@ def from_e164(text, origin=public_enum_domain):
def to_e164(name, origin=public_enum_domain, want_plus_prefix=True): def to_e164(name, origin=public_enum_domain, want_plus_prefix=True):
"""Convert an ENUM domain name into an E.164 number. """Convert an ENUM domain name into an E.164 number.
@param name: the ENUM domain name.
@type name: dns.name.Name object. Note that dnspython does not have any information about preferred
@param origin: A domain containing the ENUM domain name. The number formats within national numbering plans, so all numbers are
name is relativized to this domain before being converted to text. emitted as a simple string of digits, prefixed by a '+' (unless
@type origin: dns.name.Name object or None *want_plus_prefix* is ``False``).
@param want_plus_prefix: if True, add a '+' to the beginning of the
returned number. *name* is a ``dns.name.Name``, the ENUM domain name.
@rtype: str
*origin* is a ``dns.name.Name``, a domain containing the ENUM
domain name. The name is relativized to this domain before being
converted to text. If ``None``, no relativization is done.
*want_plus_prefix* is a ``bool``. If True, add a '+' to the beginning of
the returned number.
Returns a ``text``.
""" """
if origin is not None: if origin is not None:
name = name.relativize(origin) name = name.relativize(origin)
@@ -63,14 +75,22 @@ def to_e164(name, origin=public_enum_domain, want_plus_prefix=True):
text = b''.join(dlabels) text = b''.join(dlabels)
if want_plus_prefix: if want_plus_prefix:
text = b'+' + text text = b'+' + text
return text return maybe_decode(text)
def query(number, domains, resolver=None): def query(number, domains, resolver=None):
"""Look for NAPTR RRs for the specified number in the specified domains. """Look for NAPTR RRs for the specified number in the specified domains.
e.g. lookup('16505551212', ['e164.dnspython.org.', 'e164.arpa.']) e.g. lookup('16505551212', ['e164.dnspython.org.', 'e164.arpa.'])
*number*, a ``text`` is the number to look for.
*domains* is an iterable containing ``dns.name.Name`` values.
*resolver*, a ``dns.resolver.Resolver``, is the resolver to use. If
``None``, the default resolver is used.
""" """
if resolver is None: if resolver is None:
resolver = dns.resolver.get_default_resolver() resolver = dns.resolver.get_default_resolver()
e_nx = dns.resolver.NXDOMAIN() e_nx = dns.resolver.NXDOMAIN()

10
src/dns/e164.pyi Normal file
View File

@@ -0,0 +1,10 @@
from typing import Optional, Iterable
from . import name, resolver
def from_e164(text : str, origin=name.Name(".")) -> name.Name:
...
def to_e164(name : name.Name, origin : Optional[name.Name] = None, want_plus_prefix=True) -> str:
...
def query(number : str, domains : Iterable[str], resolver : Optional[resolver.Resolver] = None) -> resolver.Answer:
...

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2009, 2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2009-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -15,18 +17,42 @@
"""EDNS Options""" """EDNS Options"""
NSID = 3 from __future__ import absolute_import
import math
import struct
import dns.inet
#: NSID
NSID = 3
#: DAU
DAU = 5
#: DHU
DHU = 6
#: N3U
N3U = 7
#: ECS (client-subnet)
ECS = 8
#: EXPIRE
EXPIRE = 9
#: COOKIE
COOKIE = 10
#: KEEPALIVE
KEEPALIVE = 11
#: PADDING
PADDING = 12
#: CHAIN
CHAIN = 13
class Option(object): class Option(object):
"""Base class for all EDNS option types. """Base class for all EDNS option types."""
"""
def __init__(self, otype): def __init__(self, otype):
"""Initialize an option. """Initialize an option.
@param otype: The rdata type
@type otype: int *otype*, an ``int``, is the option type.
""" """
self.otype = otype self.otype = otype
@@ -37,23 +63,26 @@ class Option(object):
@classmethod @classmethod
def from_wire(cls, otype, wire, current, olen): def from_wire(cls, otype, wire, current, olen):
"""Build an EDNS option object from wire format """Build an EDNS option object from wire format.
*otype*, an ``int``, is the option type.
*wire*, a ``binary``, is the wire-format message.
*current*, an ``int``, is the offset in *wire* of the beginning
of the rdata.
*olen*, an ``int``, is the length of the wire-format option data
Returns a ``dns.edns.Option``.
"""
@param otype: The option type
@type otype: int
@param wire: The wire-format message
@type wire: string
@param current: The offset in wire of the beginning of the rdata.
@type current: int
@param olen: The length of the wire-format option data
@type olen: int
@rtype: dns.edns.Option instance"""
raise NotImplementedError raise NotImplementedError
def _cmp(self, other): def _cmp(self, other):
"""Compare an EDNS option with another option of the same type. """Compare an EDNS option with another option of the same type.
Return < 0 if self < other, 0 if self == other,
and > 0 if self > other. Returns < 0 if < *other*, 0 if == *other*, and > 0 if > *other*.
""" """
raise NotImplementedError raise NotImplementedError
@@ -98,7 +127,7 @@ class Option(object):
class GenericOption(Option): class GenericOption(Option):
"""Generate Rdata Class """Generic Option Class
This class is used for EDNS option types for which we have no better This class is used for EDNS option types for which we have no better
implementation. implementation.
@@ -111,6 +140,9 @@ class GenericOption(Option):
def to_wire(self, file): def to_wire(self, file):
file.write(self.data) file.write(self.data)
def to_text(self):
return "Generic %d" % self.otype
@classmethod @classmethod
def from_wire(cls, otype, wire, current, olen): def from_wire(cls, otype, wire, current, olen):
return cls(otype, wire[current: current + olen]) return cls(otype, wire[current: current + olen])
@@ -122,11 +154,96 @@ class GenericOption(Option):
return 1 return 1
return -1 return -1
class ECSOption(Option):
"""EDNS Client Subnet (ECS, RFC7871)"""
def __init__(self, address, srclen=None, scopelen=0):
"""*address*, a ``text``, is the client address information.
*srclen*, an ``int``, the source prefix length, which is the
leftmost number of bits of the address to be used for the
lookup. The default is 24 for IPv4 and 56 for IPv6.
*scopelen*, an ``int``, the scope prefix length. This value
must be 0 in queries, and should be set in responses.
"""
super(ECSOption, self).__init__(ECS)
af = dns.inet.af_for_address(address)
if af == dns.inet.AF_INET6:
self.family = 2
if srclen is None:
srclen = 56
elif af == dns.inet.AF_INET:
self.family = 1
if srclen is None:
srclen = 24
else:
raise ValueError('Bad ip family')
self.address = address
self.srclen = srclen
self.scopelen = scopelen
addrdata = dns.inet.inet_pton(af, address)
nbytes = int(math.ceil(srclen/8.0))
# Truncate to srclen and pad to the end of the last octet needed
# See RFC section 6
self.addrdata = addrdata[:nbytes]
nbits = srclen % 8
if nbits != 0:
last = struct.pack('B', ord(self.addrdata[-1:]) & (0xff << nbits))
self.addrdata = self.addrdata[:-1] + last
def to_text(self):
return "ECS {}/{} scope/{}".format(self.address, self.srclen,
self.scopelen)
def to_wire(self, file):
file.write(struct.pack('!H', self.family))
file.write(struct.pack('!BB', self.srclen, self.scopelen))
file.write(self.addrdata)
@classmethod
def from_wire(cls, otype, wire, cur, olen):
family, src, scope = struct.unpack('!HBB', wire[cur:cur+4])
cur += 4
addrlen = int(math.ceil(src/8.0))
if family == 1:
af = dns.inet.AF_INET
pad = 4 - addrlen
elif family == 2:
af = dns.inet.AF_INET6
pad = 16 - addrlen
else:
raise ValueError('unsupported family')
addr = dns.inet.inet_ntop(af, wire[cur:cur+addrlen] + b'\x00' * pad)
return cls(addr, src, scope)
def _cmp(self, other):
if self.addrdata == other.addrdata:
return 0
if self.addrdata > other.addrdata:
return 1
return -1
_type_to_class = { _type_to_class = {
ECS: ECSOption
} }
def get_option_class(otype): def get_option_class(otype):
"""Return the class for the specified option type.
The GenericOption class is used if a more specific class is not
known.
"""
cls = _type_to_class.get(otype) cls = _type_to_class.get(otype)
if cls is None: if cls is None:
cls = GenericOption cls = GenericOption
@@ -134,17 +251,19 @@ def get_option_class(otype):
def option_from_wire(otype, wire, current, olen): def option_from_wire(otype, wire, current, olen):
"""Build an EDNS option object from wire format """Build an EDNS option object from wire format.
@param otype: The option type *otype*, an ``int``, is the option type.
@type otype: int
@param wire: The wire-format message *wire*, a ``binary``, is the wire-format message.
@type wire: string
@param current: The offset in wire of the beginning of the rdata. *current*, an ``int``, is the offset in *wire* of the beginning
@type current: int of the rdata.
@param olen: The length of the wire-format option data
@type olen: int *olen*, an ``int``, is the length of the wire-format option data
@rtype: dns.edns.Option instance"""
Returns an instance of a subclass of ``dns.edns.Option``.
"""
cls = get_option_class(otype) cls = get_option_class(otype)
return cls.from_wire(otype, wire, current, olen) return cls.from_wire(otype, wire, current, olen)

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2009, 2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2009-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -25,6 +27,11 @@ except ImportError:
class EntropyPool(object): class EntropyPool(object):
# This is an entropy pool for Python implementations that do not
# have a working SystemRandom. I'm not sure there are any, but
# leaving this code doesn't hurt anything as the library code
# is used if present.
def __init__(self, seed=None): def __init__(self, seed=None):
self.pool_index = 0 self.pool_index = 0
self.digest = None self.digest = None

10
src/dns/entropy.pyi Normal file
View File

@@ -0,0 +1,10 @@
from typing import Optional
from random import SystemRandom
system_random : Optional[SystemRandom]
def random_16() -> int:
pass
def between(first: int, last: int) -> int:
pass

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -13,32 +15,35 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Common DNS Exceptions.""" """Common DNS Exceptions.
Dnspython modules may also define their own exceptions, which will
always be subclasses of ``DNSException``.
"""
class DNSException(Exception): class DNSException(Exception):
"""Abstract base class shared by all dnspython exceptions. """Abstract base class shared by all dnspython exceptions.
It supports two basic modes of operation: It supports two basic modes of operation:
a) Old/compatible mode is used if __init__ was called with a) Old/compatible mode is used if ``__init__`` was called with
empty **kwargs. empty *kwargs*. In compatible mode all *args* are passed
In compatible mode all *args are passed to standard Python Exception class to the standard Python Exception class as before and all *args* are
as before and all *args are printed by standard __str__ implementation. printed by the standard ``__str__`` implementation. Class variable
Class variable msg (or doc string if msg is None) is returned from str() ``msg`` (or doc string if ``msg`` is ``None``) is returned from ``str()``
if *args is empty. if *args* is empty.
b) New/parametrized mode is used if __init__ was called with b) New/parametrized mode is used if ``__init__`` was called with
non-empty **kwargs. non-empty *kwargs*.
In the new mode *args has to be empty and all kwargs has to exactly match In the new mode *args* must be empty and all kwargs must match
set in class variable self.supp_kwargs. All kwargs are stored inside those set in class variable ``supp_kwargs``. All kwargs are stored inside
self.kwargs and used in new __str__ implementation to construct ``self.kwargs`` and used in a new ``__str__`` implementation to construct
formatted message based on self.fmt string. a formatted message based on the ``fmt`` class variable, a ``string``.
In the simplest case it is enough to override supp_kwargs and fmt In the simplest case it is enough to override the ``supp_kwargs``
class variables to get nice parametrized messages. and ``fmt`` class variables to get nice parametrized messages.
""" """
msg = None # non-parametrized message msg = None # non-parametrized message
supp_kwargs = set() # accepted parameters for _fmt_kwargs (sanity check) supp_kwargs = set() # accepted parameters for _fmt_kwargs (sanity check)
fmt = None # message parametrized with results from _fmt_kwargs fmt = None # message parametrized with results from _fmt_kwargs
@@ -102,27 +107,22 @@ class DNSException(Exception):
class FormError(DNSException): class FormError(DNSException):
"""DNS message is malformed.""" """DNS message is malformed."""
class SyntaxError(DNSException): class SyntaxError(DNSException):
"""Text input is malformed.""" """Text input is malformed."""
class UnexpectedEnd(SyntaxError): class UnexpectedEnd(SyntaxError):
"""Text input ended unexpectedly.""" """Text input ended unexpectedly."""
class TooBig(DNSException): class TooBig(DNSException):
"""The DNS message is too big.""" """The DNS message is too big."""
class Timeout(DNSException): class Timeout(DNSException):
"""The DNS operation timed out.""" """The DNS operation timed out."""
supp_kwargs = set(['timeout']) supp_kwargs = {'timeout'}
fmt = "The DNS operation timed out after {timeout} seconds" fmt = "The DNS operation timed out after {timeout} seconds"

9
src/dns/exception.pyi Normal file
View File

@@ -0,0 +1,9 @@
from typing import Set, Optional, Dict
class DNSException(Exception):
supp_kwargs : Set[str]
kwargs : Optional[Dict]
class SyntaxError(DNSException): ...
class FormError(DNSException): ...
class Timeout(DNSException): ...

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2001-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -17,16 +19,24 @@
# Standard DNS flags # Standard DNS flags
#: Query Response
QR = 0x8000 QR = 0x8000
#: Authoritative Answer
AA = 0x0400 AA = 0x0400
#: Truncated Response
TC = 0x0200 TC = 0x0200
#: Recursion Desired
RD = 0x0100 RD = 0x0100
#: Recursion Available
RA = 0x0080 RA = 0x0080
#: Authentic Data
AD = 0x0020 AD = 0x0020
#: Checking Disabled
CD = 0x0010 CD = 0x0010
# EDNS flags # EDNS flags
#: DNSSEC answer OK
DO = 0x8000 DO = 0x8000
_by_text = { _by_text = {
@@ -48,9 +58,9 @@ _edns_by_text = {
# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that # cannot make any mistakes (e.g. omissions, cut-and-paste errors) that
# would cause the mappings not to be true inverses. # would cause the mappings not to be true inverses.
_by_value = dict((y, x) for x, y in _by_text.items()) _by_value = {y: x for x, y in _by_text.items()}
_edns_by_value = dict((y, x) for x, y in _edns_by_text.items()) _edns_by_value = {y: x for x, y in _edns_by_text.items()}
def _order_flags(table): def _order_flags(table):
@@ -83,7 +93,9 @@ def _to_text(flags, table, order):
def from_text(text): def from_text(text):
"""Convert a space-separated list of flag text values into a flags """Convert a space-separated list of flag text values into a flags
value. value.
@rtype: int"""
Returns an ``int``
"""
return _from_text(text, _by_text) return _from_text(text, _by_text)
@@ -91,7 +103,9 @@ def from_text(text):
def to_text(flags): def to_text(flags):
"""Convert a flags value into a space-separated list of flag text """Convert a flags value into a space-separated list of flag text
values. values.
@rtype: string"""
Returns a ``text``.
"""
return _to_text(flags, _by_value, _flags_order) return _to_text(flags, _by_value, _flags_order)
@@ -99,7 +113,9 @@ def to_text(flags):
def edns_from_text(text): def edns_from_text(text):
"""Convert a space-separated list of EDNS flag text values into a EDNS """Convert a space-separated list of EDNS flag text values into a EDNS
flags value. flags value.
@rtype: int"""
Returns an ``int``
"""
return _from_text(text, _edns_by_text) return _from_text(text, _edns_by_text)
@@ -107,6 +123,8 @@ def edns_from_text(text):
def edns_to_text(flags): def edns_to_text(flags):
"""Convert an EDNS flags value into a space-separated list of EDNS flag """Convert an EDNS flags value into a space-separated list of EDNS flag
text values. text values.
@rtype: string"""
Returns a ``text``.
"""
return _to_text(flags, _edns_by_value, _edns_flags_order) return _to_text(flags, _edns_by_value, _edns_flags_order)

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2012-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -17,18 +19,16 @@
import dns import dns
def from_text(text): def from_text(text):
"""Convert the text form of a range in a GENERATE statement to an """Convert the text form of a range in a ``$GENERATE`` statement to an
integer. integer.
@param text: the textual range *text*, a ``str``, the textual range in ``$GENERATE`` form.
@type text: string
@return: The start, stop and step values.
@rtype: tuple
"""
# TODO, figure out the bounds on start, stop and step.
Returns a tuple of three ``int`` values ``(start, stop, step)``.
"""
# TODO, figure out the bounds on start, stop and step.
step = 1 step = 1
cur = '' cur = ''
state = 0 state = 0

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2011 Nominum, Inc. # Copyright (C) 2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
@@ -16,7 +18,11 @@
"""Hashing backwards compatibility wrapper""" """Hashing backwards compatibility wrapper"""
import hashlib import hashlib
import warnings
warnings.warn(
"dns.hash module will be removed in future versions. Please use hashlib instead.",
DeprecationWarning)
hashes = {} hashes = {}
hashes['MD5'] = hashlib.md5 hashes['MD5'] = hashlib.md5

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -20,6 +22,7 @@ import socket
import dns.ipv4 import dns.ipv4
import dns.ipv6 import dns.ipv6
from ._compat import maybe_ord
# We assume that AF_INET is always defined. # We assume that AF_INET is always defined.
@@ -38,13 +41,14 @@ except AttributeError:
def inet_pton(family, text): def inet_pton(family, text):
"""Convert the textual form of a network address into its binary form. """Convert the textual form of a network address into its binary form.
@param family: the address family *family* is an ``int``, the address family.
@type family: int
@param text: the textual address *text* is a ``text``, the textual address.
@type text: string
@raises NotImplementedError: the address family specified is not Raises ``NotImplementedError`` if the address family specified is not
implemented. implemented.
@rtype: string
Returns a ``binary``.
""" """
if family == AF_INET: if family == AF_INET:
@@ -58,14 +62,16 @@ def inet_pton(family, text):
def inet_ntop(family, address): def inet_ntop(family, address):
"""Convert the binary form of a network address into its textual form. """Convert the binary form of a network address into its textual form.
@param family: the address family *family* is an ``int``, the address family.
@type family: int
@param address: the binary address *address* is a ``binary``, the network address in binary form.
@type address: string
@raises NotImplementedError: the address family specified is not Raises ``NotImplementedError`` if the address family specified is not
implemented. implemented.
@rtype: string
Returns a ``text``.
""" """
if family == AF_INET: if family == AF_INET:
return dns.ipv4.inet_ntoa(address) return dns.ipv4.inet_ntoa(address)
elif family == AF_INET6: elif family == AF_INET6:
@@ -77,11 +83,14 @@ def inet_ntop(family, address):
def af_for_address(text): def af_for_address(text):
"""Determine the address family of a textual-form network address. """Determine the address family of a textual-form network address.
@param text: the textual address *text*, a ``text``, the textual address.
@type text: string
@raises ValueError: the address family cannot be determined from the input. Raises ``ValueError`` if the address family cannot be determined
@rtype: int from the input.
Returns an ``int``.
""" """
try: try:
dns.ipv4.inet_aton(text) dns.ipv4.inet_aton(text)
return AF_INET return AF_INET
@@ -96,16 +105,20 @@ def af_for_address(text):
def is_multicast(text): def is_multicast(text):
"""Is the textual-form network address a multicast address? """Is the textual-form network address a multicast address?
@param text: the textual address *text*, a ``text``, the textual address.
@raises ValueError: the address family cannot be determined from the input.
@rtype: bool Raises ``ValueError`` if the address family cannot be determined
from the input.
Returns a ``bool``.
""" """
try: try:
first = ord(dns.ipv4.inet_aton(text)[0]) first = maybe_ord(dns.ipv4.inet_aton(text)[0])
return first >= 224 and first <= 239 return first >= 224 and first <= 239
except Exception: except Exception:
try: try:
first = ord(dns.ipv6.inet_aton(text)[0]) first = maybe_ord(dns.ipv6.inet_aton(text)[0])
return first == 255 return first == 255
except Exception: except Exception:
raise ValueError raise ValueError

4
src/dns/inet.pyi Normal file
View File

@@ -0,0 +1,4 @@
from typing import Union
from socket import AddressFamily
AF_INET6 : Union[int, AddressFamily]

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -21,26 +23,28 @@ import dns.exception
from ._compat import binary_type from ._compat import binary_type
def inet_ntoa(address): def inet_ntoa(address):
"""Convert an IPv4 address in network form to text form. """Convert an IPv4 address in binary form to text form.
@param address: The IPv4 address *address*, a ``binary``, the IPv4 address in binary form.
@type address: string
@returns: string Returns a ``text``.
""" """
if len(address) != 4: if len(address) != 4:
raise dns.exception.SyntaxError raise dns.exception.SyntaxError
if not isinstance(address, bytearray): if not isinstance(address, bytearray):
address = bytearray(address) address = bytearray(address)
return (u'%u.%u.%u.%u' % (address[0], address[1], return ('%u.%u.%u.%u' % (address[0], address[1],
address[2], address[3])).encode() address[2], address[3]))
def inet_aton(text): def inet_aton(text):
"""Convert an IPv4 address in text form to network form. """Convert an IPv4 address in text form to binary form.
@param text: The IPv4 address *text*, a ``text``, the IPv4 address in textual form.
@type text: string
@returns: string Returns a ``binary``.
""" """
if not isinstance(text, binary_type): if not isinstance(text, binary_type):
text = text.encode() text = text.encode()
parts = text.split(b'.') parts = text.split(b'.')

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -22,15 +24,15 @@ import dns.exception
import dns.ipv4 import dns.ipv4
from ._compat import xrange, binary_type, maybe_decode from ._compat import xrange, binary_type, maybe_decode
_leading_zero = re.compile(b'0+([0-9a-f]+)') _leading_zero = re.compile(r'0+([0-9a-f]+)')
def inet_ntoa(address): def inet_ntoa(address):
"""Convert a network format IPv6 address into text. """Convert an IPv6 address in binary form to text form.
@param address: the binary address *address*, a ``binary``, the IPv6 address in binary form.
@type address: string
@rtype: string Raises ``ValueError`` if the address isn't 16 bytes long.
@raises ValueError: the address isn't 16 bytes long Returns a ``text``.
""" """
if len(address) != 16: if len(address) != 16:
@@ -40,7 +42,7 @@ def inet_ntoa(address):
i = 0 i = 0
l = len(hex) l = len(hex)
while i < l: while i < l:
chunk = hex[i : i + 4] chunk = maybe_decode(hex[i : i + 4])
# strip leading zeros. we do this with an re instead of # strip leading zeros. we do this with an re instead of
# with lstrip() because lstrip() didn't support chars until # with lstrip() because lstrip() didn't support chars until
# python 2.2.2 # python 2.2.2
@@ -57,7 +59,7 @@ def inet_ntoa(address):
start = -1 start = -1
last_was_zero = False last_was_zero = False
for i in xrange(8): for i in xrange(8):
if chunks[i] != b'0': if chunks[i] != '0':
if last_was_zero: if last_was_zero:
end = i end = i
current_len = end - start current_len = end - start
@@ -77,31 +79,30 @@ def inet_ntoa(address):
if best_len > 1: if best_len > 1:
if best_start == 0 and \ if best_start == 0 and \
(best_len == 6 or (best_len == 6 or
best_len == 5 and chunks[5] == b'ffff'): best_len == 5 and chunks[5] == 'ffff'):
# We have an embedded IPv4 address # We have an embedded IPv4 address
if best_len == 6: if best_len == 6:
prefix = b'::' prefix = '::'
else: else:
prefix = b'::ffff:' prefix = '::ffff:'
hex = prefix + dns.ipv4.inet_ntoa(address[12:]) hex = prefix + dns.ipv4.inet_ntoa(address[12:])
else: else:
hex = b':'.join(chunks[:best_start]) + b'::' + \ hex = ':'.join(chunks[:best_start]) + '::' + \
b':'.join(chunks[best_start + best_len:]) ':'.join(chunks[best_start + best_len:])
else: else:
hex = b':'.join(chunks) hex = ':'.join(chunks)
return maybe_decode(hex) return hex
_v4_ending = re.compile(b'(.*):(\d+\.\d+\.\d+\.\d+)$') _v4_ending = re.compile(br'(.*):(\d+\.\d+\.\d+\.\d+)$')
_colon_colon_start = re.compile(b'::.*') _colon_colon_start = re.compile(br'::.*')
_colon_colon_end = re.compile(b'.*::$') _colon_colon_end = re.compile(br'.*::$')
def inet_aton(text): def inet_aton(text):
"""Convert a text format IPv6 address into network format. """Convert an IPv6 address in text form to binary form.
@param text: the textual address *text*, a ``text``, the IPv6 address in textual form.
@type text: string
@rtype: string Returns a ``binary``.
@raises dns.exception.SyntaxError: the text was not properly formatted
""" """
# #
@@ -118,8 +119,9 @@ def inet_aton(text):
m = _v4_ending.match(text) m = _v4_ending.match(text)
if not m is None: if not m is None:
b = bytearray(dns.ipv4.inet_aton(m.group(2))) b = bytearray(dns.ipv4.inet_aton(m.group(2)))
text = (u"%s:%02x%02x:%02x%02x" % (m.group(1).decode(), b[0], b[1], text = (u"{}:{:02x}{:02x}:{:02x}{:02x}".format(m.group(1).decode(),
b[2], b[3])).encode() b[0], b[1], b[2],
b[3])).encode()
# #
# Try to turn '::<whatever>' into ':<whatever>'; if no match try to # Try to turn '::<whatever>' into ':<whatever>'; if no match try to
# turn '<whatever>::' into '<whatever>:' # turn '<whatever>::' into '<whatever>:'
@@ -169,4 +171,11 @@ def inet_aton(text):
_mapped_prefix = b'\x00' * 10 + b'\xff\xff' _mapped_prefix = b'\x00' * 10 + b'\xff\xff'
def is_mapped(address): def is_mapped(address):
"""Is the specified address a mapped IPv4 address?
*address*, a ``binary`` is an IPv6 address in binary form.
Returns a ``bool``.
"""
return address.startswith(_mapped_prefix) return address.startswith(_mapped_prefix)

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2001-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -40,114 +42,46 @@ from ._compat import long, xrange, string_types
class ShortHeader(dns.exception.FormError): class ShortHeader(dns.exception.FormError):
"""The DNS packet passed to from_wire() is too short.""" """The DNS packet passed to from_wire() is too short."""
class TrailingJunk(dns.exception.FormError): class TrailingJunk(dns.exception.FormError):
"""The DNS packet passed to from_wire() has extra junk at the end of it.""" """The DNS packet passed to from_wire() has extra junk at the end of it."""
class UnknownHeaderField(dns.exception.DNSException): class UnknownHeaderField(dns.exception.DNSException):
"""The header field name was not recognized when converting from text """The header field name was not recognized when converting from text
into a message.""" into a message."""
class BadEDNS(dns.exception.FormError): class BadEDNS(dns.exception.FormError):
"""An OPT record occurred somewhere other than the start of
"""OPT record occurred somewhere other than the start of
the additional data section.""" the additional data section."""
class BadTSIG(dns.exception.FormError): class BadTSIG(dns.exception.FormError):
"""A TSIG record occurred somewhere other than the end of """A TSIG record occurred somewhere other than the end of
the additional data section.""" the additional data section."""
class UnknownTSIGKey(dns.exception.DNSException): class UnknownTSIGKey(dns.exception.DNSException):
"""A TSIG with an unknown key was received.""" """A TSIG with an unknown key was received."""
#: The question section number
QUESTION = 0
#: The answer section number
ANSWER = 1
#: The authority section number
AUTHORITY = 2
#: The additional section number
ADDITIONAL = 3
class Message(object): class Message(object):
"""A DNS message."""
"""A DNS message.
@ivar id: The query id; the default is a randomly chosen id.
@type id: int
@ivar flags: The DNS flags of the message. @see: RFC 1035 for an
explanation of these flags.
@type flags: int
@ivar question: The question section.
@type question: list of dns.rrset.RRset objects
@ivar answer: The answer section.
@type answer: list of dns.rrset.RRset objects
@ivar authority: The authority section.
@type authority: list of dns.rrset.RRset objects
@ivar additional: The additional data section.
@type additional: list of dns.rrset.RRset objects
@ivar edns: The EDNS level to use. The default is -1, no Edns.
@type edns: int
@ivar ednsflags: The EDNS flags
@type ednsflags: long
@ivar payload: The EDNS payload size. The default is 0.
@type payload: int
@ivar options: The EDNS options
@type options: list of dns.edns.Option objects
@ivar request_payload: The associated request's EDNS payload size.
@type request_payload: int
@ivar keyring: The TSIG keyring to use. The default is None.
@type keyring: dict
@ivar keyname: The TSIG keyname to use. The default is None.
@type keyname: dns.name.Name object
@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
more information on TSIG fields.
@type request_mac: string
@ivar fudge: TSIG time fudge; default is 300 seconds.
@type fudge: int
@ivar original_id: TSIG original id; defaults to the message's id
@type original_id: int
@ivar tsig_error: TSIG error code; default is 0.
@type tsig_error: int
@ivar other_data: TSIG other data.
@type other_data: string
@ivar mac: The TSIG MAC for this message.
@type mac: string
@ivar xfr: Is the message being used to contain the results of a DNS
zone transfer? The default is False.
@type xfr: bool
@ivar origin: The origin of the zone in messages which are used for
zone transfers or for DNS dynamic updates. The default is None.
@type origin: dns.name.Name object
@ivar tsig_ctx: The TSIG signature context associated with this
message. The default is None.
@type tsig_ctx: hmac.HMAC object
@ivar had_tsig: Did the message decoded from wire format have a TSIG
signature?
@type had_tsig: bool
@ivar multi: Is this message part of a multi-message sequence? The
default is false. This variable is used when validating TSIG signatures
on messages which are part of a zone transfer.
@type multi: bool
@ivar first: Is this message standalone, or the first of a multi
message sequence? This variable is used when validating TSIG signatures
on messages which are part of a zone transfer.
@type first: bool
@ivar index: An index of rrsets in the message. The index key is
(section, name, rdclass, rdtype, covers, deleting). Indexing can be
disabled by setting the index to None.
@type index: dict
"""
def __init__(self, id=None): def __init__(self, id=None):
if id is None: if id is None:
@@ -167,12 +101,12 @@ class Message(object):
self.keyring = None self.keyring = None
self.keyname = None self.keyname = None
self.keyalgorithm = dns.tsig.default_algorithm self.keyalgorithm = dns.tsig.default_algorithm
self.request_mac = '' self.request_mac = b''
self.other_data = '' self.other_data = b''
self.tsig_error = 0 self.tsig_error = 0
self.fudge = 300 self.fudge = 300
self.original_id = self.id self.original_id = self.id
self.mac = '' self.mac = b''
self.xfr = False self.xfr = False
self.origin = None self.origin = None
self.tsig_ctx = None self.tsig_ctx = None
@@ -190,10 +124,10 @@ class Message(object):
def to_text(self, origin=None, relativize=True, **kw): def to_text(self, origin=None, relativize=True, **kw):
"""Convert the message to text. """Convert the message to text.
The I{origin}, I{relativize}, and any other keyword The *origin*, *relativize*, and any other keyword
arguments are passed to the rrset to_wire() method. arguments are passed to the RRset ``to_wire()`` method.
@rtype: string Returns a ``text``.
""" """
s = StringIO() s = StringIO()
@@ -209,6 +143,8 @@ class Message(object):
s.write(u'eflags %s\n' % s.write(u'eflags %s\n' %
dns.flags.edns_to_text(self.ednsflags)) dns.flags.edns_to_text(self.ednsflags))
s.write(u'payload %d\n' % self.payload) s.write(u'payload %d\n' % self.payload)
for opt in self.options:
s.write(u'option %s\n' % opt.to_text())
is_update = dns.opcode.is_update(self.flags) is_update = dns.opcode.is_update(self.flags)
if is_update: if is_update:
s.write(u';ZONE\n') s.write(u';ZONE\n')
@@ -245,7 +181,10 @@ class Message(object):
def __eq__(self, other): def __eq__(self, other):
"""Two messages are equal if they have the same content in the """Two messages are equal if they have the same content in the
header, question, answer, and authority sections. header, question, answer, and authority sections.
@rtype: bool"""
Returns a ``bool``.
"""
if not isinstance(other, Message): if not isinstance(other, Message):
return False return False
if self.id != other.id: if self.id != other.id:
@@ -273,13 +212,14 @@ class Message(object):
return True return True
def __ne__(self, other): def __ne__(self, other):
"""Are two messages not equal?
@rtype: bool"""
return not self.__eq__(other) return not self.__eq__(other)
def is_response(self, other): def is_response(self, other):
"""Is other a response to self? """Is this message a response to *other*?
@rtype: bool"""
Returns a ``bool``.
"""
if other.flags & dns.flags.QR == 0 or \ if other.flags & dns.flags.QR == 0 or \
self.id != other.id or \ self.id != other.id or \
dns.opcode.from_flags(self.flags) != \ dns.opcode.from_flags(self.flags) != \
@@ -299,14 +239,48 @@ class Message(object):
return True return True
def section_number(self, section): def section_number(self, section):
"""Return the "section number" of the specified section for use
in indexing. The question section is 0, the answer section is 1,
the authority section is 2, and the additional section is 3.
*section* is one of the section attributes of this message.
Raises ``ValueError`` if the section isn't known.
Returns an ``int``.
"""
if section is self.question: if section is self.question:
return 0 return QUESTION
elif section is self.answer: elif section is self.answer:
return 1 return ANSWER
elif section is self.authority: elif section is self.authority:
return 2 return AUTHORITY
elif section is self.additional: elif section is self.additional:
return 3 return ADDITIONAL
else:
raise ValueError('unknown section')
def section_from_number(self, number):
"""Return the "section number" of the specified section for use
in indexing. The question section is 0, the answer section is 1,
the authority section is 2, and the additional section is 3.
*section* is one of the section attributes of this message.
Raises ``ValueError`` if the section isn't known.
Returns an ``int``.
"""
if number == QUESTION:
return self.question
elif number == ANSWER:
return self.answer
elif number == AUTHORITY:
return self.authority
elif number == ADDITIONAL:
return self.additional
else: else:
raise ValueError('unknown section') raise ValueError('unknown section')
@@ -315,30 +289,45 @@ class Message(object):
force_unique=False): force_unique=False):
"""Find the RRset with the given attributes in the specified section. """Find the RRset with the given attributes in the specified section.
@param section: the section of the message to look in, e.g. *section*, an ``int`` section number, or one of the section
self.answer. attributes of this message. This specifies the
@type section: list of dns.rrset.RRset objects the section of the message to search. For example::
@param name: the name of the RRset
@type name: dns.name.Name object
@param rdclass: the class of the RRset
@type rdclass: int
@param rdtype: the type of the RRset
@type rdtype: int
@param covers: the covers value of the RRset
@type covers: int
@param deleting: the deleting value of the RRset
@type deleting: int
@param create: If True, create the RRset if it is not found.
The created RRset is appended to I{section}.
@type create: bool
@param force_unique: If True and create is also True, create a
new RRset regardless of whether a matching RRset exists already.
@type force_unique: bool
@raises KeyError: the RRset was not found and create was False
@rtype: dns.rrset.RRset object"""
key = (self.section_number(section), my_message.find_rrset(my_message.answer, name, rdclass, rdtype)
name, rdclass, rdtype, covers, deleting) my_message.find_rrset(dns.message.ANSWER, name, rdclass, rdtype)
*name*, a ``dns.name.Name``, the name of the RRset.
*rdclass*, an ``int``, the class of the RRset.
*rdtype*, an ``int``, the type of the RRset.
*covers*, an ``int`` or ``None``, the covers value of the RRset.
The default is ``None``.
*deleting*, an ``int`` or ``None``, the deleting value of the RRset.
The default is ``None``.
*create*, a ``bool``. If ``True``, create the RRset if it is not found.
The created RRset is appended to *section*.
*force_unique*, a ``bool``. If ``True`` and *create* is also ``True``,
create a new RRset regardless of whether a matching RRset exists
already. The default is ``False``. This is useful when creating
DDNS Update messages, as order matters for them.
Raises ``KeyError`` if the RRset was not found and create was
``False``.
Returns a ``dns.rrset.RRset object``.
"""
if isinstance(section, int):
section_number = section
section = self.section_from_number(section_number)
else:
section_number = self.section_number(section)
key = (section_number, name, rdclass, rdtype, covers, deleting)
if not force_unique: if not force_unique:
if self.index is not None: if self.index is not None:
rrset = self.index.get(key) rrset = self.index.get(key)
@@ -363,26 +352,35 @@ class Message(object):
If the RRset is not found, None is returned. If the RRset is not found, None is returned.
@param section: the section of the message to look in, e.g. *section*, an ``int`` section number, or one of the section
self.answer. attributes of this message. This specifies the
@type section: list of dns.rrset.RRset objects the section of the message to search. For example::
@param name: the name of the RRset
@type name: dns.name.Name object my_message.get_rrset(my_message.answer, name, rdclass, rdtype)
@param rdclass: the class of the RRset my_message.get_rrset(dns.message.ANSWER, name, rdclass, rdtype)
@type rdclass: int
@param rdtype: the type of the RRset *name*, a ``dns.name.Name``, the name of the RRset.
@type rdtype: int
@param covers: the covers value of the RRset *rdclass*, an ``int``, the class of the RRset.
@type covers: int
@param deleting: the deleting value of the RRset *rdtype*, an ``int``, the type of the RRset.
@type deleting: int
@param create: If True, create the RRset if it is not found. *covers*, an ``int`` or ``None``, the covers value of the RRset.
The created RRset is appended to I{section}. The default is ``None``.
@type create: bool
@param force_unique: If True and create is also True, create a *deleting*, an ``int`` or ``None``, the deleting value of the RRset.
new RRset regardless of whether a matching RRset exists already. The default is ``None``.
@type force_unique: bool
@rtype: dns.rrset.RRset object or None""" *create*, a ``bool``. If ``True``, create the RRset if it is not found.
The created RRset is appended to *section*.
*force_unique*, a ``bool``. If ``True`` and *create* is also ``True``,
create a new RRset regardless of whether a matching RRset exists
already. The default is ``False``. This is useful when creating
DDNS Update messages, as order matters for them.
Returns a ``dns.rrset.RRset object`` or ``None``.
"""
try: try:
rrset = self.find_rrset(section, name, rdclass, rdtype, covers, rrset = self.find_rrset(section, name, rdclass, rdtype, covers,
@@ -395,17 +393,19 @@ class Message(object):
"""Return a string containing the message in DNS compressed wire """Return a string containing the message in DNS compressed wire
format. format.
Additional keyword arguments are passed to the rrset to_wire() Additional keyword arguments are passed to the RRset ``to_wire()``
method. method.
@param origin: The origin to be appended to any relative names. *origin*, a ``dns.name.Name`` or ``None``, the origin to be appended
@type origin: dns.name.Name object to any relative names.
@param max_size: The maximum size of the wire format output; default
is 0, which means 'the message's request payload, if nonzero, or *max_size*, an ``int``, the maximum size of the wire format
65536'. output; default is 0, which means "the message's request
@type max_size: int payload, if nonzero, or 65535".
@raises dns.exception.TooBig: max_size was exceeded
@rtype: string Raises ``dns.exception.TooBig`` if *max_size* was exceeded.
Returns a ``binary``.
""" """
if max_size == 0: if max_size == 0:
@@ -438,30 +438,34 @@ class Message(object):
return r.get_wire() return r.get_wire()
def use_tsig(self, keyring, keyname=None, fudge=300, def use_tsig(self, keyring, keyname=None, fudge=300,
original_id=None, tsig_error=0, other_data='', original_id=None, tsig_error=0, other_data=b'',
algorithm=dns.tsig.default_algorithm): algorithm=dns.tsig.default_algorithm):
"""When sending, a TSIG signature using the specified keyring """When sending, a TSIG signature using the specified keyring
and keyname should be added. and keyname should be added.
@param keyring: The TSIG keyring to use; defaults to None. See the documentation of the Message class for a complete
@type keyring: dict description of the keyring dictionary.
@param keyname: The name of the TSIG key to use; defaults to None.
The key must be defined in the keyring. If a keyring is specified *keyring*, a ``dict``, the TSIG keyring to use. If a
but a keyname is not, then the key used will be the first key in the *keyring* is specified but a *keyname* is not, then the key
keyring. Note that the order of keys in a dictionary is not defined, used will be the first key in the *keyring*. Note that the
so applications should supply a keyname when a keyring is used, unless order of keys in a dictionary is not defined, so applications
they know the keyring contains only one key. should supply a keyname when a keyring is used, unless they
@type keyname: dns.name.Name or string know the keyring contains only one key.
@param fudge: TSIG time fudge; default is 300 seconds.
@type fudge: int *keyname*, a ``dns.name.Name`` or ``None``, the name of the TSIG key
@param original_id: TSIG original id; defaults to the message's id to use; defaults to ``None``. The key must be defined in the keyring.
@type original_id: int
@param tsig_error: TSIG error code; default is 0. *fudge*, an ``int``, the TSIG time fudge.
@type tsig_error: int
@param other_data: TSIG other data. *original_id*, an ``int``, the TSIG original id. If ``None``,
@type other_data: string the message's id is used.
@param algorithm: The TSIG algorithm to use; defaults to
dns.tsig.default_algorithm *tsig_error*, an ``int``, the TSIG error code.
*other_data*, a ``binary``, the TSIG other data.
*algorithm*, a ``dns.name.Name``, the TSIG algorithm to use.
""" """
self.keyring = keyring self.keyring = keyring
@@ -483,23 +487,26 @@ class Message(object):
def use_edns(self, edns=0, ednsflags=0, payload=1280, request_payload=None, def use_edns(self, edns=0, ednsflags=0, payload=1280, request_payload=None,
options=None): options=None):
"""Configure EDNS behavior. """Configure EDNS behavior.
@param edns: The EDNS level to use. Specifying None, False, or -1
means 'do not use EDNS', and in this case the other parameters are *edns*, an ``int``, is the EDNS level to use. Specifying
ignored. Specifying True is equivalent to specifying 0, i.e. 'use ``None``, ``False``, or ``-1`` means "do not use EDNS", and in this case
EDNS0'. the other parameters are ignored. Specifying ``True`` is
@type edns: int or bool or None equivalent to specifying 0, i.e. "use EDNS0".
@param ednsflags: EDNS flag values.
@type ednsflags: int *ednsflags*, an ``int``, the EDNS flag values.
@param payload: The EDNS sender's payload field, which is the maximum
size of UDP datagram the sender can handle. *payload*, an ``int``, is the EDNS sender's payload field, which is the
@type payload: int maximum size of UDP datagram the sender can handle. I.e. how big
@param request_payload: The EDNS payload size to use when sending a response to this message can be.
this message. If not specified, defaults to the value of payload.
@type request_payload: int or None *request_payload*, an ``int``, is the EDNS payload size to use when
@param options: The EDNS options sending this message. If not specified, defaults to the value of
@type options: None or list of dns.edns.Option objects *payload*.
@see: RFC 2671
*options*, a list of ``dns.edns.Option`` objects or ``None``, the EDNS
options.
""" """
if edns is None or edns is False: if edns is None or edns is False:
edns = -1 edns = -1
if edns is True: if edns is True:
@@ -525,11 +532,13 @@ class Message(object):
def want_dnssec(self, wanted=True): def want_dnssec(self, wanted=True):
"""Enable or disable 'DNSSEC desired' flag in requests. """Enable or disable 'DNSSEC desired' flag in requests.
@param wanted: Is DNSSEC desired? If True, EDNS is enabled if
required, and then the DO bit is set. If False, the DO bit is *wanted*, a ``bool``. If ``True``, then DNSSEC data is
cleared if EDNS is enabled. desired in the response, EDNS is enabled if required, and then
@type wanted: bool the DO bit is set. If ``False``, the DO bit is cleared if
EDNS is enabled.
""" """
if wanted: if wanted:
if self.edns < 0: if self.edns < 0:
self.use_edns() self.use_edns()
@@ -539,14 +548,15 @@ class Message(object):
def rcode(self): def rcode(self):
"""Return the rcode. """Return the rcode.
@rtype: int
Returns an ``int``.
""" """
return dns.rcode.from_flags(self.flags, self.ednsflags) return dns.rcode.from_flags(self.flags, self.ednsflags)
def set_rcode(self, rcode): def set_rcode(self, rcode):
"""Set the rcode. """Set the rcode.
@param rcode: the rcode
@type rcode: int *rcode*, an ``int``, is the rcode to set.
""" """
(value, evalue) = dns.rcode.to_flags(rcode) (value, evalue) = dns.rcode.to_flags(rcode)
self.flags &= 0xFFF0 self.flags &= 0xFFF0
@@ -558,14 +568,15 @@ class Message(object):
def opcode(self): def opcode(self):
"""Return the opcode. """Return the opcode.
@rtype: int
Returns an ``int``.
""" """
return dns.opcode.from_flags(self.flags) return dns.opcode.from_flags(self.flags)
def set_opcode(self, opcode): def set_opcode(self, opcode):
"""Set the opcode. """Set the opcode.
@param opcode: the opcode
@type opcode: int *opcode*, an ``int``, is the opcode to set.
""" """
self.flags &= 0x87FF self.flags &= 0x87FF
self.flags |= dns.opcode.to_flags(opcode) self.flags |= dns.opcode.to_flags(opcode)
@@ -575,23 +586,16 @@ class _WireReader(object):
"""Wire format reader. """Wire format reader.
@ivar wire: the wire-format message. wire: a binary, is the wire-format message.
@type wire: string message: The message object being built
@ivar message: The message object being built current: When building a message object from wire format, this
@type message: dns.message.Message object
@ivar current: When building a message object from wire format, this
variable contains the offset from the beginning of wire of the next octet variable contains the offset from the beginning of wire of the next octet
to be read. to be read.
@type current: int updating: Is the message a dynamic update?
@ivar updating: Is the message a dynamic update? one_rr_per_rrset: Put each RR into its own RRset?
@type updating: bool ignore_trailing: Ignore trailing junk at end of request?
@ivar one_rr_per_rrset: Put each RR into its own RRset? zone_rdclass: The class of the zone in messages which are
@type one_rr_per_rrset: bool
@ivar ignore_trailing: Ignore trailing junk at end of request?
@type ignore_trailing: bool
@ivar zone_rdclass: The class of the zone in messages which are
DNS dynamic updates. DNS dynamic updates.
@type zone_rdclass: int
""" """
def __init__(self, wire, message, question_only=False, def __init__(self, wire, message, question_only=False,
@@ -606,10 +610,9 @@ class _WireReader(object):
self.ignore_trailing = ignore_trailing self.ignore_trailing = ignore_trailing
def _get_question(self, qcount): def _get_question(self, qcount):
"""Read the next I{qcount} records from the wire data and add them to """Read the next *qcount* records from the wire data and add them to
the question section. the question section.
@param qcount: the number of questions in the message """
@type qcount: int"""
if self.updating and qcount > 1: if self.updating and qcount > 1:
raise dns.exception.FormError raise dns.exception.FormError
@@ -632,10 +635,10 @@ class _WireReader(object):
def _get_section(self, section, count): def _get_section(self, section, count):
"""Read the next I{count} records from the wire data and add them to """Read the next I{count} records from the wire data and add them to
the specified section. the specified section.
@param section: the section of the message to which to add records
@type section: list of dns.rrset.RRset objects section: the section of the message to which to add records
@param count: the number of records to read count: the number of records to read
@type count: int""" """
if self.updating or self.one_rr_per_rrset: if self.updating or self.one_rr_per_rrset:
force_unique = True force_unique = True
@@ -753,45 +756,58 @@ class _WireReader(object):
self.message.tsig_ctx.update(self.wire) self.message.tsig_ctx.update(self.wire)
def from_wire(wire, keyring=None, request_mac='', xfr=False, origin=None, def from_wire(wire, keyring=None, request_mac=b'', xfr=False, origin=None,
tsig_ctx=None, multi=False, first=True, tsig_ctx=None, multi=False, first=True,
question_only=False, one_rr_per_rrset=False, question_only=False, one_rr_per_rrset=False,
ignore_trailing=False): ignore_trailing=False):
"""Convert a DNS wire format message into a message """Convert a DNS wire format message into a message
object. object.
@param keyring: The keyring to use if the message is signed. *keyring*, a ``dict``, the keyring to use if the message is signed.
@type keyring: dict
@param request_mac: If the message is a response to a TSIG-signed request, *request_mac*, a ``binary``. If the message is a response to a
I{request_mac} should be set to the MAC of that request. TSIG-signed request, *request_mac* should be set to the MAC of
@type request_mac: string that request.
@param xfr: Is this message part of a zone transfer?
@type xfr: bool *xfr*, a ``bool``, should be set to ``True`` if this message is part of
@param origin: If the message is part of a zone transfer, I{origin} a zone transfer.
should be the origin name of the zone.
@type origin: dns.name.Name object *origin*, a ``dns.name.Name`` or ``None``. If the message is part
@param tsig_ctx: The ongoing TSIG context, used when validating zone of a zone transfer, *origin* should be the origin name of the
transfers. zone.
@type tsig_ctx: hmac.HMAC object
@param multi: Is this message part of a multiple message sequence? *tsig_ctx*, a ``hmac.HMAC`` objext, the ongoing TSIG context, used
@type multi: bool when validating zone transfers.
@param first: Is this message standalone, or the first of a multi
message sequence? *multi*, a ``bool``, should be set to ``True`` if this message
@type first: bool part of a multiple message sequence.
@param question_only: Read only up to the end of the question section?
@type question_only: bool *first*, a ``bool``, should be set to ``True`` if this message is
@param one_rr_per_rrset: Put each RR into its own RRset stand-alone, or the first message in a multi-message sequence.
@type one_rr_per_rrset: bool
@param ignore_trailing: Ignore trailing junk at end of request? *question_only*, a ``bool``. If ``True``, read only up to
@type ignore_trailing: bool the end of the question section.
@raises ShortHeader: The message is less than 12 octets long.
@raises TrailingJunk: There were octets in the message past the end *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its
of the proper DNS message. own RRset.
@raises BadEDNS: An OPT record was in the wrong section, or occurred more
than once. *ignore_trailing*, a ``bool``. If ``True``, ignore trailing
@raises BadTSIG: A TSIG record was not the last record of the additional junk at end of the message.
data section.
@rtype: dns.message.Message object""" Raises ``dns.message.ShortHeader`` if the message is less than 12 octets
long.
Raises ``dns.messaage.TrailingJunk`` if there were octets in the message
past the end of the proper DNS message, and *ignore_trailing* is ``False``.
Raises ``dns.message.BadEDNS`` if an OPT record was in the
wrong section, or occurred more than once.
Raises ``dns.message.BadTSIG`` if a TSIG record was not the last
record of the additional data section.
Returns a ``dns.message.Message``.
"""
m = Message(id=0) m = Message(id=0)
m.keyring = keyring m.keyring = keyring
@@ -813,18 +829,12 @@ class _TextReader(object):
"""Text format reader. """Text format reader.
@ivar tok: the tokenizer tok: the tokenizer.
@type tok: dns.tokenizer.Tokenizer object message: The message object being built.
@ivar message: The message object being built updating: Is the message a dynamic update?
@type message: dns.message.Message object zone_rdclass: The class of the zone in messages which are
@ivar updating: Is the message a dynamic update?
@type updating: bool
@ivar zone_rdclass: The class of the zone in messages which are
DNS dynamic updates. DNS dynamic updates.
@type zone_rdclass: int last_name: The most recently read name when building a message object.
@ivar last_name: The most recently read name when building a message object
from text format.
@type last_name: dns.name.Name object
""" """
def __init__(self, text, message): def __init__(self, text, message):
@@ -997,11 +1007,14 @@ class _TextReader(object):
def from_text(text): def from_text(text):
"""Convert the text format message into a message object. """Convert the text format message into a message object.
@param text: The text format message. *text*, a ``text``, the text format message.
@type text: string
@raises UnknownHeaderField: Raises ``dns.message.UnknownHeaderField`` if a header is unknown.
@raises dns.exception.SyntaxError:
@rtype: dns.message.Message object""" Raises ``dns.exception.SyntaxError`` if the text is badly formed.
Returns a ``dns.message.Message object``
"""
# 'text' can also be a file, but we don't publish that fact # 'text' can also be a file, but we don't publish that fact
# since it's an implementation detail. The official file # since it's an implementation detail. The official file
@@ -1018,11 +1031,15 @@ def from_text(text):
def from_file(f): def from_file(f):
"""Read the next text format message from the specified file. """Read the next text format message from the specified file.
@param f: file or string. If I{f} is a string, it is treated *f*, a ``file`` or ``text``. If *f* is text, it is treated as the
as the name of a file to open. pathname of a file to open.
@raises UnknownHeaderField:
@raises dns.exception.SyntaxError: Raises ``dns.message.UnknownHeaderField`` if a header is unknown.
@rtype: dns.message.Message object"""
Raises ``dns.exception.SyntaxError`` if the text is badly formed.
Returns a ``dns.message.Message object``
"""
str_type = string_types str_type = string_types
opts = 'rU' opts = 'rU'
@@ -1052,30 +1069,35 @@ def make_query(qname, rdtype, rdclass=dns.rdataclass.IN, use_edns=None,
The query will have a randomly chosen query id, and its DNS flags The query will have a randomly chosen query id, and its DNS flags
will be set to dns.flags.RD. will be set to dns.flags.RD.
@param qname: The query name. qname, a ``dns.name.Name`` or ``text``, the query name.
@type qname: dns.name.Name object or string
@param rdtype: The desired rdata type. *rdtype*, an ``int`` or ``text``, the desired rdata type.
@type rdtype: int
@param rdclass: The desired rdata class; the default is class IN. *rdclass*, an ``int`` or ``text``, the desired rdata class; the default
@type rdclass: int is class IN.
@param use_edns: The EDNS level to use; the default is None (no EDNS).
*use_edns*, an ``int``, ``bool`` or ``None``. The EDNS level to use; the
default is None (no EDNS).
See the description of dns.message.Message.use_edns() for the possible See the description of dns.message.Message.use_edns() for the possible
values for use_edns and their meanings. values for use_edns and their meanings.
@type use_edns: int or bool or None
@param want_dnssec: Should the query indicate that DNSSEC is desired? *want_dnssec*, a ``bool``. If ``True``, DNSSEC data is desired.
@type want_dnssec: bool
@param ednsflags: EDNS flag values. *ednsflags*, an ``int``, the EDNS flag values.
@type ednsflags: int
@param payload: The EDNS sender's payload field, which is the maximum *payload*, an ``int``, is the EDNS sender's payload field, which is the
size of UDP datagram the sender can handle. maximum size of UDP datagram the sender can handle. I.e. how big
@type payload: int a response to this message can be.
@param request_payload: The EDNS payload size to use when sending
this message. If not specified, defaults to the value of payload. *request_payload*, an ``int``, is the EDNS payload size to use when
@type request_payload: int or None sending this message. If not specified, defaults to the value of
@param options: The EDNS options *payload*.
@type options: None or list of dns.edns.Option objects
@see: RFC 2671 *options*, a list of ``dns.edns.Option`` objects or ``None``, the EDNS
@rtype: dns.message.Message object""" options.
Returns a ``dns.message.Message``
"""
if isinstance(qname, string_types): if isinstance(qname, string_types):
qname = dns.name.from_text(qname) qname = dns.name.from_text(qname)
@@ -1124,16 +1146,17 @@ def make_response(query, recursion_available=False, our_payload=8192,
question section, so the query's question RRsets should not be question section, so the query's question RRsets should not be
changed. changed.
@param query: the query to respond to *query*, a ``dns.message.Message``, the query to respond to.
@type query: dns.message.Message object
@param recursion_available: should RA be set in the response? *recursion_available*, a ``bool``, should RA be set in the response?
@type recursion_available: bool
@param our_payload: payload size to advertise in EDNS responses; default *our_payload*, an ``int``, the payload size to advertise in EDNS
is 8192. responses.
@type our_payload: int
@param fudge: TSIG time fudge; default is 300 seconds. *fudge*, an ``int``, the TSIG time fudge.
@type fudge: int
@rtype: dns.message.Message object""" Returns a ``dns.message.Message`` object.
"""
if query.flags & dns.flags.QR: if query.flags & dns.flags.QR:
raise dns.exception.FormError('specified query message is not a query') raise dns.exception.FormError('specified query message is not a query')
@@ -1146,7 +1169,7 @@ def make_response(query, recursion_available=False, our_payload=8192,
if query.edns >= 0: if query.edns >= 0:
response.use_edns(0, 0, our_payload, query.payload) response.use_edns(0, 0, our_payload, query.payload)
if query.had_tsig: if query.had_tsig:
response.use_tsig(query.keyring, query.keyname, fudge, None, 0, '', response.use_tsig(query.keyring, query.keyname, fudge, None, 0, b'',
query.keyalgorithm) query.keyalgorithm)
response.request_mac = query.mac response.request_mac = query.mac
return response return response

55
src/dns/message.pyi Normal file
View File

@@ -0,0 +1,55 @@
from typing import Optional, Dict, List, Tuple, Union
from . import name, rrset, tsig, rdatatype, entropy, edns, rdataclass
import hmac
class Message:
def to_wire(self, origin : Optional[name.Name]=None, max_size=0, **kw) -> bytes:
...
def find_rrset(self, section : List[rrset.RRset], name : name.Name, rdclass : int, rdtype : int,
covers=rdatatype.NONE, deleting : Optional[int]=None, create=False,
force_unique=False) -> rrset.RRset:
...
def __init__(self, id : Optional[int] =None) -> None:
self.id : int
self.flags = 0
self.question : List[rrset.RRset] = []
self.answer : List[rrset.RRset] = []
self.authority : List[rrset.RRset] = []
self.additional : List[rrset.RRset] = []
self.edns = -1
self.ednsflags = 0
self.payload = 0
self.options : List[edns.Option] = []
self.request_payload = 0
self.keyring = None
self.keyname = None
self.keyalgorithm = tsig.default_algorithm
self.request_mac = b''
self.other_data = b''
self.tsig_error = 0
self.fudge = 300
self.original_id = self.id
self.mac = b''
self.xfr = False
self.origin = None
self.tsig_ctx = None
self.had_tsig = False
self.multi = False
self.first = True
self.index : Dict[Tuple[rrset.RRset, name.Name, int, int, Union[int,str], int], rrset.RRset] = {}
def from_text(a : str) -> Message:
...
def from_wire(wire, keyring : Optional[Dict[name.Name,bytes]] = None, request_mac = b'', xfr=False, origin=None,
tsig_ctx : Optional[hmac.HMAC] = None, multi=False, first=True,
question_only=False, one_rr_per_rrset=False,
ignore_trailing=False) -> Message:
...
def make_response(query : Message, recursion_available=False, our_payload=8192,
fudge=300) -> Message:
...
def make_query(qname : Union[name.Name,str], rdtype : Union[str,int], rdclass : Union[int,str] =rdataclass.IN, use_edns : Optional[bool] = None,
want_dnssec=False, ednsflags : Optional[int] = None, payload : Optional[int] = None,
request_payload : Optional[int] = None, options : Optional[List[edns.Option]] = None) -> Message:
...

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2001-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -14,11 +16,6 @@
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS Names. """DNS Names.
@var root: The DNS root name.
@type root: dns.name.Name object
@var empty: The empty DNS name.
@type empty: dns.name.Name object
""" """
from io import BytesIO from io import BytesIO
@@ -38,79 +35,76 @@ import dns.wiredata
from ._compat import long, binary_type, text_type, unichr, maybe_decode from ._compat import long, binary_type, text_type, unichr, maybe_decode
try: try:
maxint = sys.maxint maxint = sys.maxint # pylint: disable=sys-max-int
except AttributeError: except AttributeError:
maxint = (1 << (8 * struct.calcsize("P"))) // 2 - 1 maxint = (1 << (8 * struct.calcsize("P"))) // 2 - 1
# fullcompare() result values
#: The compared names have no relationship to each other.
NAMERELN_NONE = 0 NAMERELN_NONE = 0
#: the first name is a superdomain of the second.
NAMERELN_SUPERDOMAIN = 1 NAMERELN_SUPERDOMAIN = 1
#: The first name is a subdomain of the second.
NAMERELN_SUBDOMAIN = 2 NAMERELN_SUBDOMAIN = 2
#: The compared names are equal.
NAMERELN_EQUAL = 3 NAMERELN_EQUAL = 3
#: The compared names have a common ancestor.
NAMERELN_COMMONANCESTOR = 4 NAMERELN_COMMONANCESTOR = 4
class EmptyLabel(dns.exception.SyntaxError): class EmptyLabel(dns.exception.SyntaxError):
"""A DNS label is empty.""" """A DNS label is empty."""
class BadEscape(dns.exception.SyntaxError): class BadEscape(dns.exception.SyntaxError):
"""An escaped code in a text format of DNS name is invalid.""" """An escaped code in a text format of DNS name is invalid."""
class BadPointer(dns.exception.FormError): class BadPointer(dns.exception.FormError):
"""A DNS compression pointer points forward instead of backward.""" """A DNS compression pointer points forward instead of backward."""
class BadLabelType(dns.exception.FormError): class BadLabelType(dns.exception.FormError):
"""The label type in DNS name wire format is unknown.""" """The label type in DNS name wire format is unknown."""
class NeedAbsoluteNameOrOrigin(dns.exception.DNSException): class NeedAbsoluteNameOrOrigin(dns.exception.DNSException):
"""An attempt was made to convert a non-absolute name to """An attempt was made to convert a non-absolute name to
wire when there was also a non-absolute (or missing) origin.""" wire when there was also a non-absolute (or missing) origin."""
class NameTooLong(dns.exception.FormError): class NameTooLong(dns.exception.FormError):
"""A DNS name is > 255 octets long.""" """A DNS name is > 255 octets long."""
class LabelTooLong(dns.exception.SyntaxError): class LabelTooLong(dns.exception.SyntaxError):
"""A DNS label is > 63 octets long.""" """A DNS label is > 63 octets long."""
class AbsoluteConcatenation(dns.exception.DNSException): class AbsoluteConcatenation(dns.exception.DNSException):
"""An attempt was made to append anything other than the """An attempt was made to append anything other than the
empty name to an absolute DNS name.""" empty name to an absolute DNS name."""
class NoParent(dns.exception.DNSException): class NoParent(dns.exception.DNSException):
"""An attempt was made to get the parent of the root name """An attempt was made to get the parent of the root name
or the empty name.""" or the empty name."""
class NoIDNA2008(dns.exception.DNSException): class NoIDNA2008(dns.exception.DNSException):
"""IDNA 2008 processing was requested but the idna module is not """IDNA 2008 processing was requested but the idna module is not
available.""" available."""
class IDNAException(dns.exception.DNSException): class IDNAException(dns.exception.DNSException):
"""IDNA processing raised an exception.""" """IDNA processing raised an exception."""
supp_kwargs = set(['idna_exception']) supp_kwargs = {'idna_exception'}
fmt = "IDNA processing exception: {idna_exception}" fmt = "IDNA processing exception: {idna_exception}"
class IDNACodec(object):
class IDNACodec(object):
"""Abstract base class for IDNA encoder/decoders.""" """Abstract base class for IDNA encoder/decoders."""
def __init__(self): def __init__(self):
@@ -131,21 +125,24 @@ class IDNACodec(object):
label = maybe_decode(label) label = maybe_decode(label)
return _escapify(label, True) return _escapify(label, True)
class IDNA2003Codec(IDNACodec):
class IDNA2003Codec(IDNACodec):
"""IDNA 2003 encoder/decoder.""" """IDNA 2003 encoder/decoder."""
def __init__(self, strict_decode=False): def __init__(self, strict_decode=False):
"""Initialize the IDNA 2003 encoder/decoder. """Initialize the IDNA 2003 encoder/decoder.
@param strict_decode: If True, then IDNA2003 checking is done when
decoding. This can cause failures if the name was encoded with *strict_decode* is a ``bool``. If `True`, then IDNA2003 checking
IDNA2008. The default is False. is done when decoding. This can cause failures if the name
@type strict_decode: bool was encoded with IDNA2008. The default is `False`.
""" """
super(IDNA2003Codec, self).__init__() super(IDNA2003Codec, self).__init__()
self.strict_decode = strict_decode self.strict_decode = strict_decode
def encode(self, label): def encode(self, label):
"""Encode *label*."""
if label == '': if label == '':
return b'' return b''
try: try:
@@ -154,6 +151,7 @@ class IDNA2003Codec(IDNACodec):
raise LabelTooLong raise LabelTooLong
def decode(self, label): def decode(self, label):
"""Decode *label*."""
if not self.strict_decode: if not self.strict_decode:
return super(IDNA2003Codec, self).decode(label) return super(IDNA2003Codec, self).decode(label)
if label == b'': if label == b'':
@@ -163,34 +161,34 @@ class IDNA2003Codec(IDNACodec):
except Exception as e: except Exception as e:
raise IDNAException(idna_exception=e) raise IDNAException(idna_exception=e)
class IDNA2008Codec(IDNACodec):
"""IDNA 2008 encoder/decoder.""" class IDNA2008Codec(IDNACodec):
"""IDNA 2008 encoder/decoder.
*uts_46* is a ``bool``. If True, apply Unicode IDNA
compatibility processing as described in Unicode Technical
Standard #46 (http://unicode.org/reports/tr46/).
If False, do not apply the mapping. The default is False.
*transitional* is a ``bool``: If True, use the
"transitional" mode described in Unicode Technical Standard
#46. The default is False.
*allow_pure_ascii* is a ``bool``. If True, then a label which
consists of only ASCII characters is allowed. This is less
strict than regular IDNA 2008, but is also necessary for mixed
names, e.g. a name with starting with "_sip._tcp." and ending
in an IDN suffix which would otherwise be disallowed. The
default is False.
*strict_decode* is a ``bool``: If True, then IDNA2008 checking
is done when decoding. This can cause failures if the name
was encoded with IDNA2003. The default is False.
"""
def __init__(self, uts_46=False, transitional=False, def __init__(self, uts_46=False, transitional=False,
allow_pure_ascii=False, strict_decode=False): allow_pure_ascii=False, strict_decode=False):
"""Initialize the IDNA 2008 encoder/decoder. """Initialize the IDNA 2008 encoder/decoder."""
@param uts_46: If True, apply Unicode IDNA compatibility processing
as described in Unicode Technical Standard #46
(U{http://unicode.org/reports/tr46/}). This parameter is only
meaningful if IDNA 2008 is in use. If False, do not apply
the mapping. The default is False
@type uts_46: bool
@param transitional: If True, use the "transitional" mode described
in Unicode Technical Standard #46. This parameter is only
meaningful if IDNA 2008 is in use. The default is False.
@type transitional: bool
@param allow_pure_ascii: If True, then a label which
consists of only ASCII characters is allowed. This is less strict
than regular IDNA 2008, but is also necessary for mixed names,
e.g. a name with starting with "_sip._tcp." and ending in an IDN
suffixm which would otherwise be disallowed. The default is False
@type allow_pure_ascii: bool
@param strict_decode: If True, then IDNA2008 checking is done when
decoding. This can cause failures if the name was encoded with
IDNA2003. The default is False.
@type strict_decode: bool
"""
super(IDNA2008Codec, self).__init__() super(IDNA2008Codec, self).__init__()
self.uts_46 = uts_46 self.uts_46 = uts_46
self.transitional = transitional self.transitional = transitional
@@ -277,9 +275,14 @@ def _escapify(label, unicode_mode=False):
def _validate_labels(labels): def _validate_labels(labels):
"""Check for empty labels in the middle of a label sequence, """Check for empty labels in the middle of a label sequence,
labels that are too long, and for too many labels. labels that are too long, and for too many labels.
@raises NameTooLong: the name as a whole is too long
@raises EmptyLabel: a label is empty (i.e. the root label) and appears Raises ``dns.name.NameTooLong`` if the name as a whole is too long.
in a position other than the end of the label sequence"""
Raises ``dns.name.EmptyLabel`` if a label is empty (i.e. the root
label) and appears in a position other than the end of the label
sequence
"""
l = len(labels) l = len(labels)
total = 0 total = 0
@@ -299,7 +302,12 @@ def _validate_labels(labels):
raise EmptyLabel raise EmptyLabel
def _ensure_bytes(label): def _maybe_convert_to_binary(label):
"""If label is ``text``, convert it to ``binary``. If it is already
``binary`` just return it.
"""
if isinstance(label, binary_type): if isinstance(label, binary_type):
return label return label
if isinstance(label, text_type): if isinstance(label, text_type):
@@ -311,24 +319,23 @@ class Name(object):
"""A DNS name. """A DNS name.
The dns.name.Name class represents a DNS name as a tuple of labels. The dns.name.Name class represents a DNS name as a tuple of
Instances of the class are immutable. labels. Each label is a `binary` in DNS wire format. Instances
of the class are immutable.
@ivar labels: The tuple of labels in the name. Each label is a string of """
up to 63 octets."""
__slots__ = ['labels'] __slots__ = ['labels']
def __init__(self, labels): def __init__(self, labels):
"""Initialize a domain name from a list of labels. """*labels* is any iterable whose values are ``text`` or ``binary``.
@param labels: the labels
@type labels: any iterable whose values are strings
""" """
labels = [_ensure_bytes(x) for x in labels]
labels = [_maybe_convert_to_binary(x) for x in labels]
super(Name, self).__setattr__('labels', tuple(labels)) super(Name, self).__setattr__('labels', tuple(labels))
_validate_labels(self.labels) _validate_labels(self.labels)
def __setattr__(self, name, value): def __setattr__(self, name, value):
# Names are immutable
raise TypeError("object doesn't support attribute assignment") raise TypeError("object doesn't support attribute assignment")
def __copy__(self): def __copy__(self):
@@ -338,6 +345,7 @@ class Name(object):
return Name(copy.deepcopy(self.labels, memo)) return Name(copy.deepcopy(self.labels, memo))
def __getstate__(self): def __getstate__(self):
# Names can be pickled
return {'labels': self.labels} return {'labels': self.labels}
def __setstate__(self, state): def __setstate__(self, state):
@@ -346,21 +354,24 @@ class Name(object):
def is_absolute(self): def is_absolute(self):
"""Is the most significant label of this name the root label? """Is the most significant label of this name the root label?
@rtype: bool
Returns a ``bool``.
""" """
return len(self.labels) > 0 and self.labels[-1] == b'' return len(self.labels) > 0 and self.labels[-1] == b''
def is_wild(self): def is_wild(self):
"""Is this name wild? (I.e. Is the least significant label '*'?) """Is this name wild? (I.e. Is the least significant label '*'?)
@rtype: bool
Returns a ``bool``.
""" """
return len(self.labels) > 0 and self.labels[0] == b'*' return len(self.labels) > 0 and self.labels[0] == b'*'
def __hash__(self): def __hash__(self):
"""Return a case-insensitive hash of the name. """Return a case-insensitive hash of the name.
@rtype: int
Returns an ``int``.
""" """
h = long(0) h = long(0)
@@ -370,20 +381,35 @@ class Name(object):
return int(h % maxint) return int(h % maxint)
def fullcompare(self, other): def fullcompare(self, other):
"""Compare two names, returning a 3-tuple (relation, order, nlabels). """Compare two names, returning a 3-tuple
``(relation, order, nlabels)``.
I{relation} describes the relation ship between the names, *relation* describes the relation ship between the names,
and is one of: dns.name.NAMERELN_NONE, and is one of: ``dns.name.NAMERELN_NONE``,
dns.name.NAMERELN_SUPERDOMAIN, dns.name.NAMERELN_SUBDOMAIN, ``dns.name.NAMERELN_SUPERDOMAIN``, ``dns.name.NAMERELN_SUBDOMAIN``,
dns.name.NAMERELN_EQUAL, or dns.name.NAMERELN_COMMONANCESTOR ``dns.name.NAMERELN_EQUAL``, or ``dns.name.NAMERELN_COMMONANCESTOR``.
I{order} is < 0 if self < other, > 0 if self > other, and == *order* is < 0 if *self* < *other*, > 0 if *self* > *other*, and ==
0 if self == other. A relative name is always less than an 0 if *self* == *other*. A relative name is always less than an
absolute name. If both names have the same relativity, then absolute name. If both names have the same relativity, then
the DNSSEC order relation is used to order them. the DNSSEC order relation is used to order them.
I{nlabels} is the number of significant labels that the two names *nlabels* is the number of significant labels that the two names
have in common. have in common.
Here are some examples. Names ending in "." are absolute names,
those not ending in "." are relative names.
============= ============= =========== ===== =======
self other relation order nlabels
============= ============= =========== ===== =======
www.example. www.example. equal 0 3
www.example. example. subdomain > 0 2
example. www.example. superdomain < 0 2
example1.com. example2.com. common anc. < 0 2
example1 example2. none < 0 0
example1. example2 none > 0 0
============= ============= =========== ===== =======
""" """
sabs = self.is_absolute() sabs = self.is_absolute()
@@ -433,8 +459,10 @@ class Name(object):
def is_subdomain(self, other): def is_subdomain(self, other):
"""Is self a subdomain of other? """Is self a subdomain of other?
The notion of subdomain includes equality. Note that the notion of subdomain includes equality, e.g.
@rtype: bool "dnpython.org" is a subdomain of itself.
Returns a ``bool``.
""" """
(nr, o, nl) = self.fullcompare(other) (nr, o, nl) = self.fullcompare(other)
@@ -445,8 +473,10 @@ class Name(object):
def is_superdomain(self, other): def is_superdomain(self, other):
"""Is self a superdomain of other? """Is self a superdomain of other?
The notion of subdomain includes equality. Note that the notion of superdomain includes equality, e.g.
@rtype: bool "dnpython.org" is a superdomain of itself.
Returns a ``bool``.
""" """
(nr, o, nl) = self.fullcompare(other) (nr, o, nl) = self.fullcompare(other)
@@ -457,7 +487,6 @@ class Name(object):
def canonicalize(self): def canonicalize(self):
"""Return a name which is equal to the current name, but is in """Return a name which is equal to the current name, but is in
DNSSEC canonical form. DNSSEC canonical form.
@rtype: dns.name.Name object
""" """
return Name([x.lower() for x in self.labels]) return Name([x.lower() for x in self.labels])
@@ -505,10 +534,13 @@ class Name(object):
return self.to_text(False) return self.to_text(False)
def to_text(self, omit_final_dot=False): def to_text(self, omit_final_dot=False):
"""Convert name to text format. """Convert name to DNS text format.
@param omit_final_dot: If True, don't emit the final dot (denoting the
root label) for absolute names. The default is False. *omit_final_dot* is a ``bool``. If True, don't emit the final
@rtype: string dot (denoting the root label) for absolute names. The default
is False.
Returns a ``text``.
""" """
if len(self.labels) == 0: if len(self.labels) == 0:
@@ -527,16 +559,17 @@ class Name(object):
IDN ACE labels are converted to Unicode. IDN ACE labels are converted to Unicode.
@param omit_final_dot: If True, don't emit the final dot (denoting the *omit_final_dot* is a ``bool``. If True, don't emit the final
root label) for absolute names. The default is False. dot (denoting the root label) for absolute names. The default
@type omit_final_dot: bool is False.
@param idna_codec: IDNA encoder/decoder. If None, the *idna_codec* specifies the IDNA encoder/decoder. If None, the
IDNA_2003_Practical encoder/decoder is used. The IDNA_2003_Practical dns.name.IDNA_2003_Practical encoder/decoder is used.
decoder does not impose any policy, it just decodes punycode, so if The IDNA_2003_Practical decoder does
you don't want checking for compliance, you can use this decoder for not impose any policy, it just decodes punycode, so if you
IDNA2008 as well. don't want checking for compliance, you can use this decoder
@type idna_codec: dns.name.IDNA for IDNA2008 as well.
@rtype: string
Returns a ``text``.
""" """
if len(self.labels) == 0: if len(self.labels) == 0:
@@ -554,15 +587,18 @@ class Name(object):
def to_digestable(self, origin=None): def to_digestable(self, origin=None):
"""Convert name to a format suitable for digesting in hashes. """Convert name to a format suitable for digesting in hashes.
The name is canonicalized and converted to uncompressed wire format. The name is canonicalized and converted to uncompressed wire
format. All names in wire format are absolute. If the name
is a relative name, then an origin must be supplied.
@param origin: If the name is relative and origin is not None, then *origin* is a ``dns.name.Name`` or ``None``. If the name is
origin will be appended to it. relative and origin is not ``None``, then origin will be appended
@type origin: dns.name.Name object to the name.
@raises NeedAbsoluteNameOrOrigin: All names in wire format are
absolute. If self is a relative name, then an origin must be supplied; Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is
if it is missing, then this exception is raised relative and no origin was provided.
@rtype: string
Returns a ``binary``.
""" """
if not self.is_absolute(): if not self.is_absolute():
@@ -579,19 +615,21 @@ class Name(object):
def to_wire(self, file=None, compress=None, origin=None): def to_wire(self, file=None, compress=None, origin=None):
"""Convert name to wire format, possibly compressing it. """Convert name to wire format, possibly compressing it.
@param file: the file where the name is emitted (typically *file* is the file where the name is emitted (typically a
a BytesIO file). If None, a string containing the wire name BytesIO file). If ``None`` (the default), a ``binary``
will be returned. containing the wire name will be returned.
@type file: file or None
@param compress: The compression table. If None (the default) names *compress*, a ``dict``, is the compression table to use. If
will not be compressed. ``None`` (the default), names will not be compressed.
@type compress: dict
@param origin: If the name is relative and origin is not None, then *origin* is a ``dns.name.Name`` or ``None``. If the name is
origin will be appended to it. relative and origin is not ``None``, then *origin* will be appended
@type origin: dns.name.Name object to it.
@raises NeedAbsoluteNameOrOrigin: All names in wire format are
absolute. If self is a relative name, then an origin must be supplied; Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is
if it is missing, then this exception is raised relative and no origin was provided.
Returns a ``binary`` or ``None``.
""" """
if file is None: if file is None:
@@ -634,7 +672,8 @@ class Name(object):
def __len__(self): def __len__(self):
"""The length of the name (in labels). """The length of the name (in labels).
@rtype: int
Returns an ``int``.
""" """
return len(self.labels) return len(self.labels)
@@ -649,14 +688,14 @@ class Name(object):
return self.relativize(other) return self.relativize(other)
def split(self, depth): def split(self, depth):
"""Split a name into a prefix and suffix at depth. """Split a name into a prefix and suffix names at the specified depth.
@param depth: the number of labels in the suffix *depth* is an ``int`` specifying the number of labels in the suffix
@type depth: int
@raises ValueError: the depth was not >= 0 and <= the length of the Raises ``ValueError`` if *depth* was not >= 0 and <= the length of the
name. name.
@returns: the tuple (prefix, suffix)
@rtype: tuple Returns the tuple ``(prefix, suffix)``.
""" """
l = len(self.labels) l = len(self.labels)
@@ -671,9 +710,11 @@ class Name(object):
def concatenate(self, other): def concatenate(self, other):
"""Return a new name which is the concatenation of self and other. """Return a new name which is the concatenation of self and other.
@rtype: dns.name.Name object
@raises AbsoluteConcatenation: self is absolute and other is Raises ``dns.name.AbsoluteConcatenation`` if the name is
not the empty name absolute and *other* is not the empty name.
Returns a ``dns.name.Name``.
""" """
if self.is_absolute() and len(other) > 0: if self.is_absolute() and len(other) > 0:
@@ -683,9 +724,14 @@ class Name(object):
return Name(labels) return Name(labels)
def relativize(self, origin): def relativize(self, origin):
"""If self is a subdomain of origin, return a new name which is self """If the name is a subdomain of *origin*, return a new name which is
relative to origin. Otherwise return self. the name relative to origin. Otherwise return the name.
@rtype: dns.name.Name object
For example, relativizing ``www.dnspython.org.`` to origin
``dnspython.org.`` returns the name ``www``. Relativizing ``example.``
to origin ``dnspython.org.`` returns ``example.``.
Returns a ``dns.name.Name``.
""" """
if origin is not None and self.is_subdomain(origin): if origin is not None and self.is_subdomain(origin):
@@ -694,9 +740,14 @@ class Name(object):
return self return self
def derelativize(self, origin): def derelativize(self, origin):
"""If self is a relative name, return a new name which is the """If the name is a relative name, return a new name which is the
concatenation of self and origin. Otherwise return self. concatenation of the name and origin. Otherwise return the name.
@rtype: dns.name.Name object
For example, derelativizing ``www`` to origin ``dnspython.org.``
returns the name ``www.dnspython.org.``. Derelativizing ``example.``
to origin ``dnspython.org.`` returns ``example.``.
Returns a ``dns.name.Name``.
""" """
if not self.is_absolute(): if not self.is_absolute():
@@ -705,11 +756,14 @@ class Name(object):
return self return self
def choose_relativity(self, origin=None, relativize=True): def choose_relativity(self, origin=None, relativize=True):
"""Return a name with the relativity desired by the caller. If """Return a name with the relativity desired by the caller.
origin is None, then self is returned. Otherwise, if
relativize is true the name is relativized, and if relativize is If *origin* is ``None``, then the name is returned.
false the name is derelativized. Otherwise, if *relativize* is ``True`` the name is
@rtype: dns.name.Name object relativized, and if *relativize* is ``False`` the name is
derelativized.
Returns a ``dns.name.Name``.
""" """
if origin: if origin:
@@ -722,31 +776,41 @@ class Name(object):
def parent(self): def parent(self):
"""Return the parent of the name. """Return the parent of the name.
@rtype: dns.name.Name object
@raises NoParent: the name is either the root name or the empty name, For example, the parent of ``www.dnspython.org.`` is ``dnspython.org``.
and thus has no parent.
Raises ``dns.name.NoParent`` if the name is either the root name or the
empty name, and thus has no parent.
Returns a ``dns.name.Name``.
""" """
if self == root or self == empty: if self == root or self == empty:
raise NoParent raise NoParent
return Name(self.labels[1:]) return Name(self.labels[1:])
#: The root name, '.'
root = Name([b'']) root = Name([b''])
empty = Name([])
#: The empty name.
empty = Name([])
def from_unicode(text, origin=root, idna_codec=None): def from_unicode(text, origin=root, idna_codec=None):
"""Convert unicode text into a Name object. """Convert unicode text into a Name object.
Labels are encoded in IDN ACE form. Labels are encoded in IDN ACE form according to rules specified by
the IDNA codec.
@param text: The text to convert into a name. *text*, a ``text``, is the text to convert into a name.
@type text: Unicode string
@param origin: The origin to append to non-absolute names. *origin*, a ``dns.name.Name``, specifies the origin to
@type origin: dns.name.Name append to non-absolute names. The default is the root name.
@param idna_codec: IDNA encoder/decoder. If None, the default IDNA 2003
encoder/decoder is used. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
@type idna_codec: dns.name.IDNA encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder
@rtype: dns.name.Name object is used.
Returns a ``dns.name.Name``.
""" """
if not isinstance(text, text_type): if not isinstance(text, text_type):
@@ -809,14 +873,16 @@ def from_unicode(text, origin=root, idna_codec=None):
def from_text(text, origin=root, idna_codec=None): def from_text(text, origin=root, idna_codec=None):
"""Convert text into a Name object. """Convert text into a Name object.
@param text: The text to convert into a name. *text*, a ``text``, is the text to convert into a name.
@type text: string
@param origin: The origin to append to non-absolute names. *origin*, a ``dns.name.Name``, specifies the origin to
@type origin: dns.name.Name append to non-absolute names. The default is the root name.
@param idna_codec: IDNA encoder/decoder. If None, the default IDNA 2003
encoder/decoder is used. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
@type idna_codec: dns.name.IDNA encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder
@rtype: dns.name.Name object is used.
Returns a ``dns.name.Name``.
""" """
if isinstance(text, text_type): if isinstance(text, text_type):
@@ -878,17 +944,21 @@ def from_text(text, origin=root, idna_codec=None):
def from_wire(message, current): def from_wire(message, current):
"""Convert possibly compressed wire format into a Name. """Convert possibly compressed wire format into a Name.
@param message: the entire DNS message
@type message: string *message* is a ``binary`` containing an entire DNS message in DNS
@param current: the offset of the beginning of the name from the start wire form.
of the message
@type current: int *current*, an ``int``, is the offset of the beginning of the name
@raises dns.name.BadPointer: a compression pointer did not point backwards from the start of the message
in the message
@raises dns.name.BadLabelType: an invalid label type was encountered. Raises ``dns.name.BadPointer`` if a compression pointer did not
@returns: a tuple consisting of the name that was read and the number point backwards in the message.
of bytes of the wire format message which were consumed reading it
@rtype: (dns.name.Name object, int) tuple Raises ``dns.name.BadLabelType`` if an invalid label type was encountered.
Returns a ``(dns.name.Name, int)`` tuple consisting of the name
that was read and the number of bytes of the wire format message
which were consumed reading it.
""" """
if not isinstance(message, binary_type): if not isinstance(message, binary_type):

35
src/dns/name.pyi Normal file
View File

@@ -0,0 +1,35 @@
from typing import Optional, Union, Tuple, Iterable, List
class Name:
def is_subdomain(self, o : Name) -> bool: ...
def is_superdomain(self, o : Name) -> bool: ...
def __init__(self, labels : Iterable[Union[bytes,str]]) -> None:
self.labels : List[bytes]
def is_absolute(self) -> bool: ...
def is_wild(self) -> bool: ...
def fullcompare(self, other) -> Tuple[int,int,int]: ...
def canonicalize(self) -> Name: ...
def __lt__(self, other : Name): ...
def __le__(self, other : Name): ...
def __ge__(self, other : Name): ...
def __gt__(self, other : Name): ...
def to_text(self, omit_final_dot=False) -> str: ...
def to_unicode(self, omit_final_dot=False, idna_codec=None) -> str: ...
def to_digestable(self, origin=None) -> bytes: ...
def to_wire(self, file=None, compress=None, origin=None) -> Optional[bytes]: ...
def __add__(self, other : Name): ...
def __sub__(self, other : Name): ...
def split(self, depth) -> List[Tuple[str,str]]: ...
def concatenate(self, other : Name) -> Name: ...
def relativize(self, origin): ...
def derelativize(self, origin): ...
def choose_relativity(self, origin : Optional[Name] = None, relativize=True): ...
def parent(self) -> Name: ...
class IDNACodec:
pass
def from_text(text, origin : Optional[Name] = Name('.'), idna_codec : Optional[IDNACodec] = None) -> Name:
...
empty : Name

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2017 Nominum, Inc.
# Copyright (C) 2016 Coresec Systems AB # Copyright (C) 2016 Coresec Systems AB
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
@@ -31,20 +33,20 @@ from ._compat import xrange
class NameDict(collections.MutableMapping): class NameDict(collections.MutableMapping):
"""A dictionary whose keys are dns.name.Name objects. """A dictionary whose keys are dns.name.Name objects.
@ivar max_depth: the maximum depth of the keys that have ever been
added to the dictionary. In addition to being like a regular Python dictionary, this
@type max_depth: int dictionary can also get the deepest match for a given key.
@ivar max_depth_items: the number of items of maximum depth
@type max_depth_items: int
""" """
__slots__ = ["max_depth", "max_depth_items", "__store"] __slots__ = ["max_depth", "max_depth_items", "__store"]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(NameDict, self).__init__()
self.__store = dict() self.__store = dict()
#: the maximum depth of the keys that have ever been added
self.max_depth = 0 self.max_depth = 0
#: the number of items of maximum depth
self.max_depth_items = 0 self.max_depth_items = 0
self.update(dict(*args, **kwargs)) self.update(dict(*args, **kwargs))
@@ -83,14 +85,16 @@ class NameDict(collections.MutableMapping):
return key in self.__store return key in self.__store
def get_deepest_match(self, name): def get_deepest_match(self, name):
"""Find the deepest match to I{name} in the dictionary. """Find the deepest match to *fname* in the dictionary.
The deepest match is the longest name in the dictionary which is The deepest match is the longest name in the dictionary which is
a superdomain of I{name}. a superdomain of *name*. Note that *superdomain* includes matching
*name* itself.
@param name: the name *name*, a ``dns.name.Name``, the name to find.
@type name: dns.name.Name object
@rtype: (key, value) tuple Returns a ``(key, value)`` where *key* is the deepest
``dns.name.Name``, and *value* is the value associated with *key*.
""" """
depth = len(name) depth = len(name)

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2001-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -24,19 +26,12 @@ import dns.renderer
class Node(object): class Node(object):
"""A DNS node. """A Node is a set of rdatasets."""
A node is a set of rdatasets
@ivar rdatasets: the node's rdatasets
@type rdatasets: list of dns.rdataset.Rdataset objects"""
__slots__ = ['rdatasets'] __slots__ = ['rdatasets']
def __init__(self): def __init__(self):
"""Initialize a DNS node. #: the set of rdatsets, represented as a list.
"""
self.rdatasets = [] self.rdatasets = []
def to_text(self, name, **kw): def to_text(self, name, **kw):
@@ -44,9 +39,10 @@ class Node(object):
Each rdataset at the node is printed. Any keyword arguments Each rdataset at the node is printed. Any keyword arguments
to this method are passed on to the rdataset's to_text() method. to this method are passed on to the rdataset's to_text() method.
@param name: the owner name of the rdatasets
@type name: dns.name.Name object *name*, a ``dns.name.Name`` or ``text``, the owner name of the rdatasets.
@rtype: string
Returns a ``text``.
""" """
s = StringIO() s = StringIO()
@@ -60,10 +56,6 @@ class Node(object):
return '<DNS node ' + str(id(self)) + '>' return '<DNS node ' + str(id(self)) + '>'
def __eq__(self, other): def __eq__(self, other):
"""Two nodes are equal if they have the same rdatasets.
@rtype: bool
"""
# #
# This is inefficient. Good thing we don't need to do it much. # This is inefficient. Good thing we don't need to do it much.
# #
@@ -89,11 +81,11 @@ class Node(object):
"""Find an rdataset matching the specified properties in the """Find an rdataset matching the specified properties in the
current node. current node.
@param rdclass: The class of the rdataset *rdclass*, an ``int``, the class of the rdataset.
@type rdclass: int
@param rdtype: The type of the rdataset *rdtype*, an ``int``, the type of the rdataset.
@type rdtype: int
@param covers: The covered type. Usually this value is *covers*, an ``int``, the covered type. Usually this value is
dns.rdatatype.NONE, but if the rdtype is dns.rdatatype.SIG or dns.rdatatype.NONE, but if the rdtype is dns.rdatatype.SIG or
dns.rdatatype.RRSIG, then the covers value will be the rdata dns.rdatatype.RRSIG, then the covers value will be the rdata
type the SIG/RRSIG covers. The library treats the SIG and RRSIG type the SIG/RRSIG covers. The library treats the SIG and RRSIG
@@ -101,12 +93,13 @@ class Node(object):
types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much
easier to work with than if RRSIGs covering different rdata easier to work with than if RRSIGs covering different rdata
types were aggregated into a single RRSIG rdataset. types were aggregated into a single RRSIG rdataset.
@type covers: int
@param create: If True, create the rdataset if it is not found. *create*, a ``bool``. If True, create the rdataset if it is not found.
@type create: bool
@raises KeyError: An rdataset of the desired type and class does Raises ``KeyError`` if an rdataset of the desired type and class does
not exist and I{create} is not True. not exist and *create* is not ``True``.
@rtype: dns.rdataset.Rdataset object
Returns a ``dns.rdataset.Rdataset``.
""" """
for rds in self.rdatasets: for rds in self.rdatasets:
@@ -124,17 +117,24 @@ class Node(object):
current node. current node.
None is returned if an rdataset of the specified type and None is returned if an rdataset of the specified type and
class does not exist and I{create} is not True. class does not exist and *create* is not ``True``.
@param rdclass: The class of the rdataset *rdclass*, an ``int``, the class of the rdataset.
@type rdclass: int
@param rdtype: The type of the rdataset *rdtype*, an ``int``, the type of the rdataset.
@type rdtype: int
@param covers: The covered type. *covers*, an ``int``, the covered type. Usually this value is
@type covers: int dns.rdatatype.NONE, but if the rdtype is dns.rdatatype.SIG or
@param create: If True, create the rdataset if it is not found. dns.rdatatype.RRSIG, then the covers value will be the rdata
@type create: bool type the SIG/RRSIG covers. The library treats the SIG and RRSIG
@rtype: dns.rdataset.Rdataset object or None types as if they were a family of
types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much
easier to work with than if RRSIGs covering different rdata
types were aggregated into a single RRSIG rdataset.
*create*, a ``bool``. If True, create the rdataset if it is not found.
Returns a ``dns.rdataset.Rdataset`` or ``None``.
""" """
try: try:
@@ -149,12 +149,11 @@ class Node(object):
If a matching rdataset does not exist, it is not an error. If a matching rdataset does not exist, it is not an error.
@param rdclass: The class of the rdataset *rdclass*, an ``int``, the class of the rdataset.
@type rdclass: int
@param rdtype: The type of the rdataset *rdtype*, an ``int``, the type of the rdataset.
@type rdtype: int
@param covers: The covered type. *covers*, an ``int``, the covered type.
@type covers: int
""" """
rds = self.get_rdataset(rdclass, rdtype, covers) rds = self.get_rdataset(rdclass, rdtype, covers)
@@ -164,11 +163,16 @@ class Node(object):
def replace_rdataset(self, replacement): def replace_rdataset(self, replacement):
"""Replace an rdataset. """Replace an rdataset.
It is not an error if there is no rdataset matching I{replacement}. It is not an error if there is no rdataset matching *replacement*.
Ownership of the I{replacement} object is transferred to the node; Ownership of the *replacement* object is transferred to the node;
in other words, this method does not store a copy of I{replacement} in other words, this method does not store a copy of *replacement*
at the node, it stores I{replacement} itself. at the node, it stores *replacement* itself.
*replacement*, a ``dns.rdataset.Rdataset``.
Raises ``ValueError`` if *replacement* is not a
``dns.rdataset.Rdataset``.
""" """
if not isinstance(replacement, dns.rdataset.Rdataset): if not isinstance(replacement, dns.rdataset.Rdataset):

17
src/dns/node.pyi Normal file
View File

@@ -0,0 +1,17 @@
from typing import List, Optional, Union
from . import rdataset, rdatatype, name
class Node:
def __init__(self):
self.rdatasets : List[rdataset.Rdataset]
def to_text(self, name : Union[str,name.Name], **kw) -> str:
...
def find_rdataset(self, rdclass : int, rdtype : int, covers=rdatatype.NONE,
create=False) -> rdataset.Rdataset:
...
def get_rdataset(self, rdclass : int, rdtype : int, covers=rdatatype.NONE,
create=False) -> Optional[rdataset.Rdataset]:
...
def delete_rdataset(self, rdclass : int, rdtype : int, covers=rdatatype.NONE):
...
def replace_rdataset(self, replacement : rdataset.Rdataset) -> None:
...

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2001-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -17,10 +19,15 @@
import dns.exception import dns.exception
#: Query
QUERY = 0 QUERY = 0
#: Inverse Query (historical)
IQUERY = 1 IQUERY = 1
#: Server Status (unspecified and unimplemented anywhere)
STATUS = 2 STATUS = 2
#: Notify
NOTIFY = 4 NOTIFY = 4
#: Dynamic Update
UPDATE = 5 UPDATE = 5
_by_text = { _by_text = {
@@ -35,21 +42,21 @@ _by_text = {
# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that # cannot make any mistakes (e.g. omissions, cut-and-paste errors) that
# would cause the mapping not to be true inverse. # would cause the mapping not to be true inverse.
_by_value = dict((y, x) for x, y in _by_text.items()) _by_value = {y: x for x, y in _by_text.items()}
class UnknownOpcode(dns.exception.DNSException): class UnknownOpcode(dns.exception.DNSException):
"""An DNS opcode is unknown.""" """An DNS opcode is unknown."""
def from_text(text): def from_text(text):
"""Convert text into an opcode. """Convert text into an opcode.
@param text: the textual opcode *text*, a ``text``, the textual opcode
@type text: string
@raises UnknownOpcode: the opcode is unknown Raises ``dns.opcode.UnknownOpcode`` if the opcode is unknown.
@rtype: int
Returns an ``int``.
""" """
if text.isdigit(): if text.isdigit():
@@ -65,8 +72,9 @@ def from_text(text):
def from_flags(flags): def from_flags(flags):
"""Extract an opcode from DNS message flags. """Extract an opcode from DNS message flags.
@param flags: int *flags*, an ``int``, the DNS flags.
@rtype: int
Returns an ``int``.
""" """
return (flags & 0x7800) >> 11 return (flags & 0x7800) >> 11
@@ -75,7 +83,10 @@ def from_flags(flags):
def to_flags(value): def to_flags(value):
"""Convert an opcode to a value suitable for ORing into DNS message """Convert an opcode to a value suitable for ORing into DNS message
flags. flags.
@rtype: int
*value*, an ``int``, the DNS opcode value.
Returns an ``int``.
""" """
return (value << 11) & 0x7800 return (value << 11) & 0x7800
@@ -84,10 +95,11 @@ def to_flags(value):
def to_text(value): def to_text(value):
"""Convert an opcode to text. """Convert an opcode to text.
@param value: the opcdoe *value*, an ``int`` the opcode value,
@type value: int
@raises UnknownOpcode: the opcode is unknown Raises ``dns.opcode.UnknownOpcode`` if the opcode is unknown.
@rtype: string
Returns a ``text``.
""" """
text = _by_value.get(value) text = _by_value.get(value)
@@ -97,11 +109,11 @@ def to_text(value):
def is_update(flags): def is_update(flags):
"""True if the opcode in flags is UPDATE. """Is the opcode in flags UPDATE?
@param flags: DNS flags *flags*, an ``int``, the DNS message flags.
@type flags: int
@rtype: bool Returns a ``bool``.
""" """
return from_flags(flags) == UPDATE return from_flags(flags) == UPDATE

0
src/dns/py.typed Normal file
View File

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -28,11 +30,12 @@ import dns.exception
import dns.inet import dns.inet
import dns.name import dns.name
import dns.message import dns.message
import dns.rcode
import dns.rdataclass import dns.rdataclass
import dns.rdatatype import dns.rdatatype
from ._compat import long, string_types from ._compat import long, string_types, PY3
if sys.version_info > (3,): if PY3:
select_error = OSError select_error = OSError
else: else:
select_error = select.error select_error = select.error
@@ -42,34 +45,36 @@ else:
socket_factory = socket.socket socket_factory = socket.socket
class UnexpectedSource(dns.exception.DNSException): class UnexpectedSource(dns.exception.DNSException):
"""A DNS query response came from an unexpected address or port.""" """A DNS query response came from an unexpected address or port."""
class BadResponse(dns.exception.FormError): class BadResponse(dns.exception.FormError):
"""A DNS query response does not respond to the question asked.""" """A DNS query response does not respond to the question asked."""
class TransferError(dns.exception.DNSException):
"""A zone transfer response got a non-zero rcode."""
def __init__(self, rcode):
message = 'Zone transfer error: %s' % dns.rcode.to_text(rcode)
super(TransferError, self).__init__(message)
self.rcode = rcode
def _compute_expiration(timeout): def _compute_expiration(timeout):
if timeout is None: if timeout is None:
return None return None
else: else:
return time.time() + timeout return time.time() + timeout
# This module can use either poll() or select() as the "polling backend".
#
# A backend function takes an fd, bools for readability, writablity, and
# error detection, and a timeout.
def _poll_for(fd, readable, writable, error, timeout): def _poll_for(fd, readable, writable, error, timeout):
"""Poll polling backend. """Poll polling backend."""
@param fd: File descriptor
@type fd: int
@param readable: Whether to wait for readability
@type readable: bool
@param writable: Whether to wait for writability
@type writable: bool
@param timeout: Deadline timeout (expiration time, in seconds)
@type timeout: float
@return True on success, False on timeout
"""
event_mask = 0 event_mask = 0
if readable: if readable:
event_mask |= select.POLLIN event_mask |= select.POLLIN
@@ -90,17 +95,8 @@ def _poll_for(fd, readable, writable, error, timeout):
def _select_for(fd, readable, writable, error, timeout): def _select_for(fd, readable, writable, error, timeout):
"""Select polling backend. """Select polling backend."""
@param fd: File descriptor
@type fd: int
@param readable: Whether to wait for readability
@type readable: bool
@param writable: Whether to wait for writability
@type writable: bool
@param timeout: Deadline timeout (expiration time, in seconds)
@type timeout: float
@return True on success, False on timeout
"""
rset, wset, xset = [], [], [] rset, wset, xset = [], [], []
if readable: if readable:
@@ -119,6 +115,10 @@ def _select_for(fd, readable, writable, error, timeout):
def _wait_for(fd, readable, writable, error, expiration): def _wait_for(fd, readable, writable, error, expiration):
# Use the selected polling backend to wait for any of the specified
# events. An "expiration" absolute time is converted into a relative
# timeout.
done = False done = False
while not done: while not done:
if expiration is None: if expiration is None:
@@ -137,9 +137,8 @@ def _wait_for(fd, readable, writable, error, expiration):
def _set_polling_backend(fn): def _set_polling_backend(fn):
""" # Internal API. Do not use.
Internal API. Do not use.
"""
global _polling_backend global _polling_backend
_polling_backend = fn _polling_backend = fn
@@ -165,8 +164,11 @@ def _addresses_equal(af, a1, a2):
# Convert the first value of the tuple, which is a textual format # Convert the first value of the tuple, which is a textual format
# address into binary form, so that we are not confused by different # address into binary form, so that we are not confused by different
# textual representations of the same address # textual representations of the same address
try:
n1 = dns.inet.inet_pton(af, a1[0]) n1 = dns.inet.inet_pton(af, a1[0])
n2 = dns.inet.inet_pton(af, a2[0]) n2 = dns.inet.inet_pton(af, a2[0])
except dns.exception.SyntaxError:
return False
return n1 == n2 and a1[1:] == a2[1:] return n1 == n2 and a1[1:] == a2[1:]
@@ -193,68 +195,140 @@ def _destination_and_source(af, where, port, source, source_port):
return (af, destination, source) return (af, destination, source)
def udp(q, where, timeout=None, port=53, af=None, source=None, source_port=0, def send_udp(sock, what, destination, expiration=None):
ignore_unexpected=False, one_rr_per_rrset=False): """Send a DNS message to the specified UDP socket.
"""Return the response obtained after sending a query via UDP.
@param q: the query *sock*, a ``socket``.
@type q: dns.message.Message
@param where: where to send the message *what*, a ``binary`` or ``dns.message.Message``, the message to send.
@type where: string containing an IPv4 or IPv6 address
@param timeout: The number of seconds to wait before the query times out. *destination*, a destination tuple appropriate for the address family
If None, the default, wait forever. of the socket, specifying where to send the query.
@type timeout: float
@param port: The port to which to send the message. The default is 53. *expiration*, a ``float`` or ``None``, the absolute time at which
@type port: int a timeout exception should be raised. If ``None``, no timeout will
@param af: the address family to use. The default is None, which occur.
causes the address family to use to be inferred from the form of where.
If the inference attempt fails, AF_INET is used. Returns an ``(int, float)`` tuple of bytes sent and the sent time.
@type af: int
@rtype: dns.message.Message object
@param source: source address. The default is the wildcard address.
@type source: string
@param source_port: The port from which to send the message.
The default is 0.
@type source_port: int
@param ignore_unexpected: If True, ignore responses from unexpected
sources. The default is False.
@type ignore_unexpected: bool
@param one_rr_per_rrset: Put each RR into its own RRset
@type one_rr_per_rrset: bool
""" """
wire = q.to_wire() if isinstance(what, dns.message.Message):
(af, destination, source) = _destination_and_source(af, where, port, what = what.to_wire()
source, source_port) _wait_for_writable(sock, expiration)
s = socket_factory(af, socket.SOCK_DGRAM, 0) sent_time = time.time()
begin_time = None n = sock.sendto(what, destination)
try: return (n, sent_time)
expiration = _compute_expiration(timeout)
s.setblocking(0)
if source is not None: def receive_udp(sock, destination, expiration=None,
s.bind(source) ignore_unexpected=False, one_rr_per_rrset=False,
_wait_for_writable(s, expiration) keyring=None, request_mac=b'', ignore_trailing=False):
begin_time = time.time() """Read a DNS message from a UDP socket.
s.sendto(wire, destination)
*sock*, a ``socket``.
*destination*, a destination tuple appropriate for the address family
of the socket, specifying where the associated query was sent.
*expiration*, a ``float`` or ``None``, the absolute time at which
a timeout exception should be raised. If ``None``, no timeout will
occur.
*ignore_unexpected*, a ``bool``. If ``True``, ignore responses from
unexpected sources.
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
RRset.
*keyring*, a ``dict``, the keyring to use for TSIG.
*request_mac*, a ``binary``, the MAC of the request (for TSIG).
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing
junk at end of the received message.
Raises if the message is malformed, if network errors occur, of if
there is a timeout.
Returns a ``dns.message.Message`` object.
"""
wire = b''
while 1: while 1:
_wait_for_readable(s, expiration) _wait_for_readable(sock, expiration)
(wire, from_address) = s.recvfrom(65535) (wire, from_address) = sock.recvfrom(65535)
if _addresses_equal(af, from_address, destination) or \ if _addresses_equal(sock.family, from_address, destination) or \
(dns.inet.is_multicast(where) and (dns.inet.is_multicast(destination[0]) and
from_address[1:] == destination[1:]): from_address[1:] == destination[1:]):
break break
if not ignore_unexpected: if not ignore_unexpected:
raise UnexpectedSource('got a response from ' raise UnexpectedSource('got a response from '
'%s instead of %s' % (from_address, '%s instead of %s' % (from_address,
destination)) destination))
received_time = time.time()
r = dns.message.from_wire(wire, keyring=keyring, request_mac=request_mac,
one_rr_per_rrset=one_rr_per_rrset,
ignore_trailing=ignore_trailing)
return (r, received_time)
def udp(q, where, timeout=None, port=53, af=None, source=None, source_port=0,
ignore_unexpected=False, one_rr_per_rrset=False, ignore_trailing=False):
"""Return the response obtained after sending a query via UDP.
*q*, a ``dns.message.Message``, the query to send
*where*, a ``text`` containing an IPv4 or IPv6 address, where
to send the message.
*timeout*, a ``float`` or ``None``, the number of seconds to wait before the
query times out. If ``None``, the default, wait forever.
*port*, an ``int``, the port send the message to. The default is 53.
*af*, an ``int``, the address family to use. The default is ``None``,
which causes the address family to use to be inferred from the form of
*where*. If the inference attempt fails, AF_INET is used. This
parameter is historical; you need never set it.
*source*, a ``text`` containing an IPv4 or IPv6 address, specifying
the source address. The default is the wildcard address.
*source_port*, an ``int``, the port from which to send the message.
The default is 0.
*ignore_unexpected*, a ``bool``. If ``True``, ignore responses from
unexpected sources.
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
RRset.
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing
junk at end of the received message.
Returns a ``dns.message.Message``.
"""
wire = q.to_wire()
(af, destination, source) = _destination_and_source(af, where, port,
source, source_port)
s = socket_factory(af, socket.SOCK_DGRAM, 0)
received_time = None
sent_time = None
try:
expiration = _compute_expiration(timeout)
s.setblocking(0)
if source is not None:
s.bind(source)
(_, sent_time) = send_udp(s, wire, destination, expiration)
(r, received_time) = receive_udp(s, destination, expiration,
ignore_unexpected, one_rr_per_rrset,
q.keyring, q.mac, ignore_trailing)
finally: finally:
if begin_time is None: if sent_time is None or received_time is None:
response_time = 0 response_time = 0
else: else:
response_time = time.time() - begin_time response_time = received_time - sent_time
s.close() s.close()
r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac,
one_rr_per_rrset=one_rr_per_rrset)
r.time = response_time r.time = response_time
if not q.is_response(r): if not q.is_response(r):
raise BadResponse raise BadResponse
@@ -290,6 +364,67 @@ def _net_write(sock, data, expiration):
current += sock.send(data[current:]) current += sock.send(data[current:])
def send_tcp(sock, what, expiration=None):
"""Send a DNS message to the specified TCP socket.
*sock*, a ``socket``.
*what*, a ``binary`` or ``dns.message.Message``, the message to send.
*expiration*, a ``float`` or ``None``, the absolute time at which
a timeout exception should be raised. If ``None``, no timeout will
occur.
Returns an ``(int, float)`` tuple of bytes sent and the sent time.
"""
if isinstance(what, dns.message.Message):
what = what.to_wire()
l = len(what)
# copying the wire into tcpmsg is inefficient, but lets us
# avoid writev() or doing a short write that would get pushed
# onto the net
tcpmsg = struct.pack("!H", l) + what
_wait_for_writable(sock, expiration)
sent_time = time.time()
_net_write(sock, tcpmsg, expiration)
return (len(tcpmsg), sent_time)
def receive_tcp(sock, expiration=None, one_rr_per_rrset=False,
keyring=None, request_mac=b'', ignore_trailing=False):
"""Read a DNS message from a TCP socket.
*sock*, a ``socket``.
*expiration*, a ``float`` or ``None``, the absolute time at which
a timeout exception should be raised. If ``None``, no timeout will
occur.
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
RRset.
*keyring*, a ``dict``, the keyring to use for TSIG.
*request_mac*, a ``binary``, the MAC of the request (for TSIG).
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing
junk at end of the received message.
Raises if the message is malformed, if network errors occur, of if
there is a timeout.
Returns a ``dns.message.Message`` object.
"""
ldata = _net_read(sock, 2, expiration)
(l,) = struct.unpack("!H", ldata)
wire = _net_read(sock, l, expiration)
received_time = time.time()
r = dns.message.from_wire(wire, keyring=keyring, request_mac=request_mac,
one_rr_per_rrset=one_rr_per_rrset,
ignore_trailing=ignore_trailing)
return (r, received_time)
def _connect(s, address): def _connect(s, address):
try: try:
s.connect(address) s.connect(address)
@@ -305,30 +440,37 @@ def _connect(s, address):
def tcp(q, where, timeout=None, port=53, af=None, source=None, source_port=0, def tcp(q, where, timeout=None, port=53, af=None, source=None, source_port=0,
one_rr_per_rrset=False): one_rr_per_rrset=False, ignore_trailing=False):
"""Return the response obtained after sending a query via TCP. """Return the response obtained after sending a query via TCP.
@param q: the query *q*, a ``dns.message.Message``, the query to send
@type q: dns.message.Message object
@param where: where to send the message *where*, a ``text`` containing an IPv4 or IPv6 address, where
@type where: string containing an IPv4 or IPv6 address to send the message.
@param timeout: The number of seconds to wait before the query times out.
If None, the default, wait forever. *timeout*, a ``float`` or ``None``, the number of seconds to wait before the
@type timeout: float query times out. If ``None``, the default, wait forever.
@param port: The port to which to send the message. The default is 53.
@type port: int *port*, an ``int``, the port send the message to. The default is 53.
@param af: the address family to use. The default is None, which
causes the address family to use to be inferred from the form of where. *af*, an ``int``, the address family to use. The default is ``None``,
If the inference attempt fails, AF_INET is used. which causes the address family to use to be inferred from the form of
@type af: int *where*. If the inference attempt fails, AF_INET is used. This
@rtype: dns.message.Message object parameter is historical; you need never set it.
@param source: source address. The default is the wildcard address.
@type source: string *source*, a ``text`` containing an IPv4 or IPv6 address, specifying
@param source_port: The port from which to send the message. the source address. The default is the wildcard address.
*source_port*, an ``int``, the port from which to send the message.
The default is 0. The default is 0.
@type source_port: int
@param one_rr_per_rrset: Put each RR into its own RRset *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
@type one_rr_per_rrset: bool RRset.
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing
junk at end of the received message.
Returns a ``dns.message.Message``.
""" """
wire = q.to_wire() wire = q.to_wire()
@@ -336,6 +478,7 @@ def tcp(q, where, timeout=None, port=53, af=None, source=None, source_port=0,
source, source_port) source, source_port)
s = socket_factory(af, socket.SOCK_STREAM, 0) s = socket_factory(af, socket.SOCK_STREAM, 0)
begin_time = None begin_time = None
received_time = None
try: try:
expiration = _compute_expiration(timeout) expiration = _compute_expiration(timeout)
s.setblocking(0) s.setblocking(0)
@@ -343,25 +486,15 @@ def tcp(q, where, timeout=None, port=53, af=None, source=None, source_port=0,
if source is not None: if source is not None:
s.bind(source) s.bind(source)
_connect(s, destination) _connect(s, destination)
send_tcp(s, wire, expiration)
l = len(wire) (r, received_time) = receive_tcp(s, expiration, one_rr_per_rrset,
q.keyring, q.mac, ignore_trailing)
# copying the wire into tcpmsg is inefficient, but lets us
# avoid writev() or doing a short write that would get pushed
# onto the net
tcpmsg = struct.pack("!H", l) + wire
_net_write(s, tcpmsg, expiration)
ldata = _net_read(s, 2, expiration)
(l,) = struct.unpack("!H", ldata)
wire = _net_read(s, l, expiration)
finally: finally:
if begin_time is None: if begin_time is None or received_time is None:
response_time = 0 response_time = 0
else: else:
response_time = time.time() - begin_time response_time = received_time - begin_time
s.close() s.close()
r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac,
one_rr_per_rrset=one_rr_per_rrset)
r.time = response_time r.time = response_time
if not q.is_response(r): if not q.is_response(r):
raise BadResponse raise BadResponse
@@ -374,51 +507,59 @@ def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN,
use_udp=False, keyalgorithm=dns.tsig.default_algorithm): use_udp=False, keyalgorithm=dns.tsig.default_algorithm):
"""Return a generator for the responses to a zone transfer. """Return a generator for the responses to a zone transfer.
@param where: where to send the message *where*. If the inference attempt fails, AF_INET is used. This
@type where: string containing an IPv4 or IPv6 address parameter is historical; you need never set it.
@param zone: The name of the zone to transfer
@type zone: dns.name.Name object or string *zone*, a ``dns.name.Name`` or ``text``, the name of the zone to transfer.
@param rdtype: The type of zone transfer. The default is
dns.rdatatype.AXFR. *rdtype*, an ``int`` or ``text``, the type of zone transfer. The
@type rdtype: int or string default is ``dns.rdatatype.AXFR``. ``dns.rdatatype.IXFR`` can be
@param rdclass: The class of the zone transfer. The default is used to do an incremental transfer instead.
dns.rdataclass.IN.
@type rdclass: int or string *rdclass*, an ``int`` or ``text``, the class of the zone transfer.
@param timeout: The number of seconds to wait for each response message. The default is ``dns.rdataclass.IN``.
If None, the default, wait forever.
@type timeout: float *timeout*, a ``float``, the number of seconds to wait for each
@param port: The port to which to send the message. The default is 53. response message. If None, the default, wait forever.
@type port: int
@param keyring: The TSIG keyring to use *port*, an ``int``, the port send the message to. The default is 53.
@type keyring: dict
@param keyname: The name of the TSIG key to use *keyring*, a ``dict``, the keyring to use for TSIG.
@type keyname: dns.name.Name object or string
@param relativize: If True, all names in the zone will be relativized to *keyname*, a ``dns.name.Name`` or ``text``, the name of the TSIG
the zone origin. It is essential that the relativize setting matches key to use.
the one specified to dns.zone.from_xfr().
@type relativize: bool *relativize*, a ``bool``. If ``True``, all names in the zone will be
@param af: the address family to use. The default is None, which relativized to the zone origin. It is essential that the
causes the address family to use to be inferred from the form of where. relativize setting matches the one specified to
If the inference attempt fails, AF_INET is used. ``dns.zone.from_xfr()`` if using this generator to make a zone.
@type af: int
@param lifetime: The total number of seconds to spend doing the transfer. *af*, an ``int``, the address family to use. The default is ``None``,
If None, the default, then there is no limit on the time the transfer may which causes the address family to use to be inferred from the form of
take. *where*. If the inference attempt fails, AF_INET is used. This
@type lifetime: float parameter is historical; you need never set it.
@rtype: generator of dns.message.Message objects.
@param source: source address. The default is the wildcard address. *lifetime*, a ``float``, the total number of seconds to spend
@type source: string doing the transfer. If ``None``, the default, then there is no
@param source_port: The port from which to send the message. limit on the time the transfer may take.
*source*, a ``text`` containing an IPv4 or IPv6 address, specifying
the source address. The default is the wildcard address.
*source_port*, an ``int``, the port from which to send the message.
The default is 0. The default is 0.
@type source_port: int
@param serial: The SOA serial number to use as the base for an IXFR diff *serial*, an ``int``, the SOA serial number to use as the base for
sequence (only meaningful if rdtype == dns.rdatatype.IXFR). an IXFR diff sequence (only meaningful if *rdtype* is
@type serial: int ``dns.rdatatype.IXFR``).
@param use_udp: Use UDP (only meaningful for IXFR)
@type use_udp: bool *use_udp*, a ``bool``. If ``True``, use UDP (only meaningful for IXFR).
@param keyalgorithm: The TSIG algorithm to use; defaults to
dns.tsig.default_algorithm *keyalgorithm*, a ``dns.name.Name`` or ``text``, the TSIG algorithm to use.
@type keyalgorithm: string
Raises on errors, and so does the generator.
Returns a generator of ``dns.message.Message`` objects.
""" """
if isinstance(zone, string_types): if isinstance(zone, string_types):
@@ -481,6 +622,9 @@ def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN,
xfr=True, origin=origin, tsig_ctx=tsig_ctx, xfr=True, origin=origin, tsig_ctx=tsig_ctx,
multi=True, first=first, multi=True, first=first,
one_rr_per_rrset=is_ixfr) one_rr_per_rrset=is_ixfr)
rcode = r.rcode()
if rcode != dns.rcode.NOERROR:
raise TransferError(rcode)
tsig_ctx = r.tsig_ctx tsig_ctx = r.tsig_ctx
first = False first = False
answer_index = 0 answer_index = 0

15
src/dns/query.pyi Normal file
View File

@@ -0,0 +1,15 @@
from typing import Optional, Union, Dict, Generator, Any
from . import message, tsig, rdatatype, rdataclass, name, message
def tcp(q : message.Message, where : str, timeout : float = None, port=53, af : Optional[int] = None, source : Optional[str] = None, source_port : int = 0,
one_rr_per_rrset=False) -> message.Message:
pass
def xfr(where : None, zone : Union[name.Name,str], rdtype=rdatatype.AXFR, rdclass=rdataclass.IN,
timeout : Optional[float] =None, port=53, keyring : Optional[Dict[name.Name, bytes]] =None, keyname : Union[str,name.Name]=None, relativize=True,
af : Optional[int] =None, lifetime : Optional[float]=None, source : Optional[str] =None, source_port=0, serial=0,
use_udp=False, keyalgorithm=tsig.default_algorithm) -> Generator[Any,Any,message.Message]:
pass
def udp(q : message.Message, where : str, timeout : Optional[float] = None, port=53, af : Optional[int] = None, source : Optional[str] = None, source_port=0,
ignore_unexpected=False, one_rr_per_rrset=False) -> message.Message:
...

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2001-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -18,18 +20,29 @@
import dns.exception import dns.exception
from ._compat import long from ._compat import long
#: No error
NOERROR = 0 NOERROR = 0
#: Form error
FORMERR = 1 FORMERR = 1
#: Server failure
SERVFAIL = 2 SERVFAIL = 2
#: Name does not exist ("Name Error" in RFC 1025 terminology).
NXDOMAIN = 3 NXDOMAIN = 3
#: Not implemented
NOTIMP = 4 NOTIMP = 4
#: Refused
REFUSED = 5 REFUSED = 5
#: Name exists.
YXDOMAIN = 6 YXDOMAIN = 6
#: RRset exists.
YXRRSET = 7 YXRRSET = 7
#: RRset does not exist.
NXRRSET = 8 NXRRSET = 8
#: Not authoritative.
NOTAUTH = 9 NOTAUTH = 9
#: Name not in zone.
NOTZONE = 10 NOTZONE = 10
#: Bad EDNS version.
BADVERS = 16 BADVERS = 16
_by_text = { _by_text = {
@@ -51,21 +64,21 @@ _by_text = {
# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that # cannot make any mistakes (e.g. omissions, cut-and-paste errors) that
# would cause the mapping not to be a true inverse. # would cause the mapping not to be a true inverse.
_by_value = dict((y, x) for x, y in _by_text.items()) _by_value = {y: x for x, y in _by_text.items()}
class UnknownRcode(dns.exception.DNSException): class UnknownRcode(dns.exception.DNSException):
"""A DNS rcode is unknown.""" """A DNS rcode is unknown."""
def from_text(text): def from_text(text):
"""Convert text into an rcode. """Convert text into an rcode.
@param text: the textual rcode *text*, a ``text``, the textual rcode or an integer in textual form.
@type text: string
@raises UnknownRcode: the rcode is unknown Raises ``dns.rcode.UnknownRcode`` if the rcode mnemonic is unknown.
@rtype: int
Returns an ``int``.
""" """
if text.isdigit(): if text.isdigit():
@@ -81,12 +94,13 @@ def from_text(text):
def from_flags(flags, ednsflags): def from_flags(flags, ednsflags):
"""Return the rcode value encoded by flags and ednsflags. """Return the rcode value encoded by flags and ednsflags.
@param flags: the DNS flags *flags*, an ``int``, the DNS flags field.
@type flags: int
@param ednsflags: the EDNS flags *ednsflags*, an ``int``, the EDNS flags field.
@type ednsflags: int
@raises ValueError: rcode is < 0 or > 4095 Raises ``ValueError`` if rcode is < 0 or > 4095
@rtype: int
Returns an ``int``.
""" """
value = (flags & 0x000f) | ((ednsflags >> 20) & 0xff0) value = (flags & 0x000f) | ((ednsflags >> 20) & 0xff0)
@@ -98,10 +112,11 @@ def from_flags(flags, ednsflags):
def to_flags(value): def to_flags(value):
"""Return a (flags, ednsflags) tuple which encodes the rcode. """Return a (flags, ednsflags) tuple which encodes the rcode.
@param value: the rcode *value*, an ``int``, the rcode.
@type value: int
@raises ValueError: rcode is < 0 or > 4095 Raises ``ValueError`` if rcode is < 0 or > 4095.
@rtype: (int, int) tuple
Returns an ``(int, int)`` tuple.
""" """
if value < 0 or value > 4095: if value < 0 or value > 4095:
@@ -114,11 +129,15 @@ def to_flags(value):
def to_text(value): def to_text(value):
"""Convert rcode into text. """Convert rcode into text.
@param value: the rcode *value*, and ``int``, the rcode.
@type value: int
@rtype: string Raises ``ValueError`` if rcode is < 0 or > 4095.
Returns a ``text``.
""" """
if value < 0 or value > 4095:
raise ValueError('rcode must be >= 0 and <= 4095')
text = _by_value.get(value) text = _by_value.get(value)
if text is None: if text is None:
text = str(value) text = str(value)

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2001-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -13,17 +15,7 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS rdata. """DNS rdata."""
@var _rdata_modules: A dictionary mapping a (rdclass, rdtype) tuple to
the module which implements that type.
@type _rdata_modules: dict
@var _module_prefix: The prefix to use when forming modules names. The
default is 'dns.rdtypes'. Changing this value will break the library.
@type _module_prefix: string
@var _hex_chunk: At most this many octets that will be represented in each
chunk of hexstring that _hexify() produces before whitespace occurs.
@type _hex_chunk: int"""
from io import BytesIO from io import BytesIO
import base64 import base64
@@ -37,17 +29,17 @@ import dns.tokenizer
import dns.wiredata import dns.wiredata
from ._compat import xrange, string_types, text_type from ._compat import xrange, string_types, text_type
try:
import threading as _threading
except ImportError:
import dummy_threading as _threading
_hex_chunksize = 32 _hex_chunksize = 32
def _hexify(data, chunksize=_hex_chunksize): def _hexify(data, chunksize=_hex_chunksize):
"""Convert a binary string into its hex encoding, broken up into chunks """Convert a binary string into its hex encoding, broken up into chunks
of I{chunksize} characters separated by a space. of chunksize characters separated by a space.
@param data: the binary string
@type data: string
@param chunksize: the chunk size. Default is L{dns.rdata._hex_chunksize}
@rtype: string
""" """
line = binascii.hexlify(data) line = binascii.hexlify(data)
@@ -60,13 +52,7 @@ _base64_chunksize = 32
def _base64ify(data, chunksize=_base64_chunksize): def _base64ify(data, chunksize=_base64_chunksize):
"""Convert a binary string into its base64 encoding, broken up into chunks """Convert a binary string into its base64 encoding, broken up into chunks
of I{chunksize} characters separated by a space. of chunksize characters separated by a space.
@param data: the binary string
@type data: string
@param chunksize: the chunk size. Default is
L{dns.rdata._base64_chunksize}
@rtype: string
""" """
line = base64.b64encode(data) line = base64.b64encode(data)
@@ -77,13 +63,7 @@ def _base64ify(data, chunksize=_base64_chunksize):
__escaped = bytearray(b'"\\') __escaped = bytearray(b'"\\')
def _escapify(qstring): def _escapify(qstring):
"""Escape the characters in a quoted string which need it. """Escape the characters in a quoted string which need it."""
@param qstring: the string
@type qstring: string
@returns: the escaped string
@rtype: string
"""
if isinstance(qstring, text_type): if isinstance(qstring, text_type):
qstring = qstring.encode() qstring = qstring.encode()
@@ -104,10 +84,6 @@ def _escapify(qstring):
def _truncate_bitmap(what): def _truncate_bitmap(what):
"""Determine the index of greatest byte that isn't all zeros, and """Determine the index of greatest byte that isn't all zeros, and
return the bitmap that contains all the bytes less than that index. return the bitmap that contains all the bytes less than that index.
@param what: a string of octets representing a bitmap.
@type what: string
@rtype: string
""" """
for i in xrange(len(what) - 1, -1, -1): for i in xrange(len(what) - 1, -1, -1):
@@ -117,30 +93,30 @@ def _truncate_bitmap(what):
class Rdata(object): class Rdata(object):
"""Base class for all DNS rdata types."""
"""Base class for all DNS rdata types.
"""
__slots__ = ['rdclass', 'rdtype'] __slots__ = ['rdclass', 'rdtype']
def __init__(self, rdclass, rdtype): def __init__(self, rdclass, rdtype):
"""Initialize an rdata. """Initialize an rdata.
@param rdclass: The rdata class
@type rdclass: int *rdclass*, an ``int`` is the rdataclass of the Rdata.
@param rdtype: The rdata type *rdtype*, an ``int`` is the rdatatype of the Rdata.
@type rdtype: int
""" """
self.rdclass = rdclass self.rdclass = rdclass
self.rdtype = rdtype self.rdtype = rdtype
def covers(self): def covers(self):
"""DNS SIG/RRSIG rdatas apply to a specific type; this type is """Return the type a Rdata covers.
DNS SIG/RRSIG rdatas apply to a specific type; this type is
returned by the covers() function. If the rdata type is not returned by the covers() function. If the rdata type is not
SIG or RRSIG, dns.rdatatype.NONE is returned. This is useful when SIG or RRSIG, dns.rdatatype.NONE is returned. This is useful when
creating rdatasets, allowing the rdataset to contain only RRSIGs creating rdatasets, allowing the rdataset to contain only RRSIGs
of a particular type, e.g. RRSIG(NS). of a particular type, e.g. RRSIG(NS).
@rtype: int
Returns an ``int``.
""" """
return dns.rdatatype.NONE return dns.rdatatype.NONE
@@ -149,37 +125,52 @@ class Rdata(object):
"""Return a 32-bit type value, the least significant 16 bits of """Return a 32-bit type value, the least significant 16 bits of
which are the ordinary DNS type, and the upper 16 bits of which are which are the ordinary DNS type, and the upper 16 bits of which are
the "covered" type, if any. the "covered" type, if any.
@rtype: int
Returns an ``int``.
""" """
return self.covers() << 16 | self.rdtype return self.covers() << 16 | self.rdtype
def to_text(self, origin=None, relativize=True, **kw): def to_text(self, origin=None, relativize=True, **kw):
"""Convert an rdata to text format. """Convert an rdata to text format.
@rtype: string
Returns a ``text``.
""" """
raise NotImplementedError raise NotImplementedError
def to_wire(self, file, compress=None, origin=None): def to_wire(self, file, compress=None, origin=None):
"""Convert an rdata to wire format. """Convert an rdata to wire format.
@rtype: string
Returns a ``binary``.
""" """
raise NotImplementedError raise NotImplementedError
def to_digestable(self, origin=None): def to_digestable(self, origin=None):
"""Convert rdata to a format suitable for digesting in hashes. This """Convert rdata to a format suitable for digesting in hashes. This
is also the DNSSEC canonical form.""" is also the DNSSEC canonical form.
Returns a ``binary``.
"""
f = BytesIO() f = BytesIO()
self.to_wire(f, None, origin) self.to_wire(f, None, origin)
return f.getvalue() return f.getvalue()
def validate(self): def validate(self):
"""Check that the current contents of the rdata's fields are """Check that the current contents of the rdata's fields are
valid. If you change an rdata by assigning to its fields, valid.
If you change an rdata by assigning to its fields,
it is a good idea to call validate() when you are done making it is a good idea to call validate() when you are done making
changes. changes.
Raises various exceptions if there are problems.
Returns ``None``.
""" """
dns.rdata.from_text(self.rdclass, self.rdtype, self.to_text()) dns.rdata.from_text(self.rdclass, self.rdtype, self.to_text())
def __repr__(self): def __repr__(self):
@@ -197,16 +188,19 @@ class Rdata(object):
def _cmp(self, other): def _cmp(self, other):
"""Compare an rdata with another rdata of the same rdtype and """Compare an rdata with another rdata of the same rdtype and
rdclass. Return < 0 if self < other in the DNSSEC ordering, rdclass.
0 if self == other, and > 0 if self > other.
Return < 0 if self < other in the DNSSEC ordering, 0 if self
== other, and > 0 if self > other.
""" """
our = self.to_digestable(dns.name.root) our = self.to_digestable(dns.name.root)
their = other.to_digestable(dns.name.root) their = other.to_digestable(dns.name.root)
if our == their: if our == their:
return 0 return 0
if our > their: elif our > their:
return 1 return 1
else:
return -1 return -1
def __eq__(self, other): def __eq__(self, other):
@@ -253,42 +247,10 @@ class Rdata(object):
@classmethod @classmethod
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
"""Build an rdata object from text format.
@param rdclass: The rdata class
@type rdclass: int
@param rdtype: The rdata type
@type rdtype: int
@param tok: The tokenizer
@type tok: dns.tokenizer.Tokenizer
@param origin: The origin to use for relative names
@type origin: dns.name.Name
@param relativize: should names be relativized?
@type relativize: bool
@rtype: dns.rdata.Rdata instance
"""
raise NotImplementedError raise NotImplementedError
@classmethod @classmethod
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
"""Build an rdata object from wire format
@param rdclass: The rdata class
@type rdclass: int
@param rdtype: The rdata type
@type rdtype: int
@param wire: The wire-format message
@type wire: string
@param current: The offset in wire of the beginning of the rdata.
@type current: int
@param rdlen: The length of the wire-format rdata
@type rdlen: int
@param origin: The origin to use for relative names
@type origin: dns.name.Name
@rtype: dns.rdata.Rdata instance
"""
raise NotImplementedError raise NotImplementedError
def choose_relativity(self, origin=None, relativize=True): def choose_relativity(self, origin=None, relativize=True):
@@ -296,12 +258,9 @@ class Rdata(object):
relativization. relativization.
""" """
pass
class GenericRdata(Rdata): class GenericRdata(Rdata):
"""Generate Rdata Class """Generic Rdata Class
This class is used for rdata types for which we have no better This class is used for rdata types for which we have no better
implementation. It implements the DNS "unknown RRs" scheme. implementation. It implements the DNS "unknown RRs" scheme.
@@ -319,7 +278,7 @@ class GenericRdata(Rdata):
@classmethod @classmethod
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
token = tok.get() token = tok.get()
if not token.is_identifier() or token.value != '\#': if not token.is_identifier() or token.value != r'\#':
raise dns.exception.SyntaxError( raise dns.exception.SyntaxError(
r'generic rdata does not start with \#') r'generic rdata does not start with \#')
length = tok.get_int() length = tok.get_int()
@@ -345,11 +304,12 @@ class GenericRdata(Rdata):
_rdata_modules = {} _rdata_modules = {}
_module_prefix = 'dns.rdtypes' _module_prefix = 'dns.rdtypes'
_import_lock = _threading.Lock()
def get_rdata_class(rdclass, rdtype): def get_rdata_class(rdclass, rdtype):
def import_module(name): def import_module(name):
with _import_lock:
mod = __import__(name) mod = __import__(name)
components = name.split('.') components = name.split('.')
for comp in components[1:]: for comp in components[1:]:
@@ -392,20 +352,23 @@ def from_text(rdclass, rdtype, tok, origin=None, relativize=True):
Once a class is chosen, its from_text() class method is called Once a class is chosen, its from_text() class method is called
with the parameters to this function. with the parameters to this function.
If I{tok} is a string, then a tokenizer is created and the string If *tok* is a ``text``, then a tokenizer is created and the string
is used as its input. is used as its input.
@param rdclass: The rdata class *rdclass*, an ``int``, the rdataclass.
@type rdclass: int
@param rdtype: The rdata type *rdtype*, an ``int``, the rdatatype.
@type rdtype: int
@param tok: The tokenizer or input text *tok*, a ``dns.tokenizer.Tokenizer`` or a ``text``.
@type tok: dns.tokenizer.Tokenizer or string
@param origin: The origin to use for relative names *origin*, a ``dns.name.Name`` (or ``None``), the
@type origin: dns.name.Name origin to use for relative names.
@param relativize: Should names be relativized?
@type relativize: bool *relativize*, a ``bool``. If true, name will be relativized to
@rtype: dns.rdata.Rdata instance""" the specified origin.
Returns an instance of the chosen Rdata subclass.
"""
if isinstance(tok, string_types): if isinstance(tok, string_types):
tok = dns.tokenizer.Tokenizer(tok) tok = dns.tokenizer.Tokenizer(tok)
@@ -439,20 +402,55 @@ def from_wire(rdclass, rdtype, wire, current, rdlen, origin=None):
Once a class is chosen, its from_wire() class method is called Once a class is chosen, its from_wire() class method is called
with the parameters to this function. with the parameters to this function.
@param rdclass: The rdata class *rdclass*, an ``int``, the rdataclass.
@type rdclass: int
@param rdtype: The rdata type *rdtype*, an ``int``, the rdatatype.
@type rdtype: int
@param wire: The wire-format message *wire*, a ``binary``, the wire-format message.
@type wire: string
@param current: The offset in wire of the beginning of the rdata. *current*, an ``int``, the offset in wire of the beginning of
@type current: int the rdata.
@param rdlen: The length of the wire-format rdata
@type rdlen: int *rdlen*, an ``int``, the length of the wire-format rdata
@param origin: The origin to use for relative names
@type origin: dns.name.Name *origin*, a ``dns.name.Name`` (or ``None``). If not ``None``,
@rtype: dns.rdata.Rdata instance""" then names will be relativized to this origin.
Returns an instance of the chosen Rdata subclass.
"""
wire = dns.wiredata.maybe_wrap(wire) wire = dns.wiredata.maybe_wrap(wire)
cls = get_rdata_class(rdclass, rdtype) cls = get_rdata_class(rdclass, rdtype)
return cls.from_wire(rdclass, rdtype, wire, current, rdlen, origin) return cls.from_wire(rdclass, rdtype, wire, current, rdlen, origin)
class RdatatypeExists(dns.exception.DNSException):
"""DNS rdatatype already exists."""
supp_kwargs = {'rdclass', 'rdtype'}
fmt = "The rdata type with class {rdclass} and rdtype {rdtype} " + \
"already exists."
def register_type(implementation, rdtype, rdtype_text, is_singleton=False,
rdclass=dns.rdataclass.IN):
"""Dynamically register a module to handle an rdatatype.
*implementation*, a module implementing the type in the usual dnspython
way.
*rdtype*, an ``int``, the rdatatype to register.
*rdtype_text*, a ``text``, the textual form of the rdatatype.
*is_singleton*, a ``bool``, indicating if the type is a singleton (i.e.
RRsets of the type can have only one member.)
*rdclass*, the rdataclass of the type, or ``dns.rdataclass.ANY`` if
it applies to all classes.
"""
existing_cls = get_rdata_class(rdclass, rdtype)
if existing_cls != GenericRdata:
raise RdatatypeExists(rdclass=rdclass, rdtype=rdtype)
_rdata_modules[(rdclass, rdtype)] = implementation
dns.rdatatype.register_type(rdtype, rdtype_text, is_singleton)

17
src/dns/rdata.pyi Normal file
View File

@@ -0,0 +1,17 @@
from typing import Dict, Tuple, Any, Optional
from .name import Name
class Rdata:
def __init__(self):
self.address : str
def to_wire(self, file, compress : Optional[Dict[Name,int]], origin : Optional[Name]) -> bytes:
...
@classmethod
def from_text(cls, rdclass : int, rdtype : int, tok, origin=None, relativize=True):
...
_rdata_modules : Dict[Tuple[Any,Rdata],Any]
def from_text(rdclass : int, rdtype : int, tok : Optional[str], origin : Optional[Name] = None, relativize : bool = True):
...
def from_wire(rdclass : int, rdtype : int, wire : bytes, current : int, rdlen : int, origin : Optional[Name] = None):
...

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2001-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -13,15 +15,7 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS Rdata Classes. """DNS Rdata Classes."""
@var _by_text: The rdata class textual name to value mapping
@type _by_text: dict
@var _by_value: The rdata class value to textual name mapping
@type _by_value: dict
@var _metaclasses: If an rdataclass is a metaclass, there will be a mapping
whose key is the rdatatype value and whose value is True in this dictionary.
@type _metaclasses: dict"""
import re import re
@@ -47,7 +41,7 @@ _by_text = {
# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that # cannot make any mistakes (e.g. omissions, cut-and-paste errors) that
# would cause the mapping not to be true inverse. # would cause the mapping not to be true inverse.
_by_value = dict((y, x) for x, y in _by_text.items()) _by_value = {y: x for x, y in _by_text.items()}
# Now that we've built the inverse map, we can add class aliases to # Now that we've built the inverse map, we can add class aliases to
# the _by_text mapping. # the _by_text mapping.
@@ -67,17 +61,22 @@ _unknown_class_pattern = re.compile('CLASS([0-9]+)$', re.I)
class UnknownRdataclass(dns.exception.DNSException): class UnknownRdataclass(dns.exception.DNSException):
"""A DNS class is unknown.""" """A DNS class is unknown."""
def from_text(text): def from_text(text):
"""Convert text into a DNS rdata class value. """Convert text into a DNS rdata class value.
@param text: the text
@type text: string The input text can be a defined DNS RR class mnemonic or
@rtype: int instance of the DNS generic class syntax.
@raises dns.rdataclass.UnknownRdataclass: the class is unknown
@raises ValueError: the rdata class value is not >= 0 and <= 65535 For example, "IN" and "CLASS1" will both result in a value of 1.
Raises ``dns.rdatatype.UnknownRdataclass`` if the class is unknown.
Raises ``ValueError`` if the rdata class value is not >= 0 and <= 65535.
Returns an ``int``.
""" """
value = _by_text.get(text.upper()) value = _by_text.get(text.upper())
@@ -92,11 +91,14 @@ def from_text(text):
def to_text(value): def to_text(value):
"""Convert a DNS rdata class to text. """Convert a DNS rdata type value to text.
@param value: the rdata class value
@type value: int If the value has a known mnemonic, it will be used, otherwise the
@rtype: string DNS generic class syntax will be used.
@raises ValueError: the rdata class value is not >= 0 and <= 65535
Raises ``ValueError`` if the rdata class value is not >= 0 and <= 65535.
Returns a ``str``.
""" """
if value < 0 or value > 65535: if value < 0 or value > 65535:
@@ -108,10 +110,12 @@ def to_text(value):
def is_metaclass(rdclass): def is_metaclass(rdclass):
"""True if the class is a metaclass. """True if the specified class is a metaclass.
@param rdclass: the rdata class
@type rdclass: int The currently defined metaclasses are ANY and NONE.
@rtype: bool"""
*rdclass* is an ``int``.
"""
if rdclass in _metaclasses: if rdclass in _metaclasses:
return True return True

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2001-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -31,50 +33,37 @@ SimpleSet = dns.set.Set
class DifferingCovers(dns.exception.DNSException): class DifferingCovers(dns.exception.DNSException):
"""An attempt was made to add a DNS SIG/RRSIG whose covered type """An attempt was made to add a DNS SIG/RRSIG whose covered type
is not the same as that of the other rdatas in the rdataset.""" is not the same as that of the other rdatas in the rdataset."""
class IncompatibleTypes(dns.exception.DNSException): class IncompatibleTypes(dns.exception.DNSException):
"""An attempt was made to add DNS RR data of an incompatible type.""" """An attempt was made to add DNS RR data of an incompatible type."""
class Rdataset(dns.set.Set): class Rdataset(dns.set.Set):
"""A DNS rdataset. """A DNS rdataset."""
@ivar rdclass: The class of the rdataset
@type rdclass: int
@ivar rdtype: The type of the rdataset
@type rdtype: int
@ivar covers: The covered type. Usually this value is
dns.rdatatype.NONE, but if the rdtype is dns.rdatatype.SIG or
dns.rdatatype.RRSIG, then the covers value will be the rdata
type the SIG/RRSIG covers. The library treats the SIG and RRSIG
types as if they were a family of
types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much
easier to work with than if RRSIGs covering different rdata
types were aggregated into a single RRSIG rdataset.
@type covers: int
@ivar ttl: The DNS TTL (Time To Live) value
@type ttl: int
"""
__slots__ = ['rdclass', 'rdtype', 'covers', 'ttl'] __slots__ = ['rdclass', 'rdtype', 'covers', 'ttl']
def __init__(self, rdclass, rdtype, covers=dns.rdatatype.NONE): def __init__(self, rdclass, rdtype, covers=dns.rdatatype.NONE, ttl=0):
"""Create a new rdataset of the specified class and type. """Create a new rdataset of the specified class and type.
@see: the description of the class instance variables for the *rdclass*, an ``int``, the rdataclass.
meaning of I{rdclass} and I{rdtype}"""
*rdtype*, an ``int``, the rdatatype.
*covers*, an ``int``, the covered rdatatype.
*ttl*, an ``int``, the TTL.
"""
super(Rdataset, self).__init__() super(Rdataset, self).__init__()
self.rdclass = rdclass self.rdclass = rdclass
self.rdtype = rdtype self.rdtype = rdtype
self.covers = covers self.covers = covers
self.ttl = 0 self.ttl = ttl
def _clone(self): def _clone(self):
obj = super(Rdataset, self)._clone() obj = super(Rdataset, self)._clone()
@@ -85,11 +74,14 @@ class Rdataset(dns.set.Set):
return obj return obj
def update_ttl(self, ttl): def update_ttl(self, ttl):
"""Set the TTL of the rdataset to be the lesser of the set's current """Perform TTL minimization.
Set the TTL of the rdataset to be the lesser of the set's current
TTL or the specified TTL. If the set contains no rdatas, set the TTL TTL or the specified TTL. If the set contains no rdatas, set the TTL
to the specified TTL. to the specified TTL.
@param ttl: The TTL
@type ttl: int""" *ttl*, an ``int``.
"""
if len(self) == 0: if len(self) == 0:
self.ttl = ttl self.ttl = ttl
@@ -99,13 +91,19 @@ class Rdataset(dns.set.Set):
def add(self, rd, ttl=None): def add(self, rd, ttl=None):
"""Add the specified rdata to the rdataset. """Add the specified rdata to the rdataset.
If the optional I{ttl} parameter is supplied, then If the optional *ttl* parameter is supplied, then
self.update_ttl(ttl) will be called prior to adding the rdata. ``self.update_ttl(ttl)`` will be called prior to adding the rdata.
@param rd: The rdata *rd*, a ``dns.rdata.Rdata``, the rdata
@type rd: dns.rdata.Rdata object
@param ttl: The TTL *ttl*, an ``int``, the TTL.
@type ttl: int"""
Raises ``dns.rdataset.IncompatibleTypes`` if the type and class
do not match the type and class of the rdataset.
Raises ``dns.rdataset.DifferingCovers`` if the type is a signature
type and the covered type does not match that of the rdataset.
"""
# #
# If we're adding a signature, do some special handling to # If we're adding a signature, do some special handling to
@@ -139,8 +137,9 @@ class Rdataset(dns.set.Set):
def update(self, other): def update(self, other):
"""Add all rdatas in other to self. """Add all rdatas in other to self.
@param other: The rdataset from which to update *other*, a ``dns.rdataset.Rdataset``, the rdataset from which
@type other: dns.rdataset.Rdataset object""" to update.
"""
self.update_ttl(other.ttl) self.update_ttl(other.ttl)
super(Rdataset, self).update(other) super(Rdataset, self).update(other)
@@ -157,10 +156,6 @@ class Rdataset(dns.set.Set):
return self.to_text() return self.to_text()
def __eq__(self, other): def __eq__(self, other):
"""Two rdatasets are equal if they have the same class, type, and
covers, and contain the same rdata.
@rtype: bool"""
if not isinstance(other, Rdataset): if not isinstance(other, Rdataset):
return False return False
if self.rdclass != other.rdclass or \ if self.rdclass != other.rdclass or \
@@ -176,20 +171,23 @@ class Rdataset(dns.set.Set):
override_rdclass=None, **kw): override_rdclass=None, **kw):
"""Convert the rdataset into DNS master file format. """Convert the rdataset into DNS master file format.
@see: L{dns.name.Name.choose_relativity} for more information See ``dns.name.Name.choose_relativity`` for more information
on how I{origin} and I{relativize} determine the way names on how *origin* and *relativize* determine the way names
are emitted. are emitted.
Any additional keyword arguments are passed on to the rdata Any additional keyword arguments are passed on to the rdata
to_text() method. ``to_text()`` method.
*name*, a ``dns.name.Name``. If name is not ``None``, emit RRs with
*name* as the owner name.
*origin*, a ``dns.name.Name`` or ``None``, the origin for relative
names.
*relativize*, a ``bool``. If ``True``, names will be relativized
to *origin*.
"""
@param name: If name is not None, emit a RRs with I{name} as
the owner name.
@type name: dns.name.Name object
@param origin: The origin for relative names, or None.
@type origin: dns.name.Name object
@param relativize: True if names should names be relativized
@type relativize: bool"""
if name is not None: if name is not None:
name = name.choose_relativity(origin, relativize) name = name.choose_relativity(origin, relativize)
ntext = str(name) ntext = str(name)
@@ -208,7 +206,7 @@ class Rdataset(dns.set.Set):
# some dynamic updates, so we don't need to print out the TTL # some dynamic updates, so we don't need to print out the TTL
# (which is meaningless anyway). # (which is meaningless anyway).
# #
s.write(u'%s%s%s %s\n' % (ntext, pad, s.write(u'{}{}{} {}\n'.format(ntext, pad,
dns.rdataclass.to_text(rdclass), dns.rdataclass.to_text(rdclass),
dns.rdatatype.to_text(self.rdtype))) dns.rdatatype.to_text(self.rdtype)))
else: else:
@@ -227,16 +225,26 @@ class Rdataset(dns.set.Set):
override_rdclass=None, want_shuffle=True): override_rdclass=None, want_shuffle=True):
"""Convert the rdataset to wire format. """Convert the rdataset to wire format.
@param name: The owner name of the RRset that will be emitted *name*, a ``dns.name.Name`` is the owner name to use.
@type name: dns.name.Name object
@param file: The file to which the wire format data will be appended *file* is the file where the name is emitted (typically a
@type file: file BytesIO file).
@param compress: The compression table to use; the default is None.
@type compress: dict *compress*, a ``dict``, is the compression table to use. If
@param origin: The origin to be appended to any relative names when ``None`` (the default), names will not be compressed.
they are emitted. The default is None.
@returns: the number of records emitted *origin* is a ``dns.name.Name`` or ``None``. If the name is
@rtype: int relative and origin is not ``None``, then *origin* will be appended
to it.
*override_rdclass*, an ``int``, is used as the class instead of the
class of the rdataset. This is useful when rendering rdatasets
associated with dynamic updates.
*want_shuffle*, a ``bool``. If ``True``, then the order of the
Rdatas within the Rdataset will be shuffled before rendering.
Returns an ``int``, the number of records emitted.
""" """
if override_rdclass is not None: if override_rdclass is not None:
@@ -272,8 +280,9 @@ class Rdataset(dns.set.Set):
return len(self) return len(self)
def match(self, rdclass, rdtype, covers): def match(self, rdclass, rdtype, covers):
"""Returns True if this rdataset matches the specified class, type, """Returns ``True`` if this rdataset matches the specified class,
and covers""" type, and covers.
"""
if self.rdclass == rdclass and \ if self.rdclass == rdclass and \
self.rdtype == rdtype and \ self.rdtype == rdtype and \
self.covers == covers: self.covers == covers:
@@ -285,7 +294,7 @@ def from_text_list(rdclass, rdtype, ttl, text_rdatas):
"""Create an rdataset with the specified class, type, and TTL, and with """Create an rdataset with the specified class, type, and TTL, and with
the specified list of rdatas in text format. the specified list of rdatas in text format.
@rtype: dns.rdataset.Rdataset object Returns a ``dns.rdataset.Rdataset`` object.
""" """
if isinstance(rdclass, string_types): if isinstance(rdclass, string_types):
@@ -304,7 +313,7 @@ def from_text(rdclass, rdtype, ttl, *text_rdatas):
"""Create an rdataset with the specified class, type, and TTL, and with """Create an rdataset with the specified class, type, and TTL, and with
the specified rdatas in text format. the specified rdatas in text format.
@rtype: dns.rdataset.Rdataset object Returns a ``dns.rdataset.Rdataset`` object.
""" """
return from_text_list(rdclass, rdtype, ttl, text_rdatas) return from_text_list(rdclass, rdtype, ttl, text_rdatas)
@@ -314,7 +323,7 @@ def from_rdata_list(ttl, rdatas):
"""Create an rdataset with the specified TTL, and with """Create an rdataset with the specified TTL, and with
the specified list of rdata objects. the specified list of rdata objects.
@rtype: dns.rdataset.Rdataset object Returns a ``dns.rdataset.Rdataset`` object.
""" """
if len(rdatas) == 0: if len(rdatas) == 0:
@@ -332,7 +341,7 @@ def from_rdata(ttl, *rdatas):
"""Create an rdataset with the specified TTL, and with """Create an rdataset with the specified TTL, and with
the specified rdata objects. the specified rdata objects.
@rtype: dns.rdataset.Rdataset object Returns a ``dns.rdataset.Rdataset`` object.
""" """
return from_rdata_list(ttl, rdatas) return from_rdata_list(ttl, rdatas)

58
src/dns/rdataset.pyi Normal file
View File

@@ -0,0 +1,58 @@
from typing import Optional, Dict, List, Union
from io import BytesIO
from . import exception, name, set, rdatatype, rdata, rdataset
class DifferingCovers(exception.DNSException):
"""An attempt was made to add a DNS SIG/RRSIG whose covered type
is not the same as that of the other rdatas in the rdataset."""
class IncompatibleTypes(exception.DNSException):
"""An attempt was made to add DNS RR data of an incompatible type."""
class Rdataset(set.Set):
def __init__(self, rdclass, rdtype, covers=rdatatype.NONE, ttl=0):
self.rdclass : int = rdclass
self.rdtype : int = rdtype
self.covers : int = covers
self.ttl : int = ttl
def update_ttl(self, ttl : int) -> None:
...
def add(self, rd : rdata.Rdata, ttl : Optional[int] =None):
...
def union_update(self, other : Rdataset):
...
def intersection_update(self, other : Rdataset):
...
def update(self, other : Rdataset):
...
def to_text(self, name : Optional[name.Name] =None, origin : Optional[name.Name] =None, relativize=True,
override_rdclass : Optional[int] =None, **kw) -> bytes:
...
def to_wire(self, name : Optional[name.Name], file : BytesIO, compress : Optional[Dict[name.Name, int]] = None, origin : Optional[name.Name] = None,
override_rdclass : Optional[int] = None, want_shuffle=True) -> int:
...
def match(self, rdclass : int, rdtype : int, covers : int) -> bool:
...
def from_text_list(rdclass : Union[int,str], rdtype : Union[int,str], ttl : int, text_rdatas : str) -> rdataset.Rdataset:
...
def from_text(rdclass : Union[int,str], rdtype : Union[int,str], ttl : int, *text_rdatas : str) -> rdataset.Rdataset:
...
def from_rdata_list(ttl : int, rdatas : List[rdata.Rdata]) -> rdataset.Rdataset:
...
def from_rdata(ttl : int, *rdatas : List[rdata.Rdata]) -> rdataset.Rdataset:
...

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2001-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -13,18 +15,7 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""DNS Rdata Types. """DNS Rdata Types."""
@var _by_text: The rdata type textual name to value mapping
@type _by_text: dict
@var _by_value: The rdata type value to textual name mapping
@type _by_value: dict
@var _metatypes: If an rdatatype is a metatype, there will be a mapping
whose key is the rdatatype value and whose value is True in this dictionary.
@type _metatypes: dict
@var _singletons: If an rdatatype is a singleton, there will be a mapping
whose key is the rdatatype value and whose value is True in this dictionary.
@type _singletons: dict"""
import re import re
@@ -82,6 +73,7 @@ TLSA = 52
HIP = 55 HIP = 55
CDS = 59 CDS = 59
CDNSKEY = 60 CDNSKEY = 60
OPENPGPKEY = 61
CSYNC = 62 CSYNC = 62
SPF = 99 SPF = 99
UNSPEC = 103 UNSPEC = 103
@@ -153,6 +145,7 @@ _by_text = {
'HIP': HIP, 'HIP': HIP,
'CDS': CDS, 'CDS': CDS,
'CDNSKEY': CDNSKEY, 'CDNSKEY': CDNSKEY,
'OPENPGPKEY': OPENPGPKEY,
'CSYNC': CSYNC, 'CSYNC': CSYNC,
'SPF': SPF, 'SPF': SPF,
'UNSPEC': UNSPEC, 'UNSPEC': UNSPEC,
@@ -176,8 +169,7 @@ _by_text = {
# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that # cannot make any mistakes (e.g. omissions, cut-and-paste errors) that
# would cause the mapping not to be true inverse. # would cause the mapping not to be true inverse.
_by_value = dict((y, x) for x, y in _by_text.items()) _by_value = {y: x for x, y in _by_text.items()}
_metatypes = { _metatypes = {
OPT: True OPT: True
@@ -188,24 +180,30 @@ _singletons = {
NXT: True, NXT: True,
DNAME: True, DNAME: True,
NSEC: True, NSEC: True,
# CNAME is technically a singleton, but we allow multiple CNAMEs. CNAME: True,
} }
_unknown_type_pattern = re.compile('TYPE([0-9]+)$', re.I) _unknown_type_pattern = re.compile('TYPE([0-9]+)$', re.I)
class UnknownRdatatype(dns.exception.DNSException): class UnknownRdatatype(dns.exception.DNSException):
"""DNS resource record type is unknown.""" """DNS resource record type is unknown."""
def from_text(text): def from_text(text):
"""Convert text into a DNS rdata type value. """Convert text into a DNS rdata type value.
@param text: the text
@type text: string The input text can be a defined DNS RR type mnemonic or
@raises dns.rdatatype.UnknownRdatatype: the type is unknown instance of the DNS generic type syntax.
@raises ValueError: the rdata type value is not >= 0 and <= 65535
@rtype: int""" For example, "NS" and "TYPE2" will both result in a value of 2.
Raises ``dns.rdatatype.UnknownRdatatype`` if the type is unknown.
Raises ``ValueError`` if the rdata type value is not >= 0 and <= 65535.
Returns an ``int``.
"""
value = _by_text.get(text.upper()) value = _by_text.get(text.upper())
if value is None: if value is None:
@@ -219,11 +217,15 @@ def from_text(text):
def to_text(value): def to_text(value):
"""Convert a DNS rdata type to text. """Convert a DNS rdata type value to text.
@param value: the rdata type value
@type value: int If the value has a known mnemonic, it will be used, otherwise the
@raises ValueError: the rdata type value is not >= 0 and <= 65535 DNS generic type syntax will be used.
@rtype: string"""
Raises ``ValueError`` if the rdata type value is not >= 0 and <= 65535.
Returns a ``str``.
"""
if value < 0 or value > 65535: if value < 0 or value > 65535:
raise ValueError("type must be between >= 0 and <= 65535") raise ValueError("type must be between >= 0 and <= 65535")
@@ -234,10 +236,15 @@ def to_text(value):
def is_metatype(rdtype): def is_metatype(rdtype):
"""True if the type is a metatype. """True if the specified type is a metatype.
@param rdtype: the type
@type rdtype: int *rdtype* is an ``int``.
@rtype: bool"""
The currently defined metatypes are TKEY, TSIG, IXFR, AXFR, MAILA,
MAILB, ANY, and OPT.
Returns a ``bool``.
"""
if rdtype >= TKEY and rdtype <= ANY or rdtype in _metatypes: if rdtype >= TKEY and rdtype <= ANY or rdtype in _metatypes:
return True return True
@@ -245,11 +252,36 @@ def is_metatype(rdtype):
def is_singleton(rdtype): def is_singleton(rdtype):
"""True if the type is a singleton. """Is the specified type a singleton type?
@param rdtype: the type
@type rdtype: int Singleton types can only have a single rdata in an rdataset, or a single
@rtype: bool""" RR in an RRset.
The currently defined singleton types are CNAME, DNAME, NSEC, NXT, and
SOA.
*rdtype* is an ``int``.
Returns a ``bool``.
"""
if rdtype in _singletons: if rdtype in _singletons:
return True return True
return False return False
def register_type(rdtype, rdtype_text, is_singleton=False): # pylint: disable=redefined-outer-name
"""Dynamically register an rdatatype.
*rdtype*, an ``int``, the rdatatype to register.
*rdtype_text*, a ``text``, the textual form of the rdatatype.
*is_singleton*, a ``bool``, indicating if the type is a singleton (i.e.
RRsets of the type can have only one member.)
"""
_by_text[rdtype_text] = rdtype
_by_value[rdtype] = rdtype_text
if is_singleton:
_singletons[rdtype] = True

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2016 Nominum, Inc. # Copyright (C) 2016 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2004-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2004-2007, 2009-2011, 2016 Nominum, Inc. # Copyright (C) 2004-2007, 2009-2011, 2016 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2009-2011 Nominum, Inc. # Copyright (C) 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2004-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
@@ -80,7 +82,7 @@ class GPOS(dns.rdata.Rdata):
self.altitude = altitude self.altitude = altitude
def to_text(self, origin=None, relativize=True, **kw): def to_text(self, origin=None, relativize=True, **kw):
return '%s %s %s' % (self.latitude.decode(), return '{} {} {}'.format(self.latitude.decode(),
self.longitude.decode(), self.longitude.decode(),
self.altitude.decode()) self.altitude.decode())

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
@@ -45,7 +47,7 @@ class HINFO(dns.rdata.Rdata):
self.os = os self.os = os
def to_text(self, origin=None, relativize=True, **kw): def to_text(self, origin=None, relativize=True, **kw):
return '"%s" "%s"' % (dns.rdata._escapify(self.cpu), return '"{}" "{}"'.format(dns.rdata._escapify(self.cpu),
dns.rdata._escapify(self.os)) dns.rdata._escapify(self.os))
@classmethod @classmethod

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2010, 2011 Nominum, Inc. # Copyright (C) 2010, 2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
@@ -46,7 +48,7 @@ class ISDN(dns.rdata.Rdata):
def to_text(self, origin=None, relativize=True, **kw): def to_text(self, origin=None, relativize=True, **kw):
if self.subaddress: if self.subaddress:
return '"%s" "%s"' % (dns.rdata._escapify(self.address), return '"{}" "{}"'.format(dns.rdata._escapify(self.address),
dns.rdata._escapify(self.subaddress)) dns.rdata._escapify(self.subaddress))
else: else:
return '"%s"' % dns.rdata._escapify(self.address) return '"%s"' % dns.rdata._escapify(self.address)

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
@@ -156,7 +158,7 @@ class LOC(dns.rdata.Rdata):
if self.size != _default_size or \ if self.size != _default_size or \
self.horizontal_precision != _default_hprec or \ self.horizontal_precision != _default_hprec or \
self.vertical_precision != _default_vprec: self.vertical_precision != _default_vprec:
text += " %0.2fm %0.2fm %0.2fm" % ( text += " {:0.2f}m {:0.2f}m {:0.2f}m".format(
self.size / 100.0, self.horizontal_precision / 100.0, self.size / 100.0, self.horizontal_precision / 100.0,
self.vertical_precision / 100.0 self.vertical_precision / 100.0
) )

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2004-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
@@ -50,7 +52,7 @@ class NSEC(dns.rdata.Rdata):
bits.append(dns.rdatatype.to_text(window * 256 + bits.append(dns.rdatatype.to_text(window * 256 +
i * 8 + j)) i * 8 + j))
text += (' ' + ' '.join(bits)) text += (' ' + ' '.join(bits))
return '%s%s' % (next, text) return '{}{}'.format(next, text)
@classmethod @classmethod
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2004-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -21,18 +23,21 @@ import struct
import dns.exception import dns.exception
import dns.rdata import dns.rdata
import dns.rdatatype import dns.rdatatype
from dns._compat import xrange, text_type from dns._compat import xrange, text_type, PY3
try: # pylint: disable=deprecated-string-function
b32_hex_to_normal = string.maketrans('0123456789ABCDEFGHIJKLMNOPQRSTUV', if PY3:
'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')
b32_normal_to_hex = string.maketrans('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
'0123456789ABCDEFGHIJKLMNOPQRSTUV')
except AttributeError:
b32_hex_to_normal = bytes.maketrans(b'0123456789ABCDEFGHIJKLMNOPQRSTUV', b32_hex_to_normal = bytes.maketrans(b'0123456789ABCDEFGHIJKLMNOPQRSTUV',
b'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567') b'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')
b32_normal_to_hex = bytes.maketrans(b'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', b32_normal_to_hex = bytes.maketrans(b'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
b'0123456789ABCDEFGHIJKLMNOPQRSTUV') b'0123456789ABCDEFGHIJKLMNOPQRSTUV')
else:
b32_hex_to_normal = string.maketrans('0123456789ABCDEFGHIJKLMNOPQRSTUV',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')
b32_normal_to_hex = string.maketrans('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
'0123456789ABCDEFGHIJKLMNOPQRSTUV')
# pylint: enable=deprecated-string-function
# hash algorithm constants # hash algorithm constants
SHA1 = 1 SHA1 = 1
@@ -130,7 +135,7 @@ class NSEC3(dns.rdata.Rdata):
new_window = nrdtype // 256 new_window = nrdtype // 256
if new_window != window: if new_window != window:
if octets != 0: if octets != 0:
windows.append((window, ''.join(bitmap[0:octets]))) windows.append((window, bitmap[0:octets]))
bitmap = bytearray(b'\0' * 32) bitmap = bytearray(b'\0' * 32)
window = new_window window = new_window
offset = nrdtype % 256 offset = nrdtype % 256

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2004-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -0,0 +1,60 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2016 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 base64
import dns.exception
import dns.rdata
import dns.tokenizer
class OPENPGPKEY(dns.rdata.Rdata):
"""OPENPGPKEY record
@ivar key: the key
@type key: bytes
@see: RFC 7929
"""
def __init__(self, rdclass, rdtype, key):
super(OPENPGPKEY, self).__init__(rdclass, rdtype)
self.key = key
def to_text(self, origin=None, relativize=True, **kw):
return dns.rdata._base64ify(self.key)
@classmethod
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
chunks = []
while 1:
t = tok.get().unescape()
if t.is_eol_or_eof():
break
if not t.is_identifier():
raise dns.exception.SyntaxError
chunks.append(t.value.encode())
b64 = b''.join(chunks)
key = base64.b64decode(b64)
return cls(rdclass, rdtype, key)
def to_wire(self, file, compress=None, origin=None):
file.write(self.key)
@classmethod
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
key = wire[current: current + rdlen].unwrap()
return cls(rdclass, rdtype, key)

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
@@ -39,7 +41,7 @@ class RP(dns.rdata.Rdata):
def to_text(self, origin=None, relativize=True, **kw): def to_text(self, origin=None, relativize=True, **kw):
mbox = self.mbox.choose_relativity(origin, relativize) mbox = self.mbox.choose_relativity(origin, relativize)
txt = self.txt.choose_relativity(origin, relativize) txt = self.txt.choose_relativity(origin, relativize)
return "%s %s" % (str(mbox), str(txt)) return "{} {}".format(str(mbox), str(txt))
@classmethod @classmethod
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2004-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2006, 2007, 2009-2011 Nominum, Inc. # Copyright (C) 2006, 2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2005-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2005-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2005-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2005-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# Copyright (C) 2015 Red Hat, Inc. # Copyright (C) 2015 Red Hat, Inc.
# #

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
@@ -17,10 +19,13 @@
__all__ = [ __all__ = [
'AFSDB', 'AFSDB',
'AVC',
'CAA',
'CDNSKEY', 'CDNSKEY',
'CDS', 'CDS',
'CERT', 'CERT',
'CNAME', 'CNAME',
'CSYNC',
'DLV', 'DLV',
'DNAME', 'DNAME',
'DNSKEY', 'DNSKEY',
@@ -37,7 +42,7 @@ __all__ = [
'NSEC', 'NSEC',
'NSEC3', 'NSEC3',
'NSEC3PARAM', 'NSEC3PARAM',
'TLSA', 'OPENPGPKEY',
'PTR', 'PTR',
'RP', 'RP',
'RRSIG', 'RRSIG',
@@ -45,6 +50,8 @@ __all__ = [
'SOA', 'SOA',
'SPF', 'SPF',
'SSHFP', 'SSHFP',
'TLSA',
'TXT', 'TXT',
'URI',
'X25', 'X25',
] ]

70
src/dns/rdtypes/CH/A.py Normal file
View File

@@ -0,0 +1,70 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 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 dns.rdtypes.mxbase
import struct
class A(dns.rdtypes.mxbase.MXBase):
"""A record for Chaosnet
@ivar domain: the domain of the address
@type domain: dns.name.Name object
@ivar address: the 16-bit address
@type address: int"""
__slots__ = ['domain', 'address']
def __init__(self, rdclass, rdtype, address, domain):
super(A, self).__init__(rdclass, rdtype, address, domain)
self.domain = domain
self.address = address
def to_text(self, origin=None, relativize=True, **kw):
domain = self.domain.choose_relativity(origin, relativize)
return '%s %o' % (domain, self.address)
@classmethod
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
domain = tok.get_name()
address = tok.get_uint16(base=8)
domain = domain.choose_relativity(origin, relativize)
tok.get_eol()
return cls(rdclass, rdtype, address, domain)
def to_wire(self, file, compress=None, origin=None):
self.domain.to_wire(file, compress, origin)
pref = struct.pack("!H", self.address)
file.write(pref)
def to_digestable(self, origin=None):
return self.domain.to_digestable(origin) + \
struct.pack("!H", self.address)
@classmethod
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
(domain, cused) = dns.name.from_wire(wire[: current + rdlen-2],
current)
current += cused
(address,) = struct.unpack('!H', wire[current: current + 2])
if cused+2 != rdlen:
raise dns.exception.FormError
if origin is not None:
domain = domain.relativize(origin)
return cls(rdclass, rdtype, address, domain)
def choose_relativity(self, origin=None, relativize=True):
self.domain = self.domain.choose_relativity(origin, relativize)

View File

@@ -0,0 +1,22 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 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.
"""Class CH rdata type classes."""
__all__ = [
'A',
]

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
@@ -48,5 +50,5 @@ class A(dns.rdata.Rdata):
@classmethod @classmethod
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
address = dns.ipv4.inet_ntoa(wire[current: current + rdlen]).decode() address = dns.ipv4.inet_ntoa(wire[current: current + rdlen])
return cls(rdclass, rdtype, address) return cls(rdclass, rdtype, address)

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2017 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose with or without fee is hereby granted, # documentation for any purpose with or without fee is hereby granted,
@@ -13,14 +15,15 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import struct
import binascii import binascii
import codecs
import struct
import dns.exception import dns.exception
import dns.inet import dns.inet
import dns.rdata import dns.rdata
import dns.tokenizer import dns.tokenizer
from dns._compat import xrange from dns._compat import xrange, maybe_chr
class APLItem(object): class APLItem(object):
@@ -63,7 +66,7 @@ class APLItem(object):
# #
last = 0 last = 0
for i in xrange(len(address) - 1, -1, -1): for i in xrange(len(address) - 1, -1, -1):
if address[i] != chr(0): if address[i] != maybe_chr(0):
last = i + 1 last = i + 1
break break
address = address[0: last] address = address[0: last]
@@ -121,6 +124,7 @@ class APL(dns.rdata.Rdata):
@classmethod @classmethod
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
items = [] items = []
while 1: while 1:
if rdlen == 0: if rdlen == 0:
@@ -142,18 +146,18 @@ class APL(dns.rdata.Rdata):
l = len(address) l = len(address)
if header[0] == 1: if header[0] == 1:
if l < 4: if l < 4:
address += '\x00' * (4 - l) address += b'\x00' * (4 - l)
address = dns.inet.inet_ntop(dns.inet.AF_INET, address) address = dns.inet.inet_ntop(dns.inet.AF_INET, address)
elif header[0] == 2: elif header[0] == 2:
if l < 16: if l < 16:
address += '\x00' * (16 - l) address += b'\x00' * (16 - l)
address = dns.inet.inet_ntop(dns.inet.AF_INET6, address) address = dns.inet.inet_ntop(dns.inet.AF_INET6, address)
else: else:
# #
# This isn't really right according to the RFC, but it # This isn't really right according to the RFC, but it
# seems better than throwing an exception # seems better than throwing an exception
# #
address = address.encode('hex_codec') address = codecs.encode(address, 'hex_codec')
current += afdlen current += afdlen
rdlen -= afdlen rdlen -= afdlen
item = APLItem(header[0], negation, address, header[1]) item = APLItem(header[0], negation, address, header[1])

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2006, 2007, 2009-2011 Nominum, Inc. # Copyright (C) 2006, 2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2006, 2007, 2009-2011 Nominum, Inc. # Copyright (C) 2006, 2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
@@ -20,6 +22,7 @@ __all__ = [
'AAAA', 'AAAA',
'APL', 'APL',
'DHCID', 'DHCID',
'IPSECKEY',
'KX', 'KX',
'NAPTR', 'NAPTR',
'NSAP', 'NSAP',

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
@@ -18,6 +20,7 @@
__all__ = [ __all__ = [
'ANY', 'ANY',
'IN', 'IN',
'CH',
'euibase', 'euibase',
'mxbase', 'mxbase',
'nsbase', 'nsbase',

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2004-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its
@@ -38,7 +40,7 @@ _flag_by_text = {
# We construct the inverse mapping programmatically to ensure that we # We construct the inverse mapping programmatically to ensure that we
# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that # cannot make any mistakes (e.g. omissions, cut-and-paste errors) that
# would cause the mapping not to be true inverse. # would cause the mapping not to be true inverse.
_flag_by_value = dict((y, x) for x, y in _flag_by_text.items()) _flag_by_value = {y: x for x, y in _flag_by_text.items()}
def flags_to_text_set(flags): def flags_to_text_set(flags):

View File

@@ -0,0 +1,37 @@
from typing import Set, Any
SEP : int
REVOKE : int
ZONE : int
def flags_to_text_set(flags : int) -> Set[str]:
...
def flags_from_text_set(texts_set) -> int:
...
from .. import rdata
class DNSKEYBase(rdata.Rdata):
def __init__(self, rdclass, rdtype, flags, protocol, algorithm, key):
self.flags : int
self.protocol : int
self.key : str
self.algorithm : int
def to_text(self, origin : Any = None, relativize=True, **kw : Any):
...
@classmethod
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
...
def to_wire(self, file, compress=None, origin=None):
...
@classmethod
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
...
def flags_to_text_set(self) -> Set[str]:
...

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2010, 2011 Nominum, Inc. # Copyright (C) 2010, 2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

View File

@@ -1,3 +1,5 @@
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and its

Some files were not shown because too many files have changed in this diff Show More