dnspython-2.7.0/dns/__init__.py0000644000000000000000000000317713615410400013373 0ustar00# 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. """dnspython DNS toolkit""" __all__ = [ "asyncbackend", "asyncquery", "asyncresolver", "dnssec", "dnssecalgs", "dnssectypes", "e164", "edns", "entropy", "exception", "flags", "immutable", "inet", "ipv4", "ipv6", "message", "name", "namedict", "node", "opcode", "query", "quic", "rcode", "rdata", "rdataclass", "rdataset", "rdatatype", "renderer", "resolver", "reversename", "rrset", "serial", "set", "tokenizer", "transaction", "tsig", "tsigkeyring", "ttl", "rdtypes", "update", "version", "versioned", "wire", "xfr", "zone", "zonetypes", "zonefile", ] from dns.version import version as __version__ # noqa dnspython-2.7.0/dns/_asyncbackend.py0000644000000000000000000000453413615410400014416 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # This is a nullcontext for both sync and async. 3.7 has a nullcontext, # but it is only for sync use. class NullContext: def __init__(self, enter_result=None): self.enter_result = enter_result def __enter__(self): return self.enter_result def __exit__(self, exc_type, exc_value, traceback): pass async def __aenter__(self): return self.enter_result async def __aexit__(self, exc_type, exc_value, traceback): pass # These are declared here so backends can import them without creating # circular dependencies with dns.asyncbackend. class Socket: # pragma: no cover def __init__(self, family: int, type: int): self.family = family self.type = type async def close(self): pass async def getpeername(self): raise NotImplementedError async def getsockname(self): raise NotImplementedError async def getpeercert(self, timeout): raise NotImplementedError async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_value, traceback): await self.close() class DatagramSocket(Socket): # pragma: no cover async def sendto(self, what, destination, timeout): raise NotImplementedError async def recvfrom(self, size, timeout): raise NotImplementedError class StreamSocket(Socket): # pragma: no cover async def sendall(self, what, timeout): raise NotImplementedError async def recv(self, size, timeout): raise NotImplementedError class NullTransport: async def connect_tcp(self, host, port, timeout, local_address): raise NotImplementedError class Backend: # pragma: no cover def name(self): return "unknown" async def make_socket( self, af, socktype, proto=0, source=None, destination=None, timeout=None, ssl_context=None, server_hostname=None, ): raise NotImplementedError def datagram_connection_required(self): return False async def sleep(self, interval): raise NotImplementedError def get_transport_class(self): raise NotImplementedError async def wait_for(self, awaitable, timeout): raise NotImplementedError dnspython-2.7.0/dns/_asyncio_backend.py0000644000000000000000000002153313615410400015103 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license """asyncio library query support""" import asyncio import socket import sys import dns._asyncbackend import dns._features import dns.exception import dns.inet _is_win32 = sys.platform == "win32" def _get_running_loop(): try: return asyncio.get_running_loop() except AttributeError: # pragma: no cover return asyncio.get_event_loop() class _DatagramProtocol: def __init__(self): self.transport = None self.recvfrom = None def connection_made(self, transport): self.transport = transport def datagram_received(self, data, addr): if self.recvfrom and not self.recvfrom.done(): self.recvfrom.set_result((data, addr)) def error_received(self, exc): # pragma: no cover if self.recvfrom and not self.recvfrom.done(): self.recvfrom.set_exception(exc) def connection_lost(self, exc): if self.recvfrom and not self.recvfrom.done(): if exc is None: # EOF we triggered. Is there a better way to do this? try: raise EOFError("EOF") except EOFError as e: self.recvfrom.set_exception(e) else: self.recvfrom.set_exception(exc) def close(self): self.transport.close() async def _maybe_wait_for(awaitable, timeout): if timeout is not None: try: return await asyncio.wait_for(awaitable, timeout) except asyncio.TimeoutError: raise dns.exception.Timeout(timeout=timeout) else: return await awaitable class DatagramSocket(dns._asyncbackend.DatagramSocket): def __init__(self, family, transport, protocol): super().__init__(family, socket.SOCK_DGRAM) self.transport = transport self.protocol = protocol async def sendto(self, what, destination, timeout): # pragma: no cover # no timeout for asyncio sendto self.transport.sendto(what, destination) return len(what) async def recvfrom(self, size, timeout): # ignore size as there's no way I know to tell protocol about it done = _get_running_loop().create_future() try: assert self.protocol.recvfrom is None self.protocol.recvfrom = done await _maybe_wait_for(done, timeout) return done.result() finally: self.protocol.recvfrom = None async def close(self): self.protocol.close() async def getpeername(self): return self.transport.get_extra_info("peername") async def getsockname(self): return self.transport.get_extra_info("sockname") async def getpeercert(self, timeout): raise NotImplementedError class StreamSocket(dns._asyncbackend.StreamSocket): def __init__(self, af, reader, writer): super().__init__(af, socket.SOCK_STREAM) self.reader = reader self.writer = writer async def sendall(self, what, timeout): self.writer.write(what) return await _maybe_wait_for(self.writer.drain(), timeout) async def recv(self, size, timeout): return await _maybe_wait_for(self.reader.read(size), timeout) async def close(self): self.writer.close() async def getpeername(self): return self.writer.get_extra_info("peername") async def getsockname(self): return self.writer.get_extra_info("sockname") async def getpeercert(self, timeout): return self.writer.get_extra_info("peercert") if dns._features.have("doh"): import anyio import httpcore import httpcore._backends.anyio import httpx _CoreAsyncNetworkBackend = httpcore.AsyncNetworkBackend _CoreAnyIOStream = httpcore._backends.anyio.AnyIOStream from dns.query import _compute_times, _expiration_for_this_attempt, _remaining class _NetworkBackend(_CoreAsyncNetworkBackend): def __init__(self, resolver, local_port, bootstrap_address, family): super().__init__() self._local_port = local_port self._resolver = resolver self._bootstrap_address = bootstrap_address self._family = family if local_port != 0: raise NotImplementedError( "the asyncio transport for HTTPX cannot set the local port" ) async def connect_tcp( self, host, port, timeout, local_address, socket_options=None ): # pylint: disable=signature-differs addresses = [] _, expiration = _compute_times(timeout) if dns.inet.is_address(host): addresses.append(host) elif self._bootstrap_address is not None: addresses.append(self._bootstrap_address) else: timeout = _remaining(expiration) family = self._family if local_address: family = dns.inet.af_for_address(local_address) answers = await self._resolver.resolve_name( host, family=family, lifetime=timeout ) addresses = answers.addresses() for address in addresses: try: attempt_expiration = _expiration_for_this_attempt(2.0, expiration) timeout = _remaining(attempt_expiration) with anyio.fail_after(timeout): stream = await anyio.connect_tcp( remote_host=address, remote_port=port, local_host=local_address, ) return _CoreAnyIOStream(stream) except Exception: pass raise httpcore.ConnectError async def connect_unix_socket( self, path, timeout, socket_options=None ): # pylint: disable=signature-differs raise NotImplementedError async def sleep(self, seconds): # pylint: disable=signature-differs await anyio.sleep(seconds) class _HTTPTransport(httpx.AsyncHTTPTransport): def __init__( self, *args, local_port=0, bootstrap_address=None, resolver=None, family=socket.AF_UNSPEC, **kwargs, ): if resolver is None and bootstrap_address is None: # pylint: disable=import-outside-toplevel,redefined-outer-name import dns.asyncresolver resolver = dns.asyncresolver.Resolver() super().__init__(*args, **kwargs) self._pool._network_backend = _NetworkBackend( resolver, local_port, bootstrap_address, family ) else: _HTTPTransport = dns._asyncbackend.NullTransport # type: ignore class Backend(dns._asyncbackend.Backend): def name(self): return "asyncio" async def make_socket( self, af, socktype, proto=0, source=None, destination=None, timeout=None, ssl_context=None, server_hostname=None, ): loop = _get_running_loop() if socktype == socket.SOCK_DGRAM: if _is_win32 and source is None: # Win32 wants explicit binding before recvfrom(). This is the # proper fix for [#637]. source = (dns.inet.any_for_af(af), 0) transport, protocol = await loop.create_datagram_endpoint( _DatagramProtocol, source, family=af, proto=proto, remote_addr=destination, ) return DatagramSocket(af, transport, protocol) elif socktype == socket.SOCK_STREAM: if destination is None: # This shouldn't happen, but we check to make code analysis software # happier. raise ValueError("destination required for stream sockets") (r, w) = await _maybe_wait_for( asyncio.open_connection( destination[0], destination[1], ssl=ssl_context, family=af, proto=proto, local_addr=source, server_hostname=server_hostname, ), timeout, ) return StreamSocket(af, r, w) raise NotImplementedError( "unsupported socket " + f"type {socktype}" ) # pragma: no cover async def sleep(self, interval): await asyncio.sleep(interval) def datagram_connection_required(self): return False def get_transport_class(self): return _HTTPTransport async def wait_for(self, awaitable, timeout): return await _maybe_wait_for(awaitable, timeout) dnspython-2.7.0/dns/_ddr.py0000644000000000000000000001217713615410400012544 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # # Support for Discovery of Designated Resolvers import socket import time from urllib.parse import urlparse import dns.asyncbackend import dns.inet import dns.name import dns.nameserver import dns.query import dns.rdtypes.svcbbase # The special name of the local resolver when using DDR _local_resolver_name = dns.name.from_text("_dns.resolver.arpa") # # Processing is split up into I/O independent and I/O dependent parts to # make supporting sync and async versions easy. # class _SVCBInfo: def __init__(self, bootstrap_address, port, hostname, nameservers): self.bootstrap_address = bootstrap_address self.port = port self.hostname = hostname self.nameservers = nameservers def ddr_check_certificate(self, cert): """Verify that the _SVCBInfo's address is in the cert's subjectAltName (SAN)""" for name, value in cert["subjectAltName"]: if name == "IP Address" and value == self.bootstrap_address: return True return False def make_tls_context(self): ssl = dns.query.ssl ctx = ssl.create_default_context() ctx.minimum_version = ssl.TLSVersion.TLSv1_2 return ctx def ddr_tls_check_sync(self, lifetime): ctx = self.make_tls_context() expiration = time.time() + lifetime with socket.create_connection( (self.bootstrap_address, self.port), lifetime ) as s: with ctx.wrap_socket(s, server_hostname=self.hostname) as ts: ts.settimeout(dns.query._remaining(expiration)) ts.do_handshake() cert = ts.getpeercert() return self.ddr_check_certificate(cert) async def ddr_tls_check_async(self, lifetime, backend=None): if backend is None: backend = dns.asyncbackend.get_default_backend() ctx = self.make_tls_context() expiration = time.time() + lifetime async with await backend.make_socket( dns.inet.af_for_address(self.bootstrap_address), socket.SOCK_STREAM, 0, None, (self.bootstrap_address, self.port), lifetime, ctx, self.hostname, ) as ts: cert = await ts.getpeercert(dns.query._remaining(expiration)) return self.ddr_check_certificate(cert) def _extract_nameservers_from_svcb(answer): bootstrap_address = answer.nameserver if not dns.inet.is_address(bootstrap_address): return [] infos = [] for rr in answer.rrset.processing_order(): nameservers = [] param = rr.params.get(dns.rdtypes.svcbbase.ParamKey.ALPN) if param is None: continue alpns = set(param.ids) host = rr.target.to_text(omit_final_dot=True) port = None param = rr.params.get(dns.rdtypes.svcbbase.ParamKey.PORT) if param is not None: port = param.port # For now we ignore address hints and address resolution and always use the # bootstrap address if b"h2" in alpns: param = rr.params.get(dns.rdtypes.svcbbase.ParamKey.DOHPATH) if param is None or not param.value.endswith(b"{?dns}"): continue path = param.value[:-6].decode() if not path.startswith("/"): path = "/" + path if port is None: port = 443 url = f"https://{host}:{port}{path}" # check the URL try: urlparse(url) nameservers.append(dns.nameserver.DoHNameserver(url, bootstrap_address)) except Exception: # continue processing other ALPN types pass if b"dot" in alpns: if port is None: port = 853 nameservers.append( dns.nameserver.DoTNameserver(bootstrap_address, port, host) ) if b"doq" in alpns: if port is None: port = 853 nameservers.append( dns.nameserver.DoQNameserver(bootstrap_address, port, True, host) ) if len(nameservers) > 0: infos.append(_SVCBInfo(bootstrap_address, port, host, nameservers)) return infos def _get_nameservers_sync(answer, lifetime): """Return a list of TLS-validated resolver nameservers extracted from an SVCB answer.""" nameservers = [] infos = _extract_nameservers_from_svcb(answer) for info in infos: try: if info.ddr_tls_check_sync(lifetime): nameservers.extend(info.nameservers) except Exception: pass return nameservers async def _get_nameservers_async(answer, lifetime): """Return a list of TLS-validated resolver nameservers extracted from an SVCB answer.""" nameservers = [] infos = _extract_nameservers_from_svcb(answer) for info in infos: try: if await info.ddr_tls_check_async(lifetime): nameservers.extend(info.nameservers) except Exception: pass return nameservers dnspython-2.7.0/dns/_features.py0000644000000000000000000000467413615410400013614 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import importlib.metadata import itertools import string from typing import Dict, List, Tuple def _tuple_from_text(version: str) -> Tuple: text_parts = version.split(".") int_parts = [] for text_part in text_parts: digit_prefix = "".join( itertools.takewhile(lambda x: x in string.digits, text_part) ) try: int_parts.append(int(digit_prefix)) except Exception: break return tuple(int_parts) def _version_check( requirement: str, ) -> bool: """Is the requirement fulfilled? The requirement must be of the form package>=version """ package, minimum = requirement.split(">=") try: version = importlib.metadata.version(package) # This shouldn't happen, but it apparently can. if version is None: return False except Exception: return False t_version = _tuple_from_text(version) t_minimum = _tuple_from_text(minimum) if t_version < t_minimum: return False return True _cache: Dict[str, bool] = {} def have(feature: str) -> bool: """Is *feature* available? This tests if all optional packages needed for the feature are available and recent enough. Returns ``True`` if the feature is available, and ``False`` if it is not or if metadata is missing. """ value = _cache.get(feature) if value is not None: return value requirements = _requirements.get(feature) if requirements is None: # we make a cache entry here for consistency not performance _cache[feature] = False return False ok = True for requirement in requirements: if not _version_check(requirement): ok = False break _cache[feature] = ok return ok def force(feature: str, enabled: bool) -> None: """Force the status of *feature* to be *enabled*. This method is provided as a workaround for any cases where importlib.metadata is ineffective, or for testing. """ _cache[feature] = enabled _requirements: Dict[str, List[str]] = { ### BEGIN generated requirements "dnssec": ["cryptography>=43"], "doh": ["httpcore>=1.0.0", "httpx>=0.26.0", "h2>=4.1.0"], "doq": ["aioquic>=1.0.0"], "idna": ["idna>=3.7"], "trio": ["trio>=0.23"], "wmi": ["wmi>=1.5.1"], ### END generated requirements } dnspython-2.7.0/dns/_immutable_ctx.py0000644000000000000000000000463313615410400014626 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # This implementation of the immutable decorator requires python >= # 3.7, and is significantly more storage efficient when making classes # with slots immutable. It's also faster. import contextvars import inspect _in__init__ = contextvars.ContextVar("_immutable_in__init__", default=False) class _Immutable: """Immutable mixin class""" # We set slots to the empty list to say "we don't have any attributes". # We do this so that if we're mixed in with a class with __slots__, we # don't cause a __dict__ to be added which would waste space. __slots__ = () def __setattr__(self, name, value): if _in__init__.get() is not self: raise TypeError("object doesn't support attribute assignment") else: super().__setattr__(name, value) def __delattr__(self, name): if _in__init__.get() is not self: raise TypeError("object doesn't support attribute assignment") else: super().__delattr__(name) def _immutable_init(f): def nf(*args, **kwargs): previous = _in__init__.set(args[0]) try: # call the actual __init__ f(*args, **kwargs) finally: _in__init__.reset(previous) nf.__signature__ = inspect.signature(f) return nf def immutable(cls): if _Immutable in cls.__mro__: # Some ancestor already has the mixin, so just make sure we keep # following the __init__ protocol. cls.__init__ = _immutable_init(cls.__init__) if hasattr(cls, "__setstate__"): cls.__setstate__ = _immutable_init(cls.__setstate__) ncls = cls else: # Mixin the Immutable class and follow the __init__ protocol. class ncls(_Immutable, cls): # We have to do the __slots__ declaration here too! __slots__ = () @_immutable_init def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if hasattr(cls, "__setstate__"): @_immutable_init def __setstate__(self, *args, **kwargs): super().__setstate__(*args, **kwargs) # make ncls have the same name and module as cls ncls.__name__ = cls.__name__ ncls.__qualname__ = cls.__qualname__ ncls.__module__ = cls.__module__ return ncls dnspython-2.7.0/dns/_trio_backend.py0000644000000000000000000002043113615410400014407 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license """trio async I/O library query support""" import socket import trio import trio.socket # type: ignore import dns._asyncbackend import dns._features import dns.exception import dns.inet if not dns._features.have("trio"): raise ImportError("trio not found or too old") def _maybe_timeout(timeout): if timeout is not None: return trio.move_on_after(timeout) else: return dns._asyncbackend.NullContext() # for brevity _lltuple = dns.inet.low_level_address_tuple # pylint: disable=redefined-outer-name class DatagramSocket(dns._asyncbackend.DatagramSocket): def __init__(self, sock): super().__init__(sock.family, socket.SOCK_DGRAM) self.socket = sock async def sendto(self, what, destination, timeout): with _maybe_timeout(timeout): if destination is None: return await self.socket.send(what) else: return await self.socket.sendto(what, destination) raise dns.exception.Timeout( timeout=timeout ) # pragma: no cover lgtm[py/unreachable-statement] async def recvfrom(self, size, timeout): with _maybe_timeout(timeout): return await self.socket.recvfrom(size) raise dns.exception.Timeout(timeout=timeout) # lgtm[py/unreachable-statement] async def close(self): self.socket.close() async def getpeername(self): return self.socket.getpeername() async def getsockname(self): return self.socket.getsockname() async def getpeercert(self, timeout): raise NotImplementedError class StreamSocket(dns._asyncbackend.StreamSocket): def __init__(self, family, stream, tls=False): super().__init__(family, socket.SOCK_STREAM) self.stream = stream self.tls = tls async def sendall(self, what, timeout): with _maybe_timeout(timeout): return await self.stream.send_all(what) raise dns.exception.Timeout(timeout=timeout) # lgtm[py/unreachable-statement] async def recv(self, size, timeout): with _maybe_timeout(timeout): return await self.stream.receive_some(size) raise dns.exception.Timeout(timeout=timeout) # lgtm[py/unreachable-statement] async def close(self): await self.stream.aclose() async def getpeername(self): if self.tls: return self.stream.transport_stream.socket.getpeername() else: return self.stream.socket.getpeername() async def getsockname(self): if self.tls: return self.stream.transport_stream.socket.getsockname() else: return self.stream.socket.getsockname() async def getpeercert(self, timeout): if self.tls: with _maybe_timeout(timeout): await self.stream.do_handshake() return self.stream.getpeercert() else: raise NotImplementedError if dns._features.have("doh"): import httpcore import httpcore._backends.trio import httpx _CoreAsyncNetworkBackend = httpcore.AsyncNetworkBackend _CoreTrioStream = httpcore._backends.trio.TrioStream from dns.query import _compute_times, _expiration_for_this_attempt, _remaining class _NetworkBackend(_CoreAsyncNetworkBackend): def __init__(self, resolver, local_port, bootstrap_address, family): super().__init__() self._local_port = local_port self._resolver = resolver self._bootstrap_address = bootstrap_address self._family = family async def connect_tcp( self, host, port, timeout, local_address, socket_options=None ): # pylint: disable=signature-differs addresses = [] _, expiration = _compute_times(timeout) if dns.inet.is_address(host): addresses.append(host) elif self._bootstrap_address is not None: addresses.append(self._bootstrap_address) else: timeout = _remaining(expiration) family = self._family if local_address: family = dns.inet.af_for_address(local_address) answers = await self._resolver.resolve_name( host, family=family, lifetime=timeout ) addresses = answers.addresses() for address in addresses: try: af = dns.inet.af_for_address(address) if local_address is not None or self._local_port != 0: source = (local_address, self._local_port) else: source = None destination = (address, port) attempt_expiration = _expiration_for_this_attempt(2.0, expiration) timeout = _remaining(attempt_expiration) sock = await Backend().make_socket( af, socket.SOCK_STREAM, 0, source, destination, timeout ) return _CoreTrioStream(sock.stream) except Exception: continue raise httpcore.ConnectError async def connect_unix_socket( self, path, timeout, socket_options=None ): # pylint: disable=signature-differs raise NotImplementedError async def sleep(self, seconds): # pylint: disable=signature-differs await trio.sleep(seconds) class _HTTPTransport(httpx.AsyncHTTPTransport): def __init__( self, *args, local_port=0, bootstrap_address=None, resolver=None, family=socket.AF_UNSPEC, **kwargs, ): if resolver is None and bootstrap_address is None: # pylint: disable=import-outside-toplevel,redefined-outer-name import dns.asyncresolver resolver = dns.asyncresolver.Resolver() super().__init__(*args, **kwargs) self._pool._network_backend = _NetworkBackend( resolver, local_port, bootstrap_address, family ) else: _HTTPTransport = dns._asyncbackend.NullTransport # type: ignore class Backend(dns._asyncbackend.Backend): def name(self): return "trio" async def make_socket( self, af, socktype, proto=0, source=None, destination=None, timeout=None, ssl_context=None, server_hostname=None, ): s = trio.socket.socket(af, socktype, proto) stream = None try: if source: await s.bind(_lltuple(source, af)) if socktype == socket.SOCK_STREAM or destination is not None: connected = False with _maybe_timeout(timeout): await s.connect(_lltuple(destination, af)) connected = True if not connected: raise dns.exception.Timeout( timeout=timeout ) # lgtm[py/unreachable-statement] except Exception: # pragma: no cover s.close() raise if socktype == socket.SOCK_DGRAM: return DatagramSocket(s) elif socktype == socket.SOCK_STREAM: stream = trio.SocketStream(s) tls = False if ssl_context: tls = True try: stream = trio.SSLStream( stream, ssl_context, server_hostname=server_hostname ) except Exception: # pragma: no cover await stream.aclose() raise return StreamSocket(af, stream, tls) raise NotImplementedError( "unsupported socket " + f"type {socktype}" ) # pragma: no cover async def sleep(self, interval): await trio.sleep(interval) def get_transport_class(self): return _HTTPTransport async def wait_for(self, awaitable, timeout): with _maybe_timeout(timeout): return await awaitable raise dns.exception.Timeout( timeout=timeout ) # pragma: no cover lgtm[py/unreachable-statement] dnspython-2.7.0/dns/asyncbackend.py0000644000000000000000000000535413615410400014260 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license from typing import Dict import dns.exception # pylint: disable=unused-import from dns._asyncbackend import ( # noqa: F401 lgtm[py/unused-import] Backend, DatagramSocket, Socket, StreamSocket, ) # pylint: enable=unused-import _default_backend = None _backends: Dict[str, Backend] = {} # Allow sniffio import to be disabled for testing purposes _no_sniffio = False class AsyncLibraryNotFoundError(dns.exception.DNSException): pass def get_backend(name: str) -> Backend: """Get the specified asynchronous backend. *name*, a ``str``, the name of the backend. Currently the "trio" and "asyncio" backends are available. Raises NotImplementedError if an unknown backend name is specified. """ # pylint: disable=import-outside-toplevel,redefined-outer-name backend = _backends.get(name) if backend: return backend if name == "trio": import dns._trio_backend backend = dns._trio_backend.Backend() elif name == "asyncio": import dns._asyncio_backend backend = dns._asyncio_backend.Backend() else: raise NotImplementedError(f"unimplemented async backend {name}") _backends[name] = backend return backend def sniff() -> str: """Attempt to determine the in-use asynchronous I/O library by using the ``sniffio`` module if it is available. Returns the name of the library, or raises AsyncLibraryNotFoundError if the library cannot be determined. """ # pylint: disable=import-outside-toplevel try: if _no_sniffio: raise ImportError import sniffio try: return sniffio.current_async_library() except sniffio.AsyncLibraryNotFoundError: raise AsyncLibraryNotFoundError("sniffio cannot determine async library") except ImportError: import asyncio try: asyncio.get_running_loop() return "asyncio" except RuntimeError: raise AsyncLibraryNotFoundError("no async library detected") def get_default_backend() -> Backend: """Get the default backend, initializing it if necessary.""" if _default_backend: return _default_backend return set_default_backend(sniff()) def set_default_backend(name: str) -> Backend: """Set the default backend. It's not normally necessary to call this method, as ``get_default_backend()`` will initialize the backend appropriately in many cases. If ``sniffio`` is not installed, or in testing situations, this function allows the backend to be set explicitly. """ global _default_backend _default_backend = get_backend(name) return _default_backend dnspython-2.7.0/dns/asyncquery.py0000644000000000000000000007414513615410400014042 0ustar00# 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 # 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. """Talk to a DNS server.""" import base64 import contextlib import random import socket import struct import time import urllib.parse from typing import Any, Dict, Optional, Tuple, Union, cast import dns.asyncbackend import dns.exception import dns.inet import dns.message import dns.name import dns.quic import dns.rcode import dns.rdataclass import dns.rdatatype import dns.transaction from dns._asyncbackend import NullContext from dns.query import ( BadResponse, HTTPVersion, NoDOH, NoDOQ, UDPMode, _check_status, _compute_times, _make_dot_ssl_context, _matches_destination, _remaining, have_doh, ssl, ) if have_doh: import httpx # for brevity _lltuple = dns.inet.low_level_address_tuple def _source_tuple(af, address, port): # Make a high level source tuple, or return None if address and port # are both None if address or port: if address is None: if af == socket.AF_INET: address = "0.0.0.0" elif af == socket.AF_INET6: address = "::" else: raise NotImplementedError(f"unknown address family {af}") return (address, port) else: return None def _timeout(expiration, now=None): if expiration is not None: if not now: now = time.time() return max(expiration - now, 0) else: return None async def send_udp( sock: dns.asyncbackend.DatagramSocket, what: Union[dns.message.Message, bytes], destination: Any, expiration: Optional[float] = None, ) -> Tuple[int, float]: """Send a DNS message to the specified UDP socket. *sock*, a ``dns.asyncbackend.DatagramSocket``. *what*, a ``bytes`` or ``dns.message.Message``, the message to send. *destination*, a destination tuple appropriate for the address family of the socket, specifying where to send the query. *expiration*, a ``float`` or ``None``, the absolute time at which a timeout exception should be raised. If ``None``, no timeout will occur. The expiration value is meaningless for the asyncio backend, as asyncio's transport sendto() never blocks. Returns an ``(int, float)`` tuple of bytes sent and the sent time. """ if isinstance(what, dns.message.Message): what = what.to_wire() sent_time = time.time() n = await sock.sendto(what, destination, _timeout(expiration, sent_time)) return (n, sent_time) async def receive_udp( sock: dns.asyncbackend.DatagramSocket, destination: Optional[Any] = None, expiration: Optional[float] = None, ignore_unexpected: bool = False, one_rr_per_rrset: bool = False, keyring: Optional[Dict[dns.name.Name, dns.tsig.Key]] = None, request_mac: Optional[bytes] = b"", ignore_trailing: bool = False, raise_on_truncation: bool = False, ignore_errors: bool = False, query: Optional[dns.message.Message] = None, ) -> Any: """Read a DNS message from a UDP socket. *sock*, a ``dns.asyncbackend.DatagramSocket``. See :py:func:`dns.query.receive_udp()` for the documentation of the other parameters, and exceptions. Returns a ``(dns.message.Message, float, tuple)`` tuple of the received message, the received time, and the address where the message arrived from. """ wire = b"" while True: (wire, from_address) = await sock.recvfrom(65535, _timeout(expiration)) if not _matches_destination( sock.family, from_address, destination, ignore_unexpected ): continue received_time = time.time() try: r = dns.message.from_wire( wire, keyring=keyring, request_mac=request_mac, one_rr_per_rrset=one_rr_per_rrset, ignore_trailing=ignore_trailing, raise_on_truncation=raise_on_truncation, ) except dns.message.Truncated as e: # See the comment in query.py for details. if ( ignore_errors and query is not None and not query.is_response(e.message()) ): continue else: raise except Exception: if ignore_errors: continue else: raise if ignore_errors and query is not None and not query.is_response(r): continue return (r, received_time, from_address) async def udp( q: dns.message.Message, where: str, timeout: Optional[float] = None, port: int = 53, source: Optional[str] = None, source_port: int = 0, ignore_unexpected: bool = False, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, raise_on_truncation: bool = False, sock: Optional[dns.asyncbackend.DatagramSocket] = None, backend: Optional[dns.asyncbackend.Backend] = None, ignore_errors: bool = False, ) -> dns.message.Message: """Return the response obtained after sending a query via UDP. *sock*, a ``dns.asyncbackend.DatagramSocket``, or ``None``, the socket to use for the query. If ``None``, the default, a socket is created. Note that if a socket is provided, the *source*, *source_port*, and *backend* are ignored. *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, the default, then dnspython will use the default backend. See :py:func:`dns.query.udp()` for the documentation of the other parameters, exceptions, and return type of this method. """ wire = q.to_wire() (begin_time, expiration) = _compute_times(timeout) af = dns.inet.af_for_address(where) destination = _lltuple((where, port), af) if sock: cm: contextlib.AbstractAsyncContextManager = NullContext(sock) else: if not backend: backend = dns.asyncbackend.get_default_backend() stuple = _source_tuple(af, source, source_port) if backend.datagram_connection_required(): dtuple = (where, port) else: dtuple = None cm = await backend.make_socket(af, socket.SOCK_DGRAM, 0, stuple, dtuple) async with cm as s: await send_udp(s, wire, destination, expiration) (r, received_time, _) = await receive_udp( s, destination, expiration, ignore_unexpected, one_rr_per_rrset, q.keyring, q.mac, ignore_trailing, raise_on_truncation, ignore_errors, q, ) r.time = received_time - begin_time # We don't need to check q.is_response() if we are in ignore_errors mode # as receive_udp() will have checked it. if not (ignore_errors or q.is_response(r)): raise BadResponse return r async def udp_with_fallback( q: dns.message.Message, where: str, timeout: Optional[float] = None, port: int = 53, source: Optional[str] = None, source_port: int = 0, ignore_unexpected: bool = False, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, udp_sock: Optional[dns.asyncbackend.DatagramSocket] = None, tcp_sock: Optional[dns.asyncbackend.StreamSocket] = None, backend: Optional[dns.asyncbackend.Backend] = None, ignore_errors: bool = False, ) -> Tuple[dns.message.Message, bool]: """Return the response to the query, trying UDP first and falling back to TCP if UDP results in a truncated response. *udp_sock*, a ``dns.asyncbackend.DatagramSocket``, or ``None``, the socket to use for the UDP query. If ``None``, the default, a socket is created. Note that if a socket is provided the *source*, *source_port*, and *backend* are ignored for the UDP query. *tcp_sock*, a ``dns.asyncbackend.StreamSocket``, or ``None``, the socket to use for the TCP query. If ``None``, the default, a socket is created. Note that if a socket is provided *where*, *source*, *source_port*, and *backend* are ignored for the TCP query. *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, the default, then dnspython will use the default backend. See :py:func:`dns.query.udp_with_fallback()` for the documentation of the other parameters, exceptions, and return type of this method. """ try: response = await udp( q, where, timeout, port, source, source_port, ignore_unexpected, one_rr_per_rrset, ignore_trailing, True, udp_sock, backend, ignore_errors, ) return (response, False) except dns.message.Truncated: response = await tcp( q, where, timeout, port, source, source_port, one_rr_per_rrset, ignore_trailing, tcp_sock, backend, ) return (response, True) async def send_tcp( sock: dns.asyncbackend.StreamSocket, what: Union[dns.message.Message, bytes], expiration: Optional[float] = None, ) -> Tuple[int, float]: """Send a DNS message to the specified TCP socket. *sock*, a ``dns.asyncbackend.StreamSocket``. See :py:func:`dns.query.send_tcp()` for the documentation of the other parameters, exceptions, and return type of this method. """ if isinstance(what, dns.message.Message): tcpmsg = what.to_wire(prepend_length=True) else: # 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 = len(what).to_bytes(2, "big") + what sent_time = time.time() await sock.sendall(tcpmsg, _timeout(expiration, sent_time)) return (len(tcpmsg), sent_time) async def _read_exactly(sock, count, expiration): """Read the specified number of bytes from stream. Keep trying until we either get the desired amount, or we hit EOF. """ s = b"" while count > 0: n = await sock.recv(count, _timeout(expiration)) if n == b"": raise EOFError("EOF") count = count - len(n) s = s + n return s async def receive_tcp( sock: dns.asyncbackend.StreamSocket, expiration: Optional[float] = None, one_rr_per_rrset: bool = False, keyring: Optional[Dict[dns.name.Name, dns.tsig.Key]] = None, request_mac: Optional[bytes] = b"", ignore_trailing: bool = False, ) -> Tuple[dns.message.Message, float]: """Read a DNS message from a TCP socket. *sock*, a ``dns.asyncbackend.StreamSocket``. See :py:func:`dns.query.receive_tcp()` for the documentation of the other parameters, exceptions, and return type of this method. """ ldata = await _read_exactly(sock, 2, expiration) (l,) = struct.unpack("!H", ldata) wire = await _read_exactly(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) async def tcp( q: dns.message.Message, where: str, timeout: Optional[float] = None, port: int = 53, source: Optional[str] = None, source_port: int = 0, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, sock: Optional[dns.asyncbackend.StreamSocket] = None, backend: Optional[dns.asyncbackend.Backend] = None, ) -> dns.message.Message: """Return the response obtained after sending a query via TCP. *sock*, a ``dns.asyncbacket.StreamSocket``, or ``None``, the socket to use for the query. If ``None``, the default, a socket is created. Note that if a socket is provided *where*, *port*, *source*, *source_port*, and *backend* are ignored. *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, the default, then dnspython will use the default backend. See :py:func:`dns.query.tcp()` for the documentation of the other parameters, exceptions, and return type of this method. """ wire = q.to_wire() (begin_time, expiration) = _compute_times(timeout) if sock: # Verify that the socket is connected, as if it's not connected, # it's not writable, and the polling in send_tcp() will time out or # hang forever. await sock.getpeername() cm: contextlib.AbstractAsyncContextManager = NullContext(sock) else: # These are simple (address, port) pairs, not family-dependent tuples # you pass to low-level socket code. af = dns.inet.af_for_address(where) stuple = _source_tuple(af, source, source_port) dtuple = (where, port) if not backend: backend = dns.asyncbackend.get_default_backend() cm = await backend.make_socket( af, socket.SOCK_STREAM, 0, stuple, dtuple, timeout ) async with cm as s: await send_tcp(s, wire, expiration) (r, received_time) = await receive_tcp( s, expiration, one_rr_per_rrset, q.keyring, q.mac, ignore_trailing ) r.time = received_time - begin_time if not q.is_response(r): raise BadResponse return r async def tls( q: dns.message.Message, where: str, timeout: Optional[float] = None, port: int = 853, source: Optional[str] = None, source_port: int = 0, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, sock: Optional[dns.asyncbackend.StreamSocket] = None, backend: Optional[dns.asyncbackend.Backend] = None, ssl_context: Optional[ssl.SSLContext] = None, server_hostname: Optional[str] = None, verify: Union[bool, str] = True, ) -> dns.message.Message: """Return the response obtained after sending a query via TLS. *sock*, an ``asyncbackend.StreamSocket``, or ``None``, the socket to use for the query. If ``None``, the default, a socket is created. Note that if a socket is provided, it must be a connected SSL stream socket, and *where*, *port*, *source*, *source_port*, *backend*, *ssl_context*, and *server_hostname* are ignored. *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, the default, then dnspython will use the default backend. See :py:func:`dns.query.tls()` for the documentation of the other parameters, exceptions, and return type of this method. """ (begin_time, expiration) = _compute_times(timeout) if sock: cm: contextlib.AbstractAsyncContextManager = NullContext(sock) else: if ssl_context is None: ssl_context = _make_dot_ssl_context(server_hostname, verify) af = dns.inet.af_for_address(where) stuple = _source_tuple(af, source, source_port) dtuple = (where, port) if not backend: backend = dns.asyncbackend.get_default_backend() cm = await backend.make_socket( af, socket.SOCK_STREAM, 0, stuple, dtuple, timeout, ssl_context, server_hostname, ) async with cm as s: timeout = _timeout(expiration) response = await tcp( q, where, timeout, port, source, source_port, one_rr_per_rrset, ignore_trailing, s, backend, ) end_time = time.time() response.time = end_time - begin_time return response def _maybe_get_resolver( resolver: Optional["dns.asyncresolver.Resolver"], ) -> "dns.asyncresolver.Resolver": # We need a separate method for this to avoid overriding the global # variable "dns" with the as-yet undefined local variable "dns" # in https(). if resolver is None: # pylint: disable=import-outside-toplevel,redefined-outer-name import dns.asyncresolver resolver = dns.asyncresolver.Resolver() return resolver async def https( q: dns.message.Message, where: str, timeout: Optional[float] = None, port: int = 443, source: Optional[str] = None, source_port: int = 0, # pylint: disable=W0613 one_rr_per_rrset: bool = False, ignore_trailing: bool = False, client: Optional["httpx.AsyncClient"] = None, path: str = "/dns-query", post: bool = True, verify: Union[bool, str] = True, bootstrap_address: Optional[str] = None, resolver: Optional["dns.asyncresolver.Resolver"] = None, family: int = socket.AF_UNSPEC, http_version: HTTPVersion = HTTPVersion.DEFAULT, ) -> dns.message.Message: """Return the response obtained after sending a query via DNS-over-HTTPS. *client*, a ``httpx.AsyncClient``. If provided, the client to use for the query. Unlike the other dnspython async functions, a backend cannot be provided in this function because httpx always auto-detects the async backend. See :py:func:`dns.query.https()` for the documentation of the other parameters, exceptions, and return type of this method. """ try: af = dns.inet.af_for_address(where) except ValueError: af = None if af is not None and dns.inet.is_address(where): if af == socket.AF_INET: url = f"https://{where}:{port}{path}" elif af == socket.AF_INET6: url = f"https://[{where}]:{port}{path}" else: url = where extensions = {} if bootstrap_address is None: # pylint: disable=possibly-used-before-assignment parsed = urllib.parse.urlparse(url) if parsed.hostname is None: raise ValueError("no hostname in URL") if dns.inet.is_address(parsed.hostname): bootstrap_address = parsed.hostname extensions["sni_hostname"] = parsed.hostname if parsed.port is not None: port = parsed.port if http_version == HTTPVersion.H3 or ( http_version == HTTPVersion.DEFAULT and not have_doh ): if bootstrap_address is None: resolver = _maybe_get_resolver(resolver) assert parsed.hostname is not None # for mypy answers = await resolver.resolve_name(parsed.hostname, family) bootstrap_address = random.choice(list(answers.addresses())) return await _http3( q, bootstrap_address, url, timeout, port, source, source_port, one_rr_per_rrset, ignore_trailing, verify=verify, post=post, ) if not have_doh: raise NoDOH # pragma: no cover # pylint: disable=possibly-used-before-assignment if client and not isinstance(client, httpx.AsyncClient): raise ValueError("session parameter must be an httpx.AsyncClient") # pylint: enable=possibly-used-before-assignment wire = q.to_wire() headers = {"accept": "application/dns-message"} h1 = http_version in (HTTPVersion.H1, HTTPVersion.DEFAULT) h2 = http_version in (HTTPVersion.H2, HTTPVersion.DEFAULT) backend = dns.asyncbackend.get_default_backend() if source is None: local_address = None local_port = 0 else: local_address = source local_port = source_port if client: cm: contextlib.AbstractAsyncContextManager = NullContext(client) else: transport = backend.get_transport_class()( local_address=local_address, http1=h1, http2=h2, verify=verify, local_port=local_port, bootstrap_address=bootstrap_address, resolver=resolver, family=family, ) cm = httpx.AsyncClient(http1=h1, http2=h2, verify=verify, transport=transport) async with cm as the_client: # see https://tools.ietf.org/html/rfc8484#section-4.1.1 for DoH # GET and POST examples if post: headers.update( { "content-type": "application/dns-message", "content-length": str(len(wire)), } ) response = await backend.wait_for( the_client.post( url, headers=headers, content=wire, extensions=extensions, ), timeout, ) else: wire = base64.urlsafe_b64encode(wire).rstrip(b"=") twire = wire.decode() # httpx does a repr() if we give it bytes response = await backend.wait_for( the_client.get( url, headers=headers, params={"dns": twire}, extensions=extensions, ), timeout, ) # see https://tools.ietf.org/html/rfc8484#section-4.2.1 for info about DoH # status codes if response.status_code < 200 or response.status_code > 299: raise ValueError( f"{where} responded with status code {response.status_code}" f"\nResponse body: {response.content!r}" ) r = dns.message.from_wire( response.content, keyring=q.keyring, request_mac=q.request_mac, one_rr_per_rrset=one_rr_per_rrset, ignore_trailing=ignore_trailing, ) r.time = response.elapsed.total_seconds() if not q.is_response(r): raise BadResponse return r async def _http3( q: dns.message.Message, where: str, url: str, timeout: Optional[float] = None, port: int = 853, source: Optional[str] = None, source_port: int = 0, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, verify: Union[bool, str] = True, backend: Optional[dns.asyncbackend.Backend] = None, hostname: Optional[str] = None, post: bool = True, ) -> dns.message.Message: if not dns.quic.have_quic: raise NoDOH("DNS-over-HTTP3 is not available.") # pragma: no cover url_parts = urllib.parse.urlparse(url) hostname = url_parts.hostname if url_parts.port is not None: port = url_parts.port q.id = 0 wire = q.to_wire() (cfactory, mfactory) = dns.quic.factories_for_backend(backend) async with cfactory() as context: async with mfactory( context, verify_mode=verify, server_name=hostname, h3=True ) as the_manager: the_connection = the_manager.connect(where, port, source, source_port) (start, expiration) = _compute_times(timeout) stream = await the_connection.make_stream(timeout) async with stream: # note that send_h3() does not need await stream.send_h3(url, wire, post) wire = await stream.receive(_remaining(expiration)) _check_status(stream.headers(), where, wire) finish = time.time() r = dns.message.from_wire( wire, keyring=q.keyring, request_mac=q.request_mac, one_rr_per_rrset=one_rr_per_rrset, ignore_trailing=ignore_trailing, ) r.time = max(finish - start, 0.0) if not q.is_response(r): raise BadResponse return r async def quic( q: dns.message.Message, where: str, timeout: Optional[float] = None, port: int = 853, source: Optional[str] = None, source_port: int = 0, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, connection: Optional[dns.quic.AsyncQuicConnection] = None, verify: Union[bool, str] = True, backend: Optional[dns.asyncbackend.Backend] = None, hostname: Optional[str] = None, server_hostname: Optional[str] = None, ) -> dns.message.Message: """Return the response obtained after sending an asynchronous query via DNS-over-QUIC. *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, the default, then dnspython will use the default backend. See :py:func:`dns.query.quic()` for the documentation of the other parameters, exceptions, and return type of this method. """ if not dns.quic.have_quic: raise NoDOQ("DNS-over-QUIC is not available.") # pragma: no cover if server_hostname is not None and hostname is None: hostname = server_hostname q.id = 0 wire = q.to_wire() the_connection: dns.quic.AsyncQuicConnection if connection: cfactory = dns.quic.null_factory mfactory = dns.quic.null_factory the_connection = connection else: (cfactory, mfactory) = dns.quic.factories_for_backend(backend) async with cfactory() as context: async with mfactory( context, verify_mode=verify, server_name=server_hostname, ) as the_manager: if not connection: the_connection = the_manager.connect(where, port, source, source_port) (start, expiration) = _compute_times(timeout) stream = await the_connection.make_stream(timeout) async with stream: await stream.send(wire, True) wire = await stream.receive(_remaining(expiration)) finish = time.time() r = dns.message.from_wire( wire, keyring=q.keyring, request_mac=q.request_mac, one_rr_per_rrset=one_rr_per_rrset, ignore_trailing=ignore_trailing, ) r.time = max(finish - start, 0.0) if not q.is_response(r): raise BadResponse return r async def _inbound_xfr( txn_manager: dns.transaction.TransactionManager, s: dns.asyncbackend.Socket, query: dns.message.Message, serial: Optional[int], timeout: Optional[float], expiration: float, ) -> Any: """Given a socket, does the zone transfer.""" rdtype = query.question[0].rdtype is_ixfr = rdtype == dns.rdatatype.IXFR origin = txn_manager.from_wire_origin() wire = query.to_wire() is_udp = s.type == socket.SOCK_DGRAM if is_udp: udp_sock = cast(dns.asyncbackend.DatagramSocket, s) await udp_sock.sendto(wire, None, _timeout(expiration)) else: tcp_sock = cast(dns.asyncbackend.StreamSocket, s) tcpmsg = struct.pack("!H", len(wire)) + wire await tcp_sock.sendall(tcpmsg, expiration) with dns.xfr.Inbound(txn_manager, rdtype, serial, is_udp) as inbound: done = False tsig_ctx = None while not done: (_, mexpiration) = _compute_times(timeout) if mexpiration is None or ( expiration is not None and mexpiration > expiration ): mexpiration = expiration if is_udp: timeout = _timeout(mexpiration) (rwire, _) = await udp_sock.recvfrom(65535, timeout) else: ldata = await _read_exactly(tcp_sock, 2, mexpiration) (l,) = struct.unpack("!H", ldata) rwire = await _read_exactly(tcp_sock, l, mexpiration) r = dns.message.from_wire( rwire, keyring=query.keyring, request_mac=query.mac, xfr=True, origin=origin, tsig_ctx=tsig_ctx, multi=(not is_udp), one_rr_per_rrset=is_ixfr, ) done = inbound.process_message(r) yield r tsig_ctx = r.tsig_ctx if query.keyring and not r.had_tsig: raise dns.exception.FormError("missing TSIG") async def inbound_xfr( where: str, txn_manager: dns.transaction.TransactionManager, query: Optional[dns.message.Message] = None, port: int = 53, timeout: Optional[float] = None, lifetime: Optional[float] = None, source: Optional[str] = None, source_port: int = 0, udp_mode: UDPMode = UDPMode.NEVER, backend: Optional[dns.asyncbackend.Backend] = None, ) -> None: """Conduct an inbound transfer and apply it via a transaction from the txn_manager. *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, the default, then dnspython will use the default backend. See :py:func:`dns.query.inbound_xfr()` for the documentation of the other parameters, exceptions, and return type of this method. """ if query is None: (query, serial) = dns.xfr.make_query(txn_manager) else: serial = dns.xfr.extract_serial_from_query(query) af = dns.inet.af_for_address(where) stuple = _source_tuple(af, source, source_port) dtuple = (where, port) if not backend: backend = dns.asyncbackend.get_default_backend() (_, expiration) = _compute_times(lifetime) if query.question[0].rdtype == dns.rdatatype.IXFR and udp_mode != UDPMode.NEVER: s = await backend.make_socket( af, socket.SOCK_DGRAM, 0, stuple, dtuple, _timeout(expiration) ) async with s: try: async for _ in _inbound_xfr( txn_manager, s, query, serial, timeout, expiration ): pass return except dns.xfr.UseTCP: if udp_mode == UDPMode.ONLY: raise s = await backend.make_socket( af, socket.SOCK_STREAM, 0, stuple, dtuple, _timeout(expiration) ) async with s: async for _ in _inbound_xfr(txn_manager, s, query, serial, timeout, expiration): pass dnspython-2.7.0/dns/asyncresolver.py0000644000000000000000000004267413615410400014540 0ustar00# 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 # 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. """Asynchronous DNS stub resolver.""" import socket import time from typing import Any, Dict, List, Optional, Union import dns._ddr import dns.asyncbackend import dns.asyncquery import dns.exception import dns.name import dns.query import dns.rdataclass import dns.rdatatype import dns.resolver # lgtm[py/import-and-import-from] # import some resolver symbols for brevity from dns.resolver import NXDOMAIN, NoAnswer, NoRootSOA, NotAbsolute # for indentation purposes below _udp = dns.asyncquery.udp _tcp = dns.asyncquery.tcp class Resolver(dns.resolver.BaseResolver): """Asynchronous DNS stub resolver.""" async def resolve( self, qname: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.A, rdclass: Union[dns.rdataclass.RdataClass, str] = dns.rdataclass.IN, tcp: bool = False, source: Optional[str] = None, raise_on_no_answer: bool = True, source_port: int = 0, lifetime: Optional[float] = None, search: Optional[bool] = None, backend: Optional[dns.asyncbackend.Backend] = None, ) -> dns.resolver.Answer: """Query nameservers asynchronously to find the answer to the question. *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, the default, then dnspython will use the default backend. See :py:func:`dns.resolver.Resolver.resolve()` for the documentation of the other parameters, exceptions, and return type of this method. """ resolution = dns.resolver._Resolution( self, qname, rdtype, rdclass, tcp, raise_on_no_answer, search ) if not backend: backend = dns.asyncbackend.get_default_backend() start = time.time() while True: (request, answer) = resolution.next_request() # Note we need to say "if answer is not None" and not just # "if answer" because answer implements __len__, and python # will call that. We want to return if we have an answer # object, including in cases where its length is 0. if answer is not None: # cache hit! return answer assert request is not None # needed for type checking done = False while not done: (nameserver, tcp, backoff) = resolution.next_nameserver() if backoff: await backend.sleep(backoff) timeout = self._compute_timeout(start, lifetime, resolution.errors) try: response = await nameserver.async_query( request, timeout=timeout, source=source, source_port=source_port, max_size=tcp, backend=backend, ) except Exception as ex: (_, done) = resolution.query_result(None, ex) continue (answer, done) = resolution.query_result(response, None) # Note we need to say "if answer is not None" and not just # "if answer" because answer implements __len__, and python # will call that. We want to return if we have an answer # object, including in cases where its length is 0. if answer is not None: return answer async def resolve_address( self, ipaddr: str, *args: Any, **kwargs: Any ) -> dns.resolver.Answer: """Use an asynchronous resolver to run a reverse query for PTR records. This utilizes the resolve() method to perform a PTR lookup on the specified IP address. *ipaddr*, a ``str``, the IPv4 or IPv6 address you want to get the PTR record for. All other arguments that can be passed to the resolve() function except for rdtype and rdclass are also supported by this function. """ # We make a modified kwargs for type checking happiness, as otherwise # we get a legit warning about possibly having rdtype and rdclass # in the kwargs more than once. modified_kwargs: Dict[str, Any] = {} modified_kwargs.update(kwargs) modified_kwargs["rdtype"] = dns.rdatatype.PTR modified_kwargs["rdclass"] = dns.rdataclass.IN return await self.resolve( dns.reversename.from_address(ipaddr), *args, **modified_kwargs ) async def resolve_name( self, name: Union[dns.name.Name, str], family: int = socket.AF_UNSPEC, **kwargs: Any, ) -> dns.resolver.HostAnswers: """Use an asynchronous resolver to query for address records. This utilizes the resolve() method to perform A and/or AAAA lookups on the specified name. *qname*, a ``dns.name.Name`` or ``str``, the name to resolve. *family*, an ``int``, the address family. If socket.AF_UNSPEC (the default), both A and AAAA records will be retrieved. All other arguments that can be passed to the resolve() function except for rdtype and rdclass are also supported by this function. """ # We make a modified kwargs for type checking happiness, as otherwise # we get a legit warning about possibly having rdtype and rdclass # in the kwargs more than once. modified_kwargs: Dict[str, Any] = {} modified_kwargs.update(kwargs) modified_kwargs.pop("rdtype", None) modified_kwargs["rdclass"] = dns.rdataclass.IN if family == socket.AF_INET: v4 = await self.resolve(name, dns.rdatatype.A, **modified_kwargs) return dns.resolver.HostAnswers.make(v4=v4) elif family == socket.AF_INET6: v6 = await self.resolve(name, dns.rdatatype.AAAA, **modified_kwargs) return dns.resolver.HostAnswers.make(v6=v6) elif family != socket.AF_UNSPEC: raise NotImplementedError(f"unknown address family {family}") raise_on_no_answer = modified_kwargs.pop("raise_on_no_answer", True) lifetime = modified_kwargs.pop("lifetime", None) start = time.time() v6 = await self.resolve( name, dns.rdatatype.AAAA, raise_on_no_answer=False, lifetime=self._compute_timeout(start, lifetime), **modified_kwargs, ) # Note that setting name ensures we query the same name # for A as we did for AAAA. (This is just in case search lists # are active by default in the resolver configuration and # we might be talking to a server that says NXDOMAIN when it # wants to say NOERROR no data. name = v6.qname v4 = await self.resolve( name, dns.rdatatype.A, raise_on_no_answer=False, lifetime=self._compute_timeout(start, lifetime), **modified_kwargs, ) answers = dns.resolver.HostAnswers.make( v6=v6, v4=v4, add_empty=not raise_on_no_answer ) if not answers: raise NoAnswer(response=v6.response) return answers # pylint: disable=redefined-outer-name async def canonical_name(self, name: Union[dns.name.Name, str]) -> dns.name.Name: """Determine the canonical name of *name*. The canonical name is the name the resolver uses for queries after all CNAME and DNAME renamings have been applied. *name*, a ``dns.name.Name`` or ``str``, the query name. This method can raise any exception that ``resolve()`` can raise, other than ``dns.resolver.NoAnswer`` and ``dns.resolver.NXDOMAIN``. Returns a ``dns.name.Name``. """ try: answer = await self.resolve(name, raise_on_no_answer=False) canonical_name = answer.canonical_name except dns.resolver.NXDOMAIN as e: canonical_name = e.canonical_name return canonical_name async def try_ddr(self, lifetime: float = 5.0) -> None: """Try to update the resolver's nameservers using Discovery of Designated Resolvers (DDR). If successful, the resolver will subsequently use DNS-over-HTTPS or DNS-over-TLS for future queries. *lifetime*, a float, is the maximum time to spend attempting DDR. The default is 5 seconds. If the SVCB query is successful and results in a non-empty list of nameservers, then the resolver's nameservers are set to the returned servers in priority order. The current implementation does not use any address hints from the SVCB record, nor does it resolve addresses for the SCVB target name, rather it assumes that the bootstrap nameserver will always be one of the addresses and uses it. A future revision to the code may offer fuller support. The code verifies that the bootstrap nameserver is in the Subject Alternative Name field of the TLS certficate. """ try: expiration = time.time() + lifetime answer = await self.resolve( dns._ddr._local_resolver_name, "svcb", lifetime=lifetime ) timeout = dns.query._remaining(expiration) nameservers = await dns._ddr._get_nameservers_async(answer, timeout) if len(nameservers) > 0: self.nameservers = nameservers except Exception: pass default_resolver = None def get_default_resolver() -> Resolver: """Get the default asynchronous resolver, initializing it if necessary.""" if default_resolver is None: reset_default_resolver() assert default_resolver is not None return default_resolver def reset_default_resolver() -> None: """Re-initialize default asynchronous resolver. Note that the resolver configuration (i.e. /etc/resolv.conf on UNIX systems) will be re-read immediately. """ global default_resolver default_resolver = Resolver() async def resolve( qname: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.A, rdclass: Union[dns.rdataclass.RdataClass, str] = dns.rdataclass.IN, tcp: bool = False, source: Optional[str] = None, raise_on_no_answer: bool = True, source_port: int = 0, lifetime: Optional[float] = None, search: Optional[bool] = None, backend: Optional[dns.asyncbackend.Backend] = None, ) -> dns.resolver.Answer: """Query nameservers asynchronously to find the answer to the question. This is a convenience function that uses the default resolver object to make the query. See :py:func:`dns.asyncresolver.Resolver.resolve` for more information on the parameters. """ return await get_default_resolver().resolve( qname, rdtype, rdclass, tcp, source, raise_on_no_answer, source_port, lifetime, search, backend, ) async def resolve_address( ipaddr: str, *args: Any, **kwargs: Any ) -> dns.resolver.Answer: """Use a resolver to run a reverse query for PTR records. See :py:func:`dns.asyncresolver.Resolver.resolve_address` for more information on the parameters. """ return await get_default_resolver().resolve_address(ipaddr, *args, **kwargs) async def resolve_name( name: Union[dns.name.Name, str], family: int = socket.AF_UNSPEC, **kwargs: Any ) -> dns.resolver.HostAnswers: """Use a resolver to asynchronously query for address records. See :py:func:`dns.asyncresolver.Resolver.resolve_name` for more information on the parameters. """ return await get_default_resolver().resolve_name(name, family, **kwargs) async def canonical_name(name: Union[dns.name.Name, str]) -> dns.name.Name: """Determine the canonical name of *name*. See :py:func:`dns.resolver.Resolver.canonical_name` for more information on the parameters and possible exceptions. """ return await get_default_resolver().canonical_name(name) async def try_ddr(timeout: float = 5.0) -> None: """Try to update the default resolver's nameservers using Discovery of Designated Resolvers (DDR). If successful, the resolver will subsequently use DNS-over-HTTPS or DNS-over-TLS for future queries. See :py:func:`dns.resolver.Resolver.try_ddr` for more information. """ return await get_default_resolver().try_ddr(timeout) async def zone_for_name( name: Union[dns.name.Name, str], rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, tcp: bool = False, resolver: Optional[Resolver] = None, backend: Optional[dns.asyncbackend.Backend] = None, ) -> dns.name.Name: """Find the name of the zone which contains the specified name. See :py:func:`dns.resolver.Resolver.zone_for_name` for more information on the parameters and possible exceptions. """ if isinstance(name, str): name = dns.name.from_text(name, dns.name.root) if resolver is None: resolver = get_default_resolver() if not name.is_absolute(): raise NotAbsolute(name) while True: try: answer = await resolver.resolve( name, dns.rdatatype.SOA, rdclass, tcp, backend=backend ) assert answer.rrset is not None if answer.rrset.name == name: return name # otherwise we were CNAMEd or DNAMEd and need to look higher except (NXDOMAIN, NoAnswer): pass try: name = name.parent() except dns.name.NoParent: # pragma: no cover raise NoRootSOA async def make_resolver_at( where: Union[dns.name.Name, str], port: int = 53, family: int = socket.AF_UNSPEC, resolver: Optional[Resolver] = None, ) -> Resolver: """Make a stub resolver using the specified destination as the full resolver. *where*, a ``dns.name.Name`` or ``str`` the domain name or IP address of the full resolver. *port*, an ``int``, the port to use. If not specified, the default is 53. *family*, an ``int``, the address family to use. This parameter is used if *where* is not an address. The default is ``socket.AF_UNSPEC`` in which case the first address returned by ``resolve_name()`` will be used, otherwise the first address of the specified family will be used. *resolver*, a ``dns.asyncresolver.Resolver`` or ``None``, the resolver to use for resolution of hostnames. If not specified, the default resolver will be used. Returns a ``dns.resolver.Resolver`` or raises an exception. """ if resolver is None: resolver = get_default_resolver() nameservers: List[Union[str, dns.nameserver.Nameserver]] = [] if isinstance(where, str) and dns.inet.is_address(where): nameservers.append(dns.nameserver.Do53Nameserver(where, port)) else: answers = await resolver.resolve_name(where, family) for address in answers.addresses(): nameservers.append(dns.nameserver.Do53Nameserver(address, port)) res = dns.asyncresolver.Resolver(configure=False) res.nameservers = nameservers return res async def resolve_at( where: Union[dns.name.Name, str], qname: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.A, rdclass: Union[dns.rdataclass.RdataClass, str] = dns.rdataclass.IN, tcp: bool = False, source: Optional[str] = None, raise_on_no_answer: bool = True, source_port: int = 0, lifetime: Optional[float] = None, search: Optional[bool] = None, backend: Optional[dns.asyncbackend.Backend] = None, port: int = 53, family: int = socket.AF_UNSPEC, resolver: Optional[Resolver] = None, ) -> dns.resolver.Answer: """Query nameservers to find the answer to the question. This is a convenience function that calls ``dns.asyncresolver.make_resolver_at()`` to make a resolver, and then uses it to resolve the query. See ``dns.asyncresolver.Resolver.resolve`` for more information on the resolution parameters, and ``dns.asyncresolver.make_resolver_at`` for information about the resolver parameters *where*, *port*, *family*, and *resolver*. If making more than one query, it is more efficient to call ``dns.asyncresolver.make_resolver_at()`` and then use that resolver for the queries instead of calling ``resolve_at()`` multiple times. """ res = await make_resolver_at(where, port, family, resolver) return await res.resolve( qname, rdtype, rdclass, tcp, source, raise_on_no_answer, source_port, lifetime, search, backend, ) dnspython-2.7.0/dns/dnssec.py0000644000000000000000000012136513615410400013113 0ustar00# 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 # 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. """Common DNSSEC-related functions and constants.""" import base64 import contextlib import functools import hashlib import struct import time from datetime import datetime from typing import Callable, Dict, List, Optional, Set, Tuple, Union, cast import dns._features import dns.exception import dns.name import dns.node import dns.rdata import dns.rdataclass import dns.rdataset import dns.rdatatype import dns.rrset import dns.transaction import dns.zone from dns.dnssectypes import Algorithm, DSDigest, NSEC3Hash from dns.exception import ( # pylint: disable=W0611 AlgorithmKeyMismatch, DeniedByPolicy, UnsupportedAlgorithm, ValidationFailure, ) from dns.rdtypes.ANY.CDNSKEY import CDNSKEY from dns.rdtypes.ANY.CDS import CDS from dns.rdtypes.ANY.DNSKEY import DNSKEY from dns.rdtypes.ANY.DS import DS from dns.rdtypes.ANY.NSEC import NSEC, Bitmap from dns.rdtypes.ANY.NSEC3PARAM import NSEC3PARAM from dns.rdtypes.ANY.RRSIG import RRSIG, sigtime_to_posixtime from dns.rdtypes.dnskeybase import Flag PublicKey = Union[ "GenericPublicKey", "rsa.RSAPublicKey", "ec.EllipticCurvePublicKey", "ed25519.Ed25519PublicKey", "ed448.Ed448PublicKey", ] PrivateKey = Union[ "GenericPrivateKey", "rsa.RSAPrivateKey", "ec.EllipticCurvePrivateKey", "ed25519.Ed25519PrivateKey", "ed448.Ed448PrivateKey", ] RRsetSigner = Callable[[dns.transaction.Transaction, dns.rrset.RRset], None] def algorithm_from_text(text: str) -> Algorithm: """Convert text into a DNSSEC algorithm value. *text*, a ``str``, the text to convert to into an algorithm value. Returns an ``int``. """ return Algorithm.from_text(text) def algorithm_to_text(value: Union[Algorithm, int]) -> str: """Convert a DNSSEC algorithm value to text *value*, a ``dns.dnssec.Algorithm``. Returns a ``str``, the name of a DNSSEC algorithm. """ return Algorithm.to_text(value) def to_timestamp(value: Union[datetime, str, float, int]) -> int: """Convert various format to a timestamp""" if isinstance(value, datetime): return int(value.timestamp()) elif isinstance(value, str): return sigtime_to_posixtime(value) elif isinstance(value, float): return int(value) elif isinstance(value, int): return value else: raise TypeError("Unsupported timestamp type") def key_id(key: Union[DNSKEY, CDNSKEY]) -> int: """Return the key id (a 16-bit number) for the specified key. *key*, a ``dns.rdtypes.ANY.DNSKEY.DNSKEY`` Returns an ``int`` between 0 and 65535 """ rdata = key.to_wire() assert rdata is not None # for mypy if key.algorithm == Algorithm.RSAMD5: return (rdata[-3] << 8) + rdata[-2] else: total = 0 for i in range(len(rdata) // 2): total += (rdata[2 * i] << 8) + rdata[2 * i + 1] if len(rdata) % 2 != 0: total += rdata[len(rdata) - 1] << 8 total += (total >> 16) & 0xFFFF return total & 0xFFFF class Policy: def __init__(self): pass def ok_to_sign(self, _: DNSKEY) -> bool: # pragma: no cover return False def ok_to_validate(self, _: DNSKEY) -> bool: # pragma: no cover return False def ok_to_create_ds(self, _: DSDigest) -> bool: # pragma: no cover return False def ok_to_validate_ds(self, _: DSDigest) -> bool: # pragma: no cover return False class SimpleDeny(Policy): def __init__(self, deny_sign, deny_validate, deny_create_ds, deny_validate_ds): super().__init__() self._deny_sign = deny_sign self._deny_validate = deny_validate self._deny_create_ds = deny_create_ds self._deny_validate_ds = deny_validate_ds def ok_to_sign(self, key: DNSKEY) -> bool: return key.algorithm not in self._deny_sign def ok_to_validate(self, key: DNSKEY) -> bool: return key.algorithm not in self._deny_validate def ok_to_create_ds(self, algorithm: DSDigest) -> bool: return algorithm not in self._deny_create_ds def ok_to_validate_ds(self, algorithm: DSDigest) -> bool: return algorithm not in self._deny_validate_ds rfc_8624_policy = SimpleDeny( {Algorithm.RSAMD5, Algorithm.DSA, Algorithm.DSANSEC3SHA1, Algorithm.ECCGOST}, {Algorithm.RSAMD5, Algorithm.DSA, Algorithm.DSANSEC3SHA1}, {DSDigest.NULL, DSDigest.SHA1, DSDigest.GOST}, {DSDigest.NULL}, ) allow_all_policy = SimpleDeny(set(), set(), set(), set()) default_policy = rfc_8624_policy def make_ds( name: Union[dns.name.Name, str], key: dns.rdata.Rdata, algorithm: Union[DSDigest, str], origin: Optional[dns.name.Name] = None, policy: Optional[Policy] = None, validating: bool = False, ) -> DS: """Create a DS record for a DNSSEC key. *name*, a ``dns.name.Name`` or ``str``, the owner name of the DS record. *key*, a ``dns.rdtypes.ANY.DNSKEY.DNSKEY`` or ``dns.rdtypes.ANY.DNSKEY.CDNSKEY``, the key the DS is about. *algorithm*, a ``str`` or ``int`` specifying the hash algorithm. The currently supported hashes are "SHA1", "SHA256", and "SHA384". Case does not matter for these strings. *origin*, a ``dns.name.Name`` or ``None``. If *key* is a relative name, then it will be made absolute using the specified origin. *policy*, a ``dns.dnssec.Policy`` or ``None``. If ``None``, the default policy, ``dns.dnssec.default_policy`` is used; this policy defaults to that of RFC 8624. *validating*, a ``bool``. If ``True``, then policy is checked in validating mode, i.e. "Is it ok to validate using this digest algorithm?". Otherwise the policy is checked in creating mode, i.e. "Is it ok to create a DS with this digest algorithm?". Raises ``UnsupportedAlgorithm`` if the algorithm is unknown. Raises ``DeniedByPolicy`` if the algorithm is denied by policy. Returns a ``dns.rdtypes.ANY.DS.DS`` """ if policy is None: policy = default_policy try: if isinstance(algorithm, str): algorithm = DSDigest[algorithm.upper()] except Exception: raise UnsupportedAlgorithm(f'unsupported algorithm "{algorithm}"') if validating: check = policy.ok_to_validate_ds else: check = policy.ok_to_create_ds if not check(algorithm): raise DeniedByPolicy if not isinstance(key, (DNSKEY, CDNSKEY)): raise ValueError("key is not a DNSKEY/CDNSKEY") if algorithm == DSDigest.SHA1: dshash = hashlib.sha1() elif algorithm == DSDigest.SHA256: dshash = hashlib.sha256() elif algorithm == DSDigest.SHA384: dshash = hashlib.sha384() else: raise UnsupportedAlgorithm(f'unsupported algorithm "{algorithm}"') if isinstance(name, str): name = dns.name.from_text(name, origin) wire = name.canonicalize().to_wire() kwire = key.to_wire(origin=origin) assert wire is not None and kwire is not None # for mypy dshash.update(wire) dshash.update(kwire) digest = dshash.digest() dsrdata = struct.pack("!HBB", key_id(key), key.algorithm, algorithm) + digest ds = dns.rdata.from_wire( dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0, len(dsrdata) ) return cast(DS, ds) def make_cds( name: Union[dns.name.Name, str], key: dns.rdata.Rdata, algorithm: Union[DSDigest, str], origin: Optional[dns.name.Name] = None, ) -> CDS: """Create a CDS record for a DNSSEC key. *name*, a ``dns.name.Name`` or ``str``, the owner name of the DS record. *key*, a ``dns.rdtypes.ANY.DNSKEY.DNSKEY`` or ``dns.rdtypes.ANY.DNSKEY.CDNSKEY``, the key the DS is about. *algorithm*, a ``str`` or ``int`` specifying the hash algorithm. The currently supported hashes are "SHA1", "SHA256", and "SHA384". Case does not matter for these strings. *origin*, a ``dns.name.Name`` or ``None``. If *key* is a relative name, then it will be made absolute using the specified origin. Raises ``UnsupportedAlgorithm`` if the algorithm is unknown. Returns a ``dns.rdtypes.ANY.DS.CDS`` """ ds = make_ds(name, key, algorithm, origin) return CDS( rdclass=ds.rdclass, rdtype=dns.rdatatype.CDS, key_tag=ds.key_tag, algorithm=ds.algorithm, digest_type=ds.digest_type, digest=ds.digest, ) def _find_candidate_keys( keys: Dict[dns.name.Name, Union[dns.rdataset.Rdataset, dns.node.Node]], rrsig: RRSIG ) -> Optional[List[DNSKEY]]: value = keys.get(rrsig.signer) if isinstance(value, dns.node.Node): rdataset = value.get_rdataset(dns.rdataclass.IN, dns.rdatatype.DNSKEY) else: rdataset = value if rdataset is None: return None return [ cast(DNSKEY, rd) for rd in rdataset if rd.algorithm == rrsig.algorithm and key_id(rd) == rrsig.key_tag and (rd.flags & Flag.ZONE) == Flag.ZONE # RFC 4034 2.1.1 and rd.protocol == 3 # RFC 4034 2.1.2 ] def _get_rrname_rdataset( rrset: Union[dns.rrset.RRset, Tuple[dns.name.Name, dns.rdataset.Rdataset]], ) -> Tuple[dns.name.Name, dns.rdataset.Rdataset]: if isinstance(rrset, tuple): return rrset[0], rrset[1] else: return rrset.name, rrset def _validate_signature(sig: bytes, data: bytes, key: DNSKEY) -> None: # pylint: disable=possibly-used-before-assignment public_cls = get_algorithm_cls_from_dnskey(key).public_cls try: public_key = public_cls.from_dnskey(key) except ValueError: raise ValidationFailure("invalid public key") public_key.verify(sig, data) def _validate_rrsig( rrset: Union[dns.rrset.RRset, Tuple[dns.name.Name, dns.rdataset.Rdataset]], rrsig: RRSIG, keys: Dict[dns.name.Name, Union[dns.node.Node, dns.rdataset.Rdataset]], origin: Optional[dns.name.Name] = None, now: Optional[float] = None, policy: Optional[Policy] = None, ) -> None: """Validate an RRset against a single signature rdata, throwing an exception if validation is not successful. *rrset*, the RRset to validate. This can be a ``dns.rrset.RRset`` or a (``dns.name.Name``, ``dns.rdataset.Rdataset``) tuple. *rrsig*, a ``dns.rdata.Rdata``, the signature to validate. *keys*, the key dictionary, used to find the DNSKEY associated with a given name. The dictionary is keyed by a ``dns.name.Name``, and has ``dns.node.Node`` or ``dns.rdataset.Rdataset`` values. *origin*, a ``dns.name.Name`` or ``None``, the origin to use for relative names. *now*, a ``float`` or ``None``, the time, in seconds since the epoch, to use as the current time when validating. If ``None``, the actual current time is used. *policy*, a ``dns.dnssec.Policy`` or ``None``. If ``None``, the default policy, ``dns.dnssec.default_policy`` is used; this policy defaults to that of RFC 8624. Raises ``ValidationFailure`` if the signature is expired, not yet valid, the public key is invalid, the algorithm is unknown, the verification fails, etc. Raises ``UnsupportedAlgorithm`` if the algorithm is recognized by dnspython but not implemented. """ if policy is None: policy = default_policy candidate_keys = _find_candidate_keys(keys, rrsig) if candidate_keys is None: raise ValidationFailure("unknown key") if now is None: now = time.time() if rrsig.expiration < now: raise ValidationFailure("expired") if rrsig.inception > now: raise ValidationFailure("not yet valid") data = _make_rrsig_signature_data(rrset, rrsig, origin) # pylint: disable=possibly-used-before-assignment for candidate_key in candidate_keys: if not policy.ok_to_validate(candidate_key): continue try: _validate_signature(rrsig.signature, data, candidate_key) return except (InvalidSignature, ValidationFailure): # this happens on an individual validation failure continue # nothing verified -- raise failure: raise ValidationFailure("verify failure") def _validate( rrset: Union[dns.rrset.RRset, Tuple[dns.name.Name, dns.rdataset.Rdataset]], rrsigset: Union[dns.rrset.RRset, Tuple[dns.name.Name, dns.rdataset.Rdataset]], keys: Dict[dns.name.Name, Union[dns.node.Node, dns.rdataset.Rdataset]], origin: Optional[dns.name.Name] = None, now: Optional[float] = None, policy: Optional[Policy] = None, ) -> None: """Validate an RRset against a signature RRset, throwing an exception if none of the signatures validate. *rrset*, the RRset to validate. This can be a ``dns.rrset.RRset`` or a (``dns.name.Name``, ``dns.rdataset.Rdataset``) tuple. *rrsigset*, the signature RRset. This can be a ``dns.rrset.RRset`` or a (``dns.name.Name``, ``dns.rdataset.Rdataset``) tuple. *keys*, the key dictionary, used to find the DNSKEY associated with a given name. The dictionary is keyed by a ``dns.name.Name``, and has ``dns.node.Node`` or ``dns.rdataset.Rdataset`` values. *origin*, a ``dns.name.Name``, the origin to use for relative names; defaults to None. *now*, an ``int`` or ``None``, the time, in seconds since the epoch, to use as the current time when validating. If ``None``, the actual current time is used. *policy*, a ``dns.dnssec.Policy`` or ``None``. If ``None``, the default policy, ``dns.dnssec.default_policy`` is used; this policy defaults to that of RFC 8624. Raises ``ValidationFailure`` if the signature is expired, not yet valid, the public key is invalid, the algorithm is unknown, the verification fails, etc. """ if policy is None: policy = default_policy if isinstance(origin, str): origin = dns.name.from_text(origin, dns.name.root) if isinstance(rrset, tuple): rrname = rrset[0] else: rrname = rrset.name if isinstance(rrsigset, tuple): rrsigname = rrsigset[0] rrsigrdataset = rrsigset[1] else: rrsigname = rrsigset.name rrsigrdataset = rrsigset rrname = rrname.choose_relativity(origin) rrsigname = rrsigname.choose_relativity(origin) if rrname != rrsigname: raise ValidationFailure("owner names do not match") for rrsig in rrsigrdataset: if not isinstance(rrsig, RRSIG): raise ValidationFailure("expected an RRSIG") try: _validate_rrsig(rrset, rrsig, keys, origin, now, policy) return except (ValidationFailure, UnsupportedAlgorithm): pass raise ValidationFailure("no RRSIGs validated") def _sign( rrset: Union[dns.rrset.RRset, Tuple[dns.name.Name, dns.rdataset.Rdataset]], private_key: PrivateKey, signer: dns.name.Name, dnskey: DNSKEY, inception: Optional[Union[datetime, str, int, float]] = None, expiration: Optional[Union[datetime, str, int, float]] = None, lifetime: Optional[int] = None, verify: bool = False, policy: Optional[Policy] = None, origin: Optional[dns.name.Name] = None, deterministic: bool = True, ) -> RRSIG: """Sign RRset using private key. *rrset*, the RRset to validate. This can be a ``dns.rrset.RRset`` or a (``dns.name.Name``, ``dns.rdataset.Rdataset``) tuple. *private_key*, the private key to use for signing, a ``cryptography.hazmat.primitives.asymmetric`` private key class applicable for DNSSEC. *signer*, a ``dns.name.Name``, the Signer's name. *dnskey*, a ``DNSKEY`` matching ``private_key``. *inception*, a ``datetime``, ``str``, ``int``, ``float`` or ``None``, the signature inception time. If ``None``, the current time is used. If a ``str``, the format is "YYYYMMDDHHMMSS" or alternatively the number of seconds since the UNIX epoch in text form; this is the same the RRSIG rdata's text form. Values of type `int` or `float` are interpreted as seconds since the UNIX epoch. *expiration*, a ``datetime``, ``str``, ``int``, ``float`` or ``None``, the signature expiration time. If ``None``, the expiration time will be the inception time plus the value of the *lifetime* parameter. See the description of *inception* above for how the various parameter types are interpreted. *lifetime*, an ``int`` or ``None``, the signature lifetime in seconds. This parameter is only meaningful if *expiration* is ``None``. *verify*, a ``bool``. If set to ``True``, the signer will verify signatures after they are created; the default is ``False``. *policy*, a ``dns.dnssec.Policy`` or ``None``. If ``None``, the default policy, ``dns.dnssec.default_policy`` is used; this policy defaults to that of RFC 8624. *origin*, a ``dns.name.Name`` or ``None``. If ``None``, the default, then all names in the rrset (including its owner name) must be absolute; otherwise the specified origin will be used to make names absolute when signing. *deterministic*, a ``bool``. If ``True``, the default, use deterministic (reproducible) signatures when supported by the algorithm used for signing. Currently, this only affects ECDSA. Raises ``DeniedByPolicy`` if the signature is denied by policy. """ if policy is None: policy = default_policy if not policy.ok_to_sign(dnskey): raise DeniedByPolicy if isinstance(rrset, tuple): rdclass = rrset[1].rdclass rdtype = rrset[1].rdtype rrname = rrset[0] original_ttl = rrset[1].ttl else: rdclass = rrset.rdclass rdtype = rrset.rdtype rrname = rrset.name original_ttl = rrset.ttl if inception is not None: rrsig_inception = to_timestamp(inception) else: rrsig_inception = int(time.time()) if expiration is not None: rrsig_expiration = to_timestamp(expiration) elif lifetime is not None: rrsig_expiration = rrsig_inception + lifetime else: raise ValueError("expiration or lifetime must be specified") # Derelativize now because we need a correct labels length for the # rrsig_template. if origin is not None: rrname = rrname.derelativize(origin) labels = len(rrname) - 1 # Adjust labels appropriately for wildcards. if rrname.is_wild(): labels -= 1 rrsig_template = RRSIG( rdclass=rdclass, rdtype=dns.rdatatype.RRSIG, type_covered=rdtype, algorithm=dnskey.algorithm, labels=labels, original_ttl=original_ttl, expiration=rrsig_expiration, inception=rrsig_inception, key_tag=key_id(dnskey), signer=signer, signature=b"", ) data = dns.dnssec._make_rrsig_signature_data(rrset, rrsig_template, origin) # pylint: disable=possibly-used-before-assignment if isinstance(private_key, GenericPrivateKey): signing_key = private_key else: try: private_cls = get_algorithm_cls_from_dnskey(dnskey) signing_key = private_cls(key=private_key) except UnsupportedAlgorithm: raise TypeError("Unsupported key algorithm") signature = signing_key.sign(data, verify, deterministic) return cast(RRSIG, rrsig_template.replace(signature=signature)) def _make_rrsig_signature_data( rrset: Union[dns.rrset.RRset, Tuple[dns.name.Name, dns.rdataset.Rdataset]], rrsig: RRSIG, origin: Optional[dns.name.Name] = None, ) -> bytes: """Create signature rdata. *rrset*, the RRset to sign/validate. This can be a ``dns.rrset.RRset`` or a (``dns.name.Name``, ``dns.rdataset.Rdataset``) tuple. *rrsig*, a ``dns.rdata.Rdata``, the signature to validate, or the signature template used when signing. *origin*, a ``dns.name.Name`` or ``None``, the origin to use for relative names. Raises ``UnsupportedAlgorithm`` if the algorithm is recognized by dnspython but not implemented. """ if isinstance(origin, str): origin = dns.name.from_text(origin, dns.name.root) signer = rrsig.signer if not signer.is_absolute(): if origin is None: raise ValidationFailure("relative RR name without an origin specified") signer = signer.derelativize(origin) # For convenience, allow the rrset to be specified as a (name, # rdataset) tuple as well as a proper rrset rrname, rdataset = _get_rrname_rdataset(rrset) data = b"" wire = rrsig.to_wire(origin=signer) assert wire is not None # for mypy data += wire[:18] data += rrsig.signer.to_digestable(signer) # Derelativize the name before considering labels. if not rrname.is_absolute(): if origin is None: raise ValidationFailure("relative RR name without an origin specified") rrname = rrname.derelativize(origin) name_len = len(rrname) if rrname.is_wild() and rrsig.labels != name_len - 2: raise ValidationFailure("wild owner name has wrong label length") if name_len - 1 < rrsig.labels: raise ValidationFailure("owner name longer than RRSIG labels") elif rrsig.labels < name_len - 1: suffix = rrname.split(rrsig.labels + 1)[1] rrname = dns.name.from_text("*", suffix) rrnamebuf = rrname.to_digestable() rrfixed = struct.pack("!HHI", rdataset.rdtype, rdataset.rdclass, rrsig.original_ttl) rdatas = [rdata.to_digestable(origin) for rdata in rdataset] for rdata in sorted(rdatas): data += rrnamebuf data += rrfixed rrlen = struct.pack("!H", len(rdata)) data += rrlen data += rdata return data def _make_dnskey( public_key: PublicKey, algorithm: Union[int, str], flags: int = Flag.ZONE, protocol: int = 3, ) -> DNSKEY: """Convert a public key to DNSKEY Rdata *public_key*, a ``PublicKey`` (``GenericPublicKey`` or ``cryptography.hazmat.primitives.asymmetric``) to convert. *algorithm*, a ``str`` or ``int`` specifying the DNSKEY algorithm. *flags*: DNSKEY flags field as an integer. *protocol*: DNSKEY protocol field as an integer. Raises ``ValueError`` if the specified key algorithm parameters are not unsupported, ``TypeError`` if the key type is unsupported, `UnsupportedAlgorithm` if the algorithm is unknown and `AlgorithmKeyMismatch` if the algorithm does not match the key type. Return DNSKEY ``Rdata``. """ algorithm = Algorithm.make(algorithm) # pylint: disable=possibly-used-before-assignment if isinstance(public_key, GenericPublicKey): return public_key.to_dnskey(flags=flags, protocol=protocol) else: public_cls = get_algorithm_cls(algorithm).public_cls return public_cls(key=public_key).to_dnskey(flags=flags, protocol=protocol) def _make_cdnskey( public_key: PublicKey, algorithm: Union[int, str], flags: int = Flag.ZONE, protocol: int = 3, ) -> CDNSKEY: """Convert a public key to CDNSKEY Rdata *public_key*, the public key to convert, a ``cryptography.hazmat.primitives.asymmetric`` public key class applicable for DNSSEC. *algorithm*, a ``str`` or ``int`` specifying the DNSKEY algorithm. *flags*: DNSKEY flags field as an integer. *protocol*: DNSKEY protocol field as an integer. Raises ``ValueError`` if the specified key algorithm parameters are not unsupported, ``TypeError`` if the key type is unsupported, `UnsupportedAlgorithm` if the algorithm is unknown and `AlgorithmKeyMismatch` if the algorithm does not match the key type. Return CDNSKEY ``Rdata``. """ dnskey = _make_dnskey(public_key, algorithm, flags, protocol) return CDNSKEY( rdclass=dnskey.rdclass, rdtype=dns.rdatatype.CDNSKEY, flags=dnskey.flags, protocol=dnskey.protocol, algorithm=dnskey.algorithm, key=dnskey.key, ) def nsec3_hash( domain: Union[dns.name.Name, str], salt: Optional[Union[str, bytes]], iterations: int, algorithm: Union[int, str], ) -> str: """ Calculate the NSEC3 hash, according to https://tools.ietf.org/html/rfc5155#section-5 *domain*, a ``dns.name.Name`` or ``str``, the name to hash. *salt*, a ``str``, ``bytes``, or ``None``, the hash salt. If a string, it is decoded as a hex string. *iterations*, an ``int``, the number of iterations. *algorithm*, a ``str`` or ``int``, the hash algorithm. The only defined algorithm is SHA1. Returns a ``str``, the encoded NSEC3 hash. """ b32_conversion = str.maketrans( "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", "0123456789ABCDEFGHIJKLMNOPQRSTUV" ) try: if isinstance(algorithm, str): algorithm = NSEC3Hash[algorithm.upper()] except Exception: raise ValueError("Wrong hash algorithm (only SHA1 is supported)") if algorithm != NSEC3Hash.SHA1: raise ValueError("Wrong hash algorithm (only SHA1 is supported)") if salt is None: salt_encoded = b"" elif isinstance(salt, str): if len(salt) % 2 == 0: salt_encoded = bytes.fromhex(salt) else: raise ValueError("Invalid salt length") else: salt_encoded = salt if not isinstance(domain, dns.name.Name): domain = dns.name.from_text(domain) domain_encoded = domain.canonicalize().to_wire() assert domain_encoded is not None digest = hashlib.sha1(domain_encoded + salt_encoded).digest() for _ in range(iterations): digest = hashlib.sha1(digest + salt_encoded).digest() output = base64.b32encode(digest).decode("utf-8") output = output.translate(b32_conversion) return output def make_ds_rdataset( rrset: Union[dns.rrset.RRset, Tuple[dns.name.Name, dns.rdataset.Rdataset]], algorithms: Set[Union[DSDigest, str]], origin: Optional[dns.name.Name] = None, ) -> dns.rdataset.Rdataset: """Create a DS record from DNSKEY/CDNSKEY/CDS. *rrset*, the RRset to create DS Rdataset for. This can be a ``dns.rrset.RRset`` or a (``dns.name.Name``, ``dns.rdataset.Rdataset``) tuple. *algorithms*, a set of ``str`` or ``int`` specifying the hash algorithms. The currently supported hashes are "SHA1", "SHA256", and "SHA384". Case does not matter for these strings. If the RRset is a CDS, only digest algorithms matching algorithms are accepted. *origin*, a ``dns.name.Name`` or ``None``. If `key` is a relative name, then it will be made absolute using the specified origin. Raises ``UnsupportedAlgorithm`` if any of the algorithms are unknown and ``ValueError`` if the given RRset is not usable. Returns a ``dns.rdataset.Rdataset`` """ rrname, rdataset = _get_rrname_rdataset(rrset) if rdataset.rdtype not in ( dns.rdatatype.DNSKEY, dns.rdatatype.CDNSKEY, dns.rdatatype.CDS, ): raise ValueError("rrset not a DNSKEY/CDNSKEY/CDS") _algorithms = set() for algorithm in algorithms: try: if isinstance(algorithm, str): algorithm = DSDigest[algorithm.upper()] except Exception: raise UnsupportedAlgorithm(f'unsupported algorithm "{algorithm}"') _algorithms.add(algorithm) if rdataset.rdtype == dns.rdatatype.CDS: res = [] for rdata in cds_rdataset_to_ds_rdataset(rdataset): if rdata.digest_type in _algorithms: res.append(rdata) if len(res) == 0: raise ValueError("no acceptable CDS rdata found") return dns.rdataset.from_rdata_list(rdataset.ttl, res) res = [] for algorithm in _algorithms: res.extend(dnskey_rdataset_to_cds_rdataset(rrname, rdataset, algorithm, origin)) return dns.rdataset.from_rdata_list(rdataset.ttl, res) def cds_rdataset_to_ds_rdataset( rdataset: dns.rdataset.Rdataset, ) -> dns.rdataset.Rdataset: """Create a CDS record from DS. *rdataset*, a ``dns.rdataset.Rdataset``, to create DS Rdataset for. Raises ``ValueError`` if the rdataset is not CDS. Returns a ``dns.rdataset.Rdataset`` """ if rdataset.rdtype != dns.rdatatype.CDS: raise ValueError("rdataset not a CDS") res = [] for rdata in rdataset: res.append( CDS( rdclass=rdata.rdclass, rdtype=dns.rdatatype.DS, key_tag=rdata.key_tag, algorithm=rdata.algorithm, digest_type=rdata.digest_type, digest=rdata.digest, ) ) return dns.rdataset.from_rdata_list(rdataset.ttl, res) def dnskey_rdataset_to_cds_rdataset( name: Union[dns.name.Name, str], rdataset: dns.rdataset.Rdataset, algorithm: Union[DSDigest, str], origin: Optional[dns.name.Name] = None, ) -> dns.rdataset.Rdataset: """Create a CDS record from DNSKEY/CDNSKEY. *name*, a ``dns.name.Name`` or ``str``, the owner name of the CDS record. *rdataset*, a ``dns.rdataset.Rdataset``, to create DS Rdataset for. *algorithm*, a ``str`` or ``int`` specifying the hash algorithm. The currently supported hashes are "SHA1", "SHA256", and "SHA384". Case does not matter for these strings. *origin*, a ``dns.name.Name`` or ``None``. If `key` is a relative name, then it will be made absolute using the specified origin. Raises ``UnsupportedAlgorithm`` if the algorithm is unknown or ``ValueError`` if the rdataset is not DNSKEY/CDNSKEY. Returns a ``dns.rdataset.Rdataset`` """ if rdataset.rdtype not in (dns.rdatatype.DNSKEY, dns.rdatatype.CDNSKEY): raise ValueError("rdataset not a DNSKEY/CDNSKEY") res = [] for rdata in rdataset: res.append(make_cds(name, rdata, algorithm, origin)) return dns.rdataset.from_rdata_list(rdataset.ttl, res) def dnskey_rdataset_to_cdnskey_rdataset( rdataset: dns.rdataset.Rdataset, ) -> dns.rdataset.Rdataset: """Create a CDNSKEY record from DNSKEY. *rdataset*, a ``dns.rdataset.Rdataset``, to create CDNSKEY Rdataset for. Returns a ``dns.rdataset.Rdataset`` """ if rdataset.rdtype != dns.rdatatype.DNSKEY: raise ValueError("rdataset not a DNSKEY") res = [] for rdata in rdataset: res.append( CDNSKEY( rdclass=rdataset.rdclass, rdtype=rdataset.rdtype, flags=rdata.flags, protocol=rdata.protocol, algorithm=rdata.algorithm, key=rdata.key, ) ) return dns.rdataset.from_rdata_list(rdataset.ttl, res) def default_rrset_signer( txn: dns.transaction.Transaction, rrset: dns.rrset.RRset, signer: dns.name.Name, ksks: List[Tuple[PrivateKey, DNSKEY]], zsks: List[Tuple[PrivateKey, DNSKEY]], inception: Optional[Union[datetime, str, int, float]] = None, expiration: Optional[Union[datetime, str, int, float]] = None, lifetime: Optional[int] = None, policy: Optional[Policy] = None, origin: Optional[dns.name.Name] = None, deterministic: bool = True, ) -> None: """Default RRset signer""" if rrset.rdtype in set( [ dns.rdatatype.RdataType.DNSKEY, dns.rdatatype.RdataType.CDS, dns.rdatatype.RdataType.CDNSKEY, ] ): keys = ksks else: keys = zsks for private_key, dnskey in keys: rrsig = dns.dnssec.sign( rrset=rrset, private_key=private_key, dnskey=dnskey, inception=inception, expiration=expiration, lifetime=lifetime, signer=signer, policy=policy, origin=origin, deterministic=deterministic, ) txn.add(rrset.name, rrset.ttl, rrsig) def sign_zone( zone: dns.zone.Zone, txn: Optional[dns.transaction.Transaction] = None, keys: Optional[List[Tuple[PrivateKey, DNSKEY]]] = None, add_dnskey: bool = True, dnskey_ttl: Optional[int] = None, inception: Optional[Union[datetime, str, int, float]] = None, expiration: Optional[Union[datetime, str, int, float]] = None, lifetime: Optional[int] = None, nsec3: Optional[NSEC3PARAM] = None, rrset_signer: Optional[RRsetSigner] = None, policy: Optional[Policy] = None, deterministic: bool = True, ) -> None: """Sign zone. *zone*, a ``dns.zone.Zone``, the zone to sign. *txn*, a ``dns.transaction.Transaction``, an optional transaction to use for signing. *keys*, a list of (``PrivateKey``, ``DNSKEY``) tuples, to use for signing. KSK/ZSK roles are assigned automatically if the SEP flag is used, otherwise all RRsets are signed by all keys. *add_dnskey*, a ``bool``. If ``True``, the default, all specified DNSKEYs are automatically added to the zone on signing. *dnskey_ttl*, a``int``, specifies the TTL for DNSKEY RRs. If not specified the TTL of the existing DNSKEY RRset used or the TTL of the SOA RRset. *inception*, a ``datetime``, ``str``, ``int``, ``float`` or ``None``, the signature inception time. If ``None``, the current time is used. If a ``str``, the format is "YYYYMMDDHHMMSS" or alternatively the number of seconds since the UNIX epoch in text form; this is the same the RRSIG rdata's text form. Values of type `int` or `float` are interpreted as seconds since the UNIX epoch. *expiration*, a ``datetime``, ``str``, ``int``, ``float`` or ``None``, the signature expiration time. If ``None``, the expiration time will be the inception time plus the value of the *lifetime* parameter. See the description of *inception* above for how the various parameter types are interpreted. *lifetime*, an ``int`` or ``None``, the signature lifetime in seconds. This parameter is only meaningful if *expiration* is ``None``. *nsec3*, a ``NSEC3PARAM`` Rdata, configures signing using NSEC3. Not yet implemented. *rrset_signer*, a ``Callable``, an optional function for signing RRsets. The function requires two arguments: transaction and RRset. If the not specified, ``dns.dnssec.default_rrset_signer`` will be used. *deterministic*, a ``bool``. If ``True``, the default, use deterministic (reproducible) signatures when supported by the algorithm used for signing. Currently, this only affects ECDSA. Returns ``None``. """ ksks = [] zsks = [] # if we have both KSKs and ZSKs, split by SEP flag. if not, sign all # records with all keys if keys: for key in keys: if key[1].flags & Flag.SEP: ksks.append(key) else: zsks.append(key) if not ksks: ksks = keys if not zsks: zsks = keys else: keys = [] if txn: cm: contextlib.AbstractContextManager = contextlib.nullcontext(txn) else: cm = zone.writer() if zone.origin is None: raise ValueError("no zone origin") with cm as _txn: if add_dnskey: if dnskey_ttl is None: dnskey = _txn.get(zone.origin, dns.rdatatype.DNSKEY) if dnskey: dnskey_ttl = dnskey.ttl else: soa = _txn.get(zone.origin, dns.rdatatype.SOA) dnskey_ttl = soa.ttl for _, dnskey in keys: _txn.add(zone.origin, dnskey_ttl, dnskey) if nsec3: raise NotImplementedError("Signing with NSEC3 not yet implemented") else: _rrset_signer = rrset_signer or functools.partial( default_rrset_signer, signer=zone.origin, ksks=ksks, zsks=zsks, inception=inception, expiration=expiration, lifetime=lifetime, policy=policy, origin=zone.origin, deterministic=deterministic, ) return _sign_zone_nsec(zone, _txn, _rrset_signer) def _sign_zone_nsec( zone: dns.zone.Zone, txn: dns.transaction.Transaction, rrset_signer: Optional[RRsetSigner] = None, ) -> None: """NSEC zone signer""" def _txn_add_nsec( txn: dns.transaction.Transaction, name: dns.name.Name, next_secure: Optional[dns.name.Name], rdclass: dns.rdataclass.RdataClass, ttl: int, rrset_signer: Optional[RRsetSigner] = None, ) -> None: """NSEC zone signer helper""" mandatory_types = set( [dns.rdatatype.RdataType.RRSIG, dns.rdatatype.RdataType.NSEC] ) node = txn.get_node(name) if node and next_secure: types = ( set([rdataset.rdtype for rdataset in node.rdatasets]) | mandatory_types ) windows = Bitmap.from_rdtypes(list(types)) rrset = dns.rrset.from_rdata( name, ttl, NSEC( rdclass=rdclass, rdtype=dns.rdatatype.RdataType.NSEC, next=next_secure, windows=windows, ), ) txn.add(rrset) if rrset_signer: rrset_signer(txn, rrset) rrsig_ttl = zone.get_soa().minimum delegation = None last_secure = None for name in sorted(txn.iterate_names()): if delegation and name.is_subdomain(delegation): # names below delegations are not secure continue elif txn.get(name, dns.rdatatype.NS) and name != zone.origin: # inside delegation delegation = name else: # outside delegation delegation = None if rrset_signer: node = txn.get_node(name) if node: for rdataset in node.rdatasets: if rdataset.rdtype == dns.rdatatype.RRSIG: # do not sign RRSIGs continue elif delegation and rdataset.rdtype != dns.rdatatype.DS: # do not sign delegations except DS records continue else: rrset = dns.rrset.from_rdata(name, rdataset.ttl, *rdataset) rrset_signer(txn, rrset) # We need "is not None" as the empty name is False because its length is 0. if last_secure is not None: _txn_add_nsec(txn, last_secure, name, zone.rdclass, rrsig_ttl, rrset_signer) last_secure = name if last_secure: _txn_add_nsec( txn, last_secure, zone.origin, zone.rdclass, rrsig_ttl, rrset_signer ) def _need_pyca(*args, **kwargs): raise ImportError( "DNSSEC validation requires python cryptography" ) # pragma: no cover if dns._features.have("dnssec"): from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives.asymmetric import dsa # pylint: disable=W0611 from cryptography.hazmat.primitives.asymmetric import ec # pylint: disable=W0611 from cryptography.hazmat.primitives.asymmetric import ed448 # pylint: disable=W0611 from cryptography.hazmat.primitives.asymmetric import rsa # pylint: disable=W0611 from cryptography.hazmat.primitives.asymmetric import ( # pylint: disable=W0611 ed25519, ) from dns.dnssecalgs import ( # pylint: disable=C0412 get_algorithm_cls, get_algorithm_cls_from_dnskey, ) from dns.dnssecalgs.base import GenericPrivateKey, GenericPublicKey validate = _validate # type: ignore validate_rrsig = _validate_rrsig # type: ignore sign = _sign make_dnskey = _make_dnskey make_cdnskey = _make_cdnskey _have_pyca = True else: # pragma: no cover validate = _need_pyca validate_rrsig = _need_pyca sign = _need_pyca make_dnskey = _need_pyca make_cdnskey = _need_pyca _have_pyca = False ### BEGIN generated Algorithm constants RSAMD5 = Algorithm.RSAMD5 DH = Algorithm.DH DSA = Algorithm.DSA ECC = Algorithm.ECC RSASHA1 = Algorithm.RSASHA1 DSANSEC3SHA1 = Algorithm.DSANSEC3SHA1 RSASHA1NSEC3SHA1 = Algorithm.RSASHA1NSEC3SHA1 RSASHA256 = Algorithm.RSASHA256 RSASHA512 = Algorithm.RSASHA512 ECCGOST = Algorithm.ECCGOST ECDSAP256SHA256 = Algorithm.ECDSAP256SHA256 ECDSAP384SHA384 = Algorithm.ECDSAP384SHA384 ED25519 = Algorithm.ED25519 ED448 = Algorithm.ED448 INDIRECT = Algorithm.INDIRECT PRIVATEDNS = Algorithm.PRIVATEDNS PRIVATEOID = Algorithm.PRIVATEOID ### END generated Algorithm constants dnspython-2.7.0/dns/dnssectypes.py0000644000000000000000000000340713615410400014174 0ustar00# 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 # 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. """Common DNSSEC-related types.""" # This is a separate file to avoid import circularity between dns.dnssec and # the implementations of the DS and DNSKEY types. import dns.enum class Algorithm(dns.enum.IntEnum): RSAMD5 = 1 DH = 2 DSA = 3 ECC = 4 RSASHA1 = 5 DSANSEC3SHA1 = 6 RSASHA1NSEC3SHA1 = 7 RSASHA256 = 8 RSASHA512 = 10 ECCGOST = 12 ECDSAP256SHA256 = 13 ECDSAP384SHA384 = 14 ED25519 = 15 ED448 = 16 INDIRECT = 252 PRIVATEDNS = 253 PRIVATEOID = 254 @classmethod def _maximum(cls): return 255 class DSDigest(dns.enum.IntEnum): """DNSSEC Delegation Signer Digest Algorithm""" NULL = 0 SHA1 = 1 SHA256 = 2 GOST = 3 SHA384 = 4 @classmethod def _maximum(cls): return 255 class NSEC3Hash(dns.enum.IntEnum): """NSEC3 hash algorithm""" SHA1 = 1 @classmethod def _maximum(cls): return 255 dnspython-2.7.0/dns/e164.py0000644000000000000000000000761213615410400012311 0ustar00# 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 # 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. """DNS E.164 helpers.""" from typing import Iterable, Optional, Union import dns.exception import dns.name import dns.resolver #: The public E.164 domain. public_enum_domain = dns.name.from_text("e164.arpa.") def from_e164( text: str, origin: Optional[dns.name.Name] = public_enum_domain ) -> dns.name.Name: """Convert an E.164 number in textual form into a Name object whose value is the ENUM domain name for that number. Non-digits in the text are ignored, i.e. "16505551212", "+1.650.555.1212" and "1 (650) 555-1212" are all the same. *text*, a ``str``, is an E.164 number in textual form. *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.reverse() return dns.name.from_text(".".join(parts), origin=origin) def to_e164( name: dns.name.Name, origin: Optional[dns.name.Name] = public_enum_domain, want_plus_prefix: bool = True, ) -> str: """Convert an ENUM domain name into an E.164 number. Note that dnspython does not have any information about preferred number formats within national numbering plans, so all numbers are emitted as a simple string of digits, prefixed by a '+' (unless *want_plus_prefix* is ``False``). *name* is a ``dns.name.Name``, the ENUM domain name. *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 ``str``. """ if origin is not None: name = name.relativize(origin) dlabels = [d for d in name.labels if d.isdigit() and len(d) == 1] if len(dlabels) != len(name.labels): raise dns.exception.SyntaxError("non-digit labels in ENUM domain name") dlabels.reverse() text = b"".join(dlabels) if want_plus_prefix: text = b"+" + text return text.decode() def query( number: str, domains: Iterable[Union[dns.name.Name, str]], resolver: Optional[dns.resolver.Resolver] = None, ) -> dns.resolver.Answer: """Look for NAPTR RRs for the specified number in the specified domains. e.g. lookup('16505551212', ['e164.dnspython.org.', 'e164.arpa.']) *number*, a ``str`` 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: resolver = dns.resolver.get_default_resolver() e_nx = dns.resolver.NXDOMAIN() for domain in domains: if isinstance(domain, str): domain = dns.name.from_text(domain) qname = dns.e164.from_e164(number, domain) try: return resolver.resolve(qname, "NAPTR") except dns.resolver.NXDOMAIN as e: e_nx += e raise e_nx dnspython-2.7.0/dns/edns.py0000644000000000000000000004130113615410400012554 0ustar00# 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 # 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. """EDNS Options""" import binascii import math import socket import struct from typing import Any, Dict, Optional, Union import dns.enum import dns.inet import dns.rdata import dns.wire class OptionType(dns.enum.IntEnum): #: 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 #: EDE (extended-dns-error) EDE = 15 #: REPORTCHANNEL REPORTCHANNEL = 18 @classmethod def _maximum(cls): return 65535 class Option: """Base class for all EDNS option types.""" def __init__(self, otype: Union[OptionType, str]): """Initialize an option. *otype*, a ``dns.edns.OptionType``, is the option type. """ self.otype = OptionType.make(otype) def to_wire(self, file: Optional[Any] = None) -> Optional[bytes]: """Convert an option to wire format. Returns a ``bytes`` or ``None``. """ raise NotImplementedError # pragma: no cover def to_text(self) -> str: raise NotImplementedError # pragma: no cover @classmethod def from_wire_parser(cls, otype: OptionType, parser: "dns.wire.Parser") -> "Option": """Build an EDNS option object from wire format. *otype*, a ``dns.edns.OptionType``, is the option type. *parser*, a ``dns.wire.Parser``, the parser, which should be restructed to the option length. Returns a ``dns.edns.Option``. """ raise NotImplementedError # pragma: no cover def _cmp(self, other): """Compare an EDNS option with another option of the same type. Returns < 0 if < *other*, 0 if == *other*, and > 0 if > *other*. """ wire = self.to_wire() owire = other.to_wire() if wire == owire: return 0 if wire > owire: return 1 return -1 def __eq__(self, other): if not isinstance(other, Option): return False if self.otype != other.otype: return False return self._cmp(other) == 0 def __ne__(self, other): if not isinstance(other, Option): return True if self.otype != other.otype: return True return self._cmp(other) != 0 def __lt__(self, other): if not isinstance(other, Option) or self.otype != other.otype: return NotImplemented return self._cmp(other) < 0 def __le__(self, other): if not isinstance(other, Option) or self.otype != other.otype: return NotImplemented return self._cmp(other) <= 0 def __ge__(self, other): if not isinstance(other, Option) or self.otype != other.otype: return NotImplemented return self._cmp(other) >= 0 def __gt__(self, other): if not isinstance(other, Option) or self.otype != other.otype: return NotImplemented return self._cmp(other) > 0 def __str__(self): return self.to_text() class GenericOption(Option): # lgtm[py/missing-equals] """Generic Option Class This class is used for EDNS option types for which we have no better implementation. """ def __init__(self, otype: Union[OptionType, str], data: Union[bytes, str]): super().__init__(otype) self.data = dns.rdata.Rdata._as_bytes(data, True) def to_wire(self, file: Optional[Any] = None) -> Optional[bytes]: if file: file.write(self.data) return None else: return self.data def to_text(self) -> str: return "Generic %d" % self.otype @classmethod def from_wire_parser( cls, otype: Union[OptionType, str], parser: "dns.wire.Parser" ) -> Option: return cls(otype, parser.get_remaining()) class ECSOption(Option): # lgtm[py/missing-equals] """EDNS Client Subnet (ECS, RFC7871)""" def __init__(self, address: str, srclen: Optional[int] = None, scopelen: int = 0): """*address*, a ``str``, 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().__init__(OptionType.ECS) af = dns.inet.af_for_address(address) if af == socket.AF_INET6: self.family = 2 if srclen is None: srclen = 56 address = dns.rdata.Rdata._as_ipv6_address(address) srclen = dns.rdata.Rdata._as_int(srclen, 0, 128) scopelen = dns.rdata.Rdata._as_int(scopelen, 0, 128) elif af == socket.AF_INET: self.family = 1 if srclen is None: srclen = 24 address = dns.rdata.Rdata._as_ipv4_address(address) srclen = dns.rdata.Rdata._as_int(srclen, 0, 32) scopelen = dns.rdata.Rdata._as_int(scopelen, 0, 32) else: # pragma: no cover (this will never happen) raise ValueError("Bad address family") assert srclen is not None 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 << (8 - nbits))) self.addrdata = self.addrdata[:-1] + last def to_text(self) -> str: return f"ECS {self.address}/{self.srclen} scope/{self.scopelen}" @staticmethod def from_text(text: str) -> Option: """Convert a string into a `dns.edns.ECSOption` *text*, a `str`, the text form of the option. Returns a `dns.edns.ECSOption`. Examples: >>> import dns.edns >>> >>> # basic example >>> dns.edns.ECSOption.from_text('1.2.3.4/24') >>> >>> # also understands scope >>> dns.edns.ECSOption.from_text('1.2.3.4/24/32') >>> >>> # IPv6 >>> dns.edns.ECSOption.from_text('2001:4b98::1/64/64') >>> >>> # it understands results from `dns.edns.ECSOption.to_text()` >>> dns.edns.ECSOption.from_text('ECS 1.2.3.4/24/32') """ optional_prefix = "ECS" tokens = text.split() ecs_text = None if len(tokens) == 1: ecs_text = tokens[0] elif len(tokens) == 2: if tokens[0] != optional_prefix: raise ValueError(f'could not parse ECS from "{text}"') ecs_text = tokens[1] else: raise ValueError(f'could not parse ECS from "{text}"') n_slashes = ecs_text.count("/") if n_slashes == 1: address, tsrclen = ecs_text.split("/") tscope = "0" elif n_slashes == 2: address, tsrclen, tscope = ecs_text.split("/") else: raise ValueError(f'could not parse ECS from "{text}"') try: scope = int(tscope) except ValueError: raise ValueError("invalid scope " + f'"{tscope}": scope must be an integer') try: srclen = int(tsrclen) except ValueError: raise ValueError( "invalid srclen " + f'"{tsrclen}": srclen must be an integer' ) return ECSOption(address, srclen, scope) def to_wire(self, file: Optional[Any] = None) -> Optional[bytes]: value = ( struct.pack("!HBB", self.family, self.srclen, self.scopelen) + self.addrdata ) if file: file.write(value) return None else: return value @classmethod def from_wire_parser( cls, otype: Union[OptionType, str], parser: "dns.wire.Parser" ) -> Option: family, src, scope = parser.get_struct("!HBB") addrlen = int(math.ceil(src / 8.0)) prefix = parser.get_bytes(addrlen) if family == 1: pad = 4 - addrlen addr = dns.ipv4.inet_ntoa(prefix + b"\x00" * pad) elif family == 2: pad = 16 - addrlen addr = dns.ipv6.inet_ntoa(prefix + b"\x00" * pad) else: raise ValueError("unsupported family") return cls(addr, src, scope) class EDECode(dns.enum.IntEnum): OTHER = 0 UNSUPPORTED_DNSKEY_ALGORITHM = 1 UNSUPPORTED_DS_DIGEST_TYPE = 2 STALE_ANSWER = 3 FORGED_ANSWER = 4 DNSSEC_INDETERMINATE = 5 DNSSEC_BOGUS = 6 SIGNATURE_EXPIRED = 7 SIGNATURE_NOT_YET_VALID = 8 DNSKEY_MISSING = 9 RRSIGS_MISSING = 10 NO_ZONE_KEY_BIT_SET = 11 NSEC_MISSING = 12 CACHED_ERROR = 13 NOT_READY = 14 BLOCKED = 15 CENSORED = 16 FILTERED = 17 PROHIBITED = 18 STALE_NXDOMAIN_ANSWER = 19 NOT_AUTHORITATIVE = 20 NOT_SUPPORTED = 21 NO_REACHABLE_AUTHORITY = 22 NETWORK_ERROR = 23 INVALID_DATA = 24 @classmethod def _maximum(cls): return 65535 class EDEOption(Option): # lgtm[py/missing-equals] """Extended DNS Error (EDE, RFC8914)""" _preserve_case = {"DNSKEY", "DS", "DNSSEC", "RRSIGs", "NSEC", "NXDOMAIN"} def __init__(self, code: Union[EDECode, str], text: Optional[str] = None): """*code*, a ``dns.edns.EDECode`` or ``str``, the info code of the extended error. *text*, a ``str`` or ``None``, specifying additional information about the error. """ super().__init__(OptionType.EDE) self.code = EDECode.make(code) if text is not None and not isinstance(text, str): raise ValueError("text must be string or None") self.text = text def to_text(self) -> str: output = f"EDE {self.code}" if self.code in EDECode: desc = EDECode.to_text(self.code) desc = " ".join( word if word in self._preserve_case else word.title() for word in desc.split("_") ) output += f" ({desc})" if self.text is not None: output += f": {self.text}" return output def to_wire(self, file: Optional[Any] = None) -> Optional[bytes]: value = struct.pack("!H", self.code) if self.text is not None: value += self.text.encode("utf8") if file: file.write(value) return None else: return value @classmethod def from_wire_parser( cls, otype: Union[OptionType, str], parser: "dns.wire.Parser" ) -> Option: code = EDECode.make(parser.get_uint16()) text = parser.get_remaining() if text: if text[-1] == 0: # text MAY be null-terminated text = text[:-1] btext = text.decode("utf8") else: btext = None return cls(code, btext) class NSIDOption(Option): def __init__(self, nsid: bytes): super().__init__(OptionType.NSID) self.nsid = nsid def to_wire(self, file: Any = None) -> Optional[bytes]: if file: file.write(self.nsid) return None else: return self.nsid def to_text(self) -> str: if all(c >= 0x20 and c <= 0x7E for c in self.nsid): # All ASCII printable, so it's probably a string. value = self.nsid.decode() else: value = binascii.hexlify(self.nsid).decode() return f"NSID {value}" @classmethod def from_wire_parser( cls, otype: Union[OptionType, str], parser: dns.wire.Parser ) -> Option: return cls(parser.get_remaining()) class CookieOption(Option): def __init__(self, client: bytes, server: bytes): super().__init__(dns.edns.OptionType.COOKIE) self.client = client self.server = server if len(client) != 8: raise ValueError("client cookie must be 8 bytes") if len(server) != 0 and (len(server) < 8 or len(server) > 32): raise ValueError("server cookie must be empty or between 8 and 32 bytes") def to_wire(self, file: Any = None) -> Optional[bytes]: if file: file.write(self.client) if len(self.server) > 0: file.write(self.server) return None else: return self.client + self.server def to_text(self) -> str: client = binascii.hexlify(self.client).decode() if len(self.server) > 0: server = binascii.hexlify(self.server).decode() else: server = "" return f"COOKIE {client}{server}" @classmethod def from_wire_parser( cls, otype: Union[OptionType, str], parser: dns.wire.Parser ) -> Option: return cls(parser.get_bytes(8), parser.get_remaining()) class ReportChannelOption(Option): # RFC 9567 def __init__(self, agent_domain: dns.name.Name): super().__init__(OptionType.REPORTCHANNEL) self.agent_domain = agent_domain def to_wire(self, file: Any = None) -> Optional[bytes]: return self.agent_domain.to_wire(file) def to_text(self) -> str: return "REPORTCHANNEL " + self.agent_domain.to_text() @classmethod def from_wire_parser( cls, otype: Union[OptionType, str], parser: dns.wire.Parser ) -> Option: return cls(parser.get_name()) _type_to_class: Dict[OptionType, Any] = { OptionType.ECS: ECSOption, OptionType.EDE: EDEOption, OptionType.NSID: NSIDOption, OptionType.COOKIE: CookieOption, OptionType.REPORTCHANNEL: ReportChannelOption, } def get_option_class(otype: OptionType) -> Any: """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) if cls is None: cls = GenericOption return cls def option_from_wire_parser( otype: Union[OptionType, str], parser: "dns.wire.Parser" ) -> Option: """Build an EDNS option object from wire format. *otype*, an ``int``, is the option type. *parser*, a ``dns.wire.Parser``, the parser, which should be restricted to the option length. Returns an instance of a subclass of ``dns.edns.Option``. """ otype = OptionType.make(otype) cls = get_option_class(otype) return cls.from_wire_parser(otype, parser) def option_from_wire( otype: Union[OptionType, str], wire: bytes, current: int, olen: int ) -> Option: """Build an EDNS option object from wire format. *otype*, an ``int``, is the option type. *wire*, a ``bytes``, 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 an instance of a subclass of ``dns.edns.Option``. """ parser = dns.wire.Parser(wire, current) with parser.restrict_to(olen): return option_from_wire_parser(otype, parser) def register_type(implementation: Any, otype: OptionType) -> None: """Register the implementation of an option type. *implementation*, a ``class``, is a subclass of ``dns.edns.Option``. *otype*, an ``int``, is the option type. """ _type_to_class[otype] = implementation ### BEGIN generated OptionType constants NSID = OptionType.NSID DAU = OptionType.DAU DHU = OptionType.DHU N3U = OptionType.N3U ECS = OptionType.ECS EXPIRE = OptionType.EXPIRE COOKIE = OptionType.COOKIE KEEPALIVE = OptionType.KEEPALIVE PADDING = OptionType.PADDING CHAIN = OptionType.CHAIN EDE = OptionType.EDE REPORTCHANNEL = OptionType.REPORTCHANNEL ### END generated OptionType constants dnspython-2.7.0/dns/entropy.py0000644000000000000000000001022213615410400013321 0ustar00# 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 # 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 hashlib import os import random import threading import time from typing import Any, Optional class EntropyPool: # 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: Optional[bytes] = None): self.pool_index = 0 self.digest: Optional[bytearray] = None self.next_byte = 0 self.lock = threading.Lock() self.hash = hashlib.sha1() self.hash_len = 20 self.pool = bytearray(b"\0" * self.hash_len) if seed is not None: self._stir(seed) self.seeded = True self.seed_pid = os.getpid() else: self.seeded = False self.seed_pid = 0 def _stir(self, entropy: bytes) -> None: for c in entropy: if self.pool_index == self.hash_len: self.pool_index = 0 b = c & 0xFF self.pool[self.pool_index] ^= b self.pool_index += 1 def stir(self, entropy: bytes) -> None: with self.lock: self._stir(entropy) def _maybe_seed(self) -> None: if not self.seeded or self.seed_pid != os.getpid(): try: seed = os.urandom(16) except Exception: # pragma: no cover try: with open("/dev/urandom", "rb", 0) as r: seed = r.read(16) except Exception: seed = str(time.time()).encode() self.seeded = True self.seed_pid = os.getpid() self.digest = None seed = bytearray(seed) self._stir(seed) def random_8(self) -> int: with self.lock: self._maybe_seed() if self.digest is None or self.next_byte == self.hash_len: self.hash.update(bytes(self.pool)) self.digest = bytearray(self.hash.digest()) self._stir(self.digest) self.next_byte = 0 value = self.digest[self.next_byte] self.next_byte += 1 return value def random_16(self) -> int: return self.random_8() * 256 + self.random_8() def random_32(self) -> int: return self.random_16() * 65536 + self.random_16() def random_between(self, first: int, last: int) -> int: size = last - first + 1 if size > 4294967296: raise ValueError("too big") if size > 65536: rand = self.random_32 max = 4294967295 elif size > 256: rand = self.random_16 max = 65535 else: rand = self.random_8 max = 255 return first + size * rand() // (max + 1) pool = EntropyPool() system_random: Optional[Any] try: system_random = random.SystemRandom() except Exception: # pragma: no cover system_random = None def random_16() -> int: if system_random is not None: return system_random.randrange(0, 65536) else: return pool.random_16() def between(first: int, last: int) -> int: if system_random is not None: return system_random.randrange(first, last + 1) else: return pool.random_between(first, last) dnspython-2.7.0/dns/enum.py0000644000000000000000000000715313615410400012576 0ustar00# 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 # 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 enum from typing import Type, TypeVar, Union TIntEnum = TypeVar("TIntEnum", bound="IntEnum") class IntEnum(enum.IntEnum): @classmethod def _missing_(cls, value): cls._check_value(value) val = int.__new__(cls, value) val._name_ = cls._extra_to_text(value, None) or f"{cls._prefix()}{value}" val._value_ = value return val @classmethod def _check_value(cls, value): max = cls._maximum() if not isinstance(value, int): raise TypeError if value < 0 or value > max: name = cls._short_name() raise ValueError(f"{name} must be an int between >= 0 and <= {max}") @classmethod def from_text(cls: Type[TIntEnum], text: str) -> TIntEnum: text = text.upper() try: return cls[text] except KeyError: pass value = cls._extra_from_text(text) if value: return value prefix = cls._prefix() if text.startswith(prefix) and text[len(prefix) :].isdigit(): value = int(text[len(prefix) :]) cls._check_value(value) try: return cls(value) except ValueError: return value raise cls._unknown_exception_class() @classmethod def to_text(cls: Type[TIntEnum], value: int) -> str: cls._check_value(value) try: text = cls(value).name except ValueError: text = None text = cls._extra_to_text(value, text) if text is None: text = f"{cls._prefix()}{value}" return text @classmethod def make(cls: Type[TIntEnum], value: Union[int, str]) -> TIntEnum: """Convert text or a value into an enumerated type, if possible. *value*, the ``int`` or ``str`` to convert. Raises a class-specific exception if a ``str`` is provided that cannot be converted. Raises ``ValueError`` if the value is out of range. Returns an enumeration from the calling class corresponding to the value, if one is defined, or an ``int`` otherwise. """ if isinstance(value, str): return cls.from_text(value) cls._check_value(value) return cls(value) @classmethod def _maximum(cls): raise NotImplementedError # pragma: no cover @classmethod def _short_name(cls): return cls.__name__.lower() @classmethod def _prefix(cls): return "" @classmethod def _extra_from_text(cls, text): # pylint: disable=W0613 return None @classmethod def _extra_to_text(cls, value, current_text): # pylint: disable=W0613 return current_text @classmethod def _unknown_exception_class(cls): return ValueError dnspython-2.7.0/dns/exception.py0000644000000000000000000001350113615410400013622 0ustar00# 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 # 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. """Common DNS Exceptions. Dnspython modules may also define their own exceptions, which will always be subclasses of ``DNSException``. """ from typing import Optional, Set class DNSException(Exception): """Abstract base class shared by all dnspython exceptions. It supports two basic modes of operation: a) Old/compatible mode is used if ``__init__`` was called with empty *kwargs*. In compatible mode all *args* are passed to the standard Python Exception class as before and all *args* are printed by the standard ``__str__`` implementation. Class variable ``msg`` (or doc string if ``msg`` is ``None``) is returned from ``str()`` if *args* is empty. b) New/parametrized mode is used if ``__init__`` was called with non-empty *kwargs*. In the new mode *args* must be empty and all kwargs must match those set in class variable ``supp_kwargs``. All kwargs are stored inside ``self.kwargs`` and used in a new ``__str__`` implementation to construct a formatted message based on the ``fmt`` class variable, a ``string``. In the simplest case it is enough to override the ``supp_kwargs`` and ``fmt`` class variables to get nice parametrized messages. """ msg: Optional[str] = None # non-parametrized message supp_kwargs: Set[str] = set() # accepted parameters for _fmt_kwargs (sanity check) fmt: Optional[str] = None # message parametrized with results from _fmt_kwargs def __init__(self, *args, **kwargs): self._check_params(*args, **kwargs) if kwargs: # This call to a virtual method from __init__ is ok in our usage self.kwargs = self._check_kwargs(**kwargs) # lgtm[py/init-calls-subclass] self.msg = str(self) else: self.kwargs = dict() # defined but empty for old mode exceptions if self.msg is None: # doc string is better implicit message than empty string self.msg = self.__doc__ if args: super().__init__(*args) else: super().__init__(self.msg) def _check_params(self, *args, **kwargs): """Old exceptions supported only args and not kwargs. For sanity we do not allow to mix old and new behavior.""" if args or kwargs: assert bool(args) != bool( kwargs ), "keyword arguments are mutually exclusive with positional args" def _check_kwargs(self, **kwargs): if kwargs: assert ( set(kwargs.keys()) == self.supp_kwargs ), f"following set of keyword args is required: {self.supp_kwargs}" return kwargs def _fmt_kwargs(self, **kwargs): """Format kwargs before printing them. Resulting dictionary has to have keys necessary for str.format call on fmt class variable. """ fmtargs = {} for kw, data in kwargs.items(): if isinstance(data, (list, set)): # convert list of to list of str() fmtargs[kw] = list(map(str, data)) if len(fmtargs[kw]) == 1: # remove list brackets [] from single-item lists fmtargs[kw] = fmtargs[kw].pop() else: fmtargs[kw] = data return fmtargs def __str__(self): if self.kwargs and self.fmt: # provide custom message constructed from keyword arguments fmtargs = self._fmt_kwargs(**self.kwargs) return self.fmt.format(**fmtargs) else: # print *args directly in the same way as old DNSException return super().__str__() class FormError(DNSException): """DNS message is malformed.""" class SyntaxError(DNSException): """Text input is malformed.""" class UnexpectedEnd(SyntaxError): """Text input ended unexpectedly.""" class TooBig(DNSException): """The DNS message is too big.""" class Timeout(DNSException): """The DNS operation timed out.""" supp_kwargs = {"timeout"} fmt = "The DNS operation timed out after {timeout:.3f} seconds" # We do this as otherwise mypy complains about unexpected keyword argument # idna_exception def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) class UnsupportedAlgorithm(DNSException): """The DNSSEC algorithm is not supported.""" class AlgorithmKeyMismatch(UnsupportedAlgorithm): """The DNSSEC algorithm is not supported for the given key type.""" class ValidationFailure(DNSException): """The DNSSEC signature is invalid.""" class DeniedByPolicy(DNSException): """Denied by DNSSEC policy.""" class ExceptionWrapper: def __init__(self, exception_class): self.exception_class = exception_class def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is not None and not isinstance(exc_val, self.exception_class): raise self.exception_class(str(exc_val)) from exc_val return False dnspython-2.7.0/dns/flags.py0000644000000000000000000000527613615410400012732 0ustar00# 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 # 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. """DNS Message Flags.""" import enum from typing import Any # Standard DNS flags class Flag(enum.IntFlag): #: Query Response QR = 0x8000 #: Authoritative Answer AA = 0x0400 #: Truncated Response TC = 0x0200 #: Recursion Desired RD = 0x0100 #: Recursion Available RA = 0x0080 #: Authentic Data AD = 0x0020 #: Checking Disabled CD = 0x0010 # EDNS flags class EDNSFlag(enum.IntFlag): #: DNSSEC answer OK DO = 0x8000 def _from_text(text: str, enum_class: Any) -> int: flags = 0 tokens = text.split() for t in tokens: flags |= enum_class[t.upper()] return flags def _to_text(flags: int, enum_class: Any) -> str: text_flags = [] for k, v in enum_class.__members__.items(): if flags & v != 0: text_flags.append(k) return " ".join(text_flags) def from_text(text: str) -> int: """Convert a space-separated list of flag text values into a flags value. Returns an ``int`` """ return _from_text(text, Flag) def to_text(flags: int) -> str: """Convert a flags value into a space-separated list of flag text values. Returns a ``str``. """ return _to_text(flags, Flag) def edns_from_text(text: str) -> int: """Convert a space-separated list of EDNS flag text values into a EDNS flags value. Returns an ``int`` """ return _from_text(text, EDNSFlag) def edns_to_text(flags: int) -> str: """Convert an EDNS flags value into a space-separated list of EDNS flag text values. Returns a ``str``. """ return _to_text(flags, EDNSFlag) ### BEGIN generated Flag constants QR = Flag.QR AA = Flag.AA TC = Flag.TC RD = Flag.RD RA = Flag.RA AD = Flag.AD CD = Flag.CD ### END generated Flag constants ### BEGIN generated EDNSFlag constants DO = EDNSFlag.DO ### END generated EDNSFlag constants dnspython-2.7.0/dns/grange.py0000644000000000000000000000414013615410400013066 0ustar00# 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 # 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. """DNS GENERATE range conversion.""" from typing import Tuple import dns def from_text(text: str) -> Tuple[int, int, int]: """Convert the text form of a range in a ``$GENERATE`` statement to an integer. *text*, a ``str``, the textual range in ``$GENERATE`` form. Returns a tuple of three ``int`` values ``(start, stop, step)``. """ start = -1 stop = -1 step = 1 cur = "" state = 0 # state 0 1 2 # x - y / z if text and text[0] == "-": raise dns.exception.SyntaxError("Start cannot be a negative number") for c in text: if c == "-" and state == 0: start = int(cur) cur = "" state = 1 elif c == "/": stop = int(cur) cur = "" state = 2 elif c.isdigit(): cur += c else: raise dns.exception.SyntaxError(f"Could not parse {c}") if state == 0: raise dns.exception.SyntaxError("no stop value specified") elif state == 1: stop = int(cur) else: assert state == 2 step = int(cur) assert step >= 1 assert start >= 0 if start > stop: raise dns.exception.SyntaxError("start must be <= stop") return (start, stop, step) dnspython-2.7.0/dns/immutable.py0000644000000000000000000000374113615410400013610 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import collections.abc from typing import Any, Callable from dns._immutable_ctx import immutable @immutable class Dict(collections.abc.Mapping): # lgtm[py/missing-equals] def __init__( self, dictionary: Any, no_copy: bool = False, map_factory: Callable[[], collections.abc.MutableMapping] = dict, ): """Make an immutable dictionary from the specified dictionary. If *no_copy* is `True`, then *dictionary* will be wrapped instead of copied. Only set this if you are sure there will be no external references to the dictionary. """ if no_copy and isinstance(dictionary, collections.abc.MutableMapping): self._odict = dictionary else: self._odict = map_factory() self._odict.update(dictionary) self._hash = None def __getitem__(self, key): return self._odict.__getitem__(key) def __hash__(self): # pylint: disable=invalid-hash-returned if self._hash is None: h = 0 for key in sorted(self._odict.keys()): h ^= hash(key) object.__setattr__(self, "_hash", h) # this does return an int, but pylint doesn't figure that out return self._hash def __len__(self): return len(self._odict) def __iter__(self): return iter(self._odict) def constify(o: Any) -> Any: """ Convert mutable types to immutable types. """ if isinstance(o, bytearray): return bytes(o) if isinstance(o, tuple): try: hash(o) return o except Exception: return tuple(constify(elt) for elt in o) if isinstance(o, list): return tuple(constify(elt) for elt in o) if isinstance(o, dict): cdict = dict() for k, v in o.items(): cdict[k] = constify(v) return Dict(cdict, True) return o dnspython-2.7.0/dns/inet.py0000644000000000000000000001321413615410400012564 0ustar00# 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 # 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. """Generic Internet address helper functions.""" import socket from typing import Any, Optional, Tuple import dns.ipv4 import dns.ipv6 # We assume that AF_INET and AF_INET6 are always defined. We keep # these here for the benefit of any old code (unlikely though that # is!). AF_INET = socket.AF_INET AF_INET6 = socket.AF_INET6 def inet_pton(family: int, text: str) -> bytes: """Convert the textual form of a network address into its binary form. *family* is an ``int``, the address family. *text* is a ``str``, the textual address. Raises ``NotImplementedError`` if the address family specified is not implemented. Returns a ``bytes``. """ if family == AF_INET: return dns.ipv4.inet_aton(text) elif family == AF_INET6: return dns.ipv6.inet_aton(text, True) else: raise NotImplementedError def inet_ntop(family: int, address: bytes) -> str: """Convert the binary form of a network address into its textual form. *family* is an ``int``, the address family. *address* is a ``bytes``, the network address in binary form. Raises ``NotImplementedError`` if the address family specified is not implemented. Returns a ``str``. """ if family == AF_INET: return dns.ipv4.inet_ntoa(address) elif family == AF_INET6: return dns.ipv6.inet_ntoa(address) else: raise NotImplementedError def af_for_address(text: str) -> int: """Determine the address family of a textual-form network address. *text*, a ``str``, the textual address. Raises ``ValueError`` if the address family cannot be determined from the input. Returns an ``int``. """ try: dns.ipv4.inet_aton(text) return AF_INET except Exception: try: dns.ipv6.inet_aton(text, True) return AF_INET6 except Exception: raise ValueError def is_multicast(text: str) -> bool: """Is the textual-form network address a multicast address? *text*, a ``str``, the textual address. Raises ``ValueError`` if the address family cannot be determined from the input. Returns a ``bool``. """ try: first = dns.ipv4.inet_aton(text)[0] return first >= 224 and first <= 239 except Exception: try: first = dns.ipv6.inet_aton(text, True)[0] return first == 255 except Exception: raise ValueError def is_address(text: str) -> bool: """Is the specified string an IPv4 or IPv6 address? *text*, a ``str``, the textual address. Returns a ``bool``. """ try: dns.ipv4.inet_aton(text) return True except Exception: try: dns.ipv6.inet_aton(text, True) return True except Exception: return False def low_level_address_tuple( high_tuple: Tuple[str, int], af: Optional[int] = None ) -> Any: """Given a "high-level" address tuple, i.e. an (address, port) return the appropriate "low-level" address tuple suitable for use in socket calls. If an *af* other than ``None`` is provided, it is assumed the address in the high-level tuple is valid and has that af. If af is ``None``, then af_for_address will be called. """ address, port = high_tuple if af is None: af = af_for_address(address) if af == AF_INET: return (address, port) elif af == AF_INET6: i = address.find("%") if i < 0: # no scope, shortcut! return (address, port, 0, 0) # try to avoid getaddrinfo() addrpart = address[:i] scope = address[i + 1 :] if scope.isdigit(): return (addrpart, port, 0, int(scope)) try: return (addrpart, port, 0, socket.if_nametoindex(scope)) except AttributeError: # pragma: no cover (we can't really test this) ai_flags = socket.AI_NUMERICHOST ((*_, tup), *_) = socket.getaddrinfo(address, port, flags=ai_flags) return tup else: raise NotImplementedError(f"unknown address family {af}") def any_for_af(af): """Return the 'any' address for the specified address family.""" if af == socket.AF_INET: return "0.0.0.0" elif af == socket.AF_INET6: return "::" raise NotImplementedError(f"unknown address family {af}") def canonicalize(text: str) -> str: """Verify that *address* is a valid text form IPv4 or IPv6 address and return its canonical text form. IPv6 addresses with scopes are rejected. *text*, a ``str``, the address in textual form. Raises ``ValueError`` if the text is not valid. """ try: return dns.ipv6.canonicalize(text) except Exception: try: return dns.ipv4.canonicalize(text) except Exception: raise ValueError dnspython-2.7.0/dns/ipv4.py0000644000000000000000000000477013615410400012516 0ustar00# 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 # 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. """IPv4 helper functions.""" import struct from typing import Union import dns.exception def inet_ntoa(address: bytes) -> str: """Convert an IPv4 address in binary form to text form. *address*, a ``bytes``, the IPv4 address in binary form. Returns a ``str``. """ if len(address) != 4: raise dns.exception.SyntaxError return "%u.%u.%u.%u" % (address[0], address[1], address[2], address[3]) def inet_aton(text: Union[str, bytes]) -> bytes: """Convert an IPv4 address in text form to binary form. *text*, a ``str`` or ``bytes``, the IPv4 address in textual form. Returns a ``bytes``. """ if not isinstance(text, bytes): btext = text.encode() else: btext = text parts = btext.split(b".") if len(parts) != 4: raise dns.exception.SyntaxError for part in parts: if not part.isdigit(): raise dns.exception.SyntaxError if len(part) > 1 and part[0] == ord("0"): # No leading zeros raise dns.exception.SyntaxError try: b = [int(part) for part in parts] return struct.pack("BBBB", *b) except Exception: raise dns.exception.SyntaxError def canonicalize(text: Union[str, bytes]) -> str: """Verify that *address* is a valid text form IPv4 address and return its canonical text form. *text*, a ``str`` or ``bytes``, the IPv4 address in textual form. Raises ``dns.exception.SyntaxError`` if the text is not valid. """ # Note that inet_aton() only accepts canonial form, but we still run through # inet_ntoa() to ensure the output is a str. return dns.ipv4.inet_ntoa(dns.ipv4.inet_aton(text)) dnspython-2.7.0/dns/ipv6.py0000644000000000000000000001463213615410400012516 0ustar00# 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 # 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. """IPv6 helper functions.""" import binascii import re from typing import List, Union import dns.exception import dns.ipv4 _leading_zero = re.compile(r"0+([0-9a-f]+)") def inet_ntoa(address: bytes) -> str: """Convert an IPv6 address in binary form to text form. *address*, a ``bytes``, the IPv6 address in binary form. Raises ``ValueError`` if the address isn't 16 bytes long. Returns a ``str``. """ if len(address) != 16: raise ValueError("IPv6 addresses are 16 bytes long") hex = binascii.hexlify(address) chunks = [] i = 0 l = len(hex) while i < l: chunk = hex[i : i + 4].decode() # strip leading zeros. we do this with an re instead of # with lstrip() because lstrip() didn't support chars until # python 2.2.2 m = _leading_zero.match(chunk) if m is not None: chunk = m.group(1) chunks.append(chunk) i += 4 # # Compress the longest subsequence of 0-value chunks to :: # best_start = 0 best_len = 0 start = -1 last_was_zero = False for i in range(8): if chunks[i] != "0": if last_was_zero: end = i current_len = end - start if current_len > best_len: best_start = start best_len = current_len last_was_zero = False elif not last_was_zero: start = i last_was_zero = True if last_was_zero: end = 8 current_len = end - start if current_len > best_len: best_start = start best_len = current_len if best_len > 1: if best_start == 0 and (best_len == 6 or best_len == 5 and chunks[5] == "ffff"): # We have an embedded IPv4 address if best_len == 6: prefix = "::" else: prefix = "::ffff:" thex = prefix + dns.ipv4.inet_ntoa(address[12:]) else: thex = ( ":".join(chunks[:best_start]) + "::" + ":".join(chunks[best_start + best_len :]) ) else: thex = ":".join(chunks) return thex _v4_ending = re.compile(rb"(.*):(\d+\.\d+\.\d+\.\d+)$") _colon_colon_start = re.compile(rb"::.*") _colon_colon_end = re.compile(rb".*::$") def inet_aton(text: Union[str, bytes], ignore_scope: bool = False) -> bytes: """Convert an IPv6 address in text form to binary form. *text*, a ``str`` or ``bytes``, the IPv6 address in textual form. *ignore_scope*, a ``bool``. If ``True``, a scope will be ignored. If ``False``, the default, it is an error for a scope to be present. Returns a ``bytes``. """ # # Our aim here is not something fast; we just want something that works. # if not isinstance(text, bytes): btext = text.encode() else: btext = text if ignore_scope: parts = btext.split(b"%") l = len(parts) if l == 2: btext = parts[0] elif l > 2: raise dns.exception.SyntaxError if btext == b"": raise dns.exception.SyntaxError elif btext.endswith(b":") and not btext.endswith(b"::"): raise dns.exception.SyntaxError elif btext.startswith(b":") and not btext.startswith(b"::"): raise dns.exception.SyntaxError elif btext == b"::": btext = b"0::" # # Get rid of the icky dot-quad syntax if we have it. # m = _v4_ending.match(btext) if m is not None: b = dns.ipv4.inet_aton(m.group(2)) btext = ( f"{m.group(1).decode()}:{b[0]:02x}{b[1]:02x}:{b[2]:02x}{b[3]:02x}" ).encode() # # Try to turn '::' into ':'; if no match try to # turn '::' into ':' # m = _colon_colon_start.match(btext) if m is not None: btext = btext[1:] else: m = _colon_colon_end.match(btext) if m is not None: btext = btext[:-1] # # Now canonicalize into 8 chunks of 4 hex digits each # chunks = btext.split(b":") l = len(chunks) if l > 8: raise dns.exception.SyntaxError seen_empty = False canonical: List[bytes] = [] for c in chunks: if c == b"": if seen_empty: raise dns.exception.SyntaxError seen_empty = True for _ in range(0, 8 - l + 1): canonical.append(b"0000") else: lc = len(c) if lc > 4: raise dns.exception.SyntaxError if lc != 4: c = (b"0" * (4 - lc)) + c canonical.append(c) if l < 8 and not seen_empty: raise dns.exception.SyntaxError btext = b"".join(canonical) # # Finally we can go to binary. # try: return binascii.unhexlify(btext) except (binascii.Error, TypeError): raise dns.exception.SyntaxError _mapped_prefix = b"\x00" * 10 + b"\xff\xff" def is_mapped(address: bytes) -> bool: """Is the specified address a mapped IPv4 address? *address*, a ``bytes`` is an IPv6 address in binary form. Returns a ``bool``. """ return address.startswith(_mapped_prefix) def canonicalize(text: Union[str, bytes]) -> str: """Verify that *address* is a valid text form IPv6 address and return its canonical text form. Addresses with scopes are rejected. *text*, a ``str`` or ``bytes``, the IPv6 address in textual form. Raises ``dns.exception.SyntaxError`` if the text is not valid. """ return dns.ipv6.inet_ntoa(dns.ipv6.inet_aton(text)) dnspython-2.7.0/dns/message.py0000644000000000000000000020513113615410400013252 0ustar00# 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 # 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. """DNS Messages""" import contextlib import enum import io import time from typing import Any, Dict, List, Optional, Tuple, Union, cast import dns.edns import dns.entropy import dns.enum import dns.exception import dns.flags import dns.name import dns.opcode import dns.rcode import dns.rdata import dns.rdataclass import dns.rdatatype import dns.rdtypes.ANY.OPT import dns.rdtypes.ANY.TSIG import dns.renderer import dns.rrset import dns.tsig import dns.ttl import dns.wire class ShortHeader(dns.exception.FormError): """The DNS packet passed to from_wire() is too short.""" class TrailingJunk(dns.exception.FormError): """The DNS packet passed to from_wire() has extra junk at the end of it.""" class UnknownHeaderField(dns.exception.DNSException): """The header field name was not recognized when converting from text into a message.""" class BadEDNS(dns.exception.FormError): """An OPT record occurred somewhere other than the additional data section.""" class BadTSIG(dns.exception.FormError): """A TSIG record occurred somewhere other than the end of the additional data section.""" class UnknownTSIGKey(dns.exception.DNSException): """A TSIG with an unknown key was received.""" class Truncated(dns.exception.DNSException): """The truncated flag is set.""" supp_kwargs = {"message"} # We do this as otherwise mypy complains about unexpected keyword argument # idna_exception def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def message(self): """As much of the message as could be processed. Returns a ``dns.message.Message``. """ return self.kwargs["message"] class NotQueryResponse(dns.exception.DNSException): """Message is not a response to a query.""" class ChainTooLong(dns.exception.DNSException): """The CNAME chain is too long.""" class AnswerForNXDOMAIN(dns.exception.DNSException): """The rcode is NXDOMAIN but an answer was found.""" class NoPreviousName(dns.exception.SyntaxError): """No previous name was known.""" class MessageSection(dns.enum.IntEnum): """Message sections""" QUESTION = 0 ANSWER = 1 AUTHORITY = 2 ADDITIONAL = 3 @classmethod def _maximum(cls): return 3 class MessageError: def __init__(self, exception: Exception, offset: int): self.exception = exception self.offset = offset DEFAULT_EDNS_PAYLOAD = 1232 MAX_CHAIN = 16 IndexKeyType = Tuple[ int, dns.name.Name, dns.rdataclass.RdataClass, dns.rdatatype.RdataType, Optional[dns.rdatatype.RdataType], Optional[dns.rdataclass.RdataClass], ] IndexType = Dict[IndexKeyType, dns.rrset.RRset] SectionType = Union[int, str, List[dns.rrset.RRset]] class Message: """A DNS message.""" _section_enum = MessageSection def __init__(self, id: Optional[int] = None): if id is None: self.id = dns.entropy.random_16() else: self.id = id self.flags = 0 self.sections: List[List[dns.rrset.RRset]] = [[], [], [], []] self.opt: Optional[dns.rrset.RRset] = None self.request_payload = 0 self.pad = 0 self.keyring: Any = None self.tsig: Optional[dns.rrset.RRset] = None self.request_mac = b"" self.xfr = False self.origin: Optional[dns.name.Name] = None self.tsig_ctx: Optional[Any] = None self.index: IndexType = {} self.errors: List[MessageError] = [] self.time = 0.0 self.wire: Optional[bytes] = None @property def question(self) -> List[dns.rrset.RRset]: """The question section.""" return self.sections[0] @question.setter def question(self, v): self.sections[0] = v @property def answer(self) -> List[dns.rrset.RRset]: """The answer section.""" return self.sections[1] @answer.setter def answer(self, v): self.sections[1] = v @property def authority(self) -> List[dns.rrset.RRset]: """The authority section.""" return self.sections[2] @authority.setter def authority(self, v): self.sections[2] = v @property def additional(self) -> List[dns.rrset.RRset]: """The additional data section.""" return self.sections[3] @additional.setter def additional(self, v): self.sections[3] = v def __repr__(self): return "" def __str__(self): return self.to_text() def to_text( self, origin: Optional[dns.name.Name] = None, relativize: bool = True, **kw: Dict[str, Any], ) -> str: """Convert the message to text. The *origin*, *relativize*, and any other keyword arguments are passed to the RRset ``to_wire()`` method. Returns a ``str``. """ s = io.StringIO() s.write("id %d\n" % self.id) s.write(f"opcode {dns.opcode.to_text(self.opcode())}\n") s.write(f"rcode {dns.rcode.to_text(self.rcode())}\n") s.write(f"flags {dns.flags.to_text(self.flags)}\n") if self.edns >= 0: s.write(f"edns {self.edns}\n") if self.ednsflags != 0: s.write(f"eflags {dns.flags.edns_to_text(self.ednsflags)}\n") s.write("payload %d\n" % self.payload) for opt in self.options: s.write(f"option {opt.to_text()}\n") for name, which in self._section_enum.__members__.items(): s.write(f";{name}\n") for rrset in self.section_from_number(which): s.write(rrset.to_text(origin, relativize, **kw)) s.write("\n") # # We strip off the final \n so the caller can print the result without # doing weird things to get around eccentricities in Python print # formatting # return s.getvalue()[:-1] def __eq__(self, other): """Two messages are equal if they have the same content in the header, question, answer, and authority sections. Returns a ``bool``. """ if not isinstance(other, Message): return False if self.id != other.id: return False if self.flags != other.flags: return False for i, section in enumerate(self.sections): other_section = other.sections[i] for n in section: if n not in other_section: return False for n in other_section: if n not in section: return False return True def __ne__(self, other): return not self.__eq__(other) def is_response(self, other: "Message") -> bool: """Is *other*, also a ``dns.message.Message``, a response to this message? Returns a ``bool``. """ if ( other.flags & dns.flags.QR == 0 or self.id != other.id or dns.opcode.from_flags(self.flags) != dns.opcode.from_flags(other.flags) ): return False if other.rcode() in { dns.rcode.FORMERR, dns.rcode.SERVFAIL, dns.rcode.NOTIMP, dns.rcode.REFUSED, }: # We don't check the question section in these cases if # the other question section is empty, even though they # still really ought to have a question section. if len(other.question) == 0: return True if dns.opcode.is_update(self.flags): # This is assuming the "sender doesn't include anything # from the update", but we don't care to check the other # case, which is that all the sections are returned and # identical. return True for n in self.question: if n not in other.question: return False for n in other.question: if n not in self.question: return False return True def section_number(self, section: List[dns.rrset.RRset]) -> int: """Return the "section number" of the specified section for use in indexing. *section* is one of the section attributes of this message. Raises ``ValueError`` if the section isn't known. Returns an ``int``. """ for i, our_section in enumerate(self.sections): if section is our_section: return self._section_enum(i) raise ValueError("unknown section") def section_from_number(self, number: int) -> List[dns.rrset.RRset]: """Return the section list associated with the specified section number. *number* is a section number `int` or the text form of a section name. Raises ``ValueError`` if the section isn't known. Returns a ``list``. """ section = self._section_enum.make(number) return self.sections[section] def find_rrset( self, section: SectionType, name: dns.name.Name, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, deleting: Optional[dns.rdataclass.RdataClass] = None, create: bool = False, force_unique: bool = False, idna_codec: Optional[dns.name.IDNACodec] = None, ) -> dns.rrset.RRset: """Find the RRset with the given attributes in the specified section. *section*, an ``int`` section number, a ``str`` section name, or one of the section attributes of this message. This specifies the the section of the message to search. For example:: my_message.find_rrset(my_message.answer, name, rdclass, rdtype) my_message.find_rrset(dns.message.ANSWER, name, rdclass, rdtype) my_message.find_rrset("ANSWER", name, rdclass, rdtype) *name*, a ``dns.name.Name`` or ``str``, the name of the RRset. *rdclass*, an ``int`` or ``str``, the class of the RRset. *rdtype*, an ``int`` or ``str``, the type of the RRset. *covers*, an ``int`` or ``str``, the covers value of the RRset. The default is ``dns.rdatatype.NONE``. *deleting*, an ``int``, ``str``, 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. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder is used. 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) elif isinstance(section, str): section_number = self._section_enum.from_text(section) section = self.section_from_number(section_number) else: section_number = self.section_number(section) if isinstance(name, str): name = dns.name.from_text(name, idna_codec=idna_codec) rdtype = dns.rdatatype.RdataType.make(rdtype) rdclass = dns.rdataclass.RdataClass.make(rdclass) covers = dns.rdatatype.RdataType.make(covers) if deleting is not None: deleting = dns.rdataclass.RdataClass.make(deleting) key = (section_number, name, rdclass, rdtype, covers, deleting) if not force_unique: if self.index is not None: rrset = self.index.get(key) if rrset is not None: return rrset else: for rrset in section: if rrset.full_match(name, rdclass, rdtype, covers, deleting): return rrset if not create: raise KeyError rrset = dns.rrset.RRset(name, rdclass, rdtype, covers, deleting) section.append(rrset) if self.index is not None: self.index[key] = rrset return rrset def get_rrset( self, section: SectionType, name: dns.name.Name, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, deleting: Optional[dns.rdataclass.RdataClass] = None, create: bool = False, force_unique: bool = False, idna_codec: Optional[dns.name.IDNACodec] = None, ) -> Optional[dns.rrset.RRset]: """Get the RRset with the given attributes in the specified section. If the RRset is not found, None is returned. *section*, an ``int`` section number, a ``str`` section name, or one of the section attributes of this message. This specifies the the section of the message to search. For example:: my_message.get_rrset(my_message.answer, name, rdclass, rdtype) my_message.get_rrset(dns.message.ANSWER, name, rdclass, rdtype) my_message.get_rrset("ANSWER", name, rdclass, rdtype) *name*, a ``dns.name.Name`` or ``str``, the name of the RRset. *rdclass*, an ``int`` or ``str``, the class of the RRset. *rdtype*, an ``int`` or ``str``, the type of the RRset. *covers*, an ``int`` or ``str``, the covers value of the RRset. The default is ``dns.rdatatype.NONE``. *deleting*, an ``int``, ``str``, 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. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder is used. Returns a ``dns.rrset.RRset object`` or ``None``. """ try: rrset = self.find_rrset( section, name, rdclass, rdtype, covers, deleting, create, force_unique, idna_codec, ) except KeyError: rrset = None return rrset def section_count(self, section: SectionType) -> int: """Returns the number of records in the specified section. *section*, an ``int`` section number, a ``str`` section name, or one of the section attributes of this message. This specifies the the section of the message to count. For example:: my_message.section_count(my_message.answer) my_message.section_count(dns.message.ANSWER) my_message.section_count("ANSWER") """ if isinstance(section, int): section_number = section section = self.section_from_number(section_number) elif isinstance(section, str): section_number = self._section_enum.from_text(section) section = self.section_from_number(section_number) else: section_number = self.section_number(section) count = sum(max(1, len(rrs)) for rrs in section) if section_number == MessageSection.ADDITIONAL: if self.opt is not None: count += 1 if self.tsig is not None: count += 1 return count def _compute_opt_reserve(self) -> int: """Compute the size required for the OPT RR, padding excluded""" if not self.opt: return 0 # 1 byte for the root name, 10 for the standard RR fields size = 11 # This would be more efficient if options had a size() method, but we won't # worry about that for now. We also don't worry if there is an existing padding # option, as it is unlikely and probably harmless, as the worst case is that we # may add another, and this seems to be legal. for option in self.opt[0].options: wire = option.to_wire() # We add 4 here to account for the option type and length size += len(wire) + 4 if self.pad: # Padding will be added, so again add the option type and length. size += 4 return size def _compute_tsig_reserve(self) -> int: """Compute the size required for the TSIG RR""" # This would be more efficient if TSIGs had a size method, but we won't # worry about for now. Also, we can't really cope with the potential # compressibility of the TSIG owner name, so we estimate with the uncompressed # size. We will disable compression when TSIG and padding are both is active # so that the padding comes out right. if not self.tsig: return 0 f = io.BytesIO() self.tsig.to_wire(f) return len(f.getvalue()) def to_wire( self, origin: Optional[dns.name.Name] = None, max_size: int = 0, multi: bool = False, tsig_ctx: Optional[Any] = None, prepend_length: bool = False, prefer_truncation: bool = False, **kw: Dict[str, Any], ) -> bytes: """Return a string containing the message in DNS compressed wire format. Additional keyword arguments are passed to the RRset ``to_wire()`` method. *origin*, a ``dns.name.Name`` or ``None``, the origin to be appended to any relative names. If ``None``, and the message has an origin attribute that is not ``None``, then it will be used. *max_size*, an ``int``, the maximum size of the wire format output; default is 0, which means "the message's request payload, if nonzero, or 65535". *multi*, a ``bool``, should be set to ``True`` if this message is part of a multiple message sequence. *tsig_ctx*, a ``dns.tsig.HMACTSig`` or ``dns.tsig.GSSTSig`` object, the ongoing TSIG context, used when signing zone transfers. *prepend_length*, a ``bool``, should be set to ``True`` if the caller wants the message length prepended to the message itself. This is useful for messages sent over TCP, TLS (DoT), or QUIC (DoQ). *prefer_truncation*, a ``bool``, should be set to ``True`` if the caller wants the message to be truncated if it would otherwise exceed the maximum length. If the truncation occurs before the additional section, the TC bit will be set. Raises ``dns.exception.TooBig`` if *max_size* was exceeded. Returns a ``bytes``. """ if origin is None and self.origin is not None: origin = self.origin if max_size == 0: if self.request_payload != 0: max_size = self.request_payload else: max_size = 65535 if max_size < 512: max_size = 512 elif max_size > 65535: max_size = 65535 r = dns.renderer.Renderer(self.id, self.flags, max_size, origin) opt_reserve = self._compute_opt_reserve() r.reserve(opt_reserve) tsig_reserve = self._compute_tsig_reserve() r.reserve(tsig_reserve) try: for rrset in self.question: r.add_question(rrset.name, rrset.rdtype, rrset.rdclass) for rrset in self.answer: r.add_rrset(dns.renderer.ANSWER, rrset, **kw) for rrset in self.authority: r.add_rrset(dns.renderer.AUTHORITY, rrset, **kw) for rrset in self.additional: r.add_rrset(dns.renderer.ADDITIONAL, rrset, **kw) except dns.exception.TooBig: if prefer_truncation: if r.section < dns.renderer.ADDITIONAL: r.flags |= dns.flags.TC else: raise r.release_reserved() if self.opt is not None: r.add_opt(self.opt, self.pad, opt_reserve, tsig_reserve) r.write_header() if self.tsig is not None: (new_tsig, ctx) = dns.tsig.sign( r.get_wire(), self.keyring, self.tsig[0], int(time.time()), self.request_mac, tsig_ctx, multi, ) self.tsig.clear() self.tsig.add(new_tsig) r.add_rrset(dns.renderer.ADDITIONAL, self.tsig) r.write_header() if multi: self.tsig_ctx = ctx wire = r.get_wire() self.wire = wire if prepend_length: wire = len(wire).to_bytes(2, "big") + wire return wire @staticmethod def _make_tsig( keyname, algorithm, time_signed, fudge, mac, original_id, error, other ): tsig = dns.rdtypes.ANY.TSIG.TSIG( dns.rdataclass.ANY, dns.rdatatype.TSIG, algorithm, time_signed, fudge, mac, original_id, error, other, ) return dns.rrset.from_rdata(keyname, 0, tsig) def use_tsig( self, keyring: Any, keyname: Optional[Union[dns.name.Name, str]] = None, fudge: int = 300, original_id: Optional[int] = None, tsig_error: int = 0, other_data: bytes = b"", algorithm: Union[dns.name.Name, str] = dns.tsig.default_algorithm, ) -> None: """When sending, a TSIG signature using the specified key should be added. *key*, a ``dns.tsig.Key`` is the key to use. If a key is specified, the *keyring* and *algorithm* fields are not used. *keyring*, a ``dict``, ``callable`` or ``dns.tsig.Key``, is either the TSIG keyring or key to use. The format of a keyring dict is a mapping from TSIG key name, as ``dns.name.Name`` to ``dns.tsig.Key`` or a TSIG secret, a ``bytes``. If a ``dict`` *keyring* is specified but a *keyname* is not, the key used will be the first key in the *keyring*. Note that the order of keys in a dictionary is not defined, so applications should supply a keyname when a ``dict`` keyring is used, unless they know the keyring contains only one key. If a ``callable`` keyring is specified, the callable will be called with the message and the keyname, and is expected to return a key. *keyname*, a ``dns.name.Name``, ``str`` or ``None``, the name of this TSIG key to use; defaults to ``None``. If *keyring* is a ``dict``, the key must be defined in it. If *keyring* is a ``dns.tsig.Key``, this is ignored. *fudge*, an ``int``, the TSIG time fudge. *original_id*, an ``int``, the TSIG original id. If ``None``, the message's id is used. *tsig_error*, an ``int``, the TSIG error code. *other_data*, a ``bytes``, the TSIG other data. *algorithm*, a ``dns.name.Name`` or ``str``, the TSIG algorithm to use. This is only used if *keyring* is a ``dict``, and the key entry is a ``bytes``. """ if isinstance(keyring, dns.tsig.Key): key = keyring keyname = key.name elif callable(keyring): key = keyring(self, keyname) else: if isinstance(keyname, str): keyname = dns.name.from_text(keyname) if keyname is None: keyname = next(iter(keyring)) key = keyring[keyname] if isinstance(key, bytes): key = dns.tsig.Key(keyname, key, algorithm) self.keyring = key if original_id is None: original_id = self.id self.tsig = self._make_tsig( keyname, self.keyring.algorithm, 0, fudge, b"\x00" * dns.tsig.mac_sizes[self.keyring.algorithm], original_id, tsig_error, other_data, ) @property def keyname(self) -> Optional[dns.name.Name]: if self.tsig: return self.tsig.name else: return None @property def keyalgorithm(self) -> Optional[dns.name.Name]: if self.tsig: return self.tsig[0].algorithm else: return None @property def mac(self) -> Optional[bytes]: if self.tsig: return self.tsig[0].mac else: return None @property def tsig_error(self) -> Optional[int]: if self.tsig: return self.tsig[0].error else: return None @property def had_tsig(self) -> bool: return bool(self.tsig) @staticmethod def _make_opt(flags=0, payload=DEFAULT_EDNS_PAYLOAD, options=None): opt = dns.rdtypes.ANY.OPT.OPT(payload, dns.rdatatype.OPT, options or ()) return dns.rrset.from_rdata(dns.name.root, int(flags), opt) def use_edns( self, edns: Optional[Union[int, bool]] = 0, ednsflags: int = 0, payload: int = DEFAULT_EDNS_PAYLOAD, request_payload: Optional[int] = None, options: Optional[List[dns.edns.Option]] = None, pad: int = 0, ) -> None: """Configure EDNS behavior. *edns*, an ``int``, is the EDNS level to use. Specifying ``None``, ``False``, or ``-1`` means "do not use EDNS", and in this case the other parameters are ignored. Specifying ``True`` is equivalent to specifying 0, i.e. "use EDNS0". *ednsflags*, an ``int``, the EDNS flag values. *payload*, an ``int``, is the EDNS sender's payload field, which is the maximum size of UDP datagram the sender can handle. I.e. how big a response to this message can be. *request_payload*, an ``int``, is the EDNS payload size to use when sending this message. If not specified, defaults to the value of *payload*. *options*, a list of ``dns.edns.Option`` objects or ``None``, the EDNS options. *pad*, a non-negative ``int``. If 0, the default, do not pad; otherwise add padding bytes to make the message size a multiple of *pad*. Note that if padding is non-zero, an EDNS PADDING option will always be added to the message. """ if edns is None or edns is False: edns = -1 elif edns is True: edns = 0 if edns < 0: self.opt = None self.request_payload = 0 else: # make sure the EDNS version in ednsflags agrees with edns ednsflags &= 0xFF00FFFF ednsflags |= edns << 16 if options is None: options = [] self.opt = self._make_opt(ednsflags, payload, options) if request_payload is None: request_payload = payload self.request_payload = request_payload if pad < 0: raise ValueError("pad must be non-negative") self.pad = pad @property def edns(self) -> int: if self.opt: return (self.ednsflags & 0xFF0000) >> 16 else: return -1 @property def ednsflags(self) -> int: if self.opt: return self.opt.ttl else: return 0 @ednsflags.setter def ednsflags(self, v): if self.opt: self.opt.ttl = v elif v: self.opt = self._make_opt(v) @property def payload(self) -> int: if self.opt: return self.opt[0].payload else: return 0 @property def options(self) -> Tuple: if self.opt: return self.opt[0].options else: return () def want_dnssec(self, wanted: bool = True) -> None: """Enable or disable 'DNSSEC desired' flag in requests. *wanted*, a ``bool``. If ``True``, then DNSSEC data is desired in the response, EDNS is enabled if required, and then the DO bit is set. If ``False``, the DO bit is cleared if EDNS is enabled. """ if wanted: self.ednsflags |= dns.flags.DO elif self.opt: self.ednsflags &= ~int(dns.flags.DO) def rcode(self) -> dns.rcode.Rcode: """Return the rcode. Returns a ``dns.rcode.Rcode``. """ return dns.rcode.from_flags(int(self.flags), int(self.ednsflags)) def set_rcode(self, rcode: dns.rcode.Rcode) -> None: """Set the rcode. *rcode*, a ``dns.rcode.Rcode``, is the rcode to set. """ (value, evalue) = dns.rcode.to_flags(rcode) self.flags &= 0xFFF0 self.flags |= value self.ednsflags &= 0x00FFFFFF self.ednsflags |= evalue def opcode(self) -> dns.opcode.Opcode: """Return the opcode. Returns a ``dns.opcode.Opcode``. """ return dns.opcode.from_flags(int(self.flags)) def set_opcode(self, opcode: dns.opcode.Opcode) -> None: """Set the opcode. *opcode*, a ``dns.opcode.Opcode``, is the opcode to set. """ self.flags &= 0x87FF self.flags |= dns.opcode.to_flags(opcode) def get_options(self, otype: dns.edns.OptionType) -> List[dns.edns.Option]: """Return the list of options of the specified type.""" return [option for option in self.options if option.otype == otype] def extended_errors(self) -> List[dns.edns.EDEOption]: """Return the list of Extended DNS Error (EDE) options in the message""" return cast(List[dns.edns.EDEOption], self.get_options(dns.edns.OptionType.EDE)) def _get_one_rr_per_rrset(self, value): # What the caller picked is fine. return value # pylint: disable=unused-argument def _parse_rr_header(self, section, name, rdclass, rdtype): return (rdclass, rdtype, None, False) # pylint: enable=unused-argument def _parse_special_rr_header(self, section, count, position, name, rdclass, rdtype): if rdtype == dns.rdatatype.OPT: if ( section != MessageSection.ADDITIONAL or self.opt or name != dns.name.root ): raise BadEDNS elif rdtype == dns.rdatatype.TSIG: if ( section != MessageSection.ADDITIONAL or rdclass != dns.rdatatype.ANY or position != count - 1 ): raise BadTSIG return (rdclass, rdtype, None, False) class ChainingResult: """The result of a call to dns.message.QueryMessage.resolve_chaining(). The ``answer`` attribute is the answer RRSet, or ``None`` if it doesn't exist. The ``canonical_name`` attribute is the canonical name after all chaining has been applied (this is the same name as ``rrset.name`` in cases where rrset is not ``None``). The ``minimum_ttl`` attribute is the minimum TTL, i.e. the TTL to use if caching the data. It is the smallest of all the CNAME TTLs and either the answer TTL if it exists or the SOA TTL and SOA minimum values for negative answers. The ``cnames`` attribute is a list of all the CNAME RRSets followed to get to the canonical name. """ def __init__( self, canonical_name: dns.name.Name, answer: Optional[dns.rrset.RRset], minimum_ttl: int, cnames: List[dns.rrset.RRset], ): self.canonical_name = canonical_name self.answer = answer self.minimum_ttl = minimum_ttl self.cnames = cnames class QueryMessage(Message): def resolve_chaining(self) -> ChainingResult: """Follow the CNAME chain in the response to determine the answer RRset. Raises ``dns.message.NotQueryResponse`` if the message is not a response. Raises ``dns.message.ChainTooLong`` if the CNAME chain is too long. Raises ``dns.message.AnswerForNXDOMAIN`` if the rcode is NXDOMAIN but an answer was found. Raises ``dns.exception.FormError`` if the question count is not 1. Returns a ChainingResult object. """ if self.flags & dns.flags.QR == 0: raise NotQueryResponse if len(self.question) != 1: raise dns.exception.FormError question = self.question[0] qname = question.name min_ttl = dns.ttl.MAX_TTL answer = None count = 0 cnames = [] while count < MAX_CHAIN: try: answer = self.find_rrset( self.answer, qname, question.rdclass, question.rdtype ) min_ttl = min(min_ttl, answer.ttl) break except KeyError: if question.rdtype != dns.rdatatype.CNAME: try: crrset = self.find_rrset( self.answer, qname, question.rdclass, dns.rdatatype.CNAME ) cnames.append(crrset) min_ttl = min(min_ttl, crrset.ttl) for rd in crrset: qname = rd.target break count += 1 continue except KeyError: # Exit the chaining loop break else: # Exit the chaining loop break if count >= MAX_CHAIN: raise ChainTooLong if self.rcode() == dns.rcode.NXDOMAIN and answer is not None: raise AnswerForNXDOMAIN if answer is None: # Further minimize the TTL with NCACHE. auname = qname while True: # Look for an SOA RR whose owner name is a superdomain # of qname. try: srrset = self.find_rrset( self.authority, auname, question.rdclass, dns.rdatatype.SOA ) min_ttl = min(min_ttl, srrset.ttl, srrset[0].minimum) break except KeyError: try: auname = auname.parent() except dns.name.NoParent: break return ChainingResult(qname, answer, min_ttl, cnames) def canonical_name(self) -> dns.name.Name: """Return the canonical name of the first name in the question section. Raises ``dns.message.NotQueryResponse`` if the message is not a response. Raises ``dns.message.ChainTooLong`` if the CNAME chain is too long. Raises ``dns.message.AnswerForNXDOMAIN`` if the rcode is NXDOMAIN but an answer was found. Raises ``dns.exception.FormError`` if the question count is not 1. """ return self.resolve_chaining().canonical_name def _maybe_import_update(): # We avoid circular imports by doing this here. We do it in another # function as doing it in _message_factory_from_opcode() makes "dns" # a local symbol, and the first line fails :) # pylint: disable=redefined-outer-name,import-outside-toplevel,unused-import import dns.update # noqa: F401 def _message_factory_from_opcode(opcode): if opcode == dns.opcode.QUERY: return QueryMessage elif opcode == dns.opcode.UPDATE: _maybe_import_update() return dns.update.UpdateMessage else: return Message class _WireReader: """Wire format reader. parser: the binary parser message: The message object being built initialize_message: Callback to set message parsing options question_only: Are we only reading the question? one_rr_per_rrset: Put each RR into its own RRset? keyring: TSIG keyring ignore_trailing: Ignore trailing junk at end of request? multi: Is this message part of a multi-message sequence? DNS dynamic updates. continue_on_error: try to extract as much information as possible from the message, accumulating MessageErrors in the *errors* attribute instead of raising them. """ def __init__( self, wire, initialize_message, question_only=False, one_rr_per_rrset=False, ignore_trailing=False, keyring=None, multi=False, continue_on_error=False, ): self.parser = dns.wire.Parser(wire) self.message = None self.initialize_message = initialize_message self.question_only = question_only self.one_rr_per_rrset = one_rr_per_rrset self.ignore_trailing = ignore_trailing self.keyring = keyring self.multi = multi self.continue_on_error = continue_on_error self.errors = [] def _get_question(self, section_number, qcount): """Read the next *qcount* records from the wire data and add them to the question section. """ assert self.message is not None section = self.message.sections[section_number] for _ in range(qcount): qname = self.parser.get_name(self.message.origin) (rdtype, rdclass) = self.parser.get_struct("!HH") (rdclass, rdtype, _, _) = self.message._parse_rr_header( section_number, qname, rdclass, rdtype ) self.message.find_rrset( section, qname, rdclass, rdtype, create=True, force_unique=True ) def _add_error(self, e): self.errors.append(MessageError(e, self.parser.current)) def _get_section(self, section_number, count): """Read the next I{count} records from the wire data and add them to the specified section. section_number: the section of the message to which to add records count: the number of records to read """ assert self.message is not None section = self.message.sections[section_number] force_unique = self.one_rr_per_rrset for i in range(count): rr_start = self.parser.current absolute_name = self.parser.get_name() if self.message.origin is not None: name = absolute_name.relativize(self.message.origin) else: name = absolute_name (rdtype, rdclass, ttl, rdlen) = self.parser.get_struct("!HHIH") if rdtype in (dns.rdatatype.OPT, dns.rdatatype.TSIG): ( rdclass, rdtype, deleting, empty, ) = self.message._parse_special_rr_header( section_number, count, i, name, rdclass, rdtype ) else: (rdclass, rdtype, deleting, empty) = self.message._parse_rr_header( section_number, name, rdclass, rdtype ) rdata_start = self.parser.current try: if empty: if rdlen > 0: raise dns.exception.FormError rd = None covers = dns.rdatatype.NONE else: with self.parser.restrict_to(rdlen): rd = dns.rdata.from_wire_parser( rdclass, rdtype, self.parser, self.message.origin ) covers = rd.covers() if self.message.xfr and rdtype == dns.rdatatype.SOA: force_unique = True if rdtype == dns.rdatatype.OPT: self.message.opt = dns.rrset.from_rdata(name, ttl, rd) elif rdtype == dns.rdatatype.TSIG: if self.keyring is None or self.keyring is True: raise UnknownTSIGKey("got signed message without keyring") elif isinstance(self.keyring, dict): key = self.keyring.get(absolute_name) if isinstance(key, bytes): key = dns.tsig.Key(absolute_name, key, rd.algorithm) elif callable(self.keyring): key = self.keyring(self.message, absolute_name) else: key = self.keyring if key is None: raise UnknownTSIGKey(f"key '{name}' unknown") if key: self.message.keyring = key self.message.tsig_ctx = dns.tsig.validate( self.parser.wire, key, absolute_name, rd, int(time.time()), self.message.request_mac, rr_start, self.message.tsig_ctx, self.multi, ) self.message.tsig = dns.rrset.from_rdata(absolute_name, 0, rd) else: rrset = self.message.find_rrset( section, name, rdclass, rdtype, covers, deleting, True, force_unique, ) if rd is not None: if ttl > 0x7FFFFFFF: ttl = 0 rrset.add(rd, ttl) except Exception as e: if self.continue_on_error: self._add_error(e) self.parser.seek(rdata_start + rdlen) else: raise def read(self): """Read a wire format DNS message and build a dns.message.Message object.""" if self.parser.remaining() < 12: raise ShortHeader (id, flags, qcount, ancount, aucount, adcount) = self.parser.get_struct( "!HHHHHH" ) factory = _message_factory_from_opcode(dns.opcode.from_flags(flags)) self.message = factory(id=id) self.message.flags = dns.flags.Flag(flags) self.message.wire = self.parser.wire self.initialize_message(self.message) self.one_rr_per_rrset = self.message._get_one_rr_per_rrset( self.one_rr_per_rrset ) try: self._get_question(MessageSection.QUESTION, qcount) if self.question_only: return self.message self._get_section(MessageSection.ANSWER, ancount) self._get_section(MessageSection.AUTHORITY, aucount) self._get_section(MessageSection.ADDITIONAL, adcount) if not self.ignore_trailing and self.parser.remaining() != 0: raise TrailingJunk if self.multi and self.message.tsig_ctx and not self.message.had_tsig: self.message.tsig_ctx.update(self.parser.wire) except Exception as e: if self.continue_on_error: self._add_error(e) else: raise return self.message def from_wire( wire: bytes, keyring: Optional[Any] = None, request_mac: Optional[bytes] = b"", xfr: bool = False, origin: Optional[dns.name.Name] = None, tsig_ctx: Optional[Union[dns.tsig.HMACTSig, dns.tsig.GSSTSig]] = None, multi: bool = False, question_only: bool = False, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, raise_on_truncation: bool = False, continue_on_error: bool = False, ) -> Message: """Convert a DNS wire format message into a message object. *keyring*, a ``dns.tsig.Key``, ``dict``, ``bool``, or ``None``, the key or keyring to use if the message is signed. If ``None`` or ``True``, then trying to decode a message with a TSIG will fail as it cannot be validated. If ``False``, then TSIG validation is disabled. *request_mac*, a ``bytes`` or ``None``. If the message is a response to a TSIG-signed request, *request_mac* should be set to the MAC of that request. *xfr*, a ``bool``, should be set to ``True`` if this message is part of a zone transfer. *origin*, a ``dns.name.Name`` or ``None``. If the message is part of a zone transfer, *origin* should be the origin name of the zone. If not ``None``, names will be relativized to the origin. *tsig_ctx*, a ``dns.tsig.HMACTSig`` or ``dns.tsig.GSSTSig`` object, the ongoing TSIG context, used when validating zone transfers. *multi*, a ``bool``, should be set to ``True`` if this message is part of a multiple message sequence. *question_only*, a ``bool``. If ``True``, read only up to the end of the question section. *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 message. *raise_on_truncation*, a ``bool``. If ``True``, raise an exception if the TC bit is set. *continue_on_error*, a ``bool``. If ``True``, try to continue parsing even if errors occur. Erroneous rdata will be ignored. Errors will be accumulated as a list of MessageError objects in the message's ``errors`` attribute. This option is recommended only for DNS analysis tools, or for use in a server as part of an error handling path. The default is ``False``. Raises ``dns.message.ShortHeader`` if the message is less than 12 octets long. Raises ``dns.message.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. Raises ``dns.message.Truncated`` if the TC flag is set and *raise_on_truncation* is ``True``. Returns a ``dns.message.Message``. """ # We permit None for request_mac solely for backwards compatibility if request_mac is None: request_mac = b"" def initialize_message(message): message.request_mac = request_mac message.xfr = xfr message.origin = origin message.tsig_ctx = tsig_ctx reader = _WireReader( wire, initialize_message, question_only, one_rr_per_rrset, ignore_trailing, keyring, multi, continue_on_error, ) try: m = reader.read() except dns.exception.FormError: if ( reader.message and (reader.message.flags & dns.flags.TC) and raise_on_truncation ): raise Truncated(message=reader.message) else: raise # Reading a truncated message might not have any errors, so we # have to do this check here too. if m.flags & dns.flags.TC and raise_on_truncation: raise Truncated(message=m) if continue_on_error: m.errors = reader.errors return m class _TextReader: """Text format reader. tok: the tokenizer. message: The message object being built. DNS dynamic updates. last_name: The most recently read name when building a message object. one_rr_per_rrset: Put each RR into its own RRset? origin: The origin for relative names relativize: relativize names? relativize_to: the origin to relativize to. """ def __init__( self, text, idna_codec, one_rr_per_rrset=False, origin=None, relativize=True, relativize_to=None, ): self.message = None self.tok = dns.tokenizer.Tokenizer(text, idna_codec=idna_codec) self.last_name = None self.one_rr_per_rrset = one_rr_per_rrset self.origin = origin self.relativize = relativize self.relativize_to = relativize_to self.id = None self.edns = -1 self.ednsflags = 0 self.payload = DEFAULT_EDNS_PAYLOAD self.rcode = None self.opcode = dns.opcode.QUERY self.flags = 0 def _header_line(self, _): """Process one line from the text format header section.""" token = self.tok.get() what = token.value if what == "id": self.id = self.tok.get_int() elif what == "flags": while True: token = self.tok.get() if not token.is_identifier(): self.tok.unget(token) break self.flags = self.flags | dns.flags.from_text(token.value) elif what == "edns": self.edns = self.tok.get_int() self.ednsflags = self.ednsflags | (self.edns << 16) elif what == "eflags": if self.edns < 0: self.edns = 0 while True: token = self.tok.get() if not token.is_identifier(): self.tok.unget(token) break self.ednsflags = self.ednsflags | dns.flags.edns_from_text(token.value) elif what == "payload": self.payload = self.tok.get_int() if self.edns < 0: self.edns = 0 elif what == "opcode": text = self.tok.get_string() self.opcode = dns.opcode.from_text(text) self.flags = self.flags | dns.opcode.to_flags(self.opcode) elif what == "rcode": text = self.tok.get_string() self.rcode = dns.rcode.from_text(text) else: raise UnknownHeaderField self.tok.get_eol() def _question_line(self, section_number): """Process one line from the text format question section.""" section = self.message.sections[section_number] token = self.tok.get(want_leading=True) if not token.is_whitespace(): self.last_name = self.tok.as_name( token, self.message.origin, self.relativize, self.relativize_to ) name = self.last_name if name is None: raise NoPreviousName token = self.tok.get() if not token.is_identifier(): raise dns.exception.SyntaxError # Class try: rdclass = dns.rdataclass.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise dns.exception.SyntaxError except dns.exception.SyntaxError: raise dns.exception.SyntaxError except Exception: rdclass = dns.rdataclass.IN # Type rdtype = dns.rdatatype.from_text(token.value) (rdclass, rdtype, _, _) = self.message._parse_rr_header( section_number, name, rdclass, rdtype ) self.message.find_rrset( section, name, rdclass, rdtype, create=True, force_unique=True ) self.tok.get_eol() def _rr_line(self, section_number): """Process one line from the text format answer, authority, or additional data sections. """ section = self.message.sections[section_number] # Name token = self.tok.get(want_leading=True) if not token.is_whitespace(): self.last_name = self.tok.as_name( token, self.message.origin, self.relativize, self.relativize_to ) name = self.last_name if name is None: raise NoPreviousName token = self.tok.get() if not token.is_identifier(): raise dns.exception.SyntaxError # TTL try: ttl = int(token.value, 0) token = self.tok.get() if not token.is_identifier(): raise dns.exception.SyntaxError except dns.exception.SyntaxError: raise dns.exception.SyntaxError except Exception: ttl = 0 # Class try: rdclass = dns.rdataclass.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise dns.exception.SyntaxError except dns.exception.SyntaxError: raise dns.exception.SyntaxError except Exception: rdclass = dns.rdataclass.IN # Type rdtype = dns.rdatatype.from_text(token.value) (rdclass, rdtype, deleting, empty) = self.message._parse_rr_header( section_number, name, rdclass, rdtype ) token = self.tok.get() if empty and not token.is_eol_or_eof(): raise dns.exception.SyntaxError if not empty and token.is_eol_or_eof(): raise dns.exception.UnexpectedEnd if not token.is_eol_or_eof(): self.tok.unget(token) rd = dns.rdata.from_text( rdclass, rdtype, self.tok, self.message.origin, self.relativize, self.relativize_to, ) covers = rd.covers() else: rd = None covers = dns.rdatatype.NONE rrset = self.message.find_rrset( section, name, rdclass, rdtype, covers, deleting, True, self.one_rr_per_rrset, ) if rd is not None: rrset.add(rd, ttl) def _make_message(self): factory = _message_factory_from_opcode(self.opcode) message = factory(id=self.id) message.flags = self.flags if self.edns >= 0: message.use_edns(self.edns, self.ednsflags, self.payload) if self.rcode: message.set_rcode(self.rcode) if self.origin: message.origin = self.origin return message def read(self): """Read a text format DNS message and build a dns.message.Message object.""" line_method = self._header_line section_number = None while 1: token = self.tok.get(True, True) if token.is_eol_or_eof(): break if token.is_comment(): u = token.value.upper() if u == "HEADER": line_method = self._header_line if self.message: message = self.message else: # If we don't have a message, create one with the current # opcode, so that we know which section names to parse. message = self._make_message() try: section_number = message._section_enum.from_text(u) # We found a section name. If we don't have a message, # use the one we just created. if not self.message: self.message = message self.one_rr_per_rrset = message._get_one_rr_per_rrset( self.one_rr_per_rrset ) if section_number == MessageSection.QUESTION: line_method = self._question_line else: line_method = self._rr_line except Exception: # It's just a comment. pass self.tok.get_eol() continue self.tok.unget(token) line_method(section_number) if not self.message: self.message = self._make_message() return self.message def from_text( text: str, idna_codec: Optional[dns.name.IDNACodec] = None, one_rr_per_rrset: bool = False, origin: Optional[dns.name.Name] = None, relativize: bool = True, relativize_to: Optional[dns.name.Name] = None, ) -> Message: """Convert the text format message into a message object. The reader stops after reading the first blank line in the input to facilitate reading multiple messages from a single file with ``dns.message.from_file()``. *text*, a ``str``, the text format message. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder is used. *one_rr_per_rrset*, a ``bool``. If ``True``, then each RR is put into its own rrset. The default is ``False``. *origin*, a ``dns.name.Name`` (or ``None``), the origin to use for relative names. *relativize*, a ``bool``. If true, name will be relativized. *relativize_to*, a ``dns.name.Name`` (or ``None``), the origin to use when relativizing names. If not set, the *origin* value will be used. Raises ``dns.message.UnknownHeaderField`` if a header is unknown. 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 # since it's an implementation detail. The official file # interface is from_file(). reader = _TextReader( text, idna_codec, one_rr_per_rrset, origin, relativize, relativize_to ) return reader.read() def from_file( f: Any, idna_codec: Optional[dns.name.IDNACodec] = None, one_rr_per_rrset: bool = False, ) -> Message: """Read the next text format message from the specified file. Message blocks are separated by a single blank line. *f*, a ``file`` or ``str``. If *f* is text, it is treated as the pathname of a file to open. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder is used. *one_rr_per_rrset*, a ``bool``. If ``True``, then each RR is put into its own rrset. The default is ``False``. Raises ``dns.message.UnknownHeaderField`` if a header is unknown. Raises ``dns.exception.SyntaxError`` if the text is badly formed. Returns a ``dns.message.Message object`` """ if isinstance(f, str): cm: contextlib.AbstractContextManager = open(f) else: cm = contextlib.nullcontext(f) with cm as f: return from_text(f, idna_codec, one_rr_per_rrset) assert False # for mypy lgtm[py/unreachable-statement] def make_query( qname: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str], rdclass: Union[dns.rdataclass.RdataClass, str] = dns.rdataclass.IN, use_edns: Optional[Union[int, bool]] = None, want_dnssec: bool = False, ednsflags: Optional[int] = None, payload: Optional[int] = None, request_payload: Optional[int] = None, options: Optional[List[dns.edns.Option]] = None, idna_codec: Optional[dns.name.IDNACodec] = None, id: Optional[int] = None, flags: int = dns.flags.RD, pad: int = 0, ) -> QueryMessage: """Make a query message. The query name, type, and class may all be specified either as objects of the appropriate type, or as strings. The query will have a randomly chosen query id, and its DNS flags will be set to dns.flags.RD. qname, a ``dns.name.Name`` or ``str``, the query name. *rdtype*, an ``int`` or ``str``, the desired rdata type. *rdclass*, an ``int`` or ``str``, the desired rdata class; the default is class IN. *use_edns*, an ``int``, ``bool`` or ``None``. The EDNS level to use; the default is ``None``. If ``None``, EDNS will be enabled only if other parameters (*ednsflags*, *payload*, *request_payload*, or *options*) are set. See the description of dns.message.Message.use_edns() for the possible values for use_edns and their meanings. *want_dnssec*, a ``bool``. If ``True``, DNSSEC data is desired. *ednsflags*, an ``int``, the EDNS flag values. *payload*, an ``int``, is the EDNS sender's payload field, which is the maximum size of UDP datagram the sender can handle. I.e. how big a response to this message can be. *request_payload*, an ``int``, is the EDNS payload size to use when sending this message. If not specified, defaults to the value of *payload*. *options*, a list of ``dns.edns.Option`` objects or ``None``, the EDNS options. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder is used. *id*, an ``int`` or ``None``, the desired query id. The default is ``None``, which generates a random query id. *flags*, an ``int``, the desired query flags. The default is ``dns.flags.RD``. *pad*, a non-negative ``int``. If 0, the default, do not pad; otherwise add padding bytes to make the message size a multiple of *pad*. Note that if padding is non-zero, an EDNS PADDING option will always be added to the message. Returns a ``dns.message.QueryMessage`` """ if isinstance(qname, str): qname = dns.name.from_text(qname, idna_codec=idna_codec) rdtype = dns.rdatatype.RdataType.make(rdtype) rdclass = dns.rdataclass.RdataClass.make(rdclass) m = QueryMessage(id=id) m.flags = dns.flags.Flag(flags) m.find_rrset(m.question, qname, rdclass, rdtype, create=True, force_unique=True) # only pass keywords on to use_edns if they have been set to a # non-None value. Setting a field will turn EDNS on if it hasn't # been configured. kwargs: Dict[str, Any] = {} if ednsflags is not None: kwargs["ednsflags"] = ednsflags if payload is not None: kwargs["payload"] = payload if request_payload is not None: kwargs["request_payload"] = request_payload if options is not None: kwargs["options"] = options if kwargs and use_edns is None: use_edns = 0 kwargs["edns"] = use_edns kwargs["pad"] = pad m.use_edns(**kwargs) m.want_dnssec(want_dnssec) return m class CopyMode(enum.Enum): """ How should sections be copied when making an update response? """ NOTHING = 0 QUESTION = 1 EVERYTHING = 2 def make_response( query: Message, recursion_available: bool = False, our_payload: int = 8192, fudge: int = 300, tsig_error: int = 0, pad: Optional[int] = None, copy_mode: Optional[CopyMode] = None, ) -> Message: """Make a message which is a response for the specified query. The message returned is really a response skeleton; it has all of the infrastructure required of a response, but none of the content. Response section(s) which are copied are shallow copies of the matching section(s) in the query, so the query's RRsets should not be changed. *query*, a ``dns.message.Message``, the query to respond to. *recursion_available*, a ``bool``, should RA be set in the response? *our_payload*, an ``int``, the payload size to advertise in EDNS responses. *fudge*, an ``int``, the TSIG time fudge. *tsig_error*, an ``int``, the TSIG error. *pad*, a non-negative ``int`` or ``None``. If 0, the default, do not pad; otherwise if not ``None`` add padding bytes to make the message size a multiple of *pad*. Note that if padding is non-zero, an EDNS PADDING option will always be added to the message. If ``None``, add padding following RFC 8467, namely if the request is padded, pad the response to 468 otherwise do not pad. *copy_mode*, a ``dns.message.CopyMode`` or ``None``, determines how sections are copied. The default, ``None`` copies sections according to the default for the message's opcode, which is currently ``dns.message.CopyMode.QUESTION`` for all opcodes. ``dns.message.CopyMode.QUESTION`` copies only the question section. ``dns.message.CopyMode.EVERYTHING`` copies all sections other than OPT or TSIG records, which are created appropriately if needed. ``dns.message.CopyMode.NOTHING`` copies no sections; note that this mode is for server testing purposes and is otherwise not recommended for use. In particular, ``dns.message.is_response()`` will be ``False`` if you create a response this way and the rcode is not ``FORMERR``, ``SERVFAIL``, ``NOTIMP``, or ``REFUSED``. Returns a ``dns.message.Message`` object whose specific class is appropriate for the query. For example, if query is a ``dns.update.UpdateMessage``, the response will be one too. """ if query.flags & dns.flags.QR: raise dns.exception.FormError("specified query message is not a query") opcode = query.opcode() factory = _message_factory_from_opcode(opcode) response = factory(id=query.id) response.flags = dns.flags.QR | (query.flags & dns.flags.RD) if recursion_available: response.flags |= dns.flags.RA response.set_opcode(opcode) if copy_mode is None: copy_mode = CopyMode.QUESTION if copy_mode != CopyMode.NOTHING: response.question = list(query.question) if copy_mode == CopyMode.EVERYTHING: response.answer = list(query.answer) response.authority = list(query.authority) response.additional = list(query.additional) if query.edns >= 0: if pad is None: # Set response padding per RFC 8467 pad = 0 for option in query.options: if option.otype == dns.edns.OptionType.PADDING: pad = 468 response.use_edns(0, 0, our_payload, query.payload, pad=pad) if query.had_tsig: response.use_tsig( query.keyring, query.keyname, fudge, None, tsig_error, b"", query.keyalgorithm, ) response.request_mac = query.mac return response ### BEGIN generated MessageSection constants QUESTION = MessageSection.QUESTION ANSWER = MessageSection.ANSWER AUTHORITY = MessageSection.AUTHORITY ADDITIONAL = MessageSection.ADDITIONAL ### END generated MessageSection constants dnspython-2.7.0/dns/name.py0000644000000000000000000012343213615410400012551 0ustar00# 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 # 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. """DNS Names. """ import copy import encodings.idna # type: ignore import functools import struct from typing import Any, Callable, Dict, Iterable, Optional, Tuple, Union import dns._features import dns.enum import dns.exception import dns.immutable import dns.wire if dns._features.have("idna"): import idna # type: ignore have_idna_2008 = True else: # pragma: no cover have_idna_2008 = False CompressType = Dict["Name", int] class NameRelation(dns.enum.IntEnum): """Name relation result from fullcompare().""" # This is an IntEnum for backwards compatibility in case anyone # has hardwired the constants. #: The compared names have no relationship to each other. NONE = 0 #: the first name is a superdomain of the second. SUPERDOMAIN = 1 #: The first name is a subdomain of the second. SUBDOMAIN = 2 #: The compared names are equal. EQUAL = 3 #: The compared names have a common ancestor. COMMONANCESTOR = 4 @classmethod def _maximum(cls): return cls.COMMONANCESTOR # pragma: no cover @classmethod def _short_name(cls): return cls.__name__ # pragma: no cover # Backwards compatibility NAMERELN_NONE = NameRelation.NONE NAMERELN_SUPERDOMAIN = NameRelation.SUPERDOMAIN NAMERELN_SUBDOMAIN = NameRelation.SUBDOMAIN NAMERELN_EQUAL = NameRelation.EQUAL NAMERELN_COMMONANCESTOR = NameRelation.COMMONANCESTOR class EmptyLabel(dns.exception.SyntaxError): """A DNS label is empty.""" class BadEscape(dns.exception.SyntaxError): """An escaped code in a text format of DNS name is invalid.""" class BadPointer(dns.exception.FormError): """A DNS compression pointer points forward instead of backward.""" class BadLabelType(dns.exception.FormError): """The label type in DNS name wire format is unknown.""" class NeedAbsoluteNameOrOrigin(dns.exception.DNSException): """An attempt was made to convert a non-absolute name to wire when there was also a non-absolute (or missing) origin.""" class NameTooLong(dns.exception.FormError): """A DNS name is > 255 octets long.""" class LabelTooLong(dns.exception.SyntaxError): """A DNS label is > 63 octets long.""" class AbsoluteConcatenation(dns.exception.DNSException): """An attempt was made to append anything other than the empty name to an absolute DNS name.""" class NoParent(dns.exception.DNSException): """An attempt was made to get the parent of the root name or the empty name.""" class NoIDNA2008(dns.exception.DNSException): """IDNA 2008 processing was requested but the idna module is not available.""" class IDNAException(dns.exception.DNSException): """IDNA processing raised an exception.""" supp_kwargs = {"idna_exception"} fmt = "IDNA processing exception: {idna_exception}" # We do this as otherwise mypy complains about unexpected keyword argument # idna_exception def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) class NeedSubdomainOfOrigin(dns.exception.DNSException): """An absolute name was provided that is not a subdomain of the specified origin.""" _escaped = b'"().;\\@$' _escaped_text = '"().;\\@$' def _escapify(label: Union[bytes, str]) -> str: """Escape the characters in label which need it. @returns: the escaped string @rtype: string""" if isinstance(label, bytes): # Ordinary DNS label mode. Escape special characters and values # < 0x20 or > 0x7f. text = "" for c in label: if c in _escaped: text += "\\" + chr(c) elif c > 0x20 and c < 0x7F: text += chr(c) else: text += "\\%03d" % c return text # Unicode label mode. Escape only special characters and values < 0x20 text = "" for uc in label: if uc in _escaped_text: text += "\\" + uc elif uc <= "\x20": text += "\\%03d" % ord(uc) else: text += uc return text class IDNACodec: """Abstract base class for IDNA encoder/decoders.""" def __init__(self): pass def is_idna(self, label: bytes) -> bool: return label.lower().startswith(b"xn--") def encode(self, label: str) -> bytes: raise NotImplementedError # pragma: no cover def decode(self, label: bytes) -> str: # We do not apply any IDNA policy on decode. if self.is_idna(label): try: slabel = label[4:].decode("punycode") return _escapify(slabel) except Exception as e: raise IDNAException(idna_exception=e) else: return _escapify(label) class IDNA2003Codec(IDNACodec): """IDNA 2003 encoder/decoder.""" def __init__(self, strict_decode: bool = False): """Initialize the IDNA 2003 encoder/decoder. *strict_decode* is a ``bool``. If `True`, then IDNA2003 checking is done when decoding. This can cause failures if the name was encoded with IDNA2008. The default is `False`. """ super().__init__() self.strict_decode = strict_decode def encode(self, label: str) -> bytes: """Encode *label*.""" if label == "": return b"" try: return encodings.idna.ToASCII(label) except UnicodeError: raise LabelTooLong def decode(self, label: bytes) -> str: """Decode *label*.""" if not self.strict_decode: return super().decode(label) if label == b"": return "" try: return _escapify(encodings.idna.ToUnicode(label)) except Exception as e: raise IDNAException(idna_exception=e) class IDNA2008Codec(IDNACodec): """IDNA 2008 encoder/decoder.""" def __init__( self, uts_46: bool = False, transitional: bool = False, allow_pure_ascii: bool = False, strict_decode: bool = False, ): """Initialize the IDNA 2008 encoder/decoder. *uts_46* is a ``bool``. If True, apply Unicode IDNA compatibility processing as described in Unicode Technical Standard #46 (https://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. """ super().__init__() self.uts_46 = uts_46 self.transitional = transitional self.allow_pure_ascii = allow_pure_ascii self.strict_decode = strict_decode def encode(self, label: str) -> bytes: if label == "": return b"" if self.allow_pure_ascii and is_all_ascii(label): encoded = label.encode("ascii") if len(encoded) > 63: raise LabelTooLong return encoded if not have_idna_2008: raise NoIDNA2008 try: if self.uts_46: # pylint: disable=possibly-used-before-assignment label = idna.uts46_remap(label, False, self.transitional) return idna.alabel(label) except idna.IDNAError as e: if e.args[0] == "Label too long": raise LabelTooLong else: raise IDNAException(idna_exception=e) def decode(self, label: bytes) -> str: if not self.strict_decode: return super().decode(label) if label == b"": return "" if not have_idna_2008: raise NoIDNA2008 try: ulabel = idna.ulabel(label) if self.uts_46: ulabel = idna.uts46_remap(ulabel, False, self.transitional) return _escapify(ulabel) except (idna.IDNAError, UnicodeError) as e: raise IDNAException(idna_exception=e) IDNA_2003_Practical = IDNA2003Codec(False) IDNA_2003_Strict = IDNA2003Codec(True) IDNA_2003 = IDNA_2003_Practical IDNA_2008_Practical = IDNA2008Codec(True, False, True, False) IDNA_2008_UTS_46 = IDNA2008Codec(True, False, False, False) IDNA_2008_Strict = IDNA2008Codec(False, False, False, True) IDNA_2008_Transitional = IDNA2008Codec(True, True, False, False) IDNA_2008 = IDNA_2008_Practical def _validate_labels(labels: Tuple[bytes, ...]) -> None: """Check for empty labels in the middle of a label sequence, labels that are too long, and for too many labels. Raises ``dns.name.NameTooLong`` if the name as a whole is too long. 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) total = 0 i = -1 j = 0 for label in labels: ll = len(label) total += ll + 1 if ll > 63: raise LabelTooLong if i < 0 and label == b"": i = j j += 1 if total > 255: raise NameTooLong if i >= 0 and i != l - 1: raise EmptyLabel def _maybe_convert_to_binary(label: Union[bytes, str]) -> bytes: """If label is ``str``, convert it to ``bytes``. If it is already ``bytes`` just return it. """ if isinstance(label, bytes): return label if isinstance(label, str): return label.encode() raise ValueError # pragma: no cover @dns.immutable.immutable class Name: """A DNS name. The dns.name.Name class represents a DNS name as a tuple of labels. Each label is a ``bytes`` in DNS wire format. Instances of the class are immutable. """ __slots__ = ["labels"] def __init__(self, labels: Iterable[Union[bytes, str]]): """*labels* is any iterable whose values are ``str`` or ``bytes``.""" blabels = [_maybe_convert_to_binary(x) for x in labels] self.labels = tuple(blabels) _validate_labels(self.labels) def __copy__(self): return Name(self.labels) def __deepcopy__(self, memo): return Name(copy.deepcopy(self.labels, memo)) def __getstate__(self): # Names can be pickled return {"labels": self.labels} def __setstate__(self, state): super().__setattr__("labels", state["labels"]) _validate_labels(self.labels) def is_absolute(self) -> bool: """Is the most significant label of this name the root label? Returns a ``bool``. """ return len(self.labels) > 0 and self.labels[-1] == b"" def is_wild(self) -> bool: """Is this name wild? (I.e. Is the least significant label '*'?) Returns a ``bool``. """ return len(self.labels) > 0 and self.labels[0] == b"*" def __hash__(self) -> int: """Return a case-insensitive hash of the name. Returns an ``int``. """ h = 0 for label in self.labels: for c in label.lower(): h += (h << 3) + c return h def fullcompare(self, other: "Name") -> Tuple[NameRelation, int, int]: """Compare two names, returning a 3-tuple ``(relation, order, nlabels)``. *relation* describes the relation ship between the names, and is one of: ``dns.name.NameRelation.NONE``, ``dns.name.NameRelation.SUPERDOMAIN``, ``dns.name.NameRelation.SUBDOMAIN``, ``dns.name.NameRelation.EQUAL``, or ``dns.name.NameRelation.COMMONANCESTOR``. *order* is < 0 if *self* < *other*, > 0 if *self* > *other*, and == 0 if *self* == *other*. A relative name is always less than an absolute name. If both names have the same relativity, then the DNSSEC order relation is used to order them. *nlabels* is the number of significant labels that the two names 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() oabs = other.is_absolute() if sabs != oabs: if sabs: return (NameRelation.NONE, 1, 0) else: return (NameRelation.NONE, -1, 0) l1 = len(self.labels) l2 = len(other.labels) ldiff = l1 - l2 if ldiff < 0: l = l1 else: l = l2 order = 0 nlabels = 0 namereln = NameRelation.NONE while l > 0: l -= 1 l1 -= 1 l2 -= 1 label1 = self.labels[l1].lower() label2 = other.labels[l2].lower() if label1 < label2: order = -1 if nlabels > 0: namereln = NameRelation.COMMONANCESTOR return (namereln, order, nlabels) elif label1 > label2: order = 1 if nlabels > 0: namereln = NameRelation.COMMONANCESTOR return (namereln, order, nlabels) nlabels += 1 order = ldiff if ldiff < 0: namereln = NameRelation.SUPERDOMAIN elif ldiff > 0: namereln = NameRelation.SUBDOMAIN else: namereln = NameRelation.EQUAL return (namereln, order, nlabels) def is_subdomain(self, other: "Name") -> bool: """Is self a subdomain of other? Note that the notion of subdomain includes equality, e.g. "dnspython.org" is a subdomain of itself. Returns a ``bool``. """ (nr, _, _) = self.fullcompare(other) if nr == NameRelation.SUBDOMAIN or nr == NameRelation.EQUAL: return True return False def is_superdomain(self, other: "Name") -> bool: """Is self a superdomain of other? Note that the notion of superdomain includes equality, e.g. "dnspython.org" is a superdomain of itself. Returns a ``bool``. """ (nr, _, _) = self.fullcompare(other) if nr == NameRelation.SUPERDOMAIN or nr == NameRelation.EQUAL: return True return False def canonicalize(self) -> "Name": """Return a name which is equal to the current name, but is in DNSSEC canonical form. """ return Name([x.lower() for x in self.labels]) def __eq__(self, other): if isinstance(other, Name): return self.fullcompare(other)[1] == 0 else: return False def __ne__(self, other): if isinstance(other, Name): return self.fullcompare(other)[1] != 0 else: return True def __lt__(self, other): if isinstance(other, Name): return self.fullcompare(other)[1] < 0 else: return NotImplemented def __le__(self, other): if isinstance(other, Name): return self.fullcompare(other)[1] <= 0 else: return NotImplemented def __ge__(self, other): if isinstance(other, Name): return self.fullcompare(other)[1] >= 0 else: return NotImplemented def __gt__(self, other): if isinstance(other, Name): return self.fullcompare(other)[1] > 0 else: return NotImplemented def __repr__(self): return "" def __str__(self): return self.to_text(False) def to_text(self, omit_final_dot: bool = False) -> str: """Convert name to DNS text format. *omit_final_dot* is a ``bool``. If True, don't emit the final dot (denoting the root label) for absolute names. The default is False. Returns a ``str``. """ if len(self.labels) == 0: return "@" if len(self.labels) == 1 and self.labels[0] == b"": return "." if omit_final_dot and self.is_absolute(): l = self.labels[:-1] else: l = self.labels s = ".".join(map(_escapify, l)) return s def to_unicode( self, omit_final_dot: bool = False, idna_codec: Optional[IDNACodec] = None ) -> str: """Convert name to Unicode text format. IDN ACE labels are converted to Unicode. *omit_final_dot* is a ``bool``. If True, don't emit the final dot (denoting the root label) for absolute names. The default is False. *idna_codec* specifies the IDNA encoder/decoder. If None, the dns.name.IDNA_2003_Practical encoder/decoder is used. The IDNA_2003_Practical decoder does not impose any policy, it just decodes punycode, so if you don't want checking for compliance, you can use this decoder for IDNA2008 as well. Returns a ``str``. """ if len(self.labels) == 0: return "@" if len(self.labels) == 1 and self.labels[0] == b"": return "." if omit_final_dot and self.is_absolute(): l = self.labels[:-1] else: l = self.labels if idna_codec is None: idna_codec = IDNA_2003_Practical return ".".join([idna_codec.decode(x) for x in l]) def to_digestable(self, origin: Optional["Name"] = None) -> bytes: """Convert name to a format suitable for digesting in hashes. 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. *origin* is a ``dns.name.Name`` or ``None``. If the name is relative and origin is not ``None``, then origin will be appended to the name. Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is relative and no origin was provided. Returns a ``bytes``. """ digest = self.to_wire(origin=origin, canonicalize=True) assert digest is not None return digest def to_wire( self, file: Optional[Any] = None, compress: Optional[CompressType] = None, origin: Optional["Name"] = None, canonicalize: bool = False, ) -> Optional[bytes]: """Convert name to wire format, possibly compressing it. *file* is the file where the name is emitted (typically an io.BytesIO file). If ``None`` (the default), a ``bytes`` containing the wire name will be returned. *compress*, a ``dict``, is the compression table to use. If ``None`` (the default), names will not be compressed. Note that the compression code assumes that compression offset 0 is the start of *file*, and thus compression will not be correct if this is not the case. *origin* is a ``dns.name.Name`` or ``None``. If the name is relative and origin is not ``None``, then *origin* will be appended to it. *canonicalize*, a ``bool``, indicates whether the name should be canonicalized; that is, converted to a format suitable for digesting in hashes. Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is relative and no origin was provided. Returns a ``bytes`` or ``None``. """ if file is None: out = bytearray() for label in self.labels: out.append(len(label)) if canonicalize: out += label.lower() else: out += label if not self.is_absolute(): if origin is None or not origin.is_absolute(): raise NeedAbsoluteNameOrOrigin for label in origin.labels: out.append(len(label)) if canonicalize: out += label.lower() else: out += label return bytes(out) labels: Iterable[bytes] if not self.is_absolute(): if origin is None or not origin.is_absolute(): raise NeedAbsoluteNameOrOrigin labels = list(self.labels) labels.extend(list(origin.labels)) else: labels = self.labels i = 0 for label in labels: n = Name(labels[i:]) i += 1 if compress is not None: pos = compress.get(n) else: pos = None if pos is not None: value = 0xC000 + pos s = struct.pack("!H", value) file.write(s) break else: if compress is not None and len(n) > 1: pos = file.tell() if pos <= 0x3FFF: compress[n] = pos l = len(label) file.write(struct.pack("!B", l)) if l > 0: if canonicalize: file.write(label.lower()) else: file.write(label) return None def __len__(self) -> int: """The length of the name (in labels). Returns an ``int``. """ return len(self.labels) def __getitem__(self, index): return self.labels[index] def __add__(self, other): return self.concatenate(other) def __sub__(self, other): return self.relativize(other) def split(self, depth: int) -> Tuple["Name", "Name"]: """Split a name into a prefix and suffix names at the specified depth. *depth* is an ``int`` specifying the number of labels in the suffix Raises ``ValueError`` if *depth* was not >= 0 and <= the length of the name. Returns the tuple ``(prefix, suffix)``. """ l = len(self.labels) if depth == 0: return (self, dns.name.empty) elif depth == l: return (dns.name.empty, self) elif depth < 0 or depth > l: raise ValueError("depth must be >= 0 and <= the length of the name") return (Name(self[:-depth]), Name(self[-depth:])) def concatenate(self, other: "Name") -> "Name": """Return a new name which is the concatenation of self and other. Raises ``dns.name.AbsoluteConcatenation`` if the name is absolute and *other* is not the empty name. Returns a ``dns.name.Name``. """ if self.is_absolute() and len(other) > 0: raise AbsoluteConcatenation labels = list(self.labels) labels.extend(list(other.labels)) return Name(labels) def relativize(self, origin: "Name") -> "Name": """If the name is a subdomain of *origin*, return a new name which is the name relative to origin. Otherwise return the name. 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): return Name(self[: -len(origin)]) else: return self def derelativize(self, origin: "Name") -> "Name": """If the name is a relative name, return a new name which is the concatenation of the name and origin. Otherwise return the name. 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(): return self.concatenate(origin) else: return self def choose_relativity( self, origin: Optional["Name"] = None, relativize: bool = True ) -> "Name": """Return a name with the relativity desired by the caller. If *origin* is ``None``, then the name is returned. Otherwise, if *relativize* is ``True`` the name is relativized, and if *relativize* is ``False`` the name is derelativized. Returns a ``dns.name.Name``. """ if origin: if relativize: return self.relativize(origin) else: return self.derelativize(origin) else: return self def parent(self) -> "Name": """Return the parent of the name. For example, the parent of ``www.dnspython.org.`` is ``dnspython.org``. 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: raise NoParent return Name(self.labels[1:]) def predecessor(self, origin: "Name", prefix_ok: bool = True) -> "Name": """Return the maximal predecessor of *name* in the DNSSEC ordering in the zone whose origin is *origin*, or return the longest name under *origin* if the name is origin (i.e. wrap around to the longest name, which may still be *origin* due to length considerations. The relativity of the name is preserved, so if this name is relative then the method will return a relative name, and likewise if this name is absolute then the predecessor will be absolute. *prefix_ok* indicates if prefixing labels is allowed, and defaults to ``True``. Normally it is good to allow this, but if computing a maximal predecessor at a zone cut point then ``False`` must be specified. """ return _handle_relativity_and_call( _absolute_predecessor, self, origin, prefix_ok ) def successor(self, origin: "Name", prefix_ok: bool = True) -> "Name": """Return the minimal successor of *name* in the DNSSEC ordering in the zone whose origin is *origin*, or return *origin* if the successor cannot be computed due to name length limitations. Note that *origin* is returned in the "too long" cases because wrapping around to the origin is how NSEC records express "end of the zone". The relativity of the name is preserved, so if this name is relative then the method will return a relative name, and likewise if this name is absolute then the successor will be absolute. *prefix_ok* indicates if prefixing a new minimal label is allowed, and defaults to ``True``. Normally it is good to allow this, but if computing a minimal successor at a zone cut point then ``False`` must be specified. """ return _handle_relativity_and_call(_absolute_successor, self, origin, prefix_ok) #: The root name, '.' root = Name([b""]) #: The empty name. empty = Name([]) def from_unicode( text: str, origin: Optional[Name] = root, idna_codec: Optional[IDNACodec] = None ) -> Name: """Convert unicode text into a Name object. Labels are encoded in IDN ACE form according to rules specified by the IDNA codec. *text*, a ``str``, is the text to convert into a name. *origin*, a ``dns.name.Name``, specifies the origin to append to non-absolute names. The default is the root name. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder is used. Returns a ``dns.name.Name``. """ if not isinstance(text, str): raise ValueError("input to from_unicode() must be a unicode string") if not (origin is None or isinstance(origin, Name)): raise ValueError("origin must be a Name or None") labels = [] label = "" escaping = False edigits = 0 total = 0 if idna_codec is None: idna_codec = IDNA_2003 if text == "@": text = "" if text: if text in [".", "\u3002", "\uff0e", "\uff61"]: return Name([b""]) # no Unicode "u" on this constant! for c in text: if escaping: if edigits == 0: if c.isdigit(): total = int(c) edigits += 1 else: label += c escaping = False else: if not c.isdigit(): raise BadEscape total *= 10 total += int(c) edigits += 1 if edigits == 3: escaping = False label += chr(total) elif c in [".", "\u3002", "\uff0e", "\uff61"]: if len(label) == 0: raise EmptyLabel labels.append(idna_codec.encode(label)) label = "" elif c == "\\": escaping = True edigits = 0 total = 0 else: label += c if escaping: raise BadEscape if len(label) > 0: labels.append(idna_codec.encode(label)) else: labels.append(b"") if (len(labels) == 0 or labels[-1] != b"") and origin is not None: labels.extend(list(origin.labels)) return Name(labels) def is_all_ascii(text: str) -> bool: for c in text: if ord(c) > 0x7F: return False return True def from_text( text: Union[bytes, str], origin: Optional[Name] = root, idna_codec: Optional[IDNACodec] = None, ) -> Name: """Convert text into a Name object. *text*, a ``bytes`` or ``str``, is the text to convert into a name. *origin*, a ``dns.name.Name``, specifies the origin to append to non-absolute names. The default is the root name. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder is used. Returns a ``dns.name.Name``. """ if isinstance(text, str): if not is_all_ascii(text): # Some codepoint in the input text is > 127, so IDNA applies. return from_unicode(text, origin, idna_codec) # The input is all ASCII, so treat this like an ordinary non-IDNA # domain name. Note that "all ASCII" is about the input text, # not the codepoints in the domain name. E.g. if text has value # # r'\150\151\152\153\154\155\156\157\158\159' # # then it's still "all ASCII" even though the domain name has # codepoints > 127. text = text.encode("ascii") if not isinstance(text, bytes): raise ValueError("input to from_text() must be a string") if not (origin is None or isinstance(origin, Name)): raise ValueError("origin must be a Name or None") labels = [] label = b"" escaping = False edigits = 0 total = 0 if text == b"@": text = b"" if text: if text == b".": return Name([b""]) for c in text: byte_ = struct.pack("!B", c) if escaping: if edigits == 0: if byte_.isdigit(): total = int(byte_) edigits += 1 else: label += byte_ escaping = False else: if not byte_.isdigit(): raise BadEscape total *= 10 total += int(byte_) edigits += 1 if edigits == 3: escaping = False label += struct.pack("!B", total) elif byte_ == b".": if len(label) == 0: raise EmptyLabel labels.append(label) label = b"" elif byte_ == b"\\": escaping = True edigits = 0 total = 0 else: label += byte_ if escaping: raise BadEscape if len(label) > 0: labels.append(label) else: labels.append(b"") if (len(labels) == 0 or labels[-1] != b"") and origin is not None: labels.extend(list(origin.labels)) return Name(labels) # we need 'dns.wire.Parser' quoted as dns.name and dns.wire depend on each other. def from_wire_parser(parser: "dns.wire.Parser") -> Name: """Convert possibly compressed wire format into a Name. *parser* is a dns.wire.Parser. Raises ``dns.name.BadPointer`` if a compression pointer did not point backwards in the message. Raises ``dns.name.BadLabelType`` if an invalid label type was encountered. Returns a ``dns.name.Name`` """ labels = [] biggest_pointer = parser.current with parser.restore_furthest(): count = parser.get_uint8() while count != 0: if count < 64: labels.append(parser.get_bytes(count)) elif count >= 192: current = (count & 0x3F) * 256 + parser.get_uint8() if current >= biggest_pointer: raise BadPointer biggest_pointer = current parser.seek(current) else: raise BadLabelType count = parser.get_uint8() labels.append(b"") return Name(labels) def from_wire(message: bytes, current: int) -> Tuple[Name, int]: """Convert possibly compressed wire format into a Name. *message* is a ``bytes`` containing an entire DNS message in DNS wire form. *current*, an ``int``, is the offset of the beginning of the name from the start of the message Raises ``dns.name.BadPointer`` if a compression pointer did not point backwards in the message. 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, bytes): raise ValueError("input to from_wire() must be a byte string") parser = dns.wire.Parser(message, current) name = from_wire_parser(parser) return (name, parser.current - current) # RFC 4471 Support _MINIMAL_OCTET = b"\x00" _MINIMAL_OCTET_VALUE = ord(_MINIMAL_OCTET) _SUCCESSOR_PREFIX = Name([_MINIMAL_OCTET]) _MAXIMAL_OCTET = b"\xff" _MAXIMAL_OCTET_VALUE = ord(_MAXIMAL_OCTET) _AT_SIGN_VALUE = ord("@") _LEFT_SQUARE_BRACKET_VALUE = ord("[") def _wire_length(labels): return functools.reduce(lambda v, x: v + len(x) + 1, labels, 0) def _pad_to_max_name(name): needed = 255 - _wire_length(name.labels) new_labels = [] while needed > 64: new_labels.append(_MAXIMAL_OCTET * 63) needed -= 64 if needed >= 2: new_labels.append(_MAXIMAL_OCTET * (needed - 1)) # Note we're already maximal in the needed == 1 case as while we'd like # to add one more byte as a new label, we can't, as adding a new non-empty # label requires at least 2 bytes. new_labels = list(reversed(new_labels)) new_labels.extend(name.labels) return Name(new_labels) def _pad_to_max_label(label, suffix_labels): length = len(label) # We have to subtract one here to account for the length byte of label. remaining = 255 - _wire_length(suffix_labels) - length - 1 if remaining <= 0: # Shouldn't happen! return label needed = min(63 - length, remaining) return label + _MAXIMAL_OCTET * needed def _absolute_predecessor(name: Name, origin: Name, prefix_ok: bool) -> Name: # This is the RFC 4471 predecessor algorithm using the "absolute method" of section # 3.1.1. # # Our caller must ensure that the name and origin are absolute, and that name is a # subdomain of origin. if name == origin: return _pad_to_max_name(name) least_significant_label = name[0] if least_significant_label == _MINIMAL_OCTET: return name.parent() least_octet = least_significant_label[-1] suffix_labels = name.labels[1:] if least_octet == _MINIMAL_OCTET_VALUE: new_labels = [least_significant_label[:-1]] else: octets = bytearray(least_significant_label) octet = octets[-1] if octet == _LEFT_SQUARE_BRACKET_VALUE: octet = _AT_SIGN_VALUE else: octet -= 1 octets[-1] = octet least_significant_label = bytes(octets) new_labels = [_pad_to_max_label(least_significant_label, suffix_labels)] new_labels.extend(suffix_labels) name = Name(new_labels) if prefix_ok: return _pad_to_max_name(name) else: return name def _absolute_successor(name: Name, origin: Name, prefix_ok: bool) -> Name: # This is the RFC 4471 successor algorithm using the "absolute method" of section # 3.1.2. # # Our caller must ensure that the name and origin are absolute, and that name is a # subdomain of origin. if prefix_ok: # Try prefixing \000 as new label try: return _SUCCESSOR_PREFIX.concatenate(name) except NameTooLong: pass while name != origin: # Try extending the least significant label. least_significant_label = name[0] if len(least_significant_label) < 63: # We may be able to extend the least label with a minimal additional byte. # This is only "may" because we could have a maximal length name even though # the least significant label isn't maximally long. new_labels = [least_significant_label + _MINIMAL_OCTET] new_labels.extend(name.labels[1:]) try: return dns.name.Name(new_labels) except dns.name.NameTooLong: pass # We can't extend the label either, so we'll try to increment the least # signficant non-maximal byte in it. octets = bytearray(least_significant_label) # We do this reversed iteration with an explicit indexing variable because # if we find something to increment, we're going to want to truncate everything # to the right of it. for i in range(len(octets) - 1, -1, -1): octet = octets[i] if octet == _MAXIMAL_OCTET_VALUE: # We can't increment this, so keep looking. continue # Finally, something we can increment. We have to apply a special rule for # incrementing "@", sending it to "[", because RFC 4034 6.1 says that when # comparing names, uppercase letters compare as if they were their # lower-case equivalents. If we increment "@" to "A", then it would compare # as "a", which is after "[", "\", "]", "^", "_", and "`", so we would have # skipped the most minimal successor, namely "[". if octet == _AT_SIGN_VALUE: octet = _LEFT_SQUARE_BRACKET_VALUE else: octet += 1 octets[i] = octet # We can now truncate all of the maximal values we skipped (if any) new_labels = [bytes(octets[: i + 1])] new_labels.extend(name.labels[1:]) # We haven't changed the length of the name, so the Name constructor will # always work. return Name(new_labels) # We couldn't increment, so chop off the least significant label and try # again. name = name.parent() # We couldn't increment at all, so return the origin, as wrapping around is the # DNSSEC way. return origin def _handle_relativity_and_call( function: Callable[[Name, Name, bool], Name], name: Name, origin: Name, prefix_ok: bool, ) -> Name: # Make "name" absolute if needed, ensure that the origin is absolute, # call function(), and then relativize the result if needed. if not origin.is_absolute(): raise NeedAbsoluteNameOrOrigin relative = not name.is_absolute() if relative: name = name.derelativize(origin) elif not name.is_subdomain(origin): raise NeedSubdomainOfOrigin result_name = function(name, origin, prefix_ok) if relative: result_name = result_name.relativize(origin) return result_name dnspython-2.7.0/dns/namedict.py0000644000000000000000000000764013615410400013417 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2003-2017 Nominum, Inc. # Copyright (C) 2016 Coresec Systems AB # # 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. # # THE SOFTWARE IS PROVIDED "AS IS" AND CORESEC SYSTEMS AB DISCLAIMS ALL # WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL CORESEC # SYSTEMS AB 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. """DNS name dictionary""" # pylint seems to be confused about this one! from collections.abc import MutableMapping # pylint: disable=no-name-in-module import dns.name class NameDict(MutableMapping): """A dictionary whose keys are dns.name.Name objects. In addition to being like a regular Python dictionary, this dictionary can also get the deepest match for a given key. """ __slots__ = ["max_depth", "max_depth_items", "__store"] def __init__(self, *args, **kwargs): super().__init__() self.__store = dict() #: the maximum depth of the keys that have ever been added self.max_depth = 0 #: the number of items of maximum depth self.max_depth_items = 0 self.update(dict(*args, **kwargs)) def __update_max_depth(self, key): if len(key) == self.max_depth: self.max_depth_items = self.max_depth_items + 1 elif len(key) > self.max_depth: self.max_depth = len(key) self.max_depth_items = 1 def __getitem__(self, key): return self.__store[key] def __setitem__(self, key, value): if not isinstance(key, dns.name.Name): raise ValueError("NameDict key must be a name") self.__store[key] = value self.__update_max_depth(key) def __delitem__(self, key): self.__store.pop(key) if len(key) == self.max_depth: self.max_depth_items = self.max_depth_items - 1 if self.max_depth_items == 0: self.max_depth = 0 for k in self.__store: self.__update_max_depth(k) def __iter__(self): return iter(self.__store) def __len__(self): return len(self.__store) def has_key(self, key): return key in self.__store def get_deepest_match(self, name): """Find the deepest match to *name* in the dictionary. The deepest match is the longest name in the dictionary which is a superdomain of *name*. Note that *superdomain* includes matching *name* itself. *name*, a ``dns.name.Name``, the name to find. Returns a ``(key, value)`` where *key* is the deepest ``dns.name.Name``, and *value* is the value associated with *key*. """ depth = len(name) if depth > self.max_depth: depth = self.max_depth for i in range(-depth, 0): n = dns.name.Name(name[i:]) if n in self: return (n, self[n]) v = self[dns.name.empty] return (dns.name.empty, v) dnspython-2.7.0/dns/nameserver.py0000644000000000000000000002360313615410400013777 0ustar00from typing import Optional, Union from urllib.parse import urlparse import dns.asyncbackend import dns.asyncquery import dns.inet import dns.message import dns.query class Nameserver: def __init__(self): pass def __str__(self): raise NotImplementedError def kind(self) -> str: raise NotImplementedError def is_always_max_size(self) -> bool: raise NotImplementedError def answer_nameserver(self) -> str: raise NotImplementedError def answer_port(self) -> int: raise NotImplementedError def query( self, request: dns.message.QueryMessage, timeout: float, source: Optional[str], source_port: int, max_size: bool, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, ) -> dns.message.Message: raise NotImplementedError async def async_query( self, request: dns.message.QueryMessage, timeout: float, source: Optional[str], source_port: int, max_size: bool, backend: dns.asyncbackend.Backend, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, ) -> dns.message.Message: raise NotImplementedError class AddressAndPortNameserver(Nameserver): def __init__(self, address: str, port: int): super().__init__() self.address = address self.port = port def kind(self) -> str: raise NotImplementedError def is_always_max_size(self) -> bool: return False def __str__(self): ns_kind = self.kind() return f"{ns_kind}:{self.address}@{self.port}" def answer_nameserver(self) -> str: return self.address def answer_port(self) -> int: return self.port class Do53Nameserver(AddressAndPortNameserver): def __init__(self, address: str, port: int = 53): super().__init__(address, port) def kind(self): return "Do53" def query( self, request: dns.message.QueryMessage, timeout: float, source: Optional[str], source_port: int, max_size: bool, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, ) -> dns.message.Message: if max_size: response = dns.query.tcp( request, self.address, timeout=timeout, port=self.port, source=source, source_port=source_port, one_rr_per_rrset=one_rr_per_rrset, ignore_trailing=ignore_trailing, ) else: response = dns.query.udp( request, self.address, timeout=timeout, port=self.port, source=source, source_port=source_port, raise_on_truncation=True, one_rr_per_rrset=one_rr_per_rrset, ignore_trailing=ignore_trailing, ignore_errors=True, ignore_unexpected=True, ) return response async def async_query( self, request: dns.message.QueryMessage, timeout: float, source: Optional[str], source_port: int, max_size: bool, backend: dns.asyncbackend.Backend, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, ) -> dns.message.Message: if max_size: response = await dns.asyncquery.tcp( request, self.address, timeout=timeout, port=self.port, source=source, source_port=source_port, backend=backend, one_rr_per_rrset=one_rr_per_rrset, ignore_trailing=ignore_trailing, ) else: response = await dns.asyncquery.udp( request, self.address, timeout=timeout, port=self.port, source=source, source_port=source_port, raise_on_truncation=True, backend=backend, one_rr_per_rrset=one_rr_per_rrset, ignore_trailing=ignore_trailing, ignore_errors=True, ignore_unexpected=True, ) return response class DoHNameserver(Nameserver): def __init__( self, url: str, bootstrap_address: Optional[str] = None, verify: Union[bool, str] = True, want_get: bool = False, http_version: dns.query.HTTPVersion = dns.query.HTTPVersion.DEFAULT, ): super().__init__() self.url = url self.bootstrap_address = bootstrap_address self.verify = verify self.want_get = want_get self.http_version = http_version def kind(self): return "DoH" def is_always_max_size(self) -> bool: return True def __str__(self): return self.url def answer_nameserver(self) -> str: return self.url def answer_port(self) -> int: port = urlparse(self.url).port if port is None: port = 443 return port def query( self, request: dns.message.QueryMessage, timeout: float, source: Optional[str], source_port: int, max_size: bool = False, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, ) -> dns.message.Message: return dns.query.https( request, self.url, timeout=timeout, source=source, source_port=source_port, bootstrap_address=self.bootstrap_address, one_rr_per_rrset=one_rr_per_rrset, ignore_trailing=ignore_trailing, verify=self.verify, post=(not self.want_get), http_version=self.http_version, ) async def async_query( self, request: dns.message.QueryMessage, timeout: float, source: Optional[str], source_port: int, max_size: bool, backend: dns.asyncbackend.Backend, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, ) -> dns.message.Message: return await dns.asyncquery.https( request, self.url, timeout=timeout, source=source, source_port=source_port, bootstrap_address=self.bootstrap_address, one_rr_per_rrset=one_rr_per_rrset, ignore_trailing=ignore_trailing, verify=self.verify, post=(not self.want_get), http_version=self.http_version, ) class DoTNameserver(AddressAndPortNameserver): def __init__( self, address: str, port: int = 853, hostname: Optional[str] = None, verify: Union[bool, str] = True, ): super().__init__(address, port) self.hostname = hostname self.verify = verify def kind(self): return "DoT" def query( self, request: dns.message.QueryMessage, timeout: float, source: Optional[str], source_port: int, max_size: bool = False, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, ) -> dns.message.Message: return dns.query.tls( request, self.address, port=self.port, timeout=timeout, one_rr_per_rrset=one_rr_per_rrset, ignore_trailing=ignore_trailing, server_hostname=self.hostname, verify=self.verify, ) async def async_query( self, request: dns.message.QueryMessage, timeout: float, source: Optional[str], source_port: int, max_size: bool, backend: dns.asyncbackend.Backend, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, ) -> dns.message.Message: return await dns.asyncquery.tls( request, self.address, port=self.port, timeout=timeout, one_rr_per_rrset=one_rr_per_rrset, ignore_trailing=ignore_trailing, server_hostname=self.hostname, verify=self.verify, ) class DoQNameserver(AddressAndPortNameserver): def __init__( self, address: str, port: int = 853, verify: Union[bool, str] = True, server_hostname: Optional[str] = None, ): super().__init__(address, port) self.verify = verify self.server_hostname = server_hostname def kind(self): return "DoQ" def query( self, request: dns.message.QueryMessage, timeout: float, source: Optional[str], source_port: int, max_size: bool = False, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, ) -> dns.message.Message: return dns.query.quic( request, self.address, port=self.port, timeout=timeout, one_rr_per_rrset=one_rr_per_rrset, ignore_trailing=ignore_trailing, verify=self.verify, server_hostname=self.server_hostname, ) async def async_query( self, request: dns.message.QueryMessage, timeout: float, source: Optional[str], source_port: int, max_size: bool, backend: dns.asyncbackend.Backend, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, ) -> dns.message.Message: return await dns.asyncquery.quic( request, self.address, port=self.port, timeout=timeout, one_rr_per_rrset=one_rr_per_rrset, ignore_trailing=ignore_trailing, verify=self.verify, server_hostname=self.server_hostname, ) dnspython-2.7.0/dns/node.py0000644000000000000000000003056713615410400012564 0ustar00# 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 # 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. """DNS nodes. A node is a set of rdatasets.""" import enum import io from typing import Any, Dict, Optional import dns.immutable import dns.name import dns.rdataclass import dns.rdataset import dns.rdatatype import dns.renderer import dns.rrset _cname_types = { dns.rdatatype.CNAME, } # "neutral" types can coexist with a CNAME and thus are not "other data" _neutral_types = { dns.rdatatype.NSEC, # RFC 4035 section 2.5 dns.rdatatype.NSEC3, # This is not likely to happen, but not impossible! dns.rdatatype.KEY, # RFC 4035 section 2.5, RFC 3007 } def _matches_type_or_its_signature(rdtypes, rdtype, covers): return rdtype in rdtypes or (rdtype == dns.rdatatype.RRSIG and covers in rdtypes) @enum.unique class NodeKind(enum.Enum): """Rdatasets in nodes""" REGULAR = 0 # a.k.a "other data" NEUTRAL = 1 CNAME = 2 @classmethod def classify( cls, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType ) -> "NodeKind": if _matches_type_or_its_signature(_cname_types, rdtype, covers): return NodeKind.CNAME elif _matches_type_or_its_signature(_neutral_types, rdtype, covers): return NodeKind.NEUTRAL else: return NodeKind.REGULAR @classmethod def classify_rdataset(cls, rdataset: dns.rdataset.Rdataset) -> "NodeKind": return cls.classify(rdataset.rdtype, rdataset.covers) class Node: """A Node is a set of rdatasets. A node is either a CNAME node or an "other data" node. A CNAME node contains only CNAME, KEY, NSEC, and NSEC3 rdatasets along with their covering RRSIG rdatasets. An "other data" node contains any rdataset other than a CNAME or RRSIG(CNAME) rdataset. When changes are made to a node, the CNAME or "other data" state is always consistent with the update, i.e. the most recent change wins. For example, if you have a node which contains a CNAME rdataset, and then add an MX rdataset to it, then the CNAME rdataset will be deleted. Likewise if you have a node containing an MX rdataset and add a CNAME rdataset, the MX rdataset will be deleted. """ __slots__ = ["rdatasets"] def __init__(self): # the set of rdatasets, represented as a list. self.rdatasets = [] def to_text(self, name: dns.name.Name, **kw: Dict[str, Any]) -> str: """Convert a node to text format. Each rdataset at the node is printed. Any keyword arguments to this method are passed on to the rdataset's to_text() method. *name*, a ``dns.name.Name``, the owner name of the rdatasets. Returns a ``str``. """ s = io.StringIO() for rds in self.rdatasets: if len(rds) > 0: s.write(rds.to_text(name, **kw)) # type: ignore[arg-type] s.write("\n") return s.getvalue()[:-1] def __repr__(self): return "" def __eq__(self, other): # # This is inefficient. Good thing we don't need to do it much. # for rd in self.rdatasets: if rd not in other.rdatasets: return False for rd in other.rdatasets: if rd not in self.rdatasets: return False return True def __ne__(self, other): return not self.__eq__(other) def __len__(self): return len(self.rdatasets) def __iter__(self): return iter(self.rdatasets) def _append_rdataset(self, rdataset): """Append rdataset to the node with special handling for CNAME and other data conditions. Specifically, if the rdataset being appended has ``NodeKind.CNAME``, then all rdatasets other than KEY, NSEC, NSEC3, and their covering RRSIGs are deleted. If the rdataset being appended has ``NodeKind.REGULAR`` then CNAME and RRSIG(CNAME) are deleted. """ # Make having just one rdataset at the node fast. if len(self.rdatasets) > 0: kind = NodeKind.classify_rdataset(rdataset) if kind == NodeKind.CNAME: self.rdatasets = [ rds for rds in self.rdatasets if NodeKind.classify_rdataset(rds) != NodeKind.REGULAR ] elif kind == NodeKind.REGULAR: self.rdatasets = [ rds for rds in self.rdatasets if NodeKind.classify_rdataset(rds) != NodeKind.CNAME ] # Otherwise the rdataset is NodeKind.NEUTRAL and we do not need to # edit self.rdatasets. self.rdatasets.append(rdataset) def find_rdataset( self, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, create: bool = False, ) -> dns.rdataset.Rdataset: """Find an rdataset matching the specified properties in the current node. *rdclass*, a ``dns.rdataclass.RdataClass``, the class of the rdataset. *rdtype*, a ``dns.rdatatype.RdataType``, the type of the rdataset. *covers*, a ``dns.rdatatype.RdataType``, 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. *create*, a ``bool``. If True, create the rdataset if it is not found. Raises ``KeyError`` if an rdataset of the desired type and class does not exist and *create* is not ``True``. Returns a ``dns.rdataset.Rdataset``. """ for rds in self.rdatasets: if rds.match(rdclass, rdtype, covers): return rds if not create: raise KeyError rds = dns.rdataset.Rdataset(rdclass, rdtype, covers) self._append_rdataset(rds) return rds def get_rdataset( self, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, create: bool = False, ) -> Optional[dns.rdataset.Rdataset]: """Get an rdataset matching the specified properties in the current node. None is returned if an rdataset of the specified type and class does not exist and *create* is not ``True``. *rdclass*, an ``int``, the class of the rdataset. *rdtype*, an ``int``, the type of the rdataset. *covers*, an ``int``, 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. *create*, a ``bool``. If True, create the rdataset if it is not found. Returns a ``dns.rdataset.Rdataset`` or ``None``. """ try: rds = self.find_rdataset(rdclass, rdtype, covers, create) except KeyError: rds = None return rds def delete_rdataset( self, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, ) -> None: """Delete the rdataset matching the specified properties in the current node. If a matching rdataset does not exist, it is not an error. *rdclass*, an ``int``, the class of the rdataset. *rdtype*, an ``int``, the type of the rdataset. *covers*, an ``int``, the covered type. """ rds = self.get_rdataset(rdclass, rdtype, covers) if rds is not None: self.rdatasets.remove(rds) def replace_rdataset(self, replacement: dns.rdataset.Rdataset) -> None: """Replace an rdataset. It is not an error if there is no rdataset matching *replacement*. Ownership of the *replacement* object is transferred to the node; in other words, this method does not store a copy of *replacement* 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): raise ValueError("replacement is not an rdataset") if isinstance(replacement, dns.rrset.RRset): # RRsets are not good replacements as the match() method # is not compatible. replacement = replacement.to_rdataset() self.delete_rdataset( replacement.rdclass, replacement.rdtype, replacement.covers ) self._append_rdataset(replacement) def classify(self) -> NodeKind: """Classify a node. A node which contains a CNAME or RRSIG(CNAME) is a ``NodeKind.CNAME`` node. A node which contains only "neutral" types, i.e. types allowed to co-exist with a CNAME, is a ``NodeKind.NEUTRAL`` node. The neutral types are NSEC, NSEC3, KEY, and their associated RRSIGS. An empty node is also considered neutral. A node which contains some rdataset which is not a CNAME, RRSIG(CNAME), or a neutral type is a a ``NodeKind.REGULAR`` node. Regular nodes are also commonly referred to as "other data". """ for rdataset in self.rdatasets: kind = NodeKind.classify(rdataset.rdtype, rdataset.covers) if kind != NodeKind.NEUTRAL: return kind return NodeKind.NEUTRAL def is_immutable(self) -> bool: return False @dns.immutable.immutable class ImmutableNode(Node): def __init__(self, node): super().__init__() self.rdatasets = tuple( [dns.rdataset.ImmutableRdataset(rds) for rds in node.rdatasets] ) def find_rdataset( self, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, create: bool = False, ) -> dns.rdataset.Rdataset: if create: raise TypeError("immutable") return super().find_rdataset(rdclass, rdtype, covers, False) def get_rdataset( self, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, create: bool = False, ) -> Optional[dns.rdataset.Rdataset]: if create: raise TypeError("immutable") return super().get_rdataset(rdclass, rdtype, covers, False) def delete_rdataset( self, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, ) -> None: raise TypeError("immutable") def replace_rdataset(self, replacement: dns.rdataset.Rdataset) -> None: raise TypeError("immutable") def is_immutable(self) -> bool: return True dnspython-2.7.0/dns/opcode.py0000644000000000000000000000525213615410400013101 0ustar00# 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 # 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. """DNS Opcodes.""" import dns.enum import dns.exception class Opcode(dns.enum.IntEnum): #: Query QUERY = 0 #: Inverse Query (historical) IQUERY = 1 #: Server Status (unspecified and unimplemented anywhere) STATUS = 2 #: Notify NOTIFY = 4 #: Dynamic Update UPDATE = 5 @classmethod def _maximum(cls): return 15 @classmethod def _unknown_exception_class(cls): return UnknownOpcode class UnknownOpcode(dns.exception.DNSException): """An DNS opcode is unknown.""" def from_text(text: str) -> Opcode: """Convert text into an opcode. *text*, a ``str``, the textual opcode Raises ``dns.opcode.UnknownOpcode`` if the opcode is unknown. Returns an ``int``. """ return Opcode.from_text(text) def from_flags(flags: int) -> Opcode: """Extract an opcode from DNS message flags. *flags*, an ``int``, the DNS flags. Returns an ``int``. """ return Opcode((flags & 0x7800) >> 11) def to_flags(value: Opcode) -> int: """Convert an opcode to a value suitable for ORing into DNS message flags. *value*, an ``int``, the DNS opcode value. Returns an ``int``. """ return (value << 11) & 0x7800 def to_text(value: Opcode) -> str: """Convert an opcode to text. *value*, an ``int`` the opcode value, Raises ``dns.opcode.UnknownOpcode`` if the opcode is unknown. Returns a ``str``. """ return Opcode.to_text(value) def is_update(flags: int) -> bool: """Is the opcode in flags UPDATE? *flags*, an ``int``, the DNS message flags. Returns a ``bool``. """ return from_flags(flags) == Opcode.UPDATE ### BEGIN generated Opcode constants QUERY = Opcode.QUERY IQUERY = Opcode.IQUERY STATUS = Opcode.STATUS NOTIFY = Opcode.NOTIFY UPDATE = Opcode.UPDATE ### END generated Opcode constants dnspython-2.7.0/dns/py.typed0000644000000000000000000000000013615410400012737 0ustar00dnspython-2.7.0/dns/query.py0000644000000000000000000015575213615410400013010 0ustar00# 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 # 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. """Talk to a DNS server.""" import base64 import contextlib import enum import errno import os import os.path import random import selectors import socket import struct import time import urllib.parse from typing import Any, Dict, Optional, Tuple, Union, cast import dns._features import dns.exception import dns.inet import dns.message import dns.name import dns.quic import dns.rcode import dns.rdataclass import dns.rdatatype import dns.serial import dns.transaction import dns.tsig import dns.xfr def _remaining(expiration): if expiration is None: return None timeout = expiration - time.time() if timeout <= 0.0: raise dns.exception.Timeout return timeout def _expiration_for_this_attempt(timeout, expiration): if expiration is None: return None return min(time.time() + timeout, expiration) _have_httpx = dns._features.have("doh") if _have_httpx: import httpcore._backends.sync import httpx _CoreNetworkBackend = httpcore.NetworkBackend _CoreSyncStream = httpcore._backends.sync.SyncStream class _NetworkBackend(_CoreNetworkBackend): def __init__(self, resolver, local_port, bootstrap_address, family): super().__init__() self._local_port = local_port self._resolver = resolver self._bootstrap_address = bootstrap_address self._family = family def connect_tcp( self, host, port, timeout, local_address, socket_options=None ): # pylint: disable=signature-differs addresses = [] _, expiration = _compute_times(timeout) if dns.inet.is_address(host): addresses.append(host) elif self._bootstrap_address is not None: addresses.append(self._bootstrap_address) else: timeout = _remaining(expiration) family = self._family if local_address: family = dns.inet.af_for_address(local_address) answers = self._resolver.resolve_name( host, family=family, lifetime=timeout ) addresses = answers.addresses() for address in addresses: af = dns.inet.af_for_address(address) if local_address is not None or self._local_port != 0: source = dns.inet.low_level_address_tuple( (local_address, self._local_port), af ) else: source = None sock = _make_socket(af, socket.SOCK_STREAM, source) attempt_expiration = _expiration_for_this_attempt(2.0, expiration) try: _connect( sock, dns.inet.low_level_address_tuple((address, port), af), attempt_expiration, ) return _CoreSyncStream(sock) except Exception: pass raise httpcore.ConnectError def connect_unix_socket( self, path, timeout, socket_options=None ): # pylint: disable=signature-differs raise NotImplementedError class _HTTPTransport(httpx.HTTPTransport): def __init__( self, *args, local_port=0, bootstrap_address=None, resolver=None, family=socket.AF_UNSPEC, **kwargs, ): if resolver is None and bootstrap_address is None: # pylint: disable=import-outside-toplevel,redefined-outer-name import dns.resolver resolver = dns.resolver.Resolver() super().__init__(*args, **kwargs) self._pool._network_backend = _NetworkBackend( resolver, local_port, bootstrap_address, family ) else: class _HTTPTransport: # type: ignore def connect_tcp(self, host, port, timeout, local_address): raise NotImplementedError have_doh = _have_httpx try: import ssl except ImportError: # pragma: no cover class ssl: # type: ignore CERT_NONE = 0 class WantReadException(Exception): pass class WantWriteException(Exception): pass class SSLContext: pass class SSLSocket: pass @classmethod def create_default_context(cls, *args, **kwargs): raise Exception("no ssl support") # pylint: disable=broad-exception-raised # Function used to create a socket. Can be overridden if needed in special # situations. socket_factory = socket.socket class UnexpectedSource(dns.exception.DNSException): """A DNS query response came from an unexpected address or port.""" class BadResponse(dns.exception.FormError): """A DNS query response does not respond to the question asked.""" class NoDOH(dns.exception.DNSException): """DNS over HTTPS (DOH) was requested but the httpx module is not available.""" class NoDOQ(dns.exception.DNSException): """DNS over QUIC (DOQ) was requested but the aioquic module is not available.""" # for backwards compatibility TransferError = dns.xfr.TransferError def _compute_times(timeout): now = time.time() if timeout is None: return (now, None) else: return (now, now + timeout) def _wait_for(fd, readable, writable, _, expiration): # Use the selected selector class to wait for any of the specified # events. An "expiration" absolute time is converted into a relative # timeout. # # The unused parameter is 'error', which is always set when # selecting for read or write, and we have no error-only selects. if readable and isinstance(fd, ssl.SSLSocket) and fd.pending() > 0: return True sel = selectors.DefaultSelector() events = 0 if readable: events |= selectors.EVENT_READ if writable: events |= selectors.EVENT_WRITE if events: sel.register(fd, events) if expiration is None: timeout = None else: timeout = expiration - time.time() if timeout <= 0.0: raise dns.exception.Timeout if not sel.select(timeout): raise dns.exception.Timeout def _wait_for_readable(s, expiration): _wait_for(s, True, False, True, expiration) def _wait_for_writable(s, expiration): _wait_for(s, False, True, True, expiration) def _addresses_equal(af, a1, a2): # Convert the first value of the tuple, which is a textual format # address into binary form, so that we are not confused by different # textual representations of the same address try: n1 = dns.inet.inet_pton(af, a1[0]) n2 = dns.inet.inet_pton(af, a2[0]) except dns.exception.SyntaxError: return False return n1 == n2 and a1[1:] == a2[1:] def _matches_destination(af, from_address, destination, ignore_unexpected): # Check that from_address is appropriate for a response to a query # sent to destination. if not destination: return True if _addresses_equal(af, from_address, destination) or ( dns.inet.is_multicast(destination[0]) and from_address[1:] == destination[1:] ): return True elif ignore_unexpected: return False raise UnexpectedSource( f"got a response from {from_address} instead of " f"{destination}" ) def _destination_and_source( where, port, source, source_port, where_must_be_address=True ): # Apply defaults and compute destination and source tuples # suitable for use in connect(), sendto(), or bind(). af = None destination = None try: af = dns.inet.af_for_address(where) destination = where except Exception: if where_must_be_address: raise # URLs are ok so eat the exception if source: saf = dns.inet.af_for_address(source) if af: # We know the destination af, so source had better agree! if saf != af: raise ValueError( "different address families for source and destination" ) else: # We didn't know the destination af, but we know the source, # so that's our af. af = saf if source_port and not source: # Caller has specified a source_port but not an address, so we # need to return a source, and we need to use the appropriate # wildcard address as the address. try: source = dns.inet.any_for_af(af) except Exception: # we catch this and raise ValueError for backwards compatibility raise ValueError("source_port specified but address family is unknown") # Convert high-level (address, port) tuples into low-level address # tuples. if destination: destination = dns.inet.low_level_address_tuple((destination, port), af) if source: source = dns.inet.low_level_address_tuple((source, source_port), af) return (af, destination, source) def _make_socket(af, type, source, ssl_context=None, server_hostname=None): s = socket_factory(af, type) try: s.setblocking(False) if source is not None: s.bind(source) if ssl_context: # LGTM gets a false positive here, as our default context is OK return ssl_context.wrap_socket( s, do_handshake_on_connect=False, # lgtm[py/insecure-protocol] server_hostname=server_hostname, ) else: return s except Exception: s.close() raise def _maybe_get_resolver( resolver: Optional["dns.resolver.Resolver"], ) -> "dns.resolver.Resolver": # We need a separate method for this to avoid overriding the global # variable "dns" with the as-yet undefined local variable "dns" # in https(). if resolver is None: # pylint: disable=import-outside-toplevel,redefined-outer-name import dns.resolver resolver = dns.resolver.Resolver() return resolver class HTTPVersion(enum.IntEnum): """Which version of HTTP should be used? DEFAULT will select the first version from the list [2, 1.1, 3] that is available. """ DEFAULT = 0 HTTP_1 = 1 H1 = 1 HTTP_2 = 2 H2 = 2 HTTP_3 = 3 H3 = 3 def https( q: dns.message.Message, where: str, timeout: Optional[float] = None, port: int = 443, source: Optional[str] = None, source_port: int = 0, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, session: Optional[Any] = None, path: str = "/dns-query", post: bool = True, bootstrap_address: Optional[str] = None, verify: Union[bool, str] = True, resolver: Optional["dns.resolver.Resolver"] = None, family: int = socket.AF_UNSPEC, http_version: HTTPVersion = HTTPVersion.DEFAULT, ) -> dns.message.Message: """Return the response obtained after sending a query via DNS-over-HTTPS. *q*, a ``dns.message.Message``, the query to send. *where*, a ``str``, the nameserver IP address or the full URL. If an IP address is given, the URL will be constructed using the following schema: https://:/. *timeout*, a ``float`` or ``None``, the number of seconds to wait before the query times out. If ``None``, the default, wait forever. *port*, a ``int``, the port to send the query to. The default is 443. *source*, a ``str`` 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. *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. *session*, an ``httpx.Client``. If provided, the client session to use to send the queries. *path*, a ``str``. If *where* is an IP address, then *path* will be used to construct the URL to send the DNS query to. *post*, a ``bool``. If ``True``, the default, POST method will be used. *bootstrap_address*, a ``str``, the IP address to use to bypass resolution. *verify*, a ``bool`` or ``str``. If a ``True``, then TLS certificate verification of the server is done using the default CA bundle; if ``False``, then no verification is done; if a `str` then it specifies the path to a certificate file or directory which will be used for verification. *resolver*, a ``dns.resolver.Resolver`` or ``None``, the resolver to use for resolution of hostnames in URLs. If not specified, a new resolver with a default configuration will be used; note this is *not* the default resolver as that resolver might have been configured to use DoH causing a chicken-and-egg problem. This parameter only has an effect if the HTTP library is httpx. *family*, an ``int``, the address family. If socket.AF_UNSPEC (the default), both A and AAAA records will be retrieved. *http_version*, a ``dns.query.HTTPVersion``, indicating which HTTP version to use. Returns a ``dns.message.Message``. """ (af, _, the_source) = _destination_and_source( where, port, source, source_port, False ) if af is not None and dns.inet.is_address(where): if af == socket.AF_INET: url = f"https://{where}:{port}{path}" elif af == socket.AF_INET6: url = f"https://[{where}]:{port}{path}" else: url = where extensions = {} if bootstrap_address is None: # pylint: disable=possibly-used-before-assignment parsed = urllib.parse.urlparse(url) if parsed.hostname is None: raise ValueError("no hostname in URL") if dns.inet.is_address(parsed.hostname): bootstrap_address = parsed.hostname extensions["sni_hostname"] = parsed.hostname if parsed.port is not None: port = parsed.port if http_version == HTTPVersion.H3 or ( http_version == HTTPVersion.DEFAULT and not have_doh ): if bootstrap_address is None: resolver = _maybe_get_resolver(resolver) assert parsed.hostname is not None # for mypy answers = resolver.resolve_name(parsed.hostname, family) bootstrap_address = random.choice(list(answers.addresses())) return _http3( q, bootstrap_address, url, timeout, port, source, source_port, one_rr_per_rrset, ignore_trailing, verify=verify, post=post, ) if not have_doh: raise NoDOH # pragma: no cover if session and not isinstance(session, httpx.Client): raise ValueError("session parameter must be an httpx.Client") wire = q.to_wire() headers = {"accept": "application/dns-message"} h1 = http_version in (HTTPVersion.H1, HTTPVersion.DEFAULT) h2 = http_version in (HTTPVersion.H2, HTTPVersion.DEFAULT) # set source port and source address if the_source is None: local_address = None local_port = 0 else: local_address = the_source[0] local_port = the_source[1] if session: cm: contextlib.AbstractContextManager = contextlib.nullcontext(session) else: transport = _HTTPTransport( local_address=local_address, http1=h1, http2=h2, verify=verify, local_port=local_port, bootstrap_address=bootstrap_address, resolver=resolver, family=family, ) cm = httpx.Client(http1=h1, http2=h2, verify=verify, transport=transport) with cm as session: # see https://tools.ietf.org/html/rfc8484#section-4.1.1 for DoH # GET and POST examples if post: headers.update( { "content-type": "application/dns-message", "content-length": str(len(wire)), } ) response = session.post( url, headers=headers, content=wire, timeout=timeout, extensions=extensions, ) else: wire = base64.urlsafe_b64encode(wire).rstrip(b"=") twire = wire.decode() # httpx does a repr() if we give it bytes response = session.get( url, headers=headers, timeout=timeout, params={"dns": twire}, extensions=extensions, ) # see https://tools.ietf.org/html/rfc8484#section-4.2.1 for info about DoH # status codes if response.status_code < 200 or response.status_code > 299: raise ValueError( f"{where} responded with status code {response.status_code}" f"\nResponse body: {response.content}" ) r = dns.message.from_wire( response.content, keyring=q.keyring, request_mac=q.request_mac, one_rr_per_rrset=one_rr_per_rrset, ignore_trailing=ignore_trailing, ) r.time = response.elapsed.total_seconds() if not q.is_response(r): raise BadResponse return r def _find_header(headers: dns.quic.Headers, name: bytes) -> bytes: if headers is None: raise KeyError for header, value in headers: if header == name: return value raise KeyError def _check_status(headers: dns.quic.Headers, peer: str, wire: bytes) -> None: value = _find_header(headers, b":status") if value is None: raise SyntaxError("no :status header in response") status = int(value) if status < 0: raise SyntaxError("status is negative") if status < 200 or status > 299: error = "" if len(wire) > 0: try: error = ": " + wire.decode() except Exception: pass raise ValueError(f"{peer} responded with status code {status}{error}") def _http3( q: dns.message.Message, where: str, url: str, timeout: Optional[float] = None, port: int = 853, source: Optional[str] = None, source_port: int = 0, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, verify: Union[bool, str] = True, hostname: Optional[str] = None, post: bool = True, ) -> dns.message.Message: if not dns.quic.have_quic: raise NoDOH("DNS-over-HTTP3 is not available.") # pragma: no cover url_parts = urllib.parse.urlparse(url) hostname = url_parts.hostname if url_parts.port is not None: port = url_parts.port q.id = 0 wire = q.to_wire() manager = dns.quic.SyncQuicManager( verify_mode=verify, server_name=hostname, h3=True ) with manager: connection = manager.connect(where, port, source, source_port) (start, expiration) = _compute_times(timeout) with connection.make_stream(timeout) as stream: stream.send_h3(url, wire, post) wire = stream.receive(_remaining(expiration)) _check_status(stream.headers(), where, wire) finish = time.time() r = dns.message.from_wire( wire, keyring=q.keyring, request_mac=q.request_mac, one_rr_per_rrset=one_rr_per_rrset, ignore_trailing=ignore_trailing, ) r.time = max(finish - start, 0.0) if not q.is_response(r): raise BadResponse return r def _udp_recv(sock, max_size, expiration): """Reads a datagram from the socket. A Timeout exception will be raised if the operation is not completed by the expiration time. """ while True: try: return sock.recvfrom(max_size) except BlockingIOError: _wait_for_readable(sock, expiration) def _udp_send(sock, data, destination, expiration): """Sends the specified datagram to destination over the socket. A Timeout exception will be raised if the operation is not completed by the expiration time. """ while True: try: if destination: return sock.sendto(data, destination) else: return sock.send(data) except BlockingIOError: # pragma: no cover _wait_for_writable(sock, expiration) def send_udp( sock: Any, what: Union[dns.message.Message, bytes], destination: Any, expiration: Optional[float] = None, ) -> Tuple[int, float]: """Send a DNS message to the specified UDP socket. *sock*, a ``socket``. *what*, a ``bytes`` or ``dns.message.Message``, the message to send. *destination*, a destination tuple appropriate for the address family of the socket, specifying where to send the query. *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() sent_time = time.time() n = _udp_send(sock, what, destination, expiration) return (n, sent_time) def receive_udp( sock: Any, destination: Optional[Any] = None, expiration: Optional[float] = None, ignore_unexpected: bool = False, one_rr_per_rrset: bool = False, keyring: Optional[Dict[dns.name.Name, dns.tsig.Key]] = None, request_mac: Optional[bytes] = b"", ignore_trailing: bool = False, raise_on_truncation: bool = False, ignore_errors: bool = False, query: Optional[dns.message.Message] = None, ) -> Any: """Read a DNS message from a UDP socket. *sock*, a ``socket``. *destination*, a destination tuple appropriate for the address family of the socket, specifying where the message is expected to arrive from. When receiving a response, this would be 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 ``bytes`` or ``None``, the MAC of the request (for TSIG). *ignore_trailing*, a ``bool``. If ``True``, ignore trailing junk at end of the received message. *raise_on_truncation*, a ``bool``. If ``True``, raise an exception if the TC bit is set. Raises if the message is malformed, if network errors occur, of if there is a timeout. If *destination* is not ``None``, returns a ``(dns.message.Message, float)`` tuple of the received message and the received time. If *destination* is ``None``, returns a ``(dns.message.Message, float, tuple)`` tuple of the received message, the received time, and the address where the message arrived from. *ignore_errors*, a ``bool``. If various format errors or response mismatches occur, ignore them and keep listening for a valid response. The default is ``False``. *query*, a ``dns.message.Message`` or ``None``. If not ``None`` and *ignore_errors* is ``True``, check that the received message is a response to this query, and if not keep listening for a valid response. """ wire = b"" while True: (wire, from_address) = _udp_recv(sock, 65535, expiration) if not _matches_destination( sock.family, from_address, destination, ignore_unexpected ): continue received_time = time.time() try: r = dns.message.from_wire( wire, keyring=keyring, request_mac=request_mac, one_rr_per_rrset=one_rr_per_rrset, ignore_trailing=ignore_trailing, raise_on_truncation=raise_on_truncation, ) except dns.message.Truncated as e: # If we got Truncated and not FORMERR, we at least got the header with TC # set, and very likely the question section, so we'll re-raise if the # message seems to be a response as we need to know when truncation happens. # We need to check that it seems to be a response as we don't want a random # injected message with TC set to cause us to bail out. if ( ignore_errors and query is not None and not query.is_response(e.message()) ): continue else: raise except Exception: if ignore_errors: continue else: raise if ignore_errors and query is not None and not query.is_response(r): continue if destination: return (r, received_time) else: return (r, received_time, from_address) def udp( q: dns.message.Message, where: str, timeout: Optional[float] = None, port: int = 53, source: Optional[str] = None, source_port: int = 0, ignore_unexpected: bool = False, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, raise_on_truncation: bool = False, sock: Optional[Any] = None, ignore_errors: bool = False, ) -> dns.message.Message: """Return the response obtained after sending a query via UDP. *q*, a ``dns.message.Message``, the query to send *where*, a ``str`` 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. *source*, a ``str`` 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. *raise_on_truncation*, a ``bool``. If ``True``, raise an exception if the TC bit is set. *sock*, a ``socket.socket``, or ``None``, the socket to use for the query. If ``None``, the default, a socket is created. Note that if a socket is provided, it must be a nonblocking datagram socket, and the *source* and *source_port* are ignored. *ignore_errors*, a ``bool``. If various format errors or response mismatches occur, ignore them and keep listening for a valid response. The default is ``False``. Returns a ``dns.message.Message``. """ wire = q.to_wire() (af, destination, source) = _destination_and_source( where, port, source, source_port ) (begin_time, expiration) = _compute_times(timeout) if sock: cm: contextlib.AbstractContextManager = contextlib.nullcontext(sock) else: cm = _make_socket(af, socket.SOCK_DGRAM, source) with cm as s: 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, raise_on_truncation, ignore_errors, q, ) r.time = received_time - begin_time # We don't need to check q.is_response() if we are in ignore_errors mode # as receive_udp() will have checked it. if not (ignore_errors or q.is_response(r)): raise BadResponse return r assert ( False # help mypy figure out we can't get here lgtm[py/unreachable-statement] ) def udp_with_fallback( q: dns.message.Message, where: str, timeout: Optional[float] = None, port: int = 53, source: Optional[str] = None, source_port: int = 0, ignore_unexpected: bool = False, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, udp_sock: Optional[Any] = None, tcp_sock: Optional[Any] = None, ignore_errors: bool = False, ) -> Tuple[dns.message.Message, bool]: """Return the response to the query, trying UDP first and falling back to TCP if UDP results in a truncated response. *q*, a ``dns.message.Message``, the query to send *where*, a ``str`` 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. *source*, a ``str`` 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. *udp_sock*, a ``socket.socket``, or ``None``, the socket to use for the UDP query. If ``None``, the default, a socket is created. Note that if a socket is provided, it must be a nonblocking datagram socket, and the *source* and *source_port* are ignored for the UDP query. *tcp_sock*, a ``socket.socket``, or ``None``, the connected socket to use for the TCP query. If ``None``, the default, a socket is created. Note that if a socket is provided, it must be a nonblocking connected stream socket, and *where*, *source* and *source_port* are ignored for the TCP query. *ignore_errors*, a ``bool``. If various format errors or response mismatches occur while listening for UDP, ignore them and keep listening for a valid response. The default is ``False``. Returns a (``dns.message.Message``, tcp) tuple where tcp is ``True`` if and only if TCP was used. """ try: response = udp( q, where, timeout, port, source, source_port, ignore_unexpected, one_rr_per_rrset, ignore_trailing, True, udp_sock, ignore_errors, ) return (response, False) except dns.message.Truncated: response = tcp( q, where, timeout, port, source, source_port, one_rr_per_rrset, ignore_trailing, tcp_sock, ) return (response, True) def _net_read(sock, count, expiration): """Read the specified number of bytes from sock. Keep trying until we either get the desired amount, or we hit EOF. A Timeout exception will be raised if the operation is not completed by the expiration time. """ s = b"" while count > 0: try: n = sock.recv(count) if n == b"": raise EOFError("EOF") count -= len(n) s += n except (BlockingIOError, ssl.SSLWantReadError): _wait_for_readable(sock, expiration) except ssl.SSLWantWriteError: # pragma: no cover _wait_for_writable(sock, expiration) return s def _net_write(sock, data, expiration): """Write the specified data to the socket. A Timeout exception will be raised if the operation is not completed by the expiration time. """ current = 0 l = len(data) while current < l: try: current += sock.send(data[current:]) except (BlockingIOError, ssl.SSLWantWriteError): _wait_for_writable(sock, expiration) except ssl.SSLWantReadError: # pragma: no cover _wait_for_readable(sock, expiration) def send_tcp( sock: Any, what: Union[dns.message.Message, bytes], expiration: Optional[float] = None, ) -> Tuple[int, float]: """Send a DNS message to the specified TCP socket. *sock*, a ``socket``. *what*, a ``bytes`` 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): tcpmsg = what.to_wire(prepend_length=True) else: # 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 = len(what).to_bytes(2, "big") + what sent_time = time.time() _net_write(sock, tcpmsg, expiration) return (len(tcpmsg), sent_time) def receive_tcp( sock: Any, expiration: Optional[float] = None, one_rr_per_rrset: bool = False, keyring: Optional[Dict[dns.name.Name, dns.tsig.Key]] = None, request_mac: Optional[bytes] = b"", ignore_trailing: bool = False, ) -> Tuple[dns.message.Message, float]: """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 ``bytes`` or ``None``, 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, float)`` tuple of the received message and the received time. """ 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, expiration): err = s.connect_ex(address) if err == 0: return if err in (errno.EINPROGRESS, errno.EWOULDBLOCK, errno.EALREADY): _wait_for_writable(s, expiration) err = s.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) if err != 0: raise OSError(err, os.strerror(err)) def tcp( q: dns.message.Message, where: str, timeout: Optional[float] = None, port: int = 53, source: Optional[str] = None, source_port: int = 0, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, sock: Optional[Any] = None, ) -> dns.message.Message: """Return the response obtained after sending a query via TCP. *q*, a ``dns.message.Message``, the query to send *where*, a ``str`` 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. *source*, a ``str`` 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. *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. *sock*, a ``socket.socket``, or ``None``, the connected socket to use for the query. If ``None``, the default, a socket is created. Note that if a socket is provided, it must be a nonblocking connected stream socket, and *where*, *port*, *source* and *source_port* are ignored. Returns a ``dns.message.Message``. """ wire = q.to_wire() (begin_time, expiration) = _compute_times(timeout) if sock: cm: contextlib.AbstractContextManager = contextlib.nullcontext(sock) else: (af, destination, source) = _destination_and_source( where, port, source, source_port ) cm = _make_socket(af, socket.SOCK_STREAM, source) with cm as s: if not sock: # pylint: disable=possibly-used-before-assignment _connect(s, destination, expiration) send_tcp(s, wire, expiration) (r, received_time) = receive_tcp( s, expiration, one_rr_per_rrset, q.keyring, q.mac, ignore_trailing ) r.time = received_time - begin_time if not q.is_response(r): raise BadResponse return r assert ( False # help mypy figure out we can't get here lgtm[py/unreachable-statement] ) def _tls_handshake(s, expiration): while True: try: s.do_handshake() return except ssl.SSLWantReadError: _wait_for_readable(s, expiration) except ssl.SSLWantWriteError: # pragma: no cover _wait_for_writable(s, expiration) def _make_dot_ssl_context( server_hostname: Optional[str], verify: Union[bool, str] ) -> ssl.SSLContext: cafile: Optional[str] = None capath: Optional[str] = None if isinstance(verify, str): if os.path.isfile(verify): cafile = verify elif os.path.isdir(verify): capath = verify else: raise ValueError("invalid verify string") ssl_context = ssl.create_default_context(cafile=cafile, capath=capath) ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2 if server_hostname is None: ssl_context.check_hostname = False ssl_context.set_alpn_protocols(["dot"]) if verify is False: ssl_context.verify_mode = ssl.CERT_NONE return ssl_context def tls( q: dns.message.Message, where: str, timeout: Optional[float] = None, port: int = 853, source: Optional[str] = None, source_port: int = 0, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, sock: Optional[ssl.SSLSocket] = None, ssl_context: Optional[ssl.SSLContext] = None, server_hostname: Optional[str] = None, verify: Union[bool, str] = True, ) -> dns.message.Message: """Return the response obtained after sending a query via TLS. *q*, a ``dns.message.Message``, the query to send *where*, a ``str`` 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 853. *source*, a ``str`` 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. *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. *sock*, an ``ssl.SSLSocket``, or ``None``, the socket to use for the query. If ``None``, the default, a socket is created. Note that if a socket is provided, it must be a nonblocking connected SSL stream socket, and *where*, *port*, *source*, *source_port*, and *ssl_context* are ignored. *ssl_context*, an ``ssl.SSLContext``, the context to use when establishing a TLS connection. If ``None``, the default, creates one with the default configuration. *server_hostname*, a ``str`` containing the server's hostname. The default is ``None``, which means that no hostname is known, and if an SSL context is created, hostname checking will be disabled. *verify*, a ``bool`` or ``str``. If a ``True``, then TLS certificate verification of the server is done using the default CA bundle; if ``False``, then no verification is done; if a `str` then it specifies the path to a certificate file or directory which will be used for verification. Returns a ``dns.message.Message``. """ if sock: # # If a socket was provided, there's no special TLS handling needed. # return tcp( q, where, timeout, port, source, source_port, one_rr_per_rrset, ignore_trailing, sock, ) wire = q.to_wire() (begin_time, expiration) = _compute_times(timeout) (af, destination, source) = _destination_and_source( where, port, source, source_port ) if ssl_context is None and not sock: ssl_context = _make_dot_ssl_context(server_hostname, verify) with _make_socket( af, socket.SOCK_STREAM, source, ssl_context=ssl_context, server_hostname=server_hostname, ) as s: _connect(s, destination, expiration) _tls_handshake(s, expiration) send_tcp(s, wire, expiration) (r, received_time) = receive_tcp( s, expiration, one_rr_per_rrset, q.keyring, q.mac, ignore_trailing ) r.time = received_time - begin_time if not q.is_response(r): raise BadResponse return r assert ( False # help mypy figure out we can't get here lgtm[py/unreachable-statement] ) def quic( q: dns.message.Message, where: str, timeout: Optional[float] = None, port: int = 853, source: Optional[str] = None, source_port: int = 0, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, connection: Optional[dns.quic.SyncQuicConnection] = None, verify: Union[bool, str] = True, hostname: Optional[str] = None, server_hostname: Optional[str] = None, ) -> dns.message.Message: """Return the response obtained after sending a query via DNS-over-QUIC. *q*, a ``dns.message.Message``, the query to send. *where*, a ``str``, the nameserver IP address. *timeout*, a ``float`` or ``None``, the number of seconds to wait before the query times out. If ``None``, the default, wait forever. *port*, a ``int``, the port to send the query to. The default is 853. *source*, a ``str`` 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. *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. *connection*, a ``dns.quic.SyncQuicConnection``. If provided, the connection to use to send the query. *verify*, a ``bool`` or ``str``. If a ``True``, then TLS certificate verification of the server is done using the default CA bundle; if ``False``, then no verification is done; if a `str` then it specifies the path to a certificate file or directory which will be used for verification. *hostname*, a ``str`` containing the server's hostname or ``None``. The default is ``None``, which means that no hostname is known, and if an SSL context is created, hostname checking will be disabled. This value is ignored if *url* is not ``None``. *server_hostname*, a ``str`` or ``None``. This item is for backwards compatibility only, and has the same meaning as *hostname*. Returns a ``dns.message.Message``. """ if not dns.quic.have_quic: raise NoDOQ("DNS-over-QUIC is not available.") # pragma: no cover if server_hostname is not None and hostname is None: hostname = server_hostname q.id = 0 wire = q.to_wire() the_connection: dns.quic.SyncQuicConnection the_manager: dns.quic.SyncQuicManager if connection: manager: contextlib.AbstractContextManager = contextlib.nullcontext(None) the_connection = connection else: manager = dns.quic.SyncQuicManager(verify_mode=verify, server_name=hostname) the_manager = manager # for type checking happiness with manager: if not connection: the_connection = the_manager.connect(where, port, source, source_port) (start, expiration) = _compute_times(timeout) with the_connection.make_stream(timeout) as stream: stream.send(wire, True) wire = stream.receive(_remaining(expiration)) finish = time.time() r = dns.message.from_wire( wire, keyring=q.keyring, request_mac=q.request_mac, one_rr_per_rrset=one_rr_per_rrset, ignore_trailing=ignore_trailing, ) r.time = max(finish - start, 0.0) if not q.is_response(r): raise BadResponse return r class UDPMode(enum.IntEnum): """How should UDP be used in an IXFR from :py:func:`inbound_xfr()`? NEVER means "never use UDP; always use TCP" TRY_FIRST means "try to use UDP but fall back to TCP if needed" ONLY means "raise ``dns.xfr.UseTCP`` if trying UDP does not succeed" """ NEVER = 0 TRY_FIRST = 1 ONLY = 2 def _inbound_xfr( txn_manager: dns.transaction.TransactionManager, s: socket.socket, query: dns.message.Message, serial: Optional[int], timeout: Optional[float], expiration: float, ) -> Any: """Given a socket, does the zone transfer.""" rdtype = query.question[0].rdtype is_ixfr = rdtype == dns.rdatatype.IXFR origin = txn_manager.from_wire_origin() wire = query.to_wire() is_udp = s.type == socket.SOCK_DGRAM if is_udp: _udp_send(s, wire, None, expiration) else: tcpmsg = struct.pack("!H", len(wire)) + wire _net_write(s, tcpmsg, expiration) with dns.xfr.Inbound(txn_manager, rdtype, serial, is_udp) as inbound: done = False tsig_ctx = None while not done: (_, mexpiration) = _compute_times(timeout) if mexpiration is None or ( expiration is not None and mexpiration > expiration ): mexpiration = expiration if is_udp: (rwire, _) = _udp_recv(s, 65535, mexpiration) else: ldata = _net_read(s, 2, mexpiration) (l,) = struct.unpack("!H", ldata) rwire = _net_read(s, l, mexpiration) r = dns.message.from_wire( rwire, keyring=query.keyring, request_mac=query.mac, xfr=True, origin=origin, tsig_ctx=tsig_ctx, multi=(not is_udp), one_rr_per_rrset=is_ixfr, ) done = inbound.process_message(r) yield r tsig_ctx = r.tsig_ctx if query.keyring and not r.had_tsig: raise dns.exception.FormError("missing TSIG") def xfr( where: str, zone: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.AXFR, rdclass: Union[dns.rdataclass.RdataClass, str] = dns.rdataclass.IN, timeout: Optional[float] = None, port: int = 53, keyring: Optional[Dict[dns.name.Name, dns.tsig.Key]] = None, keyname: Optional[Union[dns.name.Name, str]] = None, relativize: bool = True, lifetime: Optional[float] = None, source: Optional[str] = None, source_port: int = 0, serial: int = 0, use_udp: bool = False, keyalgorithm: Union[dns.name.Name, str] = dns.tsig.default_algorithm, ) -> Any: """Return a generator for the responses to a zone transfer. *where*, a ``str`` containing an IPv4 or IPv6 address, where to send the message. *zone*, a ``dns.name.Name`` or ``str``, the name of the zone to transfer. *rdtype*, an ``int`` or ``str``, the type of zone transfer. The default is ``dns.rdatatype.AXFR``. ``dns.rdatatype.IXFR`` can be used to do an incremental transfer instead. *rdclass*, an ``int`` or ``str``, the class of the zone transfer. The default is ``dns.rdataclass.IN``. *timeout*, a ``float``, the number of seconds to wait for each response message. If None, the default, wait forever. *port*, an ``int``, the port send the message to. The default is 53. *keyring*, a ``dict``, the keyring to use for TSIG. *keyname*, a ``dns.name.Name`` or ``str``, the name of the TSIG key to use. *relativize*, a ``bool``. If ``True``, all names in the zone will be relativized to the zone origin. It is essential that the relativize setting matches the one specified to ``dns.zone.from_xfr()`` if using this generator to make a zone. *lifetime*, a ``float``, the total number of seconds to spend doing the transfer. If ``None``, the default, then there is no limit on the time the transfer may take. *source*, a ``str`` 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. *serial*, an ``int``, the SOA serial number to use as the base for an IXFR diff sequence (only meaningful if *rdtype* is ``dns.rdatatype.IXFR``). *use_udp*, a ``bool``. If ``True``, use UDP (only meaningful for IXFR). *keyalgorithm*, a ``dns.name.Name`` or ``str``, the TSIG algorithm to use. Raises on errors, and so does the generator. Returns a generator of ``dns.message.Message`` objects. """ class DummyTransactionManager(dns.transaction.TransactionManager): def __init__(self, origin, relativize): self.info = (origin, relativize, dns.name.empty if relativize else origin) def origin_information(self): return self.info def get_class(self) -> dns.rdataclass.RdataClass: raise NotImplementedError # pragma: no cover def reader(self): raise NotImplementedError # pragma: no cover def writer(self, replacement: bool = False) -> dns.transaction.Transaction: class DummyTransaction: def nop(self, *args, **kw): pass def __getattr__(self, _): return self.nop return cast(dns.transaction.Transaction, DummyTransaction()) if isinstance(zone, str): zone = dns.name.from_text(zone) rdtype = dns.rdatatype.RdataType.make(rdtype) q = dns.message.make_query(zone, rdtype, rdclass) if rdtype == dns.rdatatype.IXFR: rrset = q.find_rrset( q.authority, zone, dns.rdataclass.IN, dns.rdatatype.SOA, create=True ) soa = dns.rdata.from_text("IN", "SOA", ". . %u 0 0 0 0" % serial) rrset.add(soa, 0) if keyring is not None: q.use_tsig(keyring, keyname, algorithm=keyalgorithm) (af, destination, source) = _destination_and_source( where, port, source, source_port ) (_, expiration) = _compute_times(lifetime) tm = DummyTransactionManager(zone, relativize) if use_udp and rdtype != dns.rdatatype.IXFR: raise ValueError("cannot do a UDP AXFR") sock_type = socket.SOCK_DGRAM if use_udp else socket.SOCK_STREAM with _make_socket(af, sock_type, source) as s: _connect(s, destination, expiration) yield from _inbound_xfr(tm, s, q, serial, timeout, expiration) def inbound_xfr( where: str, txn_manager: dns.transaction.TransactionManager, query: Optional[dns.message.Message] = None, port: int = 53, timeout: Optional[float] = None, lifetime: Optional[float] = None, source: Optional[str] = None, source_port: int = 0, udp_mode: UDPMode = UDPMode.NEVER, ) -> None: """Conduct an inbound transfer and apply it via a transaction from the txn_manager. *where*, a ``str`` containing an IPv4 or IPv6 address, where to send the message. *txn_manager*, a ``dns.transaction.TransactionManager``, the txn_manager for this transfer (typically a ``dns.zone.Zone``). *query*, the query to send. If not supplied, a default query is constructed using information from the *txn_manager*. *port*, an ``int``, the port send the message to. The default is 53. *timeout*, a ``float``, the number of seconds to wait for each response message. If None, the default, wait forever. *lifetime*, a ``float``, the total number of seconds to spend doing the transfer. If ``None``, the default, then there is no limit on the time the transfer may take. *source*, a ``str`` 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. *udp_mode*, a ``dns.query.UDPMode``, determines how UDP is used for IXFRs. The default is ``dns.UDPMode.NEVER``, i.e. only use TCP. Other possibilities are ``dns.UDPMode.TRY_FIRST``, which means "try UDP but fallback to TCP if needed", and ``dns.UDPMode.ONLY``, which means "try UDP and raise ``dns.xfr.UseTCP`` if it does not succeed. Raises on errors. """ if query is None: (query, serial) = dns.xfr.make_query(txn_manager) else: serial = dns.xfr.extract_serial_from_query(query) (af, destination, source) = _destination_and_source( where, port, source, source_port ) (_, expiration) = _compute_times(lifetime) if query.question[0].rdtype == dns.rdatatype.IXFR and udp_mode != UDPMode.NEVER: with _make_socket(af, socket.SOCK_DGRAM, source) as s: _connect(s, destination, expiration) try: for _ in _inbound_xfr( txn_manager, s, query, serial, timeout, expiration ): pass return except dns.xfr.UseTCP: if udp_mode == UDPMode.ONLY: raise with _make_socket(af, socket.SOCK_STREAM, source) as s: _connect(s, destination, expiration) for _ in _inbound_xfr(txn_manager, s, query, serial, timeout, expiration): pass dnspython-2.7.0/dns/rcode.py0000644000000000000000000001007413615410400012722 0ustar00# 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 # 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. """DNS Result Codes.""" from typing import Tuple import dns.enum import dns.exception class Rcode(dns.enum.IntEnum): #: No error NOERROR = 0 #: Format error FORMERR = 1 #: Server failure SERVFAIL = 2 #: Name does not exist ("Name Error" in RFC 1025 terminology). NXDOMAIN = 3 #: Not implemented NOTIMP = 4 #: Refused REFUSED = 5 #: Name exists. YXDOMAIN = 6 #: RRset exists. YXRRSET = 7 #: RRset does not exist. NXRRSET = 8 #: Not authoritative. NOTAUTH = 9 #: Name not in zone. NOTZONE = 10 #: DSO-TYPE Not Implemented DSOTYPENI = 11 #: Bad EDNS version. BADVERS = 16 #: TSIG Signature Failure BADSIG = 16 #: Key not recognized. BADKEY = 17 #: Signature out of time window. BADTIME = 18 #: Bad TKEY Mode. BADMODE = 19 #: Duplicate key name. BADNAME = 20 #: Algorithm not supported. BADALG = 21 #: Bad Truncation BADTRUNC = 22 #: Bad/missing Server Cookie BADCOOKIE = 23 @classmethod def _maximum(cls): return 4095 @classmethod def _unknown_exception_class(cls): return UnknownRcode class UnknownRcode(dns.exception.DNSException): """A DNS rcode is unknown.""" def from_text(text: str) -> Rcode: """Convert text into an rcode. *text*, a ``str``, the textual rcode or an integer in textual form. Raises ``dns.rcode.UnknownRcode`` if the rcode mnemonic is unknown. Returns a ``dns.rcode.Rcode``. """ return Rcode.from_text(text) def from_flags(flags: int, ednsflags: int) -> Rcode: """Return the rcode value encoded by flags and ednsflags. *flags*, an ``int``, the DNS flags field. *ednsflags*, an ``int``, the EDNS flags field. Raises ``ValueError`` if rcode is < 0 or > 4095 Returns a ``dns.rcode.Rcode``. """ value = (flags & 0x000F) | ((ednsflags >> 20) & 0xFF0) return Rcode.make(value) def to_flags(value: Rcode) -> Tuple[int, int]: """Return a (flags, ednsflags) tuple which encodes the rcode. *value*, a ``dns.rcode.Rcode``, the rcode. Raises ``ValueError`` if rcode is < 0 or > 4095. Returns an ``(int, int)`` tuple. """ if value < 0 or value > 4095: raise ValueError("rcode must be >= 0 and <= 4095") v = value & 0xF ev = (value & 0xFF0) << 20 return (v, ev) def to_text(value: Rcode, tsig: bool = False) -> str: """Convert rcode into text. *value*, a ``dns.rcode.Rcode``, the rcode. Raises ``ValueError`` if rcode is < 0 or > 4095. Returns a ``str``. """ if tsig and value == Rcode.BADVERS: return "BADSIG" return Rcode.to_text(value) ### BEGIN generated Rcode constants NOERROR = Rcode.NOERROR FORMERR = Rcode.FORMERR SERVFAIL = Rcode.SERVFAIL NXDOMAIN = Rcode.NXDOMAIN NOTIMP = Rcode.NOTIMP REFUSED = Rcode.REFUSED YXDOMAIN = Rcode.YXDOMAIN YXRRSET = Rcode.YXRRSET NXRRSET = Rcode.NXRRSET NOTAUTH = Rcode.NOTAUTH NOTZONE = Rcode.NOTZONE DSOTYPENI = Rcode.DSOTYPENI BADVERS = Rcode.BADVERS BADSIG = Rcode.BADSIG BADKEY = Rcode.BADKEY BADTIME = Rcode.BADTIME BADMODE = Rcode.BADMODE BADNAME = Rcode.BADNAME BADALG = Rcode.BADALG BADTRUNC = Rcode.BADTRUNC BADCOOKIE = Rcode.BADCOOKIE ### END generated Rcode constants dnspython-2.7.0/dns/rdata.py0000644000000000000000000007445613615410400012737 0ustar00# 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 # 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. """DNS rdata.""" import base64 import binascii import inspect import io import itertools import random from importlib import import_module from typing import Any, Dict, Optional, Tuple, Union import dns.exception import dns.immutable import dns.ipv4 import dns.ipv6 import dns.name import dns.rdataclass import dns.rdatatype import dns.tokenizer import dns.ttl import dns.wire _chunksize = 32 # We currently allow comparisons for rdata with relative names for backwards # compatibility, but in the future we will not, as these kinds of comparisons # can lead to subtle bugs if code is not carefully written. # # This switch allows the future behavior to be turned on so code can be # tested with it. _allow_relative_comparisons = True class NoRelativeRdataOrdering(dns.exception.DNSException): """An attempt was made to do an ordered comparison of one or more rdata with relative names. The only reliable way of sorting rdata is to use non-relativized rdata. """ def _wordbreak(data, chunksize=_chunksize, separator=b" "): """Break a binary string into chunks of chunksize characters separated by a space. """ if not chunksize: return data.decode() return separator.join( [data[i : i + chunksize] for i in range(0, len(data), chunksize)] ).decode() # pylint: disable=unused-argument def _hexify(data, chunksize=_chunksize, separator=b" ", **kw): """Convert a binary string into its hex encoding, broken up into chunks of chunksize characters separated by a separator. """ return _wordbreak(binascii.hexlify(data), chunksize, separator) def _base64ify(data, chunksize=_chunksize, separator=b" ", **kw): """Convert a binary string into its base64 encoding, broken up into chunks of chunksize characters separated by a separator. """ return _wordbreak(base64.b64encode(data), chunksize, separator) # pylint: enable=unused-argument __escaped = b'"\\' def _escapify(qstring): """Escape the characters in a quoted string which need it.""" if isinstance(qstring, str): qstring = qstring.encode() if not isinstance(qstring, bytearray): qstring = bytearray(qstring) text = "" for c in qstring: if c in __escaped: text += "\\" + chr(c) elif c >= 0x20 and c < 0x7F: text += chr(c) else: text += "\\%03d" % c return text def _truncate_bitmap(what): """Determine the index of greatest byte that isn't all zeros, and return the bitmap that contains all the bytes less than that index. """ for i in range(len(what) - 1, -1, -1): if what[i] != 0: return what[0 : i + 1] return what[0:1] # So we don't have to edit all the rdata classes... _constify = dns.immutable.constify @dns.immutable.immutable class Rdata: """Base class for all DNS rdata types.""" __slots__ = ["rdclass", "rdtype", "rdcomment"] def __init__(self, rdclass, rdtype): """Initialize an rdata. *rdclass*, an ``int`` is the rdataclass of the Rdata. *rdtype*, an ``int`` is the rdatatype of the Rdata. """ self.rdclass = self._as_rdataclass(rdclass) self.rdtype = self._as_rdatatype(rdtype) self.rdcomment = None def _get_all_slots(self): return itertools.chain.from_iterable( getattr(cls, "__slots__", []) for cls in self.__class__.__mro__ ) def __getstate__(self): # We used to try to do a tuple of all slots here, but it # doesn't work as self._all_slots isn't available at # __setstate__() time. Before that we tried to store a tuple # of __slots__, but that didn't work as it didn't store the # slots defined by ancestors. This older way didn't fail # outright, but ended up with partially broken objects, e.g. # if you unpickled an A RR it wouldn't have rdclass and rdtype # attributes, and would compare badly. state = {} for slot in self._get_all_slots(): state[slot] = getattr(self, slot) return state def __setstate__(self, state): for slot, val in state.items(): object.__setattr__(self, slot, val) if not hasattr(self, "rdcomment"): # Pickled rdata from 2.0.x might not have a rdcomment, so add # it if needed. object.__setattr__(self, "rdcomment", None) def covers(self) -> dns.rdatatype.RdataType: """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 SIG or RRSIG, dns.rdatatype.NONE is returned. This is useful when creating rdatasets, allowing the rdataset to contain only RRSIGs of a particular type, e.g. RRSIG(NS). Returns a ``dns.rdatatype.RdataType``. """ return dns.rdatatype.NONE def extended_rdatatype(self) -> int: """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 the "covered" type, if any. Returns an ``int``. """ return self.covers() << 16 | self.rdtype def to_text( self, origin: Optional[dns.name.Name] = None, relativize: bool = True, **kw: Dict[str, Any], ) -> str: """Convert an rdata to text format. Returns a ``str``. """ raise NotImplementedError # pragma: no cover def _to_wire( self, file: Optional[Any], compress: Optional[dns.name.CompressType] = None, origin: Optional[dns.name.Name] = None, canonicalize: bool = False, ) -> None: raise NotImplementedError # pragma: no cover def to_wire( self, file: Optional[Any] = None, compress: Optional[dns.name.CompressType] = None, origin: Optional[dns.name.Name] = None, canonicalize: bool = False, ) -> Optional[bytes]: """Convert an rdata to wire format. Returns a ``bytes`` if no output file was specified, or ``None`` otherwise. """ if file: # We call _to_wire() and then return None explicitly instead of # of just returning the None from _to_wire() as mypy's func-returns-value # unhelpfully errors out with "error: "_to_wire" of "Rdata" does not return # a value (it only ever returns None)" self._to_wire(file, compress, origin, canonicalize) return None else: f = io.BytesIO() self._to_wire(f, compress, origin, canonicalize) return f.getvalue() def to_generic( self, origin: Optional[dns.name.Name] = None ) -> "dns.rdata.GenericRdata": """Creates a dns.rdata.GenericRdata equivalent of this rdata. Returns a ``dns.rdata.GenericRdata``. """ return dns.rdata.GenericRdata( self.rdclass, self.rdtype, self.to_wire(origin=origin) ) def to_digestable(self, origin: Optional[dns.name.Name] = None) -> bytes: """Convert rdata to a format suitable for digesting in hashes. This is also the DNSSEC canonical form. Returns a ``bytes``. """ wire = self.to_wire(origin=origin, canonicalize=True) assert wire is not None # for mypy return wire def __repr__(self): covers = self.covers() if covers == dns.rdatatype.NONE: ctext = "" else: ctext = "(" + dns.rdatatype.to_text(covers) + ")" return ( "" ) def __str__(self): return self.to_text() def _cmp(self, other): """Compare an rdata with another rdata of the same rdtype and rdclass. For rdata with only absolute names: Return < 0 if self < other in the DNSSEC ordering, 0 if self == other, and > 0 if self > other. For rdata with at least one relative names: The rdata sorts before any rdata with only absolute names. When compared with another relative rdata, all names are made absolute as if they were relative to the root, as the proper origin is not available. While this creates a stable ordering, it is NOT guaranteed to be the DNSSEC ordering. In the future, all ordering comparisons for rdata with relative names will be disallowed. """ try: our = self.to_digestable() our_relative = False except dns.name.NeedAbsoluteNameOrOrigin: if _allow_relative_comparisons: our = self.to_digestable(dns.name.root) our_relative = True try: their = other.to_digestable() their_relative = False except dns.name.NeedAbsoluteNameOrOrigin: if _allow_relative_comparisons: their = other.to_digestable(dns.name.root) their_relative = True if _allow_relative_comparisons: if our_relative != their_relative: # For the purpose of comparison, all rdata with at least one # relative name is less than an rdata with only absolute names. if our_relative: return -1 else: return 1 elif our_relative or their_relative: raise NoRelativeRdataOrdering if our == their: return 0 elif our > their: return 1 else: return -1 def __eq__(self, other): if not isinstance(other, Rdata): return False if self.rdclass != other.rdclass or self.rdtype != other.rdtype: return False our_relative = False their_relative = False try: our = self.to_digestable() except dns.name.NeedAbsoluteNameOrOrigin: our = self.to_digestable(dns.name.root) our_relative = True try: their = other.to_digestable() except dns.name.NeedAbsoluteNameOrOrigin: their = other.to_digestable(dns.name.root) their_relative = True if our_relative != their_relative: return False return our == their def __ne__(self, other): if not isinstance(other, Rdata): return True if self.rdclass != other.rdclass or self.rdtype != other.rdtype: return True return not self.__eq__(other) def __lt__(self, other): if ( not isinstance(other, Rdata) or self.rdclass != other.rdclass or self.rdtype != other.rdtype ): return NotImplemented return self._cmp(other) < 0 def __le__(self, other): if ( not isinstance(other, Rdata) or self.rdclass != other.rdclass or self.rdtype != other.rdtype ): return NotImplemented return self._cmp(other) <= 0 def __ge__(self, other): if ( not isinstance(other, Rdata) or self.rdclass != other.rdclass or self.rdtype != other.rdtype ): return NotImplemented return self._cmp(other) >= 0 def __gt__(self, other): if ( not isinstance(other, Rdata) or self.rdclass != other.rdclass or self.rdtype != other.rdtype ): return NotImplemented return self._cmp(other) > 0 def __hash__(self): return hash(self.to_digestable(dns.name.root)) @classmethod def from_text( cls, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, tok: dns.tokenizer.Tokenizer, origin: Optional[dns.name.Name] = None, relativize: bool = True, relativize_to: Optional[dns.name.Name] = None, ) -> "Rdata": raise NotImplementedError # pragma: no cover @classmethod def from_wire_parser( cls, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, parser: dns.wire.Parser, origin: Optional[dns.name.Name] = None, ) -> "Rdata": raise NotImplementedError # pragma: no cover def replace(self, **kwargs: Any) -> "Rdata": """ Create a new Rdata instance based on the instance replace was invoked on. It is possible to pass different parameters to override the corresponding properties of the base Rdata. Any field specific to the Rdata type can be replaced, but the *rdtype* and *rdclass* fields cannot. Returns an instance of the same Rdata subclass as *self*. """ # Get the constructor parameters. parameters = inspect.signature(self.__init__).parameters # type: ignore # Ensure that all of the arguments correspond to valid fields. # Don't allow rdclass or rdtype to be changed, though. for key in kwargs: if key == "rdcomment": continue if key not in parameters: raise AttributeError( f"'{self.__class__.__name__}' object has no attribute '{key}'" ) if key in ("rdclass", "rdtype"): raise AttributeError( f"Cannot overwrite '{self.__class__.__name__}' attribute '{key}'" ) # Construct the parameter list. For each field, use the value in # kwargs if present, and the current value otherwise. args = (kwargs.get(key, getattr(self, key)) for key in parameters) # Create, validate, and return the new object. rd = self.__class__(*args) # The comment is not set in the constructor, so give it special # handling. rdcomment = kwargs.get("rdcomment", self.rdcomment) if rdcomment is not None: object.__setattr__(rd, "rdcomment", rdcomment) return rd # Type checking and conversion helpers. These are class methods as # they don't touch object state and may be useful to others. @classmethod def _as_rdataclass(cls, value): return dns.rdataclass.RdataClass.make(value) @classmethod def _as_rdatatype(cls, value): return dns.rdatatype.RdataType.make(value) @classmethod def _as_bytes( cls, value: Any, encode: bool = False, max_length: Optional[int] = None, empty_ok: bool = True, ) -> bytes: if encode and isinstance(value, str): bvalue = value.encode() elif isinstance(value, bytearray): bvalue = bytes(value) elif isinstance(value, bytes): bvalue = value else: raise ValueError("not bytes") if max_length is not None and len(bvalue) > max_length: raise ValueError("too long") if not empty_ok and len(bvalue) == 0: raise ValueError("empty bytes not allowed") return bvalue @classmethod def _as_name(cls, value): # Note that proper name conversion (e.g. with origin and IDNA # awareness) is expected to be done via from_text. This is just # a simple thing for people invoking the constructor directly. if isinstance(value, str): return dns.name.from_text(value) elif not isinstance(value, dns.name.Name): raise ValueError("not a name") return value @classmethod def _as_uint8(cls, value): if not isinstance(value, int): raise ValueError("not an integer") if value < 0 or value > 255: raise ValueError("not a uint8") return value @classmethod def _as_uint16(cls, value): if not isinstance(value, int): raise ValueError("not an integer") if value < 0 or value > 65535: raise ValueError("not a uint16") return value @classmethod def _as_uint32(cls, value): if not isinstance(value, int): raise ValueError("not an integer") if value < 0 or value > 4294967295: raise ValueError("not a uint32") return value @classmethod def _as_uint48(cls, value): if not isinstance(value, int): raise ValueError("not an integer") if value < 0 or value > 281474976710655: raise ValueError("not a uint48") return value @classmethod def _as_int(cls, value, low=None, high=None): if not isinstance(value, int): raise ValueError("not an integer") if low is not None and value < low: raise ValueError("value too small") if high is not None and value > high: raise ValueError("value too large") return value @classmethod def _as_ipv4_address(cls, value): if isinstance(value, str): return dns.ipv4.canonicalize(value) elif isinstance(value, bytes): return dns.ipv4.inet_ntoa(value) else: raise ValueError("not an IPv4 address") @classmethod def _as_ipv6_address(cls, value): if isinstance(value, str): return dns.ipv6.canonicalize(value) elif isinstance(value, bytes): return dns.ipv6.inet_ntoa(value) else: raise ValueError("not an IPv6 address") @classmethod def _as_bool(cls, value): if isinstance(value, bool): return value else: raise ValueError("not a boolean") @classmethod def _as_ttl(cls, value): if isinstance(value, int): return cls._as_int(value, 0, dns.ttl.MAX_TTL) elif isinstance(value, str): return dns.ttl.from_text(value) else: raise ValueError("not a TTL") @classmethod def _as_tuple(cls, value, as_value): try: # For user convenience, if value is a singleton of the list # element type, wrap it in a tuple. return (as_value(value),) except Exception: # Otherwise, check each element of the iterable *value* # against *as_value*. return tuple(as_value(v) for v in value) # Processing order @classmethod def _processing_order(cls, iterable): items = list(iterable) random.shuffle(items) return items @dns.immutable.immutable class GenericRdata(Rdata): """Generic Rdata Class This class is used for rdata types for which we have no better implementation. It implements the DNS "unknown RRs" scheme. """ __slots__ = ["data"] def __init__(self, rdclass, rdtype, data): super().__init__(rdclass, rdtype) self.data = data def to_text( self, origin: Optional[dns.name.Name] = None, relativize: bool = True, **kw: Dict[str, Any], ) -> str: return r"\# %d " % len(self.data) + _hexify(self.data, **kw) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): token = tok.get() if not token.is_identifier() or token.value != r"\#": raise dns.exception.SyntaxError(r"generic rdata does not start with \#") length = tok.get_int() hex = tok.concatenate_remaining_identifiers(True).encode() data = binascii.unhexlify(hex) if len(data) != length: raise dns.exception.SyntaxError("generic rdata hex data has wrong length") return cls(rdclass, rdtype, data) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): file.write(self.data) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): return cls(rdclass, rdtype, parser.get_remaining()) _rdata_classes: Dict[Tuple[dns.rdataclass.RdataClass, dns.rdatatype.RdataType], Any] = ( {} ) _module_prefix = "dns.rdtypes" _dynamic_load_allowed = True def get_rdata_class(rdclass, rdtype, use_generic=True): cls = _rdata_classes.get((rdclass, rdtype)) if not cls: cls = _rdata_classes.get((dns.rdatatype.ANY, rdtype)) if not cls and _dynamic_load_allowed: rdclass_text = dns.rdataclass.to_text(rdclass) rdtype_text = dns.rdatatype.to_text(rdtype) rdtype_text = rdtype_text.replace("-", "_") try: mod = import_module( ".".join([_module_prefix, rdclass_text, rdtype_text]) ) cls = getattr(mod, rdtype_text) _rdata_classes[(rdclass, rdtype)] = cls except ImportError: try: mod = import_module(".".join([_module_prefix, "ANY", rdtype_text])) cls = getattr(mod, rdtype_text) _rdata_classes[(dns.rdataclass.ANY, rdtype)] = cls _rdata_classes[(rdclass, rdtype)] = cls except ImportError: pass if not cls and use_generic: cls = GenericRdata _rdata_classes[(rdclass, rdtype)] = cls return cls def load_all_types(disable_dynamic_load=True): """Load all rdata types for which dnspython has a non-generic implementation. Normally dnspython loads DNS rdatatype implementations on demand, but in some specialized cases loading all types at an application-controlled time is preferred. If *disable_dynamic_load*, a ``bool``, is ``True`` then dnspython will not attempt to use its dynamic loading mechanism if an unknown type is subsequently encountered, and will simply use the ``GenericRdata`` class. """ # Load class IN and ANY types. for rdtype in dns.rdatatype.RdataType: get_rdata_class(dns.rdataclass.IN, rdtype, False) # Load the one non-ANY implementation we have in CH. Everything # else in CH is an ANY type, and we'll discover those on demand but won't # have to import anything. get_rdata_class(dns.rdataclass.CH, dns.rdatatype.A, False) if disable_dynamic_load: # Now disable dynamic loading so any subsequent unknown type immediately becomes # GenericRdata without a load attempt. global _dynamic_load_allowed _dynamic_load_allowed = False def from_text( rdclass: Union[dns.rdataclass.RdataClass, str], rdtype: Union[dns.rdatatype.RdataType, str], tok: Union[dns.tokenizer.Tokenizer, str], origin: Optional[dns.name.Name] = None, relativize: bool = True, relativize_to: Optional[dns.name.Name] = None, idna_codec: Optional[dns.name.IDNACodec] = None, ) -> Rdata: """Build an rdata object from text format. This function attempts to dynamically load a class which implements the specified rdata class and type. If there is no class-and-type-specific implementation, the GenericRdata class is used. Once a class is chosen, its from_text() class method is called with the parameters to this function. If *tok* is a ``str``, then a tokenizer is created and the string is used as its input. *rdclass*, a ``dns.rdataclass.RdataClass`` or ``str``, the rdataclass. *rdtype*, a ``dns.rdatatype.RdataType`` or ``str``, the rdatatype. *tok*, a ``dns.tokenizer.Tokenizer`` or a ``str``. *origin*, a ``dns.name.Name`` (or ``None``), the origin to use for relative names. *relativize*, a ``bool``. If true, name will be relativized. *relativize_to*, a ``dns.name.Name`` (or ``None``), the origin to use when relativizing names. If not set, the *origin* value will be used. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA encoder/decoder to use if a tokenizer needs to be created. If ``None``, the default IDNA 2003 encoder/decoder is used. If a tokenizer is not created, then the codec associated with the tokenizer is the one that is used. Returns an instance of the chosen Rdata subclass. """ if isinstance(tok, str): tok = dns.tokenizer.Tokenizer(tok, idna_codec=idna_codec) rdclass = dns.rdataclass.RdataClass.make(rdclass) rdtype = dns.rdatatype.RdataType.make(rdtype) cls = get_rdata_class(rdclass, rdtype) with dns.exception.ExceptionWrapper(dns.exception.SyntaxError): rdata = None if cls != GenericRdata: # peek at first token token = tok.get() tok.unget(token) if token.is_identifier() and token.value == r"\#": # # Known type using the generic syntax. Extract the # wire form from the generic syntax, and then run # from_wire on it. # grdata = GenericRdata.from_text( rdclass, rdtype, tok, origin, relativize, relativize_to ) rdata = from_wire( rdclass, rdtype, grdata.data, 0, len(grdata.data), origin ) # # If this comparison isn't equal, then there must have been # compressed names in the wire format, which is an error, # there being no reasonable context to decompress with. # rwire = rdata.to_wire() if rwire != grdata.data: raise dns.exception.SyntaxError( "compressed data in " "generic syntax form " "of known rdatatype" ) if rdata is None: rdata = cls.from_text( rdclass, rdtype, tok, origin, relativize, relativize_to ) token = tok.get_eol_as_token() if token.comment is not None: object.__setattr__(rdata, "rdcomment", token.comment) return rdata def from_wire_parser( rdclass: Union[dns.rdataclass.RdataClass, str], rdtype: Union[dns.rdatatype.RdataType, str], parser: dns.wire.Parser, origin: Optional[dns.name.Name] = None, ) -> Rdata: """Build an rdata object from wire format This function attempts to dynamically load a class which implements the specified rdata class and type. If there is no class-and-type-specific implementation, the GenericRdata class is used. Once a class is chosen, its from_wire() class method is called with the parameters to this function. *rdclass*, a ``dns.rdataclass.RdataClass`` or ``str``, the rdataclass. *rdtype*, a ``dns.rdatatype.RdataType`` or ``str``, the rdatatype. *parser*, a ``dns.wire.Parser``, the parser, which should be restricted to the rdata length. *origin*, a ``dns.name.Name`` (or ``None``). If not ``None``, then names will be relativized to this origin. Returns an instance of the chosen Rdata subclass. """ rdclass = dns.rdataclass.RdataClass.make(rdclass) rdtype = dns.rdatatype.RdataType.make(rdtype) cls = get_rdata_class(rdclass, rdtype) with dns.exception.ExceptionWrapper(dns.exception.FormError): return cls.from_wire_parser(rdclass, rdtype, parser, origin) def from_wire( rdclass: Union[dns.rdataclass.RdataClass, str], rdtype: Union[dns.rdatatype.RdataType, str], wire: bytes, current: int, rdlen: int, origin: Optional[dns.name.Name] = None, ) -> Rdata: """Build an rdata object from wire format This function attempts to dynamically load a class which implements the specified rdata class and type. If there is no class-and-type-specific implementation, the GenericRdata class is used. Once a class is chosen, its from_wire() class method is called with the parameters to this function. *rdclass*, an ``int``, the rdataclass. *rdtype*, an ``int``, the rdatatype. *wire*, a ``bytes``, the wire-format message. *current*, an ``int``, the offset in wire of the beginning of the rdata. *rdlen*, an ``int``, the length of the wire-format rdata *origin*, a ``dns.name.Name`` (or ``None``). If not ``None``, then names will be relativized to this origin. Returns an instance of the chosen Rdata subclass. """ parser = dns.wire.Parser(wire, current) with parser.restrict_to(rdlen): return from_wire_parser(rdclass, rdtype, parser, origin) class RdatatypeExists(dns.exception.DNSException): """DNS rdatatype already exists.""" supp_kwargs = {"rdclass", "rdtype"} fmt = ( "The rdata type with class {rdclass:d} and rdtype {rdtype:d} " + "already exists." ) def register_type( implementation: Any, rdtype: int, rdtype_text: str, is_singleton: bool = False, rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, ) -> None: """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 ``str``, 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. """ rdtype = dns.rdatatype.RdataType.make(rdtype) existing_cls = get_rdata_class(rdclass, rdtype) if existing_cls != GenericRdata or dns.rdatatype.is_metatype(rdtype): raise RdatatypeExists(rdclass=rdclass, rdtype=rdtype) _rdata_classes[(rdclass, rdtype)] = getattr( implementation, rdtype_text.replace("-", "_") ) dns.rdatatype.register_type(rdtype, rdtype_text, is_singleton) dnspython-2.7.0/dns/rdataclass.py0000644000000000000000000000565013615410400013753 0ustar00# 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 # 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. """DNS Rdata Classes.""" import dns.enum import dns.exception class RdataClass(dns.enum.IntEnum): """DNS Rdata Class""" RESERVED0 = 0 IN = 1 INTERNET = IN CH = 3 CHAOS = CH HS = 4 HESIOD = HS NONE = 254 ANY = 255 @classmethod def _maximum(cls): return 65535 @classmethod def _short_name(cls): return "class" @classmethod def _prefix(cls): return "CLASS" @classmethod def _unknown_exception_class(cls): return UnknownRdataclass _metaclasses = {RdataClass.NONE, RdataClass.ANY} class UnknownRdataclass(dns.exception.DNSException): """A DNS class is unknown.""" def from_text(text: str) -> RdataClass: """Convert text into a DNS rdata class value. The input text can be a defined DNS RR class mnemonic or instance of the DNS generic class syntax. 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 a ``dns.rdataclass.RdataClass``. """ return RdataClass.from_text(text) def to_text(value: RdataClass) -> str: """Convert a DNS rdata class value to text. If the value has a known mnemonic, it will be used, otherwise the DNS generic class syntax will be used. Raises ``ValueError`` if the rdata class value is not >= 0 and <= 65535. Returns a ``str``. """ return RdataClass.to_text(value) def is_metaclass(rdclass: RdataClass) -> bool: """True if the specified class is a metaclass. The currently defined metaclasses are ANY and NONE. *rdclass* is a ``dns.rdataclass.RdataClass``. """ if rdclass in _metaclasses: return True return False ### BEGIN generated RdataClass constants RESERVED0 = RdataClass.RESERVED0 IN = RdataClass.IN INTERNET = RdataClass.INTERNET CH = RdataClass.CH CHAOS = RdataClass.CHAOS HS = RdataClass.HS HESIOD = RdataClass.HESIOD NONE = RdataClass.NONE ANY = RdataClass.ANY ### END generated RdataClass constants dnspython-2.7.0/dns/rdataset.py0000644000000000000000000004043013615410400013434 0ustar00# 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 # 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. """DNS rdatasets (an rdataset is a set of rdatas of a given type and class)""" import io import random import struct from typing import Any, Collection, Dict, List, Optional, Union, cast import dns.exception import dns.immutable import dns.name import dns.rdata import dns.rdataclass import dns.rdatatype import dns.renderer import dns.set import dns.ttl # define SimpleSet here for backwards compatibility SimpleSet = dns.set.Set class DifferingCovers(dns.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(dns.exception.DNSException): """An attempt was made to add DNS RR data of an incompatible type.""" class Rdataset(dns.set.Set): """A DNS rdataset.""" __slots__ = ["rdclass", "rdtype", "covers", "ttl"] def __init__( self, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, ttl: int = 0, ): """Create a new rdataset of the specified class and type. *rdclass*, a ``dns.rdataclass.RdataClass``, the rdataclass. *rdtype*, an ``dns.rdatatype.RdataType``, the rdatatype. *covers*, an ``dns.rdatatype.RdataType``, the covered rdatatype. *ttl*, an ``int``, the TTL. """ super().__init__() self.rdclass = rdclass self.rdtype: dns.rdatatype.RdataType = rdtype self.covers: dns.rdatatype.RdataType = covers self.ttl = ttl def _clone(self): obj = super()._clone() obj.rdclass = self.rdclass obj.rdtype = self.rdtype obj.covers = self.covers obj.ttl = self.ttl return obj def update_ttl(self, ttl: int) -> None: """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 to the specified TTL. *ttl*, an ``int`` or ``str``. """ ttl = dns.ttl.make(ttl) if len(self) == 0: self.ttl = ttl elif ttl < self.ttl: self.ttl = ttl def add( # pylint: disable=arguments-differ,arguments-renamed self, rd: dns.rdata.Rdata, ttl: Optional[int] = None ) -> None: """Add the specified rdata to the rdataset. If the optional *ttl* parameter is supplied, then ``self.update_ttl(ttl)`` will be called prior to adding the rdata. *rd*, a ``dns.rdata.Rdata``, the rdata *ttl*, an ``int``, the TTL. 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 # check that the signature covers the same type as the # other rdatas in this rdataset. If this is the first rdata # in the set, initialize the covers field. # if self.rdclass != rd.rdclass or self.rdtype != rd.rdtype: raise IncompatibleTypes if ttl is not None: self.update_ttl(ttl) if self.rdtype == dns.rdatatype.RRSIG or self.rdtype == dns.rdatatype.SIG: covers = rd.covers() if len(self) == 0 and self.covers == dns.rdatatype.NONE: self.covers = covers elif self.covers != covers: raise DifferingCovers if dns.rdatatype.is_singleton(rd.rdtype) and len(self) > 0: self.clear() super().add(rd) def union_update(self, other): self.update_ttl(other.ttl) super().union_update(other) def intersection_update(self, other): self.update_ttl(other.ttl) super().intersection_update(other) def update(self, other): """Add all rdatas in other to self. *other*, a ``dns.rdataset.Rdataset``, the rdataset from which to update. """ self.update_ttl(other.ttl) super().update(other) def _rdata_repr(self): def maybe_truncate(s): if len(s) > 100: return s[:100] + "..." return s return "[" + ", ".join(f"<{maybe_truncate(str(rr))}>" for rr in self) + "]" def __repr__(self): if self.covers == 0: ctext = "" else: ctext = "(" + dns.rdatatype.to_text(self.covers) + ")" return ( "" ) def __str__(self): return self.to_text() def __eq__(self, other): if not isinstance(other, Rdataset): return False if ( self.rdclass != other.rdclass or self.rdtype != other.rdtype or self.covers != other.covers ): return False return super().__eq__(other) def __ne__(self, other): return not self.__eq__(other) def to_text( self, name: Optional[dns.name.Name] = None, origin: Optional[dns.name.Name] = None, relativize: bool = True, override_rdclass: Optional[dns.rdataclass.RdataClass] = None, want_comments: bool = False, **kw: Dict[str, Any], ) -> str: """Convert the rdataset into DNS zone file format. See ``dns.name.Name.choose_relativity`` for more information on how *origin* and *relativize* determine the way names are emitted. Any additional keyword arguments are passed on to the rdata ``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*. *override_rdclass*, a ``dns.rdataclass.RdataClass`` or ``None``. If not ``None``, use this class instead of the Rdataset's class. *want_comments*, a ``bool``. If ``True``, emit comments for rdata which have them. The default is ``False``. """ if name is not None: name = name.choose_relativity(origin, relativize) ntext = str(name) pad = " " else: ntext = "" pad = "" s = io.StringIO() if override_rdclass is not None: rdclass = override_rdclass else: rdclass = self.rdclass if len(self) == 0: # # Empty rdatasets are used for the question section, and in # some dynamic updates, so we don't need to print out the TTL # (which is meaningless anyway). # s.write( f"{ntext}{pad}{dns.rdataclass.to_text(rdclass)} " f"{dns.rdatatype.to_text(self.rdtype)}\n" ) else: for rd in self: extra = "" if want_comments: if rd.rdcomment: extra = f" ;{rd.rdcomment}" s.write( "%s%s%d %s %s %s%s\n" % ( ntext, pad, self.ttl, dns.rdataclass.to_text(rdclass), dns.rdatatype.to_text(self.rdtype), rd.to_text(origin=origin, relativize=relativize, **kw), extra, ) ) # # We strip off the final \n for the caller's convenience in printing # return s.getvalue()[:-1] def to_wire( self, name: dns.name.Name, file: Any, compress: Optional[dns.name.CompressType] = None, origin: Optional[dns.name.Name] = None, override_rdclass: Optional[dns.rdataclass.RdataClass] = None, want_shuffle: bool = True, ) -> int: """Convert the rdataset to wire format. *name*, a ``dns.name.Name`` is the owner name to use. *file* is the file where the name is emitted (typically a BytesIO file). *compress*, a ``dict``, is the compression table to use. If ``None`` (the default), names will not be compressed. *origin* is a ``dns.name.Name`` or ``None``. If the name is 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: rdclass = override_rdclass want_shuffle = False else: rdclass = self.rdclass if len(self) == 0: name.to_wire(file, compress, origin) file.write(struct.pack("!HHIH", self.rdtype, rdclass, 0, 0)) return 1 else: l: Union[Rdataset, List[dns.rdata.Rdata]] if want_shuffle: l = list(self) random.shuffle(l) else: l = self for rd in l: name.to_wire(file, compress, origin) file.write(struct.pack("!HHI", self.rdtype, rdclass, self.ttl)) with dns.renderer.prefixed_length(file, 2): rd.to_wire(file, compress, origin) return len(self) def match( self, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType, ) -> bool: """Returns ``True`` if this rdataset matches the specified class, type, and covers. """ if self.rdclass == rdclass and self.rdtype == rdtype and self.covers == covers: return True return False def processing_order(self) -> List[dns.rdata.Rdata]: """Return rdatas in a valid processing order according to the type's specification. For example, MX records are in preference order from lowest to highest preferences, with items of the same preference shuffled. For types that do not define a processing order, the rdatas are simply shuffled. """ if len(self) == 0: return [] else: return self[0]._processing_order(iter(self)) @dns.immutable.immutable class ImmutableRdataset(Rdataset): # lgtm[py/missing-equals] """An immutable DNS rdataset.""" _clone_class = Rdataset def __init__(self, rdataset: Rdataset): """Create an immutable rdataset from the specified rdataset.""" super().__init__( rdataset.rdclass, rdataset.rdtype, rdataset.covers, rdataset.ttl ) self.items = dns.immutable.Dict(rdataset.items) def update_ttl(self, ttl): raise TypeError("immutable") def add(self, rd, ttl=None): raise TypeError("immutable") def union_update(self, other): raise TypeError("immutable") def intersection_update(self, other): raise TypeError("immutable") def update(self, other): raise TypeError("immutable") def __delitem__(self, i): raise TypeError("immutable") # lgtm complains about these not raising ArithmeticError, but there is # precedent for overrides of these methods in other classes to raise # TypeError, and it seems like the better exception. def __ior__(self, other): # lgtm[py/unexpected-raise-in-special-method] raise TypeError("immutable") def __iand__(self, other): # lgtm[py/unexpected-raise-in-special-method] raise TypeError("immutable") def __iadd__(self, other): # lgtm[py/unexpected-raise-in-special-method] raise TypeError("immutable") def __isub__(self, other): # lgtm[py/unexpected-raise-in-special-method] raise TypeError("immutable") def clear(self): raise TypeError("immutable") def __copy__(self): return ImmutableRdataset(super().copy()) def copy(self): return ImmutableRdataset(super().copy()) def union(self, other): return ImmutableRdataset(super().union(other)) def intersection(self, other): return ImmutableRdataset(super().intersection(other)) def difference(self, other): return ImmutableRdataset(super().difference(other)) def symmetric_difference(self, other): return ImmutableRdataset(super().symmetric_difference(other)) def from_text_list( rdclass: Union[dns.rdataclass.RdataClass, str], rdtype: Union[dns.rdatatype.RdataType, str], ttl: int, text_rdatas: Collection[str], idna_codec: Optional[dns.name.IDNACodec] = None, origin: Optional[dns.name.Name] = None, relativize: bool = True, relativize_to: Optional[dns.name.Name] = None, ) -> Rdataset: """Create an rdataset with the specified class, type, and TTL, and with the specified list of rdatas in text format. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA encoder/decoder to use; if ``None``, the default IDNA 2003 encoder/decoder is used. *origin*, a ``dns.name.Name`` (or ``None``), the origin to use for relative names. *relativize*, a ``bool``. If true, name will be relativized. *relativize_to*, a ``dns.name.Name`` (or ``None``), the origin to use when relativizing names. If not set, the *origin* value will be used. Returns a ``dns.rdataset.Rdataset`` object. """ rdclass = dns.rdataclass.RdataClass.make(rdclass) rdtype = dns.rdatatype.RdataType.make(rdtype) r = Rdataset(rdclass, rdtype) r.update_ttl(ttl) for t in text_rdatas: rd = dns.rdata.from_text( r.rdclass, r.rdtype, t, origin, relativize, relativize_to, idna_codec ) r.add(rd) return r def from_text( rdclass: Union[dns.rdataclass.RdataClass, str], rdtype: Union[dns.rdatatype.RdataType, str], ttl: int, *text_rdatas: Any, ) -> Rdataset: """Create an rdataset with the specified class, type, and TTL, and with the specified rdatas in text format. Returns a ``dns.rdataset.Rdataset`` object. """ return from_text_list(rdclass, rdtype, ttl, cast(Collection[str], text_rdatas)) def from_rdata_list(ttl: int, rdatas: Collection[dns.rdata.Rdata]) -> Rdataset: """Create an rdataset with the specified TTL, and with the specified list of rdata objects. Returns a ``dns.rdataset.Rdataset`` object. """ if len(rdatas) == 0: raise ValueError("rdata list must not be empty") r = None for rd in rdatas: if r is None: r = Rdataset(rd.rdclass, rd.rdtype) r.update_ttl(ttl) r.add(rd) assert r is not None return r def from_rdata(ttl: int, *rdatas: Any) -> Rdataset: """Create an rdataset with the specified TTL, and with the specified rdata objects. Returns a ``dns.rdataset.Rdataset`` object. """ return from_rdata_list(ttl, cast(Collection[dns.rdata.Rdata], rdatas)) dnspython-2.7.0/dns/rdatatype.py0000644000000000000000000001643013615410400013625 0ustar00# 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 # 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. """DNS Rdata Types.""" from typing import Dict import dns.enum import dns.exception class RdataType(dns.enum.IntEnum): """DNS Rdata Type""" TYPE0 = 0 NONE = 0 A = 1 NS = 2 MD = 3 MF = 4 CNAME = 5 SOA = 6 MB = 7 MG = 8 MR = 9 NULL = 10 WKS = 11 PTR = 12 HINFO = 13 MINFO = 14 MX = 15 TXT = 16 RP = 17 AFSDB = 18 X25 = 19 ISDN = 20 RT = 21 NSAP = 22 NSAP_PTR = 23 SIG = 24 KEY = 25 PX = 26 GPOS = 27 AAAA = 28 LOC = 29 NXT = 30 SRV = 33 NAPTR = 35 KX = 36 CERT = 37 A6 = 38 DNAME = 39 OPT = 41 APL = 42 DS = 43 SSHFP = 44 IPSECKEY = 45 RRSIG = 46 NSEC = 47 DNSKEY = 48 DHCID = 49 NSEC3 = 50 NSEC3PARAM = 51 TLSA = 52 SMIMEA = 53 HIP = 55 NINFO = 56 CDS = 59 CDNSKEY = 60 OPENPGPKEY = 61 CSYNC = 62 ZONEMD = 63 SVCB = 64 HTTPS = 65 SPF = 99 UNSPEC = 103 NID = 104 L32 = 105 L64 = 106 LP = 107 EUI48 = 108 EUI64 = 109 TKEY = 249 TSIG = 250 IXFR = 251 AXFR = 252 MAILB = 253 MAILA = 254 ANY = 255 URI = 256 CAA = 257 AVC = 258 AMTRELAY = 260 RESINFO = 261 WALLET = 262 TA = 32768 DLV = 32769 @classmethod def _maximum(cls): return 65535 @classmethod def _short_name(cls): return "type" @classmethod def _prefix(cls): return "TYPE" @classmethod def _extra_from_text(cls, text): if text.find("-") >= 0: try: return cls[text.replace("-", "_")] except KeyError: # pragma: no cover pass return _registered_by_text.get(text) @classmethod def _extra_to_text(cls, value, current_text): if current_text is None: return _registered_by_value.get(value) if current_text.find("_") >= 0: return current_text.replace("_", "-") return current_text @classmethod def _unknown_exception_class(cls): return UnknownRdatatype _registered_by_text: Dict[str, RdataType] = {} _registered_by_value: Dict[RdataType, str] = {} _metatypes = {RdataType.OPT} _singletons = { RdataType.SOA, RdataType.NXT, RdataType.DNAME, RdataType.NSEC, RdataType.CNAME, } class UnknownRdatatype(dns.exception.DNSException): """DNS resource record type is unknown.""" def from_text(text: str) -> RdataType: """Convert text into a DNS rdata type value. The input text can be a defined DNS RR type mnemonic or instance of the DNS generic type syntax. 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 a ``dns.rdatatype.RdataType``. """ return RdataType.from_text(text) def to_text(value: RdataType) -> str: """Convert a DNS rdata type value to text. If the value has a known mnemonic, it will be used, otherwise the DNS generic type syntax will be used. Raises ``ValueError`` if the rdata type value is not >= 0 and <= 65535. Returns a ``str``. """ return RdataType.to_text(value) def is_metatype(rdtype: RdataType) -> bool: """True if the specified type is a metatype. *rdtype* is a ``dns.rdatatype.RdataType``. The currently defined metatypes are TKEY, TSIG, IXFR, AXFR, MAILA, MAILB, ANY, and OPT. Returns a ``bool``. """ return (256 > rdtype >= 128) or rdtype in _metatypes def is_singleton(rdtype: RdataType) -> bool: """Is the specified type a singleton type? Singleton types can only have a single rdata in an rdataset, or a single 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: return True return False # pylint: disable=redefined-outer-name def register_type( rdtype: RdataType, rdtype_text: str, is_singleton: bool = False ) -> None: """Dynamically register an rdatatype. *rdtype*, a ``dns.rdatatype.RdataType``, the rdatatype to register. *rdtype_text*, a ``str``, 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.) """ _registered_by_text[rdtype_text] = rdtype _registered_by_value[rdtype] = rdtype_text if is_singleton: _singletons.add(rdtype) ### BEGIN generated RdataType constants TYPE0 = RdataType.TYPE0 NONE = RdataType.NONE A = RdataType.A NS = RdataType.NS MD = RdataType.MD MF = RdataType.MF CNAME = RdataType.CNAME SOA = RdataType.SOA MB = RdataType.MB MG = RdataType.MG MR = RdataType.MR NULL = RdataType.NULL WKS = RdataType.WKS PTR = RdataType.PTR HINFO = RdataType.HINFO MINFO = RdataType.MINFO MX = RdataType.MX TXT = RdataType.TXT RP = RdataType.RP AFSDB = RdataType.AFSDB X25 = RdataType.X25 ISDN = RdataType.ISDN RT = RdataType.RT NSAP = RdataType.NSAP NSAP_PTR = RdataType.NSAP_PTR SIG = RdataType.SIG KEY = RdataType.KEY PX = RdataType.PX GPOS = RdataType.GPOS AAAA = RdataType.AAAA LOC = RdataType.LOC NXT = RdataType.NXT SRV = RdataType.SRV NAPTR = RdataType.NAPTR KX = RdataType.KX CERT = RdataType.CERT A6 = RdataType.A6 DNAME = RdataType.DNAME OPT = RdataType.OPT APL = RdataType.APL DS = RdataType.DS SSHFP = RdataType.SSHFP IPSECKEY = RdataType.IPSECKEY RRSIG = RdataType.RRSIG NSEC = RdataType.NSEC DNSKEY = RdataType.DNSKEY DHCID = RdataType.DHCID NSEC3 = RdataType.NSEC3 NSEC3PARAM = RdataType.NSEC3PARAM TLSA = RdataType.TLSA SMIMEA = RdataType.SMIMEA HIP = RdataType.HIP NINFO = RdataType.NINFO CDS = RdataType.CDS CDNSKEY = RdataType.CDNSKEY OPENPGPKEY = RdataType.OPENPGPKEY CSYNC = RdataType.CSYNC ZONEMD = RdataType.ZONEMD SVCB = RdataType.SVCB HTTPS = RdataType.HTTPS SPF = RdataType.SPF UNSPEC = RdataType.UNSPEC NID = RdataType.NID L32 = RdataType.L32 L64 = RdataType.L64 LP = RdataType.LP EUI48 = RdataType.EUI48 EUI64 = RdataType.EUI64 TKEY = RdataType.TKEY TSIG = RdataType.TSIG IXFR = RdataType.IXFR AXFR = RdataType.AXFR MAILB = RdataType.MAILB MAILA = RdataType.MAILA ANY = RdataType.ANY URI = RdataType.URI CAA = RdataType.CAA AVC = RdataType.AVC AMTRELAY = RdataType.AMTRELAY RESINFO = RdataType.RESINFO WALLET = RdataType.WALLET TA = RdataType.TA DLV = RdataType.DLV ### END generated RdataType constants dnspython-2.7.0/dns/renderer.py0000644000000000000000000002576613615410400013452 0ustar00# 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 # 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. """Help for building DNS wire format messages""" import contextlib import io import random import struct import time import dns.exception import dns.tsig QUESTION = 0 ANSWER = 1 AUTHORITY = 2 ADDITIONAL = 3 @contextlib.contextmanager def prefixed_length(output, length_length): output.write(b"\00" * length_length) start = output.tell() yield end = output.tell() length = end - start if length > 0: try: output.seek(start - length_length) try: output.write(length.to_bytes(length_length, "big")) except OverflowError: raise dns.exception.FormError finally: output.seek(end) class Renderer: """Helper class for building DNS wire-format messages. Most applications can use the higher-level L{dns.message.Message} class and its to_wire() method to generate wire-format messages. This class is for those applications which need finer control over the generation of messages. Typical use:: r = dns.renderer.Renderer(id=1, flags=0x80, max_size=512) r.add_question(qname, qtype, qclass) r.add_rrset(dns.renderer.ANSWER, rrset_1) r.add_rrset(dns.renderer.ANSWER, rrset_2) r.add_rrset(dns.renderer.AUTHORITY, ns_rrset) r.add_rrset(dns.renderer.ADDITIONAL, ad_rrset_1) r.add_rrset(dns.renderer.ADDITIONAL, ad_rrset_2) r.add_edns(0, 0, 4096) r.write_header() r.add_tsig(keyname, secret, 300, 1, 0, '', request_mac) wire = r.get_wire() If padding is going to be used, then the OPT record MUST be written after everything else in the additional section except for the TSIG (if any). output, an io.BytesIO, where rendering is written id: the message id flags: the message flags max_size: the maximum size of the message origin: the origin to use when rendering relative names compress: the compression table section: an int, the section currently being rendered counts: list of the number of RRs in each section mac: the MAC of the rendered message (if TSIG was used) """ def __init__(self, id=None, flags=0, max_size=65535, origin=None): """Initialize a new renderer.""" self.output = io.BytesIO() if id is None: self.id = random.randint(0, 65535) else: self.id = id self.flags = flags self.max_size = max_size self.origin = origin self.compress = {} self.section = QUESTION self.counts = [0, 0, 0, 0] self.output.write(b"\x00" * 12) self.mac = "" self.reserved = 0 self.was_padded = False def _rollback(self, where): """Truncate the output buffer at offset *where*, and remove any compression table entries that pointed beyond the truncation point. """ self.output.seek(where) self.output.truncate() keys_to_delete = [] for k, v in self.compress.items(): if v >= where: keys_to_delete.append(k) for k in keys_to_delete: del self.compress[k] def _set_section(self, section): """Set the renderer's current section. Sections must be rendered order: QUESTION, ANSWER, AUTHORITY, ADDITIONAL. Sections may be empty. Raises dns.exception.FormError if an attempt was made to set a section value less than the current section. """ if self.section != section: if self.section > section: raise dns.exception.FormError self.section = section @contextlib.contextmanager def _track_size(self): start = self.output.tell() yield start if self.output.tell() > self.max_size: self._rollback(start) raise dns.exception.TooBig @contextlib.contextmanager def _temporarily_seek_to(self, where): current = self.output.tell() try: self.output.seek(where) yield finally: self.output.seek(current) def add_question(self, qname, rdtype, rdclass=dns.rdataclass.IN): """Add a question to the message.""" self._set_section(QUESTION) with self._track_size(): qname.to_wire(self.output, self.compress, self.origin) self.output.write(struct.pack("!HH", rdtype, rdclass)) self.counts[QUESTION] += 1 def add_rrset(self, section, rrset, **kw): """Add the rrset to the specified section. Any keyword arguments are passed on to the rdataset's to_wire() routine. """ self._set_section(section) with self._track_size(): n = rrset.to_wire(self.output, self.compress, self.origin, **kw) self.counts[section] += n def add_rdataset(self, section, name, rdataset, **kw): """Add the rdataset to the specified section, using the specified name as the owner name. Any keyword arguments are passed on to the rdataset's to_wire() routine. """ self._set_section(section) with self._track_size(): n = rdataset.to_wire(name, self.output, self.compress, self.origin, **kw) self.counts[section] += n def add_opt(self, opt, pad=0, opt_size=0, tsig_size=0): """Add *opt* to the additional section, applying padding if desired. The padding will take the specified precomputed OPT size and TSIG size into account. Note that we don't have reliable way of knowing how big a GSS-TSIG digest might be, so we we might not get an even multiple of the pad in that case.""" if pad: ttl = opt.ttl assert opt_size >= 11 opt_rdata = opt[0] size_without_padding = self.output.tell() + opt_size + tsig_size remainder = size_without_padding % pad if remainder: pad = b"\x00" * (pad - remainder) else: pad = b"" options = list(opt_rdata.options) options.append(dns.edns.GenericOption(dns.edns.OptionType.PADDING, pad)) opt = dns.message.Message._make_opt(ttl, opt_rdata.rdclass, options) self.was_padded = True self.add_rrset(ADDITIONAL, opt) def add_edns(self, edns, ednsflags, payload, options=None): """Add an EDNS OPT record to the message.""" # make sure the EDNS version in ednsflags agrees with edns ednsflags &= 0xFF00FFFF ednsflags |= edns << 16 opt = dns.message.Message._make_opt(ednsflags, payload, options) self.add_opt(opt) def add_tsig( self, keyname, secret, fudge, id, tsig_error, other_data, request_mac, algorithm=dns.tsig.default_algorithm, ): """Add a TSIG signature to the message.""" s = self.output.getvalue() if isinstance(secret, dns.tsig.Key): key = secret else: key = dns.tsig.Key(keyname, secret, algorithm) tsig = dns.message.Message._make_tsig( keyname, algorithm, 0, fudge, b"", id, tsig_error, other_data ) (tsig, _) = dns.tsig.sign(s, key, tsig[0], int(time.time()), request_mac) self._write_tsig(tsig, keyname) def add_multi_tsig( self, ctx, keyname, secret, fudge, id, tsig_error, other_data, request_mac, algorithm=dns.tsig.default_algorithm, ): """Add a TSIG signature to the message. Unlike add_tsig(), this can be used for a series of consecutive DNS envelopes, e.g. for a zone transfer over TCP [RFC2845, 4.4]. For the first message in the sequence, give ctx=None. For each subsequent message, give the ctx that was returned from the add_multi_tsig() call for the previous message.""" s = self.output.getvalue() if isinstance(secret, dns.tsig.Key): key = secret else: key = dns.tsig.Key(keyname, secret, algorithm) tsig = dns.message.Message._make_tsig( keyname, algorithm, 0, fudge, b"", id, tsig_error, other_data ) (tsig, ctx) = dns.tsig.sign( s, key, tsig[0], int(time.time()), request_mac, ctx, True ) self._write_tsig(tsig, keyname) return ctx def _write_tsig(self, tsig, keyname): if self.was_padded: compress = None else: compress = self.compress self._set_section(ADDITIONAL) with self._track_size(): keyname.to_wire(self.output, compress, self.origin) self.output.write( struct.pack("!HHI", dns.rdatatype.TSIG, dns.rdataclass.ANY, 0) ) with prefixed_length(self.output, 2): tsig.to_wire(self.output) self.counts[ADDITIONAL] += 1 with self._temporarily_seek_to(10): self.output.write(struct.pack("!H", self.counts[ADDITIONAL])) def write_header(self): """Write the DNS message header. Writing the DNS message header is done after all sections have been rendered, but before the optional TSIG signature is added. """ with self._temporarily_seek_to(0): self.output.write( struct.pack( "!HHHHHH", self.id, self.flags, self.counts[0], self.counts[1], self.counts[2], self.counts[3], ) ) def get_wire(self): """Return the wire format message.""" return self.output.getvalue() def reserve(self, size: int) -> None: """Reserve *size* bytes.""" if size < 0: raise ValueError("reserved amount must be non-negative") if size > self.max_size: raise ValueError("cannot reserve more than the maximum size") self.reserved += size self.max_size -= size def release_reserved(self) -> None: """Release the reserved bytes.""" self.max_size += self.reserved self.reserved = 0 dnspython-2.7.0/dns/resolver.py0000644000000000000000000022000213615410400013461 0ustar00# 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 # 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. """DNS stub resolver.""" import contextlib import random import socket import sys import threading import time import warnings from typing import Any, Dict, Iterator, List, Optional, Sequence, Tuple, Union from urllib.parse import urlparse import dns._ddr import dns.edns import dns.exception import dns.flags import dns.inet import dns.ipv4 import dns.ipv6 import dns.message import dns.name import dns.rdata import dns.nameserver import dns.query import dns.rcode import dns.rdataclass import dns.rdatatype import dns.rdtypes.svcbbase import dns.reversename import dns.tsig if sys.platform == "win32": # pragma: no cover import dns.win32util class NXDOMAIN(dns.exception.DNSException): """The DNS query name does not exist.""" supp_kwargs = {"qnames", "responses"} fmt = None # we have our own __str__ implementation # pylint: disable=arguments-differ # We do this as otherwise mypy complains about unexpected keyword argument # idna_exception def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _check_kwargs(self, qnames, responses=None): if not isinstance(qnames, (list, tuple, set)): raise AttributeError("qnames must be a list, tuple or set") if len(qnames) == 0: raise AttributeError("qnames must contain at least one element") if responses is None: responses = {} elif not isinstance(responses, dict): raise AttributeError("responses must be a dict(qname=response)") kwargs = dict(qnames=qnames, responses=responses) return kwargs def __str__(self) -> str: if "qnames" not in self.kwargs: return super().__str__() qnames = self.kwargs["qnames"] if len(qnames) > 1: msg = "None of DNS query names exist" else: msg = "The DNS query name does not exist" qnames = ", ".join(map(str, qnames)) return f"{msg}: {qnames}" @property def canonical_name(self): """Return the unresolved canonical name.""" if "qnames" not in self.kwargs: raise TypeError("parametrized exception required") for qname in self.kwargs["qnames"]: response = self.kwargs["responses"][qname] try: cname = response.canonical_name() if cname != qname: return cname except Exception: # pragma: no cover # We can just eat this exception as it means there was # something wrong with the response. pass return self.kwargs["qnames"][0] def __add__(self, e_nx): """Augment by results from another NXDOMAIN exception.""" qnames0 = list(self.kwargs.get("qnames", [])) responses0 = dict(self.kwargs.get("responses", {})) responses1 = e_nx.kwargs.get("responses", {}) for qname1 in e_nx.kwargs.get("qnames", []): if qname1 not in qnames0: qnames0.append(qname1) if qname1 in responses1: responses0[qname1] = responses1[qname1] return NXDOMAIN(qnames=qnames0, responses=responses0) def qnames(self): """All of the names that were tried. Returns a list of ``dns.name.Name``. """ return self.kwargs["qnames"] def responses(self): """A map from queried names to their NXDOMAIN responses. Returns a dict mapping a ``dns.name.Name`` to a ``dns.message.Message``. """ return self.kwargs["responses"] def response(self, qname): """The response for query *qname*. Returns a ``dns.message.Message``. """ return self.kwargs["responses"][qname] class YXDOMAIN(dns.exception.DNSException): """The DNS query name is too long after DNAME substitution.""" ErrorTuple = Tuple[ Optional[str], bool, int, Union[Exception, str], Optional[dns.message.Message], ] def _errors_to_text(errors: List[ErrorTuple]) -> List[str]: """Turn a resolution errors trace into a list of text.""" texts = [] for err in errors: texts.append(f"Server {err[0]} answered {err[3]}") return texts class LifetimeTimeout(dns.exception.Timeout): """The resolution lifetime expired.""" msg = "The resolution lifetime expired." fmt = f"{msg[:-1]} after {{timeout:.3f}} seconds: {{errors}}" supp_kwargs = {"timeout", "errors"} # We do this as otherwise mypy complains about unexpected keyword argument # idna_exception def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _fmt_kwargs(self, **kwargs): srv_msgs = _errors_to_text(kwargs["errors"]) return super()._fmt_kwargs( timeout=kwargs["timeout"], errors="; ".join(srv_msgs) ) # We added more detail to resolution timeouts, but they are still # subclasses of dns.exception.Timeout for backwards compatibility. We also # keep dns.resolver.Timeout defined for backwards compatibility. Timeout = LifetimeTimeout class NoAnswer(dns.exception.DNSException): """The DNS response does not contain an answer to the question.""" fmt = "The DNS response does not contain an answer to the question: {query}" supp_kwargs = {"response"} # We do this as otherwise mypy complains about unexpected keyword argument # idna_exception def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _fmt_kwargs(self, **kwargs): return super()._fmt_kwargs(query=kwargs["response"].question) def response(self): return self.kwargs["response"] class NoNameservers(dns.exception.DNSException): """All nameservers failed to answer the query. errors: list of servers and respective errors The type of errors is [(server IP address, any object convertible to string)]. Non-empty errors list will add explanatory message () """ msg = "All nameservers failed to answer the query." fmt = f"{msg[:-1]} {{query}}: {{errors}}" supp_kwargs = {"request", "errors"} # We do this as otherwise mypy complains about unexpected keyword argument # idna_exception def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _fmt_kwargs(self, **kwargs): srv_msgs = _errors_to_text(kwargs["errors"]) return super()._fmt_kwargs( query=kwargs["request"].question, errors="; ".join(srv_msgs) ) class NotAbsolute(dns.exception.DNSException): """An absolute domain name is required but a relative name was provided.""" class NoRootSOA(dns.exception.DNSException): """There is no SOA RR at the DNS root name. This should never happen!""" class NoMetaqueries(dns.exception.DNSException): """DNS metaqueries are not allowed.""" class NoResolverConfiguration(dns.exception.DNSException): """Resolver configuration could not be read or specified no nameservers.""" class Answer: """DNS stub resolver answer. Instances of this class bundle up the result of a successful DNS resolution. For convenience, the answer object implements much of the sequence protocol, forwarding to its ``rrset`` attribute. E.g. ``for a in answer`` is equivalent to ``for a in answer.rrset``. ``answer[i]`` is equivalent to ``answer.rrset[i]``, and ``answer[i:j]`` is equivalent to ``answer.rrset[i:j]``. Note that CNAMEs or DNAMEs in the response may mean that answer RRset's name might not be the query name. """ def __init__( self, qname: dns.name.Name, rdtype: dns.rdatatype.RdataType, rdclass: dns.rdataclass.RdataClass, response: dns.message.QueryMessage, nameserver: Optional[str] = None, port: Optional[int] = None, ) -> None: self.qname = qname self.rdtype = rdtype self.rdclass = rdclass self.response = response self.nameserver = nameserver self.port = port self.chaining_result = response.resolve_chaining() # Copy some attributes out of chaining_result for backwards # compatibility and convenience. self.canonical_name = self.chaining_result.canonical_name self.rrset = self.chaining_result.answer self.expiration = time.time() + self.chaining_result.minimum_ttl def __getattr__(self, attr): # pragma: no cover if attr == "name": return self.rrset.name elif attr == "ttl": return self.rrset.ttl elif attr == "covers": return self.rrset.covers elif attr == "rdclass": return self.rrset.rdclass elif attr == "rdtype": return self.rrset.rdtype else: raise AttributeError(attr) def __len__(self) -> int: return self.rrset and len(self.rrset) or 0 def __iter__(self) -> Iterator[dns.rdata.Rdata]: return self.rrset and iter(self.rrset) or iter(tuple()) def __getitem__(self, i): if self.rrset is None: raise IndexError return self.rrset[i] def __delitem__(self, i): if self.rrset is None: raise IndexError del self.rrset[i] class Answers(dict): """A dict of DNS stub resolver answers, indexed by type.""" class HostAnswers(Answers): """A dict of DNS stub resolver answers to a host name lookup, indexed by type. """ @classmethod def make( cls, v6: Optional[Answer] = None, v4: Optional[Answer] = None, add_empty: bool = True, ) -> "HostAnswers": answers = HostAnswers() if v6 is not None and (add_empty or v6.rrset): answers[dns.rdatatype.AAAA] = v6 if v4 is not None and (add_empty or v4.rrset): answers[dns.rdatatype.A] = v4 return answers # Returns pairs of (address, family) from this result, potentially # filtering by address family. def addresses_and_families( self, family: int = socket.AF_UNSPEC ) -> Iterator[Tuple[str, int]]: if family == socket.AF_UNSPEC: yield from self.addresses_and_families(socket.AF_INET6) yield from self.addresses_and_families(socket.AF_INET) return elif family == socket.AF_INET6: answer = self.get(dns.rdatatype.AAAA) elif family == socket.AF_INET: answer = self.get(dns.rdatatype.A) else: # pragma: no cover raise NotImplementedError(f"unknown address family {family}") if answer: for rdata in answer: yield (rdata.address, family) # Returns addresses from this result, potentially filtering by # address family. def addresses(self, family: int = socket.AF_UNSPEC) -> Iterator[str]: return (pair[0] for pair in self.addresses_and_families(family)) # Returns the canonical name from this result. def canonical_name(self) -> dns.name.Name: answer = self.get(dns.rdatatype.AAAA, self.get(dns.rdatatype.A)) return answer.canonical_name class CacheStatistics: """Cache Statistics""" def __init__(self, hits: int = 0, misses: int = 0) -> None: self.hits = hits self.misses = misses def reset(self) -> None: self.hits = 0 self.misses = 0 def clone(self) -> "CacheStatistics": return CacheStatistics(self.hits, self.misses) class CacheBase: def __init__(self) -> None: self.lock = threading.Lock() self.statistics = CacheStatistics() def reset_statistics(self) -> None: """Reset all statistics to zero.""" with self.lock: self.statistics.reset() def hits(self) -> int: """How many hits has the cache had?""" with self.lock: return self.statistics.hits def misses(self) -> int: """How many misses has the cache had?""" with self.lock: return self.statistics.misses def get_statistics_snapshot(self) -> CacheStatistics: """Return a consistent snapshot of all the statistics. If running with multiple threads, it's better to take a snapshot than to call statistics methods such as hits() and misses() individually. """ with self.lock: return self.statistics.clone() CacheKey = Tuple[dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass] class Cache(CacheBase): """Simple thread-safe DNS answer cache.""" def __init__(self, cleaning_interval: float = 300.0) -> None: """*cleaning_interval*, a ``float`` is the number of seconds between periodic cleanings. """ super().__init__() self.data: Dict[CacheKey, Answer] = {} self.cleaning_interval = cleaning_interval self.next_cleaning: float = time.time() + self.cleaning_interval def _maybe_clean(self) -> None: """Clean the cache if it's time to do so.""" now = time.time() if self.next_cleaning <= now: keys_to_delete = [] for k, v in self.data.items(): if v.expiration <= now: keys_to_delete.append(k) for k in keys_to_delete: del self.data[k] now = time.time() self.next_cleaning = now + self.cleaning_interval def get(self, key: CacheKey) -> Optional[Answer]: """Get the answer associated with *key*. Returns None if no answer is cached for the key. *key*, a ``(dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass)`` tuple whose values are the query name, rdtype, and rdclass respectively. Returns a ``dns.resolver.Answer`` or ``None``. """ with self.lock: self._maybe_clean() v = self.data.get(key) if v is None or v.expiration <= time.time(): self.statistics.misses += 1 return None self.statistics.hits += 1 return v def put(self, key: CacheKey, value: Answer) -> None: """Associate key and value in the cache. *key*, a ``(dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass)`` tuple whose values are the query name, rdtype, and rdclass respectively. *value*, a ``dns.resolver.Answer``, the answer. """ with self.lock: self._maybe_clean() self.data[key] = value def flush(self, key: Optional[CacheKey] = None) -> None: """Flush the cache. If *key* is not ``None``, only that item is flushed. Otherwise the entire cache is flushed. *key*, a ``(dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass)`` tuple whose values are the query name, rdtype, and rdclass respectively. """ with self.lock: if key is not None: if key in self.data: del self.data[key] else: self.data = {} self.next_cleaning = time.time() + self.cleaning_interval class LRUCacheNode: """LRUCache node.""" def __init__(self, key, value): self.key = key self.value = value self.hits = 0 self.prev = self self.next = self def link_after(self, node: "LRUCacheNode") -> None: self.prev = node self.next = node.next node.next.prev = self node.next = self def unlink(self) -> None: self.next.prev = self.prev self.prev.next = self.next class LRUCache(CacheBase): """Thread-safe, bounded, least-recently-used DNS answer cache. This cache is better than the simple cache (above) if you're running a web crawler or other process that does a lot of resolutions. The LRUCache has a maximum number of nodes, and when it is full, the least-recently used node is removed to make space for a new one. """ def __init__(self, max_size: int = 100000) -> None: """*max_size*, an ``int``, is the maximum number of nodes to cache; it must be greater than 0. """ super().__init__() self.data: Dict[CacheKey, LRUCacheNode] = {} self.set_max_size(max_size) self.sentinel: LRUCacheNode = LRUCacheNode(None, None) self.sentinel.prev = self.sentinel self.sentinel.next = self.sentinel def set_max_size(self, max_size: int) -> None: if max_size < 1: max_size = 1 self.max_size = max_size def get(self, key: CacheKey) -> Optional[Answer]: """Get the answer associated with *key*. Returns None if no answer is cached for the key. *key*, a ``(dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass)`` tuple whose values are the query name, rdtype, and rdclass respectively. Returns a ``dns.resolver.Answer`` or ``None``. """ with self.lock: node = self.data.get(key) if node is None: self.statistics.misses += 1 return None # Unlink because we're either going to move the node to the front # of the LRU list or we're going to free it. node.unlink() if node.value.expiration <= time.time(): del self.data[node.key] self.statistics.misses += 1 return None node.link_after(self.sentinel) self.statistics.hits += 1 node.hits += 1 return node.value def get_hits_for_key(self, key: CacheKey) -> int: """Return the number of cache hits associated with the specified key.""" with self.lock: node = self.data.get(key) if node is None or node.value.expiration <= time.time(): return 0 else: return node.hits def put(self, key: CacheKey, value: Answer) -> None: """Associate key and value in the cache. *key*, a ``(dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass)`` tuple whose values are the query name, rdtype, and rdclass respectively. *value*, a ``dns.resolver.Answer``, the answer. """ with self.lock: node = self.data.get(key) if node is not None: node.unlink() del self.data[node.key] while len(self.data) >= self.max_size: gnode = self.sentinel.prev gnode.unlink() del self.data[gnode.key] node = LRUCacheNode(key, value) node.link_after(self.sentinel) self.data[key] = node def flush(self, key: Optional[CacheKey] = None) -> None: """Flush the cache. If *key* is not ``None``, only that item is flushed. Otherwise the entire cache is flushed. *key*, a ``(dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass)`` tuple whose values are the query name, rdtype, and rdclass respectively. """ with self.lock: if key is not None: node = self.data.get(key) if node is not None: node.unlink() del self.data[node.key] else: gnode = self.sentinel.next while gnode != self.sentinel: next = gnode.next gnode.unlink() gnode = next self.data = {} class _Resolution: """Helper class for dns.resolver.Resolver.resolve(). All of the "business logic" of resolution is encapsulated in this class, allowing us to have multiple resolve() implementations using different I/O schemes without copying all of the complicated logic. This class is a "friend" to dns.resolver.Resolver and manipulates resolver data structures directly. """ def __init__( self, resolver: "BaseResolver", qname: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str], rdclass: Union[dns.rdataclass.RdataClass, str], tcp: bool, raise_on_no_answer: bool, search: Optional[bool], ) -> None: if isinstance(qname, str): qname = dns.name.from_text(qname, None) rdtype = dns.rdatatype.RdataType.make(rdtype) if dns.rdatatype.is_metatype(rdtype): raise NoMetaqueries rdclass = dns.rdataclass.RdataClass.make(rdclass) if dns.rdataclass.is_metaclass(rdclass): raise NoMetaqueries self.resolver = resolver self.qnames_to_try = resolver._get_qnames_to_try(qname, search) self.qnames = self.qnames_to_try[:] self.rdtype = rdtype self.rdclass = rdclass self.tcp = tcp self.raise_on_no_answer = raise_on_no_answer self.nxdomain_responses: Dict[dns.name.Name, dns.message.QueryMessage] = {} # Initialize other things to help analysis tools self.qname = dns.name.empty self.nameservers: List[dns.nameserver.Nameserver] = [] self.current_nameservers: List[dns.nameserver.Nameserver] = [] self.errors: List[ErrorTuple] = [] self.nameserver: Optional[dns.nameserver.Nameserver] = None self.tcp_attempt = False self.retry_with_tcp = False self.request: Optional[dns.message.QueryMessage] = None self.backoff = 0.0 def next_request( self, ) -> Tuple[Optional[dns.message.QueryMessage], Optional[Answer]]: """Get the next request to send, and check the cache. Returns a (request, answer) tuple. At most one of request or answer will not be None. """ # We return a tuple instead of Union[Message,Answer] as it lets # the caller avoid isinstance(). while len(self.qnames) > 0: self.qname = self.qnames.pop(0) # Do we know the answer? if self.resolver.cache: answer = self.resolver.cache.get( (self.qname, self.rdtype, self.rdclass) ) if answer is not None: if answer.rrset is None and self.raise_on_no_answer: raise NoAnswer(response=answer.response) else: return (None, answer) answer = self.resolver.cache.get( (self.qname, dns.rdatatype.ANY, self.rdclass) ) if answer is not None and answer.response.rcode() == dns.rcode.NXDOMAIN: # cached NXDOMAIN; record it and continue to next # name. self.nxdomain_responses[self.qname] = answer.response continue # Build the request request = dns.message.make_query(self.qname, self.rdtype, self.rdclass) if self.resolver.keyname is not None: request.use_tsig( self.resolver.keyring, self.resolver.keyname, algorithm=self.resolver.keyalgorithm, ) request.use_edns( self.resolver.edns, self.resolver.ednsflags, self.resolver.payload, options=self.resolver.ednsoptions, ) if self.resolver.flags is not None: request.flags = self.resolver.flags self.nameservers = self.resolver._enrich_nameservers( self.resolver._nameservers, self.resolver.nameserver_ports, self.resolver.port, ) if self.resolver.rotate: random.shuffle(self.nameservers) self.current_nameservers = self.nameservers[:] self.errors = [] self.nameserver = None self.tcp_attempt = False self.retry_with_tcp = False self.request = request self.backoff = 0.10 return (request, None) # # We've tried everything and only gotten NXDOMAINs. (We know # it's only NXDOMAINs as anything else would have returned # before now.) # raise NXDOMAIN(qnames=self.qnames_to_try, responses=self.nxdomain_responses) def next_nameserver(self) -> Tuple[dns.nameserver.Nameserver, bool, float]: if self.retry_with_tcp: assert self.nameserver is not None assert not self.nameserver.is_always_max_size() self.tcp_attempt = True self.retry_with_tcp = False return (self.nameserver, True, 0) backoff = 0.0 if not self.current_nameservers: if len(self.nameservers) == 0: # Out of things to try! raise NoNameservers(request=self.request, errors=self.errors) self.current_nameservers = self.nameservers[:] backoff = self.backoff self.backoff = min(self.backoff * 2, 2) self.nameserver = self.current_nameservers.pop(0) self.tcp_attempt = self.tcp or self.nameserver.is_always_max_size() return (self.nameserver, self.tcp_attempt, backoff) def query_result( self, response: Optional[dns.message.Message], ex: Optional[Exception] ) -> Tuple[Optional[Answer], bool]: # # returns an (answer: Answer, end_loop: bool) tuple. # assert self.nameserver is not None if ex: # Exception during I/O or from_wire() assert response is None self.errors.append( ( str(self.nameserver), self.tcp_attempt, self.nameserver.answer_port(), ex, response, ) ) if ( isinstance(ex, dns.exception.FormError) or isinstance(ex, EOFError) or isinstance(ex, OSError) or isinstance(ex, NotImplementedError) ): # This nameserver is no good, take it out of the mix. self.nameservers.remove(self.nameserver) elif isinstance(ex, dns.message.Truncated): if self.tcp_attempt: # Truncation with TCP is no good! self.nameservers.remove(self.nameserver) else: self.retry_with_tcp = True return (None, False) # We got an answer! assert response is not None assert isinstance(response, dns.message.QueryMessage) rcode = response.rcode() if rcode == dns.rcode.NOERROR: try: answer = Answer( self.qname, self.rdtype, self.rdclass, response, self.nameserver.answer_nameserver(), self.nameserver.answer_port(), ) except Exception as e: self.errors.append( ( str(self.nameserver), self.tcp_attempt, self.nameserver.answer_port(), e, response, ) ) # The nameserver is no good, take it out of the mix. self.nameservers.remove(self.nameserver) return (None, False) if self.resolver.cache: self.resolver.cache.put((self.qname, self.rdtype, self.rdclass), answer) if answer.rrset is None and self.raise_on_no_answer: raise NoAnswer(response=answer.response) return (answer, True) elif rcode == dns.rcode.NXDOMAIN: # Further validate the response by making an Answer, even # if we aren't going to cache it. try: answer = Answer( self.qname, dns.rdatatype.ANY, dns.rdataclass.IN, response ) except Exception as e: self.errors.append( ( str(self.nameserver), self.tcp_attempt, self.nameserver.answer_port(), e, response, ) ) # The nameserver is no good, take it out of the mix. self.nameservers.remove(self.nameserver) return (None, False) self.nxdomain_responses[self.qname] = response if self.resolver.cache: self.resolver.cache.put( (self.qname, dns.rdatatype.ANY, self.rdclass), answer ) # Make next_nameserver() return None, so caller breaks its # inner loop and calls next_request(). return (None, True) elif rcode == dns.rcode.YXDOMAIN: yex = YXDOMAIN() self.errors.append( ( str(self.nameserver), self.tcp_attempt, self.nameserver.answer_port(), yex, response, ) ) raise yex else: # # We got a response, but we're not happy with the # rcode in it. # if rcode != dns.rcode.SERVFAIL or not self.resolver.retry_servfail: self.nameservers.remove(self.nameserver) self.errors.append( ( str(self.nameserver), self.tcp_attempt, self.nameserver.answer_port(), dns.rcode.to_text(rcode), response, ) ) return (None, False) class BaseResolver: """DNS stub resolver.""" # We initialize in reset() # # pylint: disable=attribute-defined-outside-init domain: dns.name.Name nameserver_ports: Dict[str, int] port: int search: List[dns.name.Name] use_search_by_default: bool timeout: float lifetime: float keyring: Optional[Any] keyname: Optional[Union[dns.name.Name, str]] keyalgorithm: Union[dns.name.Name, str] edns: int ednsflags: int ednsoptions: Optional[List[dns.edns.Option]] payload: int cache: Any flags: Optional[int] retry_servfail: bool rotate: bool ndots: Optional[int] _nameservers: Sequence[Union[str, dns.nameserver.Nameserver]] def __init__( self, filename: str = "/etc/resolv.conf", configure: bool = True ) -> None: """*filename*, a ``str`` or file object, specifying a file in standard /etc/resolv.conf format. This parameter is meaningful only when *configure* is true and the platform is POSIX. *configure*, a ``bool``. If True (the default), the resolver instance is configured in the normal fashion for the operating system the resolver is running on. (I.e. by reading a /etc/resolv.conf file on POSIX systems and from the registry on Windows systems.) """ self.reset() if configure: if sys.platform == "win32": # pragma: no cover self.read_registry() elif filename: self.read_resolv_conf(filename) def reset(self) -> None: """Reset all resolver configuration to the defaults.""" self.domain = dns.name.Name(dns.name.from_text(socket.gethostname())[1:]) if len(self.domain) == 0: # pragma: no cover self.domain = dns.name.root self._nameservers = [] self.nameserver_ports = {} self.port = 53 self.search = [] self.use_search_by_default = False self.timeout = 2.0 self.lifetime = 5.0 self.keyring = None self.keyname = None self.keyalgorithm = dns.tsig.default_algorithm self.edns = -1 self.ednsflags = 0 self.ednsoptions = None self.payload = 0 self.cache = None self.flags = None self.retry_servfail = False self.rotate = False self.ndots = None def read_resolv_conf(self, f: Any) -> None: """Process *f* as a file in the /etc/resolv.conf format. If f is a ``str``, it is used as the name of the file to open; otherwise it is treated as the file itself. Interprets the following items: - nameserver - name server IP address - domain - local domain name - search - search list for host-name lookup - options - supported options are rotate, timeout, edns0, and ndots """ nameservers = [] if isinstance(f, str): try: cm: contextlib.AbstractContextManager = open(f) except OSError: # /etc/resolv.conf doesn't exist, can't be read, etc. raise NoResolverConfiguration(f"cannot open {f}") else: cm = contextlib.nullcontext(f) with cm as f: for l in f: if len(l) == 0 or l[0] == "#" or l[0] == ";": continue tokens = l.split() # Any line containing less than 2 tokens is malformed if len(tokens) < 2: continue if tokens[0] == "nameserver": nameservers.append(tokens[1]) elif tokens[0] == "domain": self.domain = dns.name.from_text(tokens[1]) # domain and search are exclusive self.search = [] elif tokens[0] == "search": # the last search wins self.search = [] for suffix in tokens[1:]: self.search.append(dns.name.from_text(suffix)) # We don't set domain as it is not used if # len(self.search) > 0 elif tokens[0] == "options": for opt in tokens[1:]: if opt == "rotate": self.rotate = True elif opt == "edns0": self.use_edns() elif "timeout" in opt: try: self.timeout = int(opt.split(":")[1]) except (ValueError, IndexError): pass elif "ndots" in opt: try: self.ndots = int(opt.split(":")[1]) except (ValueError, IndexError): pass if len(nameservers) == 0: raise NoResolverConfiguration("no nameservers") # Assigning directly instead of appending means we invoke the # setter logic, with additonal checking and enrichment. self.nameservers = nameservers def read_registry(self) -> None: # pragma: no cover """Extract resolver configuration from the Windows registry.""" try: info = dns.win32util.get_dns_info() # type: ignore if info.domain is not None: self.domain = info.domain self.nameservers = info.nameservers self.search = info.search except AttributeError: raise NotImplementedError def _compute_timeout( self, start: float, lifetime: Optional[float] = None, errors: Optional[List[ErrorTuple]] = None, ) -> float: lifetime = self.lifetime if lifetime is None else lifetime now = time.time() duration = now - start if errors is None: errors = [] if duration < 0: if duration < -1: # Time going backwards is bad. Just give up. raise LifetimeTimeout(timeout=duration, errors=errors) else: # Time went backwards, but only a little. This can # happen, e.g. under vmware with older linux kernels. # Pretend it didn't happen. duration = 0 if duration >= lifetime: raise LifetimeTimeout(timeout=duration, errors=errors) return min(lifetime - duration, self.timeout) def _get_qnames_to_try( self, qname: dns.name.Name, search: Optional[bool] ) -> List[dns.name.Name]: # This is a separate method so we can unit test the search # rules without requiring the Internet. if search is None: search = self.use_search_by_default qnames_to_try = [] if qname.is_absolute(): qnames_to_try.append(qname) else: abs_qname = qname.concatenate(dns.name.root) if search: if len(self.search) > 0: # There is a search list, so use it exclusively search_list = self.search[:] elif self.domain != dns.name.root and self.domain is not None: # We have some notion of a domain that isn't the root, so # use it as the search list. search_list = [self.domain] else: search_list = [] # Figure out the effective ndots (default is 1) if self.ndots is None: ndots = 1 else: ndots = self.ndots for suffix in search_list: qnames_to_try.append(qname + suffix) if len(qname) > ndots: # The name has at least ndots dots, so we should try an # absolute query first. qnames_to_try.insert(0, abs_qname) else: # The name has less than ndots dots, so we should search # first, then try the absolute name. qnames_to_try.append(abs_qname) else: qnames_to_try.append(abs_qname) return qnames_to_try def use_tsig( self, keyring: Any, keyname: Optional[Union[dns.name.Name, str]] = None, algorithm: Union[dns.name.Name, str] = dns.tsig.default_algorithm, ) -> None: """Add a TSIG signature to each query. The parameters are passed to ``dns.message.Message.use_tsig()``; see its documentation for details. """ self.keyring = keyring self.keyname = keyname self.keyalgorithm = algorithm def use_edns( self, edns: Optional[Union[int, bool]] = 0, ednsflags: int = 0, payload: int = dns.message.DEFAULT_EDNS_PAYLOAD, options: Optional[List[dns.edns.Option]] = None, ) -> None: """Configure EDNS behavior. *edns*, an ``int``, is the EDNS level to use. Specifying ``None``, ``False``, or ``-1`` means "do not use EDNS", and in this case the other parameters are ignored. Specifying ``True`` is equivalent to specifying 0, i.e. "use EDNS0". *ednsflags*, an ``int``, the EDNS flag values. *payload*, an ``int``, is the EDNS sender's payload field, which is the maximum size of UDP datagram the sender can handle. I.e. how big a response to this message can be. *options*, a list of ``dns.edns.Option`` objects or ``None``, the EDNS options. """ if edns is None or edns is False: edns = -1 elif edns is True: edns = 0 self.edns = edns self.ednsflags = ednsflags self.payload = payload self.ednsoptions = options def set_flags(self, flags: int) -> None: """Overrides the default flags with your own. *flags*, an ``int``, the message flags to use. """ self.flags = flags @classmethod def _enrich_nameservers( cls, nameservers: Sequence[Union[str, dns.nameserver.Nameserver]], nameserver_ports: Dict[str, int], default_port: int, ) -> List[dns.nameserver.Nameserver]: enriched_nameservers = [] if isinstance(nameservers, list): for nameserver in nameservers: enriched_nameserver: dns.nameserver.Nameserver if isinstance(nameserver, dns.nameserver.Nameserver): enriched_nameserver = nameserver elif dns.inet.is_address(nameserver): port = nameserver_ports.get(nameserver, default_port) enriched_nameserver = dns.nameserver.Do53Nameserver( nameserver, port ) else: try: if urlparse(nameserver).scheme != "https": raise NotImplementedError except Exception: raise ValueError( f"nameserver {nameserver} is not a " "dns.nameserver.Nameserver instance or text form, " "IP address, nor a valid https URL" ) enriched_nameserver = dns.nameserver.DoHNameserver(nameserver) enriched_nameservers.append(enriched_nameserver) else: raise ValueError( f"nameservers must be a list or tuple (not a {type(nameservers)})" ) return enriched_nameservers @property def nameservers( self, ) -> Sequence[Union[str, dns.nameserver.Nameserver]]: return self._nameservers @nameservers.setter def nameservers( self, nameservers: Sequence[Union[str, dns.nameserver.Nameserver]] ) -> None: """ *nameservers*, a ``list`` of nameservers, where a nameserver is either a string interpretable as a nameserver, or a ``dns.nameserver.Nameserver`` instance. Raises ``ValueError`` if *nameservers* is not a list of nameservers. """ # We just call _enrich_nameservers() for checking self._enrich_nameservers(nameservers, self.nameserver_ports, self.port) self._nameservers = nameservers class Resolver(BaseResolver): """DNS stub resolver.""" def resolve( self, qname: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.A, rdclass: Union[dns.rdataclass.RdataClass, str] = dns.rdataclass.IN, tcp: bool = False, source: Optional[str] = None, raise_on_no_answer: bool = True, source_port: int = 0, lifetime: Optional[float] = None, search: Optional[bool] = None, ) -> Answer: # pylint: disable=arguments-differ """Query nameservers to find the answer to the question. The *qname*, *rdtype*, and *rdclass* parameters may be objects of the appropriate type, or strings that can be converted into objects of the appropriate type. *qname*, a ``dns.name.Name`` or ``str``, the query name. *rdtype*, an ``int`` or ``str``, the query type. *rdclass*, an ``int`` or ``str``, the query class. *tcp*, a ``bool``. If ``True``, use TCP to make the query. *source*, a ``str`` or ``None``. If not ``None``, bind to this IP address when making queries. *raise_on_no_answer*, a ``bool``. If ``True``, raise ``dns.resolver.NoAnswer`` if there's no answer to the question. *source_port*, an ``int``, the port from which to send the message. *lifetime*, a ``float``, how many seconds a query should run before timing out. *search*, a ``bool`` or ``None``, determines whether the search list configured in the system's resolver configuration are used for relative names, and whether the resolver's domain may be added to relative names. The default is ``None``, which causes the value of the resolver's ``use_search_by_default`` attribute to be used. Raises ``dns.resolver.LifetimeTimeout`` if no answers could be found in the specified lifetime. Raises ``dns.resolver.NXDOMAIN`` if the query name does not exist. Raises ``dns.resolver.YXDOMAIN`` if the query name is too long after DNAME substitution. Raises ``dns.resolver.NoAnswer`` if *raise_on_no_answer* is ``True`` and the query name exists but has no RRset of the desired type and class. Raises ``dns.resolver.NoNameservers`` if no non-broken nameservers are available to answer the question. Returns a ``dns.resolver.Answer`` instance. """ resolution = _Resolution( self, qname, rdtype, rdclass, tcp, raise_on_no_answer, search ) start = time.time() while True: (request, answer) = resolution.next_request() # Note we need to say "if answer is not None" and not just # "if answer" because answer implements __len__, and python # will call that. We want to return if we have an answer # object, including in cases where its length is 0. if answer is not None: # cache hit! return answer assert request is not None # needed for type checking done = False while not done: (nameserver, tcp, backoff) = resolution.next_nameserver() if backoff: time.sleep(backoff) timeout = self._compute_timeout(start, lifetime, resolution.errors) try: response = nameserver.query( request, timeout=timeout, source=source, source_port=source_port, max_size=tcp, ) except Exception as ex: (_, done) = resolution.query_result(None, ex) continue (answer, done) = resolution.query_result(response, None) # Note we need to say "if answer is not None" and not just # "if answer" because answer implements __len__, and python # will call that. We want to return if we have an answer # object, including in cases where its length is 0. if answer is not None: return answer def query( self, qname: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.A, rdclass: Union[dns.rdataclass.RdataClass, str] = dns.rdataclass.IN, tcp: bool = False, source: Optional[str] = None, raise_on_no_answer: bool = True, source_port: int = 0, lifetime: Optional[float] = None, ) -> Answer: # pragma: no cover """Query nameservers to find the answer to the question. This method calls resolve() with ``search=True``, and is provided for backwards compatibility with prior versions of dnspython. See the documentation for the resolve() method for further details. """ warnings.warn( "please use dns.resolver.Resolver.resolve() instead", DeprecationWarning, stacklevel=2, ) return self.resolve( qname, rdtype, rdclass, tcp, source, raise_on_no_answer, source_port, lifetime, True, ) def resolve_address(self, ipaddr: str, *args: Any, **kwargs: Any) -> Answer: """Use a resolver to run a reverse query for PTR records. This utilizes the resolve() method to perform a PTR lookup on the specified IP address. *ipaddr*, a ``str``, the IPv4 or IPv6 address you want to get the PTR record for. All other arguments that can be passed to the resolve() function except for rdtype and rdclass are also supported by this function. """ # We make a modified kwargs for type checking happiness, as otherwise # we get a legit warning about possibly having rdtype and rdclass # in the kwargs more than once. modified_kwargs: Dict[str, Any] = {} modified_kwargs.update(kwargs) modified_kwargs["rdtype"] = dns.rdatatype.PTR modified_kwargs["rdclass"] = dns.rdataclass.IN return self.resolve( dns.reversename.from_address(ipaddr), *args, **modified_kwargs ) def resolve_name( self, name: Union[dns.name.Name, str], family: int = socket.AF_UNSPEC, **kwargs: Any, ) -> HostAnswers: """Use a resolver to query for address records. This utilizes the resolve() method to perform A and/or AAAA lookups on the specified name. *qname*, a ``dns.name.Name`` or ``str``, the name to resolve. *family*, an ``int``, the address family. If socket.AF_UNSPEC (the default), both A and AAAA records will be retrieved. All other arguments that can be passed to the resolve() function except for rdtype and rdclass are also supported by this function. """ # We make a modified kwargs for type checking happiness, as otherwise # we get a legit warning about possibly having rdtype and rdclass # in the kwargs more than once. modified_kwargs: Dict[str, Any] = {} modified_kwargs.update(kwargs) modified_kwargs.pop("rdtype", None) modified_kwargs["rdclass"] = dns.rdataclass.IN if family == socket.AF_INET: v4 = self.resolve(name, dns.rdatatype.A, **modified_kwargs) return HostAnswers.make(v4=v4) elif family == socket.AF_INET6: v6 = self.resolve(name, dns.rdatatype.AAAA, **modified_kwargs) return HostAnswers.make(v6=v6) elif family != socket.AF_UNSPEC: # pragma: no cover raise NotImplementedError(f"unknown address family {family}") raise_on_no_answer = modified_kwargs.pop("raise_on_no_answer", True) lifetime = modified_kwargs.pop("lifetime", None) start = time.time() v6 = self.resolve( name, dns.rdatatype.AAAA, raise_on_no_answer=False, lifetime=self._compute_timeout(start, lifetime), **modified_kwargs, ) # Note that setting name ensures we query the same name # for A as we did for AAAA. (This is just in case search lists # are active by default in the resolver configuration and # we might be talking to a server that says NXDOMAIN when it # wants to say NOERROR no data. name = v6.qname v4 = self.resolve( name, dns.rdatatype.A, raise_on_no_answer=False, lifetime=self._compute_timeout(start, lifetime), **modified_kwargs, ) answers = HostAnswers.make(v6=v6, v4=v4, add_empty=not raise_on_no_answer) if not answers: raise NoAnswer(response=v6.response) return answers # pylint: disable=redefined-outer-name def canonical_name(self, name: Union[dns.name.Name, str]) -> dns.name.Name: """Determine the canonical name of *name*. The canonical name is the name the resolver uses for queries after all CNAME and DNAME renamings have been applied. *name*, a ``dns.name.Name`` or ``str``, the query name. This method can raise any exception that ``resolve()`` can raise, other than ``dns.resolver.NoAnswer`` and ``dns.resolver.NXDOMAIN``. Returns a ``dns.name.Name``. """ try: answer = self.resolve(name, raise_on_no_answer=False) canonical_name = answer.canonical_name except dns.resolver.NXDOMAIN as e: canonical_name = e.canonical_name return canonical_name # pylint: enable=redefined-outer-name def try_ddr(self, lifetime: float = 5.0) -> None: """Try to update the resolver's nameservers using Discovery of Designated Resolvers (DDR). If successful, the resolver will subsequently use DNS-over-HTTPS or DNS-over-TLS for future queries. *lifetime*, a float, is the maximum time to spend attempting DDR. The default is 5 seconds. If the SVCB query is successful and results in a non-empty list of nameservers, then the resolver's nameservers are set to the returned servers in priority order. The current implementation does not use any address hints from the SVCB record, nor does it resolve addresses for the SCVB target name, rather it assumes that the bootstrap nameserver will always be one of the addresses and uses it. A future revision to the code may offer fuller support. The code verifies that the bootstrap nameserver is in the Subject Alternative Name field of the TLS certficate. """ try: expiration = time.time() + lifetime answer = self.resolve( dns._ddr._local_resolver_name, "SVCB", lifetime=lifetime ) timeout = dns.query._remaining(expiration) nameservers = dns._ddr._get_nameservers_sync(answer, timeout) if len(nameservers) > 0: self.nameservers = nameservers except Exception: # pragma: no cover pass #: The default resolver. default_resolver: Optional[Resolver] = None def get_default_resolver() -> Resolver: """Get the default resolver, initializing it if necessary.""" if default_resolver is None: reset_default_resolver() assert default_resolver is not None return default_resolver def reset_default_resolver() -> None: """Re-initialize default resolver. Note that the resolver configuration (i.e. /etc/resolv.conf on UNIX systems) will be re-read immediately. """ global default_resolver default_resolver = Resolver() def resolve( qname: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.A, rdclass: Union[dns.rdataclass.RdataClass, str] = dns.rdataclass.IN, tcp: bool = False, source: Optional[str] = None, raise_on_no_answer: bool = True, source_port: int = 0, lifetime: Optional[float] = None, search: Optional[bool] = None, ) -> Answer: # pragma: no cover """Query nameservers to find the answer to the question. This is a convenience function that uses the default resolver object to make the query. See ``dns.resolver.Resolver.resolve`` for more information on the parameters. """ return get_default_resolver().resolve( qname, rdtype, rdclass, tcp, source, raise_on_no_answer, source_port, lifetime, search, ) def query( qname: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.A, rdclass: Union[dns.rdataclass.RdataClass, str] = dns.rdataclass.IN, tcp: bool = False, source: Optional[str] = None, raise_on_no_answer: bool = True, source_port: int = 0, lifetime: Optional[float] = None, ) -> Answer: # pragma: no cover """Query nameservers to find the answer to the question. This method calls resolve() with ``search=True``, and is provided for backwards compatibility with prior versions of dnspython. See the documentation for the resolve() method for further details. """ warnings.warn( "please use dns.resolver.resolve() instead", DeprecationWarning, stacklevel=2 ) return resolve( qname, rdtype, rdclass, tcp, source, raise_on_no_answer, source_port, lifetime, True, ) def resolve_address(ipaddr: str, *args: Any, **kwargs: Any) -> Answer: """Use a resolver to run a reverse query for PTR records. See ``dns.resolver.Resolver.resolve_address`` for more information on the parameters. """ return get_default_resolver().resolve_address(ipaddr, *args, **kwargs) def resolve_name( name: Union[dns.name.Name, str], family: int = socket.AF_UNSPEC, **kwargs: Any ) -> HostAnswers: """Use a resolver to query for address records. See ``dns.resolver.Resolver.resolve_name`` for more information on the parameters. """ return get_default_resolver().resolve_name(name, family, **kwargs) def canonical_name(name: Union[dns.name.Name, str]) -> dns.name.Name: """Determine the canonical name of *name*. See ``dns.resolver.Resolver.canonical_name`` for more information on the parameters and possible exceptions. """ return get_default_resolver().canonical_name(name) def try_ddr(lifetime: float = 5.0) -> None: # pragma: no cover """Try to update the default resolver's nameservers using Discovery of Designated Resolvers (DDR). If successful, the resolver will subsequently use DNS-over-HTTPS or DNS-over-TLS for future queries. See :py:func:`dns.resolver.Resolver.try_ddr` for more information. """ return get_default_resolver().try_ddr(lifetime) def zone_for_name( name: Union[dns.name.Name, str], rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, tcp: bool = False, resolver: Optional[Resolver] = None, lifetime: Optional[float] = None, ) -> dns.name.Name: """Find the name of the zone which contains the specified name. *name*, an absolute ``dns.name.Name`` or ``str``, the query name. *rdclass*, an ``int``, the query class. *tcp*, a ``bool``. If ``True``, use TCP to make the query. *resolver*, a ``dns.resolver.Resolver`` or ``None``, the resolver to use. If ``None``, the default, then the default resolver is used. *lifetime*, a ``float``, the total time to allow for the queries needed to determine the zone. If ``None``, the default, then only the individual query limits of the resolver apply. Raises ``dns.resolver.NoRootSOA`` if there is no SOA RR at the DNS root. (This is only likely to happen if you're using non-default root servers in your network and they are misconfigured.) Raises ``dns.resolver.LifetimeTimeout`` if the answer could not be found in the allotted lifetime. Returns a ``dns.name.Name``. """ if isinstance(name, str): name = dns.name.from_text(name, dns.name.root) if resolver is None: resolver = get_default_resolver() if not name.is_absolute(): raise NotAbsolute(name) start = time.time() expiration: Optional[float] if lifetime is not None: expiration = start + lifetime else: expiration = None while 1: try: rlifetime: Optional[float] if expiration is not None: rlifetime = expiration - time.time() if rlifetime <= 0: rlifetime = 0 else: rlifetime = None answer = resolver.resolve( name, dns.rdatatype.SOA, rdclass, tcp, lifetime=rlifetime ) assert answer.rrset is not None if answer.rrset.name == name: return name # otherwise we were CNAMEd or DNAMEd and need to look higher except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer) as e: if isinstance(e, dns.resolver.NXDOMAIN): response = e.responses().get(name) else: response = e.response() # pylint: disable=no-value-for-parameter if response: for rrs in response.authority: if rrs.rdtype == dns.rdatatype.SOA and rrs.rdclass == rdclass: (nr, _, _) = rrs.name.fullcompare(name) if nr == dns.name.NAMERELN_SUPERDOMAIN: # We're doing a proper superdomain check as # if the name were equal we ought to have gotten # it in the answer section! We are ignoring the # possibility that the authority is insane and # is including multiple SOA RRs for different # authorities. return rrs.name # we couldn't extract anything useful from the response (e.g. it's # a type 3 NXDOMAIN) try: name = name.parent() except dns.name.NoParent: raise NoRootSOA def make_resolver_at( where: Union[dns.name.Name, str], port: int = 53, family: int = socket.AF_UNSPEC, resolver: Optional[Resolver] = None, ) -> Resolver: """Make a stub resolver using the specified destination as the full resolver. *where*, a ``dns.name.Name`` or ``str`` the domain name or IP address of the full resolver. *port*, an ``int``, the port to use. If not specified, the default is 53. *family*, an ``int``, the address family to use. This parameter is used if *where* is not an address. The default is ``socket.AF_UNSPEC`` in which case the first address returned by ``resolve_name()`` will be used, otherwise the first address of the specified family will be used. *resolver*, a ``dns.resolver.Resolver`` or ``None``, the resolver to use for resolution of hostnames. If not specified, the default resolver will be used. Returns a ``dns.resolver.Resolver`` or raises an exception. """ if resolver is None: resolver = get_default_resolver() nameservers: List[Union[str, dns.nameserver.Nameserver]] = [] if isinstance(where, str) and dns.inet.is_address(where): nameservers.append(dns.nameserver.Do53Nameserver(where, port)) else: for address in resolver.resolve_name(where, family).addresses(): nameservers.append(dns.nameserver.Do53Nameserver(address, port)) res = dns.resolver.Resolver(configure=False) res.nameservers = nameservers return res def resolve_at( where: Union[dns.name.Name, str], qname: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.A, rdclass: Union[dns.rdataclass.RdataClass, str] = dns.rdataclass.IN, tcp: bool = False, source: Optional[str] = None, raise_on_no_answer: bool = True, source_port: int = 0, lifetime: Optional[float] = None, search: Optional[bool] = None, port: int = 53, family: int = socket.AF_UNSPEC, resolver: Optional[Resolver] = None, ) -> Answer: """Query nameservers to find the answer to the question. This is a convenience function that calls ``dns.resolver.make_resolver_at()`` to make a resolver, and then uses it to resolve the query. See ``dns.resolver.Resolver.resolve`` for more information on the resolution parameters, and ``dns.resolver.make_resolver_at`` for information about the resolver parameters *where*, *port*, *family*, and *resolver*. If making more than one query, it is more efficient to call ``dns.resolver.make_resolver_at()`` and then use that resolver for the queries instead of calling ``resolve_at()`` multiple times. """ return make_resolver_at(where, port, family, resolver).resolve( qname, rdtype, rdclass, tcp, source, raise_on_no_answer, source_port, lifetime, search, ) # # Support for overriding the system resolver for all python code in the # running process. # _protocols_for_socktype = { socket.SOCK_DGRAM: [socket.SOL_UDP], socket.SOCK_STREAM: [socket.SOL_TCP], } _resolver = None _original_getaddrinfo = socket.getaddrinfo _original_getnameinfo = socket.getnameinfo _original_getfqdn = socket.getfqdn _original_gethostbyname = socket.gethostbyname _original_gethostbyname_ex = socket.gethostbyname_ex _original_gethostbyaddr = socket.gethostbyaddr def _getaddrinfo( host=None, service=None, family=socket.AF_UNSPEC, socktype=0, proto=0, flags=0 ): if flags & socket.AI_NUMERICHOST != 0: # Short circuit directly into the system's getaddrinfo(). We're # not adding any value in this case, and this avoids infinite loops # because dns.query.* needs to call getaddrinfo() for IPv6 scoping # reasons. We will also do this short circuit below if we # discover that the host is an address literal. return _original_getaddrinfo(host, service, family, socktype, proto, flags) if flags & (socket.AI_ADDRCONFIG | socket.AI_V4MAPPED) != 0: # Not implemented. We raise a gaierror as opposed to a # NotImplementedError as it helps callers handle errors more # appropriately. [Issue #316] # # We raise EAI_FAIL as opposed to EAI_SYSTEM because there is # no EAI_SYSTEM on Windows [Issue #416]. We didn't go for # EAI_BADFLAGS as the flags aren't bad, we just don't # implement them. raise socket.gaierror( socket.EAI_FAIL, "Non-recoverable failure in name resolution" ) if host is None and service is None: raise socket.gaierror(socket.EAI_NONAME, "Name or service not known") addrs = [] canonical_name = None # pylint: disable=redefined-outer-name # Is host None or an address literal? If so, use the system's # getaddrinfo(). if host is None: return _original_getaddrinfo(host, service, family, socktype, proto, flags) try: # We don't care about the result of af_for_address(), we're just # calling it so it raises an exception if host is not an IPv4 or # IPv6 address. dns.inet.af_for_address(host) return _original_getaddrinfo(host, service, family, socktype, proto, flags) except Exception: pass # Something needs resolution! try: answers = _resolver.resolve_name(host, family) addrs = answers.addresses_and_families() canonical_name = answers.canonical_name().to_text(True) except dns.resolver.NXDOMAIN: raise socket.gaierror(socket.EAI_NONAME, "Name or service not known") except Exception: # We raise EAI_AGAIN here as the failure may be temporary # (e.g. a timeout) and EAI_SYSTEM isn't defined on Windows. # [Issue #416] raise socket.gaierror(socket.EAI_AGAIN, "Temporary failure in name resolution") port = None try: # Is it a port literal? if service is None: port = 0 else: port = int(service) except Exception: if flags & socket.AI_NUMERICSERV == 0: try: port = socket.getservbyname(service) except Exception: pass if port is None: raise socket.gaierror(socket.EAI_NONAME, "Name or service not known") tuples = [] if socktype == 0: socktypes = [socket.SOCK_DGRAM, socket.SOCK_STREAM] else: socktypes = [socktype] if flags & socket.AI_CANONNAME != 0: cname = canonical_name else: cname = "" for addr, af in addrs: for socktype in socktypes: for proto in _protocols_for_socktype[socktype]: addr_tuple = dns.inet.low_level_address_tuple((addr, port), af) tuples.append((af, socktype, proto, cname, addr_tuple)) if len(tuples) == 0: raise socket.gaierror(socket.EAI_NONAME, "Name or service not known") return tuples def _getnameinfo(sockaddr, flags=0): host = sockaddr[0] port = sockaddr[1] if len(sockaddr) == 4: scope = sockaddr[3] family = socket.AF_INET6 else: scope = None family = socket.AF_INET tuples = _getaddrinfo(host, port, family, socket.SOCK_STREAM, socket.SOL_TCP, 0) if len(tuples) > 1: raise OSError("sockaddr resolved to multiple addresses") addr = tuples[0][4][0] if flags & socket.NI_DGRAM: pname = "udp" else: pname = "tcp" qname = dns.reversename.from_address(addr) if flags & socket.NI_NUMERICHOST == 0: try: answer = _resolver.resolve(qname, "PTR") hostname = answer.rrset[0].target.to_text(True) except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): if flags & socket.NI_NAMEREQD: raise socket.gaierror(socket.EAI_NONAME, "Name or service not known") hostname = addr if scope is not None: hostname += "%" + str(scope) else: hostname = addr if scope is not None: hostname += "%" + str(scope) if flags & socket.NI_NUMERICSERV: service = str(port) else: service = socket.getservbyport(port, pname) return (hostname, service) def _getfqdn(name=None): if name is None: name = socket.gethostname() try: (name, _, _) = _gethostbyaddr(name) # Python's version checks aliases too, but our gethostbyname # ignores them, so we do so here as well. except Exception: # pragma: no cover pass return name def _gethostbyname(name): return _gethostbyname_ex(name)[2][0] def _gethostbyname_ex(name): aliases = [] addresses = [] tuples = _getaddrinfo( name, 0, socket.AF_INET, socket.SOCK_STREAM, socket.SOL_TCP, socket.AI_CANONNAME ) canonical = tuples[0][3] for item in tuples: addresses.append(item[4][0]) # XXX we just ignore aliases return (canonical, aliases, addresses) def _gethostbyaddr(ip): try: dns.ipv6.inet_aton(ip) sockaddr = (ip, 80, 0, 0) family = socket.AF_INET6 except Exception: try: dns.ipv4.inet_aton(ip) except Exception: raise socket.gaierror(socket.EAI_NONAME, "Name or service not known") sockaddr = (ip, 80) family = socket.AF_INET (name, _) = _getnameinfo(sockaddr, socket.NI_NAMEREQD) aliases = [] addresses = [] tuples = _getaddrinfo( name, 0, family, socket.SOCK_STREAM, socket.SOL_TCP, socket.AI_CANONNAME ) canonical = tuples[0][3] # We only want to include an address from the tuples if it's the # same as the one we asked about. We do this comparison in binary # to avoid any differences in text representations. bin_ip = dns.inet.inet_pton(family, ip) for item in tuples: addr = item[4][0] bin_addr = dns.inet.inet_pton(family, addr) if bin_ip == bin_addr: addresses.append(addr) # XXX we just ignore aliases return (canonical, aliases, addresses) def override_system_resolver(resolver: Optional[Resolver] = None) -> None: """Override the system resolver routines in the socket module with versions which use dnspython's resolver. This can be useful in testing situations where you want to control the resolution behavior of python code without having to change the system's resolver settings (e.g. /etc/resolv.conf). The resolver to use may be specified; if it's not, the default resolver will be used. resolver, a ``dns.resolver.Resolver`` or ``None``, the resolver to use. """ if resolver is None: resolver = get_default_resolver() global _resolver _resolver = resolver socket.getaddrinfo = _getaddrinfo socket.getnameinfo = _getnameinfo socket.getfqdn = _getfqdn socket.gethostbyname = _gethostbyname socket.gethostbyname_ex = _gethostbyname_ex socket.gethostbyaddr = _gethostbyaddr def restore_system_resolver() -> None: """Undo the effects of prior override_system_resolver().""" global _resolver _resolver = None socket.getaddrinfo = _original_getaddrinfo socket.getnameinfo = _original_getnameinfo socket.getfqdn = _original_getfqdn socket.gethostbyname = _original_gethostbyname socket.gethostbyname_ex = _original_gethostbyname_ex socket.gethostbyaddr = _original_gethostbyaddr dnspython-2.7.0/dns/reversename.py0000644000000000000000000000736413615410400014152 0ustar00# 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 # 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. """DNS Reverse Map Names.""" import binascii import dns.ipv4 import dns.ipv6 import dns.name ipv4_reverse_domain = dns.name.from_text("in-addr.arpa.") ipv6_reverse_domain = dns.name.from_text("ip6.arpa.") def from_address( text: str, v4_origin: dns.name.Name = ipv4_reverse_domain, v6_origin: dns.name.Name = ipv6_reverse_domain, ) -> dns.name.Name: """Convert an IPv4 or IPv6 address in textual form into a Name object whose value is the reverse-map domain name of the address. *text*, a ``str``, is an IPv4 or IPv6 address in textual form (e.g. '127.0.0.1', '::1') *v4_origin*, a ``dns.name.Name`` to append to the labels corresponding to the address if the address is an IPv4 address, instead of the default (in-addr.arpa.) *v6_origin*, a ``dns.name.Name`` to append to the labels corresponding to the address if the address is an IPv6 address, instead of the default (ip6.arpa.) Raises ``dns.exception.SyntaxError`` if the address is badly formed. Returns a ``dns.name.Name``. """ try: v6 = dns.ipv6.inet_aton(text) if dns.ipv6.is_mapped(v6): parts = ["%d" % byte for byte in v6[12:]] origin = v4_origin else: parts = [x for x in str(binascii.hexlify(v6).decode())] origin = v6_origin except Exception: parts = ["%d" % byte for byte in dns.ipv4.inet_aton(text)] origin = v4_origin return dns.name.from_text(".".join(reversed(parts)), origin=origin) def to_address( name: dns.name.Name, v4_origin: dns.name.Name = ipv4_reverse_domain, v6_origin: dns.name.Name = ipv6_reverse_domain, ) -> str: """Convert a reverse map domain name into textual address form. *name*, a ``dns.name.Name``, an IPv4 or IPv6 address in reverse-map name form. *v4_origin*, a ``dns.name.Name`` representing the top-level domain for IPv4 addresses, instead of the default (in-addr.arpa.) *v6_origin*, a ``dns.name.Name`` representing the top-level domain for IPv4 addresses, instead of the default (ip6.arpa.) Raises ``dns.exception.SyntaxError`` if the name does not have a reverse-map form. Returns a ``str``. """ if name.is_subdomain(v4_origin): name = name.relativize(v4_origin) text = b".".join(reversed(name.labels)) # run through inet_ntoa() to check syntax and make pretty. return dns.ipv4.inet_ntoa(dns.ipv4.inet_aton(text)) elif name.is_subdomain(v6_origin): name = name.relativize(v6_origin) labels = list(reversed(name.labels)) parts = [] for i in range(0, len(labels), 4): parts.append(b"".join(labels[i : i + 4])) text = b":".join(parts) # run through inet_ntoa() to check syntax and make pretty. return dns.ipv6.inet_ntoa(dns.ipv6.inet_aton(text)) else: raise dns.exception.SyntaxError("unknown reverse-map address family") dnspython-2.7.0/dns/rrset.py0000644000000000000000000002172213615410400012767 0ustar00# 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 # 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. """DNS RRsets (an RRset is a named rdataset)""" from typing import Any, Collection, Dict, Optional, Union, cast import dns.name import dns.rdataclass import dns.rdataset import dns.renderer class RRset(dns.rdataset.Rdataset): """A DNS RRset (named rdataset). RRset inherits from Rdataset, and RRsets can be treated as Rdatasets in most cases. There are, however, a few notable exceptions. RRsets have different to_wire() and to_text() method arguments, reflecting the fact that RRsets always have an owner name. """ __slots__ = ["name", "deleting"] def __init__( self, name: dns.name.Name, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, deleting: Optional[dns.rdataclass.RdataClass] = None, ): """Create a new RRset.""" super().__init__(rdclass, rdtype, covers) self.name = name self.deleting = deleting def _clone(self): obj = super()._clone() obj.name = self.name obj.deleting = self.deleting return obj def __repr__(self): if self.covers == 0: ctext = "" else: ctext = "(" + dns.rdatatype.to_text(self.covers) + ")" if self.deleting is not None: dtext = " delete=" + dns.rdataclass.to_text(self.deleting) else: dtext = "" return ( "" ) def __str__(self): return self.to_text() def __eq__(self, other): if isinstance(other, RRset): if self.name != other.name: return False elif not isinstance(other, dns.rdataset.Rdataset): return False return super().__eq__(other) def match(self, *args: Any, **kwargs: Any) -> bool: # type: ignore[override] """Does this rrset match the specified attributes? Behaves as :py:func:`full_match()` if the first argument is a ``dns.name.Name``, and as :py:func:`dns.rdataset.Rdataset.match()` otherwise. (This behavior fixes a design mistake where the signature of this method became incompatible with that of its superclass. The fix makes RRsets matchable as Rdatasets while preserving backwards compatibility.) """ if isinstance(args[0], dns.name.Name): return self.full_match(*args, **kwargs) # type: ignore[arg-type] else: return super().match(*args, **kwargs) # type: ignore[arg-type] def full_match( self, name: dns.name.Name, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType, deleting: Optional[dns.rdataclass.RdataClass] = None, ) -> bool: """Returns ``True`` if this rrset matches the specified name, class, type, covers, and deletion state. """ if not super().match(rdclass, rdtype, covers): return False if self.name != name or self.deleting != deleting: return False return True # pylint: disable=arguments-differ def to_text( # type: ignore[override] self, origin: Optional[dns.name.Name] = None, relativize: bool = True, **kw: Dict[str, Any], ) -> str: """Convert the RRset into DNS zone file format. See ``dns.name.Name.choose_relativity`` for more information on how *origin* and *relativize* determine the way names are emitted. Any additional keyword arguments are passed on to the rdata ``to_text()`` method. *origin*, a ``dns.name.Name`` or ``None``, the origin for relative names. *relativize*, a ``bool``. If ``True``, names will be relativized to *origin*. """ return super().to_text( self.name, origin, relativize, self.deleting, **kw # type: ignore ) def to_wire( # type: ignore[override] self, file: Any, compress: Optional[dns.name.CompressType] = None, # type: ignore origin: Optional[dns.name.Name] = None, **kw: Dict[str, Any], ) -> int: """Convert the RRset to wire format. All keyword arguments are passed to ``dns.rdataset.to_wire()``; see that function for details. Returns an ``int``, the number of records emitted. """ return super().to_wire( self.name, file, compress, origin, self.deleting, **kw # type:ignore ) # pylint: enable=arguments-differ def to_rdataset(self) -> dns.rdataset.Rdataset: """Convert an RRset into an Rdataset. Returns a ``dns.rdataset.Rdataset``. """ return dns.rdataset.from_rdata_list(self.ttl, list(self)) def from_text_list( name: Union[dns.name.Name, str], ttl: int, rdclass: Union[dns.rdataclass.RdataClass, str], rdtype: Union[dns.rdatatype.RdataType, str], text_rdatas: Collection[str], idna_codec: Optional[dns.name.IDNACodec] = None, origin: Optional[dns.name.Name] = None, relativize: bool = True, relativize_to: Optional[dns.name.Name] = None, ) -> RRset: """Create an RRset with the specified name, TTL, class, and type, and with the specified list of rdatas in text format. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA encoder/decoder to use; if ``None``, the default IDNA 2003 encoder/decoder is used. *origin*, a ``dns.name.Name`` (or ``None``), the origin to use for relative names. *relativize*, a ``bool``. If true, name will be relativized. *relativize_to*, a ``dns.name.Name`` (or ``None``), the origin to use when relativizing names. If not set, the *origin* value will be used. Returns a ``dns.rrset.RRset`` object. """ if isinstance(name, str): name = dns.name.from_text(name, None, idna_codec=idna_codec) rdclass = dns.rdataclass.RdataClass.make(rdclass) rdtype = dns.rdatatype.RdataType.make(rdtype) r = RRset(name, rdclass, rdtype) r.update_ttl(ttl) for t in text_rdatas: rd = dns.rdata.from_text( r.rdclass, r.rdtype, t, origin, relativize, relativize_to, idna_codec ) r.add(rd) return r def from_text( name: Union[dns.name.Name, str], ttl: int, rdclass: Union[dns.rdataclass.RdataClass, str], rdtype: Union[dns.rdatatype.RdataType, str], *text_rdatas: Any, ) -> RRset: """Create an RRset with the specified name, TTL, class, and type and with the specified rdatas in text format. Returns a ``dns.rrset.RRset`` object. """ return from_text_list( name, ttl, rdclass, rdtype, cast(Collection[str], text_rdatas) ) def from_rdata_list( name: Union[dns.name.Name, str], ttl: int, rdatas: Collection[dns.rdata.Rdata], idna_codec: Optional[dns.name.IDNACodec] = None, ) -> RRset: """Create an RRset with the specified name and TTL, and with the specified list of rdata objects. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA encoder/decoder to use; if ``None``, the default IDNA 2003 encoder/decoder is used. Returns a ``dns.rrset.RRset`` object. """ if isinstance(name, str): name = dns.name.from_text(name, None, idna_codec=idna_codec) if len(rdatas) == 0: raise ValueError("rdata list must not be empty") r = None for rd in rdatas: if r is None: r = RRset(name, rd.rdclass, rd.rdtype) r.update_ttl(ttl) r.add(rd) assert r is not None return r def from_rdata(name: Union[dns.name.Name, str], ttl: int, *rdatas: Any) -> RRset: """Create an RRset with the specified name and TTL, and with the specified rdata objects. Returns a ``dns.rrset.RRset`` object. """ return from_rdata_list(name, ttl, cast(Collection[dns.rdata.Rdata], rdatas)) dnspython-2.7.0/dns/serial.py0000644000000000000000000000702613615410400013110 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license """Serial Number Arthimetic from RFC 1982""" class Serial: def __init__(self, value: int, bits: int = 32): self.value = value % 2**bits self.bits = bits def __repr__(self): return f"dns.serial.Serial({self.value}, {self.bits})" def __eq__(self, other): if isinstance(other, int): other = Serial(other, self.bits) elif not isinstance(other, Serial) or other.bits != self.bits: return NotImplemented return self.value == other.value def __ne__(self, other): if isinstance(other, int): other = Serial(other, self.bits) elif not isinstance(other, Serial) or other.bits != self.bits: return NotImplemented return self.value != other.value def __lt__(self, other): if isinstance(other, int): other = Serial(other, self.bits) elif not isinstance(other, Serial) or other.bits != self.bits: return NotImplemented if self.value < other.value and other.value - self.value < 2 ** (self.bits - 1): return True elif self.value > other.value and self.value - other.value > 2 ** ( self.bits - 1 ): return True else: return False def __le__(self, other): return self == other or self < other def __gt__(self, other): if isinstance(other, int): other = Serial(other, self.bits) elif not isinstance(other, Serial) or other.bits != self.bits: return NotImplemented if self.value < other.value and other.value - self.value > 2 ** (self.bits - 1): return True elif self.value > other.value and self.value - other.value < 2 ** ( self.bits - 1 ): return True else: return False def __ge__(self, other): return self == other or self > other def __add__(self, other): v = self.value if isinstance(other, Serial): delta = other.value elif isinstance(other, int): delta = other else: raise ValueError if abs(delta) > (2 ** (self.bits - 1) - 1): raise ValueError v += delta v = v % 2**self.bits return Serial(v, self.bits) def __iadd__(self, other): v = self.value if isinstance(other, Serial): delta = other.value elif isinstance(other, int): delta = other else: raise ValueError if abs(delta) > (2 ** (self.bits - 1) - 1): raise ValueError v += delta v = v % 2**self.bits self.value = v return self def __sub__(self, other): v = self.value if isinstance(other, Serial): delta = other.value elif isinstance(other, int): delta = other else: raise ValueError if abs(delta) > (2 ** (self.bits - 1) - 1): raise ValueError v -= delta v = v % 2**self.bits return Serial(v, self.bits) def __isub__(self, other): v = self.value if isinstance(other, Serial): delta = other.value elif isinstance(other, int): delta = other else: raise ValueError if abs(delta) > (2 ** (self.bits - 1) - 1): raise ValueError v -= delta v = v % 2**self.bits self.value = v return self dnspython-2.7.0/dns/set.py0000644000000000000000000002177513615410400012433 0ustar00# 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 # 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 itertools class Set: """A simple set class. This class was originally used to deal with python not having a set class, and originally the class used lists in its implementation. The ordered and indexable nature of RRsets and Rdatasets is unfortunately widely used in dnspython applications, so for backwards compatibility sets continue to be a custom class, now based on an ordered dictionary. """ __slots__ = ["items"] def __init__(self, items=None): """Initialize the set. *items*, an iterable or ``None``, the initial set of items. """ self.items = dict() if items is not None: for item in items: # This is safe for how we use set, but if other code # subclasses it could be a legitimate issue. self.add(item) # lgtm[py/init-calls-subclass] def __repr__(self): return f"dns.set.Set({repr(list(self.items.keys()))})" # pragma: no cover def add(self, item): """Add an item to the set.""" if item not in self.items: self.items[item] = None def remove(self, item): """Remove an item from the set.""" try: del self.items[item] except KeyError: raise ValueError def discard(self, item): """Remove an item from the set if present.""" self.items.pop(item, None) def pop(self): """Remove an arbitrary item from the set.""" (k, _) = self.items.popitem() return k def _clone(self) -> "Set": """Make a (shallow) copy of the set. There is a 'clone protocol' that subclasses of this class should use. To make a copy, first call your super's _clone() method, and use the object returned as the new instance. Then make shallow copies of the attributes defined in the subclass. This protocol allows us to write the set algorithms that return new instances (e.g. union) once, and keep using them in subclasses. """ if hasattr(self, "_clone_class"): cls = self._clone_class # type: ignore else: cls = self.__class__ obj = cls.__new__(cls) obj.items = dict() obj.items.update(self.items) return obj def __copy__(self): """Make a (shallow) copy of the set.""" return self._clone() def copy(self): """Make a (shallow) copy of the set.""" return self._clone() def union_update(self, other): """Update the set, adding any elements from other which are not already in the set. """ if not isinstance(other, Set): raise ValueError("other must be a Set instance") if self is other: # lgtm[py/comparison-using-is] return for item in other.items: self.add(item) def intersection_update(self, other): """Update the set, removing any elements from other which are not in both sets. """ if not isinstance(other, Set): raise ValueError("other must be a Set instance") if self is other: # lgtm[py/comparison-using-is] return # we make a copy of the list so that we can remove items from # the list without breaking the iterator. for item in list(self.items): if item not in other.items: del self.items[item] def difference_update(self, other): """Update the set, removing any elements from other which are in the set. """ if not isinstance(other, Set): raise ValueError("other must be a Set instance") if self is other: # lgtm[py/comparison-using-is] self.items.clear() else: for item in other.items: self.discard(item) def symmetric_difference_update(self, other): """Update the set, retaining only elements unique to both sets.""" if not isinstance(other, Set): raise ValueError("other must be a Set instance") if self is other: # lgtm[py/comparison-using-is] self.items.clear() else: overlap = self.intersection(other) self.union_update(other) self.difference_update(overlap) def union(self, other): """Return a new set which is the union of ``self`` and ``other``. Returns the same Set type as this set. """ obj = self._clone() obj.union_update(other) return obj def intersection(self, other): """Return a new set which is the intersection of ``self`` and ``other``. Returns the same Set type as this set. """ obj = self._clone() obj.intersection_update(other) return obj def difference(self, other): """Return a new set which ``self`` - ``other``, i.e. the items in ``self`` which are not also in ``other``. Returns the same Set type as this set. """ obj = self._clone() obj.difference_update(other) return obj def symmetric_difference(self, other): """Return a new set which (``self`` - ``other``) | (``other`` - ``self), ie: the items in either ``self`` or ``other`` which are not contained in their intersection. Returns the same Set type as this set. """ obj = self._clone() obj.symmetric_difference_update(other) return obj def __or__(self, other): return self.union(other) def __and__(self, other): return self.intersection(other) def __add__(self, other): return self.union(other) def __sub__(self, other): return self.difference(other) def __xor__(self, other): return self.symmetric_difference(other) def __ior__(self, other): self.union_update(other) return self def __iand__(self, other): self.intersection_update(other) return self def __iadd__(self, other): self.union_update(other) return self def __isub__(self, other): self.difference_update(other) return self def __ixor__(self, other): self.symmetric_difference_update(other) return self def update(self, other): """Update the set, adding any elements from other which are not already in the set. *other*, the collection of items with which to update the set, which may be any iterable type. """ for item in other: self.add(item) def clear(self): """Make the set empty.""" self.items.clear() def __eq__(self, other): return self.items == other.items def __ne__(self, other): return not self.__eq__(other) def __len__(self): return len(self.items) def __iter__(self): return iter(self.items) def __getitem__(self, i): if isinstance(i, slice): return list(itertools.islice(self.items, i.start, i.stop, i.step)) else: return next(itertools.islice(self.items, i, i + 1)) def __delitem__(self, i): if isinstance(i, slice): for elt in list(self[i]): del self.items[elt] else: del self.items[self[i]] def issubset(self, other): """Is this set a subset of *other*? Returns a ``bool``. """ if not isinstance(other, Set): raise ValueError("other must be a Set instance") for item in self.items: if item not in other.items: return False return True def issuperset(self, other): """Is this set a superset of *other*? Returns a ``bool``. """ if not isinstance(other, Set): raise ValueError("other must be a Set instance") for item in other.items: if item not in self.items: return False return True def isdisjoint(self, other): if not isinstance(other, Set): raise ValueError("other must be a Set instance") for item in other.items: if item in self.items: return False return True dnspython-2.7.0/dns/tokenizer.py0000644000000000000000000005603713615410400013651 0ustar00# 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 # 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. """Tokenize DNS zone file format""" import io import sys from typing import Any, List, Optional, Tuple import dns.exception import dns.name import dns.ttl _DELIMITERS = {" ", "\t", "\n", ";", "(", ")", '"'} _QUOTING_DELIMITERS = {'"'} EOF = 0 EOL = 1 WHITESPACE = 2 IDENTIFIER = 3 QUOTED_STRING = 4 COMMENT = 5 DELIMITER = 6 class UngetBufferFull(dns.exception.DNSException): """An attempt was made to unget a token when the unget buffer was full.""" class Token: """A DNS zone file format token. ttype: The token type value: The token value has_escape: Does the token value contain escapes? """ def __init__( self, ttype: int, value: Any = "", has_escape: bool = False, comment: Optional[str] = None, ): """Initialize a token instance.""" self.ttype = ttype self.value = value self.has_escape = has_escape self.comment = comment def is_eof(self) -> bool: return self.ttype == EOF def is_eol(self) -> bool: return self.ttype == EOL def is_whitespace(self) -> bool: return self.ttype == WHITESPACE def is_identifier(self) -> bool: return self.ttype == IDENTIFIER def is_quoted_string(self) -> bool: return self.ttype == QUOTED_STRING def is_comment(self) -> bool: return self.ttype == COMMENT def is_delimiter(self) -> bool: # pragma: no cover (we don't return delimiters yet) return self.ttype == DELIMITER def is_eol_or_eof(self) -> bool: return self.ttype == EOL or self.ttype == EOF def __eq__(self, other): if not isinstance(other, Token): return False return self.ttype == other.ttype and self.value == other.value def __ne__(self, other): if not isinstance(other, Token): return True return self.ttype != other.ttype or self.value != other.value def __str__(self): return '%d "%s"' % (self.ttype, self.value) def unescape(self) -> "Token": if not self.has_escape: return self unescaped = "" l = len(self.value) i = 0 while i < l: c = self.value[i] i += 1 if c == "\\": if i >= l: # pragma: no cover (can't happen via get()) raise dns.exception.UnexpectedEnd c = self.value[i] i += 1 if c.isdigit(): if i >= l: raise dns.exception.UnexpectedEnd c2 = self.value[i] i += 1 if i >= l: raise dns.exception.UnexpectedEnd c3 = self.value[i] i += 1 if not (c2.isdigit() and c3.isdigit()): raise dns.exception.SyntaxError codepoint = int(c) * 100 + int(c2) * 10 + int(c3) if codepoint > 255: raise dns.exception.SyntaxError c = chr(codepoint) unescaped += c return Token(self.ttype, unescaped) def unescape_to_bytes(self) -> "Token": # We used to use unescape() for TXT-like records, but this # caused problems as we'd process DNS escapes into Unicode code # points instead of byte values, and then a to_text() of the # processed data would not equal the original input. For # example, \226 in the TXT record would have a to_text() of # \195\162 because we applied UTF-8 encoding to Unicode code # point 226. # # We now apply escapes while converting directly to bytes, # avoiding this double encoding. # # This code also handles cases where the unicode input has # non-ASCII code-points in it by converting it to UTF-8. TXT # records aren't defined for Unicode, but this is the best we # can do to preserve meaning. For example, # # foo\u200bbar # # (where \u200b is Unicode code point 0x200b) will be treated # as if the input had been the UTF-8 encoding of that string, # namely: # # foo\226\128\139bar # unescaped = b"" l = len(self.value) i = 0 while i < l: c = self.value[i] i += 1 if c == "\\": if i >= l: # pragma: no cover (can't happen via get()) raise dns.exception.UnexpectedEnd c = self.value[i] i += 1 if c.isdigit(): if i >= l: raise dns.exception.UnexpectedEnd c2 = self.value[i] i += 1 if i >= l: raise dns.exception.UnexpectedEnd c3 = self.value[i] i += 1 if not (c2.isdigit() and c3.isdigit()): raise dns.exception.SyntaxError codepoint = int(c) * 100 + int(c2) * 10 + int(c3) if codepoint > 255: raise dns.exception.SyntaxError unescaped += b"%c" % (codepoint) else: # Note that as mentioned above, if c is a Unicode # code point outside of the ASCII range, then this # += is converting that code point to its UTF-8 # encoding and appending multiple bytes to # unescaped. unescaped += c.encode() else: unescaped += c.encode() return Token(self.ttype, bytes(unescaped)) class Tokenizer: """A DNS zone file format tokenizer. A token object is basically a (type, value) tuple. The valid types are EOF, EOL, WHITESPACE, IDENTIFIER, QUOTED_STRING, COMMENT, and DELIMITER. file: The file to tokenize ungotten_char: The most recently ungotten character, or None. ungotten_token: The most recently ungotten token, or None. multiline: The current multiline level. This value is increased by one every time a '(' delimiter is read, and decreased by one every time a ')' delimiter is read. quoting: This variable is true if the tokenizer is currently reading a quoted string. eof: This variable is true if the tokenizer has encountered EOF. delimiters: The current delimiter dictionary. line_number: The current line number filename: A filename that will be returned by the where() method. idna_codec: A dns.name.IDNACodec, specifies the IDNA encoder/decoder. If None, the default IDNA 2003 encoder/decoder is used. """ def __init__( self, f: Any = sys.stdin, filename: Optional[str] = None, idna_codec: Optional[dns.name.IDNACodec] = None, ): """Initialize a tokenizer instance. f: The file to tokenize. The default is sys.stdin. This parameter may also be a string, in which case the tokenizer will take its input from the contents of the string. filename: the name of the filename that the where() method will return. idna_codec: A dns.name.IDNACodec, specifies the IDNA encoder/decoder. If None, the default IDNA 2003 encoder/decoder is used. """ if isinstance(f, str): f = io.StringIO(f) if filename is None: filename = "" elif isinstance(f, bytes): f = io.StringIO(f.decode()) if filename is None: filename = "" else: if filename is None: if f is sys.stdin: filename = "" else: filename = "" self.file = f self.ungotten_char: Optional[str] = None self.ungotten_token: Optional[Token] = None self.multiline = 0 self.quoting = False self.eof = False self.delimiters = _DELIMITERS self.line_number = 1 assert filename is not None self.filename = filename if idna_codec is None: self.idna_codec: dns.name.IDNACodec = dns.name.IDNA_2003 else: self.idna_codec = idna_codec def _get_char(self) -> str: """Read a character from input.""" if self.ungotten_char is None: if self.eof: c = "" else: c = self.file.read(1) if c == "": self.eof = True elif c == "\n": self.line_number += 1 else: c = self.ungotten_char self.ungotten_char = None return c def where(self) -> Tuple[str, int]: """Return the current location in the input. Returns a (string, int) tuple. The first item is the filename of the input, the second is the current line number. """ return (self.filename, self.line_number) def _unget_char(self, c: str) -> None: """Unget a character. The unget buffer for characters is only one character large; it is an error to try to unget a character when the unget buffer is not empty. c: the character to unget raises UngetBufferFull: there is already an ungotten char """ if self.ungotten_char is not None: # this should never happen! raise UngetBufferFull # pragma: no cover self.ungotten_char = c def skip_whitespace(self) -> int: """Consume input until a non-whitespace character is encountered. The non-whitespace character is then ungotten, and the number of whitespace characters consumed is returned. If the tokenizer is in multiline mode, then newlines are whitespace. Returns the number of characters skipped. """ skipped = 0 while True: c = self._get_char() if c != " " and c != "\t": if (c != "\n") or not self.multiline: self._unget_char(c) return skipped skipped += 1 def get(self, want_leading: bool = False, want_comment: bool = False) -> Token: """Get the next token. want_leading: If True, return a WHITESPACE token if the first character read is whitespace. The default is False. want_comment: If True, return a COMMENT token if the first token read is a comment. The default is False. Raises dns.exception.UnexpectedEnd: input ended prematurely Raises dns.exception.SyntaxError: input was badly formed Returns a Token. """ if self.ungotten_token is not None: utoken = self.ungotten_token self.ungotten_token = None if utoken.is_whitespace(): if want_leading: return utoken elif utoken.is_comment(): if want_comment: return utoken else: return utoken skipped = self.skip_whitespace() if want_leading and skipped > 0: return Token(WHITESPACE, " ") token = "" ttype = IDENTIFIER has_escape = False while True: c = self._get_char() if c == "" or c in self.delimiters: if c == "" and self.quoting: raise dns.exception.UnexpectedEnd if token == "" and ttype != QUOTED_STRING: if c == "(": self.multiline += 1 self.skip_whitespace() continue elif c == ")": if self.multiline <= 0: raise dns.exception.SyntaxError self.multiline -= 1 self.skip_whitespace() continue elif c == '"': if not self.quoting: self.quoting = True self.delimiters = _QUOTING_DELIMITERS ttype = QUOTED_STRING continue else: self.quoting = False self.delimiters = _DELIMITERS self.skip_whitespace() continue elif c == "\n": return Token(EOL, "\n") elif c == ";": while 1: c = self._get_char() if c == "\n" or c == "": break token += c if want_comment: self._unget_char(c) return Token(COMMENT, token) elif c == "": if self.multiline: raise dns.exception.SyntaxError( "unbalanced parentheses" ) return Token(EOF, comment=token) elif self.multiline: self.skip_whitespace() token = "" continue else: return Token(EOL, "\n", comment=token) else: # This code exists in case we ever want a # delimiter to be returned. It never produces # a token currently. token = c ttype = DELIMITER else: self._unget_char(c) break elif self.quoting and c == "\n": raise dns.exception.SyntaxError("newline in quoted string") elif c == "\\": # # It's an escape. Put it and the next character into # the token; it will be checked later for goodness. # token += c has_escape = True c = self._get_char() if c == "" or (c == "\n" and not self.quoting): raise dns.exception.UnexpectedEnd token += c if token == "" and ttype != QUOTED_STRING: if self.multiline: raise dns.exception.SyntaxError("unbalanced parentheses") ttype = EOF return Token(ttype, token, has_escape) def unget(self, token: Token) -> None: """Unget a token. The unget buffer for tokens is only one token large; it is an error to try to unget a token when the unget buffer is not empty. token: the token to unget Raises UngetBufferFull: there is already an ungotten token """ if self.ungotten_token is not None: raise UngetBufferFull self.ungotten_token = token def next(self): """Return the next item in an iteration. Returns a Token. """ token = self.get() if token.is_eof(): raise StopIteration return token __next__ = next def __iter__(self): return self # Helpers def get_int(self, base: int = 10) -> int: """Read the next token and interpret it as an unsigned integer. Raises dns.exception.SyntaxError if not an unsigned integer. Returns an int. """ token = self.get().unescape() if not token.is_identifier(): raise dns.exception.SyntaxError("expecting an identifier") if not token.value.isdigit(): raise dns.exception.SyntaxError("expecting an integer") return int(token.value, base) def get_uint8(self) -> int: """Read the next token and interpret it as an 8-bit unsigned integer. Raises dns.exception.SyntaxError if not an 8-bit unsigned integer. Returns an int. """ value = self.get_int() if value < 0 or value > 255: raise dns.exception.SyntaxError( "%d is not an unsigned 8-bit integer" % value ) return value def get_uint16(self, base: int = 10) -> int: """Read the next token and interpret it as a 16-bit unsigned integer. Raises dns.exception.SyntaxError if not a 16-bit unsigned integer. Returns an int. """ value = self.get_int(base=base) if value < 0 or value > 65535: if base == 8: raise dns.exception.SyntaxError( f"{value:o} is not an octal unsigned 16-bit integer" ) else: raise dns.exception.SyntaxError( "%d is not an unsigned 16-bit integer" % value ) return value def get_uint32(self, base: int = 10) -> int: """Read the next token and interpret it as a 32-bit unsigned integer. Raises dns.exception.SyntaxError if not a 32-bit unsigned integer. Returns an int. """ value = self.get_int(base=base) if value < 0 or value > 4294967295: raise dns.exception.SyntaxError( "%d is not an unsigned 32-bit integer" % value ) return value def get_uint48(self, base: int = 10) -> int: """Read the next token and interpret it as a 48-bit unsigned integer. Raises dns.exception.SyntaxError if not a 48-bit unsigned integer. Returns an int. """ value = self.get_int(base=base) if value < 0 or value > 281474976710655: raise dns.exception.SyntaxError( "%d is not an unsigned 48-bit integer" % value ) return value def get_string(self, max_length: Optional[int] = None) -> str: """Read the next token and interpret it as a string. Raises dns.exception.SyntaxError if not a string. Raises dns.exception.SyntaxError if token value length exceeds max_length (if specified). Returns a string. """ token = self.get().unescape() if not (token.is_identifier() or token.is_quoted_string()): raise dns.exception.SyntaxError("expecting a string") if max_length and len(token.value) > max_length: raise dns.exception.SyntaxError("string too long") return token.value def get_identifier(self) -> str: """Read the next token, which should be an identifier. Raises dns.exception.SyntaxError if not an identifier. Returns a string. """ token = self.get().unescape() if not token.is_identifier(): raise dns.exception.SyntaxError("expecting an identifier") return token.value def get_remaining(self, max_tokens: Optional[int] = None) -> List[Token]: """Return the remaining tokens on the line, until an EOL or EOF is seen. max_tokens: If not None, stop after this number of tokens. Returns a list of tokens. """ tokens = [] while True: token = self.get() if token.is_eol_or_eof(): self.unget(token) break tokens.append(token) if len(tokens) == max_tokens: break return tokens def concatenate_remaining_identifiers(self, allow_empty: bool = False) -> str: """Read the remaining tokens on the line, which should be identifiers. Raises dns.exception.SyntaxError if there are no remaining tokens, unless `allow_empty=True` is given. Raises dns.exception.SyntaxError if a token is seen that is not an identifier. Returns a string containing a concatenation of the remaining identifiers. """ s = "" while True: token = self.get().unescape() if token.is_eol_or_eof(): self.unget(token) break if not token.is_identifier(): raise dns.exception.SyntaxError s += token.value if not (allow_empty or s): raise dns.exception.SyntaxError("expecting another identifier") return s def as_name( self, token: Token, origin: Optional[dns.name.Name] = None, relativize: bool = False, relativize_to: Optional[dns.name.Name] = None, ) -> dns.name.Name: """Try to interpret the token as a DNS name. Raises dns.exception.SyntaxError if not a name. Returns a dns.name.Name. """ if not token.is_identifier(): raise dns.exception.SyntaxError("expecting an identifier") name = dns.name.from_text(token.value, origin, self.idna_codec) return name.choose_relativity(relativize_to or origin, relativize) def get_name( self, origin: Optional[dns.name.Name] = None, relativize: bool = False, relativize_to: Optional[dns.name.Name] = None, ) -> dns.name.Name: """Read the next token and interpret it as a DNS name. Raises dns.exception.SyntaxError if not a name. Returns a dns.name.Name. """ token = self.get() return self.as_name(token, origin, relativize, relativize_to) def get_eol_as_token(self) -> Token: """Read the next token and raise an exception if it isn't EOL or EOF. Returns a string. """ token = self.get() if not token.is_eol_or_eof(): raise dns.exception.SyntaxError( 'expected EOL or EOF, got %d "%s"' % (token.ttype, token.value) ) return token def get_eol(self) -> str: return self.get_eol_as_token().value def get_ttl(self) -> int: """Read the next token and interpret it as a DNS TTL. Raises dns.exception.SyntaxError or dns.ttl.BadTTL if not an identifier or badly formed. Returns an int. """ token = self.get().unescape() if not token.is_identifier(): raise dns.exception.SyntaxError("expecting an identifier") return dns.ttl.from_text(token.value) dnspython-2.7.0/dns/transaction.py0000644000000000000000000005407513615410400014164 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import collections from typing import Any, Callable, Iterator, List, Optional, Tuple, Union import dns.exception import dns.name import dns.node import dns.rdataclass import dns.rdataset import dns.rdatatype import dns.rrset import dns.serial import dns.ttl class TransactionManager: def reader(self) -> "Transaction": """Begin a read-only transaction.""" raise NotImplementedError # pragma: no cover def writer(self, replacement: bool = False) -> "Transaction": """Begin a writable transaction. *replacement*, a ``bool``. If `True`, the content of the transaction completely replaces any prior content. If False, the default, then the content of the transaction updates the existing content. """ raise NotImplementedError # pragma: no cover def origin_information( self, ) -> Tuple[Optional[dns.name.Name], bool, Optional[dns.name.Name]]: """Returns a tuple (absolute_origin, relativize, effective_origin) giving the absolute name of the default origin for any relative domain names, the "effective origin", and whether names should be relativized. The "effective origin" is the absolute origin if relativize is False, and the empty name if relativize is true. (The effective origin is provided even though it can be computed from the absolute_origin and relativize setting because it avoids a lot of code duplication.) If the returned names are `None`, then no origin information is available. This information is used by code working with transactions to allow it to coordinate relativization. The transaction code itself takes what it gets (i.e. does not change name relativity). """ raise NotImplementedError # pragma: no cover def get_class(self) -> dns.rdataclass.RdataClass: """The class of the transaction manager.""" raise NotImplementedError # pragma: no cover def from_wire_origin(self) -> Optional[dns.name.Name]: """Origin to use in from_wire() calls.""" (absolute_origin, relativize, _) = self.origin_information() if relativize: return absolute_origin else: return None class DeleteNotExact(dns.exception.DNSException): """Existing data did not match data specified by an exact delete.""" class ReadOnly(dns.exception.DNSException): """Tried to write to a read-only transaction.""" class AlreadyEnded(dns.exception.DNSException): """Tried to use an already-ended transaction.""" def _ensure_immutable_rdataset(rdataset): if rdataset is None or isinstance(rdataset, dns.rdataset.ImmutableRdataset): return rdataset return dns.rdataset.ImmutableRdataset(rdataset) def _ensure_immutable_node(node): if node is None or node.is_immutable(): return node return dns.node.ImmutableNode(node) CheckPutRdatasetType = Callable[ ["Transaction", dns.name.Name, dns.rdataset.Rdataset], None ] CheckDeleteRdatasetType = Callable[ ["Transaction", dns.name.Name, dns.rdatatype.RdataType, dns.rdatatype.RdataType], None, ] CheckDeleteNameType = Callable[["Transaction", dns.name.Name], None] class Transaction: def __init__( self, manager: TransactionManager, replacement: bool = False, read_only: bool = False, ): self.manager = manager self.replacement = replacement self.read_only = read_only self._ended = False self._check_put_rdataset: List[CheckPutRdatasetType] = [] self._check_delete_rdataset: List[CheckDeleteRdatasetType] = [] self._check_delete_name: List[CheckDeleteNameType] = [] # # This is the high level API # # Note that we currently use non-immutable types in the return type signature to # avoid covariance problems, e.g. if the caller has a List[Rdataset], mypy will be # unhappy if we return an ImmutableRdataset. def get( self, name: Optional[Union[dns.name.Name, str]], rdtype: Union[dns.rdatatype.RdataType, str], covers: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.NONE, ) -> dns.rdataset.Rdataset: """Return the rdataset associated with *name*, *rdtype*, and *covers*, or `None` if not found. Note that the returned rdataset is immutable. """ self._check_ended() if isinstance(name, str): name = dns.name.from_text(name, None) rdtype = dns.rdatatype.RdataType.make(rdtype) covers = dns.rdatatype.RdataType.make(covers) rdataset = self._get_rdataset(name, rdtype, covers) return _ensure_immutable_rdataset(rdataset) def get_node(self, name: dns.name.Name) -> Optional[dns.node.Node]: """Return the node at *name*, if any. Returns an immutable node or ``None``. """ return _ensure_immutable_node(self._get_node(name)) def _check_read_only(self) -> None: if self.read_only: raise ReadOnly def add(self, *args: Any) -> None: """Add records. The arguments may be: - rrset - name, rdataset... - name, ttl, rdata... """ self._check_ended() self._check_read_only() self._add(False, args) def replace(self, *args: Any) -> None: """Replace the existing rdataset at the name with the specified rdataset, or add the specified rdataset if there was no existing rdataset. The arguments may be: - rrset - name, rdataset... - name, ttl, rdata... Note that if you want to replace the entire node, you should do a delete of the name followed by one or more calls to add() or replace(). """ self._check_ended() self._check_read_only() self._add(True, args) def delete(self, *args: Any) -> None: """Delete records. It is not an error if some of the records are not in the existing set. The arguments may be: - rrset - name - name, rdatatype, [covers] - name, rdataset... - name, rdata... """ self._check_ended() self._check_read_only() self._delete(False, args) def delete_exact(self, *args: Any) -> None: """Delete records. The arguments may be: - rrset - name - name, rdatatype, [covers] - name, rdataset... - name, rdata... Raises dns.transaction.DeleteNotExact if some of the records are not in the existing set. """ self._check_ended() self._check_read_only() self._delete(True, args) def name_exists(self, name: Union[dns.name.Name, str]) -> bool: """Does the specified name exist?""" self._check_ended() if isinstance(name, str): name = dns.name.from_text(name, None) return self._name_exists(name) def update_serial( self, value: int = 1, relative: bool = True, name: dns.name.Name = dns.name.empty, ) -> None: """Update the serial number. *value*, an `int`, is an increment if *relative* is `True`, or the actual value to set if *relative* is `False`. Raises `KeyError` if there is no SOA rdataset at *name*. Raises `ValueError` if *value* is negative or if the increment is so large that it would cause the new serial to be less than the prior value. """ self._check_ended() if value < 0: raise ValueError("negative update_serial() value") if isinstance(name, str): name = dns.name.from_text(name, None) rdataset = self._get_rdataset(name, dns.rdatatype.SOA, dns.rdatatype.NONE) if rdataset is None or len(rdataset) == 0: raise KeyError if relative: serial = dns.serial.Serial(rdataset[0].serial) + value else: serial = dns.serial.Serial(value) serial = serial.value # convert back to int if serial == 0: serial = 1 rdata = rdataset[0].replace(serial=serial) new_rdataset = dns.rdataset.from_rdata(rdataset.ttl, rdata) self.replace(name, new_rdataset) def __iter__(self): self._check_ended() return self._iterate_rdatasets() def changed(self) -> bool: """Has this transaction changed anything? For read-only transactions, the result is always `False`. For writable transactions, the result is `True` if at some time during the life of the transaction, the content was changed. """ self._check_ended() return self._changed() def commit(self) -> None: """Commit the transaction. Normally transactions are used as context managers and commit or rollback automatically, but it may be done explicitly if needed. A ``dns.transaction.Ended`` exception will be raised if you try to use a transaction after it has been committed or rolled back. Raises an exception if the commit fails (in which case the transaction is also rolled back. """ self._end(True) def rollback(self) -> None: """Rollback the transaction. Normally transactions are used as context managers and commit or rollback automatically, but it may be done explicitly if needed. A ``dns.transaction.AlreadyEnded`` exception will be raised if you try to use a transaction after it has been committed or rolled back. Rollback cannot otherwise fail. """ self._end(False) def check_put_rdataset(self, check: CheckPutRdatasetType) -> None: """Call *check* before putting (storing) an rdataset. The function is called with the transaction, the name, and the rdataset. The check function may safely make non-mutating transaction method calls, but behavior is undefined if mutating transaction methods are called. The check function should raise an exception if it objects to the put, and otherwise should return ``None``. """ self._check_put_rdataset.append(check) def check_delete_rdataset(self, check: CheckDeleteRdatasetType) -> None: """Call *check* before deleting an rdataset. The function is called with the transaction, the name, the rdatatype, and the covered rdatatype. The check function may safely make non-mutating transaction method calls, but behavior is undefined if mutating transaction methods are called. The check function should raise an exception if it objects to the put, and otherwise should return ``None``. """ self._check_delete_rdataset.append(check) def check_delete_name(self, check: CheckDeleteNameType) -> None: """Call *check* before putting (storing) an rdataset. The function is called with the transaction and the name. The check function may safely make non-mutating transaction method calls, but behavior is undefined if mutating transaction methods are called. The check function should raise an exception if it objects to the put, and otherwise should return ``None``. """ self._check_delete_name.append(check) def iterate_rdatasets( self, ) -> Iterator[Tuple[dns.name.Name, dns.rdataset.Rdataset]]: """Iterate all the rdatasets in the transaction, returning (`dns.name.Name`, `dns.rdataset.Rdataset`) tuples. Note that as is usual with python iterators, adding or removing items while iterating will invalidate the iterator and may raise `RuntimeError` or fail to iterate over all entries.""" self._check_ended() return self._iterate_rdatasets() def iterate_names(self) -> Iterator[dns.name.Name]: """Iterate all the names in the transaction. Note that as is usual with python iterators, adding or removing names while iterating will invalidate the iterator and may raise `RuntimeError` or fail to iterate over all entries.""" self._check_ended() return self._iterate_names() # # Helper methods # def _raise_if_not_empty(self, method, args): if len(args) != 0: raise TypeError(f"extra parameters to {method}") def _rdataset_from_args(self, method, deleting, args): try: arg = args.popleft() if isinstance(arg, dns.rrset.RRset): rdataset = arg.to_rdataset() elif isinstance(arg, dns.rdataset.Rdataset): rdataset = arg else: if deleting: ttl = 0 else: if isinstance(arg, int): ttl = arg if ttl > dns.ttl.MAX_TTL: raise ValueError(f"{method}: TTL value too big") else: raise TypeError(f"{method}: expected a TTL") arg = args.popleft() if isinstance(arg, dns.rdata.Rdata): rdataset = dns.rdataset.from_rdata(ttl, arg) else: raise TypeError(f"{method}: expected an Rdata") return rdataset except IndexError: if deleting: return None else: # reraise raise TypeError(f"{method}: expected more arguments") def _add(self, replace, args): try: args = collections.deque(args) if replace: method = "replace()" else: method = "add()" arg = args.popleft() if isinstance(arg, str): arg = dns.name.from_text(arg, None) if isinstance(arg, dns.name.Name): name = arg rdataset = self._rdataset_from_args(method, False, args) elif isinstance(arg, dns.rrset.RRset): rrset = arg name = rrset.name # rrsets are also rdatasets, but they don't print the # same and can't be stored in nodes, so convert. rdataset = rrset.to_rdataset() else: raise TypeError( f"{method} requires a name or RRset as the first argument" ) if rdataset.rdclass != self.manager.get_class(): raise ValueError(f"{method} has objects of wrong RdataClass") if rdataset.rdtype == dns.rdatatype.SOA: (_, _, origin) = self._origin_information() if name != origin: raise ValueError(f"{method} has non-origin SOA") self._raise_if_not_empty(method, args) if not replace: existing = self._get_rdataset(name, rdataset.rdtype, rdataset.covers) if existing is not None: if isinstance(existing, dns.rdataset.ImmutableRdataset): trds = dns.rdataset.Rdataset( existing.rdclass, existing.rdtype, existing.covers ) trds.update(existing) existing = trds rdataset = existing.union(rdataset) self._checked_put_rdataset(name, rdataset) except IndexError: raise TypeError(f"not enough parameters to {method}") def _delete(self, exact, args): try: args = collections.deque(args) if exact: method = "delete_exact()" else: method = "delete()" arg = args.popleft() if isinstance(arg, str): arg = dns.name.from_text(arg, None) if isinstance(arg, dns.name.Name): name = arg if len(args) > 0 and ( isinstance(args[0], int) or isinstance(args[0], str) ): # deleting by type and (optionally) covers rdtype = dns.rdatatype.RdataType.make(args.popleft()) if len(args) > 0: covers = dns.rdatatype.RdataType.make(args.popleft()) else: covers = dns.rdatatype.NONE self._raise_if_not_empty(method, args) existing = self._get_rdataset(name, rdtype, covers) if existing is None: if exact: raise DeleteNotExact(f"{method}: missing rdataset") else: self._checked_delete_rdataset(name, rdtype, covers) return else: rdataset = self._rdataset_from_args(method, True, args) elif isinstance(arg, dns.rrset.RRset): rdataset = arg # rrsets are also rdatasets name = rdataset.name else: raise TypeError( f"{method} requires a name or RRset as the first argument" ) self._raise_if_not_empty(method, args) if rdataset: if rdataset.rdclass != self.manager.get_class(): raise ValueError(f"{method} has objects of wrong RdataClass") existing = self._get_rdataset(name, rdataset.rdtype, rdataset.covers) if existing is not None: if exact: intersection = existing.intersection(rdataset) if intersection != rdataset: raise DeleteNotExact(f"{method}: missing rdatas") rdataset = existing.difference(rdataset) if len(rdataset) == 0: self._checked_delete_rdataset( name, rdataset.rdtype, rdataset.covers ) else: self._checked_put_rdataset(name, rdataset) elif exact: raise DeleteNotExact(f"{method}: missing rdataset") else: if exact and not self._name_exists(name): raise DeleteNotExact(f"{method}: name not known") self._checked_delete_name(name) except IndexError: raise TypeError(f"not enough parameters to {method}") def _check_ended(self): if self._ended: raise AlreadyEnded def _end(self, commit): self._check_ended() try: self._end_transaction(commit) finally: self._ended = True def _checked_put_rdataset(self, name, rdataset): for check in self._check_put_rdataset: check(self, name, rdataset) self._put_rdataset(name, rdataset) def _checked_delete_rdataset(self, name, rdtype, covers): for check in self._check_delete_rdataset: check(self, name, rdtype, covers) self._delete_rdataset(name, rdtype, covers) def _checked_delete_name(self, name): for check in self._check_delete_name: check(self, name) self._delete_name(name) # # Transactions are context managers. # def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): if not self._ended: if exc_type is None: self.commit() else: self.rollback() return False # # This is the low level API, which must be implemented by subclasses # of Transaction. # def _get_rdataset(self, name, rdtype, covers): """Return the rdataset associated with *name*, *rdtype*, and *covers*, or `None` if not found. """ raise NotImplementedError # pragma: no cover def _put_rdataset(self, name, rdataset): """Store the rdataset.""" raise NotImplementedError # pragma: no cover def _delete_name(self, name): """Delete all data associated with *name*. It is not an error if the name does not exist. """ raise NotImplementedError # pragma: no cover def _delete_rdataset(self, name, rdtype, covers): """Delete all data associated with *name*, *rdtype*, and *covers*. It is not an error if the rdataset does not exist. """ raise NotImplementedError # pragma: no cover def _name_exists(self, name): """Does name exist? Returns a bool. """ raise NotImplementedError # pragma: no cover def _changed(self): """Has this transaction changed anything?""" raise NotImplementedError # pragma: no cover def _end_transaction(self, commit): """End the transaction. *commit*, a bool. If ``True``, commit the transaction, otherwise roll it back. If committing and the commit fails, then roll back and raise an exception. """ raise NotImplementedError # pragma: no cover def _set_origin(self, origin): """Set the origin. This method is called when reading a possibly relativized source, and an origin setting operation occurs (e.g. $ORIGIN in a zone file). """ raise NotImplementedError # pragma: no cover def _iterate_rdatasets(self): """Return an iterator that yields (name, rdataset) tuples.""" raise NotImplementedError # pragma: no cover def _iterate_names(self): """Return an iterator that yields a name.""" raise NotImplementedError # pragma: no cover def _get_node(self, name): """Return the node at *name*, if any. Returns a node or ``None``. """ raise NotImplementedError # pragma: no cover # # Low-level API with a default implementation, in case a subclass needs # to override. # def _origin_information(self): # This is only used by _add() return self.manager.origin_information() dnspython-2.7.0/dns/tsig.py0000644000000000000000000002622513615410400012601 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2001-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. """DNS TSIG support.""" import base64 import hashlib import hmac import struct import dns.exception import dns.name import dns.rcode import dns.rdataclass class BadTime(dns.exception.DNSException): """The current time is not within the TSIG's validity time.""" class BadSignature(dns.exception.DNSException): """The TSIG signature fails to verify.""" class BadKey(dns.exception.DNSException): """The TSIG record owner name does not match the key.""" class BadAlgorithm(dns.exception.DNSException): """The TSIG algorithm does not match the key.""" class PeerError(dns.exception.DNSException): """Base class for all TSIG errors generated by the remote peer""" class PeerBadKey(PeerError): """The peer didn't know the key we used""" class PeerBadSignature(PeerError): """The peer didn't like the signature we sent""" class PeerBadTime(PeerError): """The peer didn't like the time we sent""" class PeerBadTruncation(PeerError): """The peer didn't like amount of truncation in the TSIG we sent""" # TSIG Algorithms HMAC_MD5 = dns.name.from_text("HMAC-MD5.SIG-ALG.REG.INT") HMAC_SHA1 = dns.name.from_text("hmac-sha1") HMAC_SHA224 = dns.name.from_text("hmac-sha224") HMAC_SHA256 = dns.name.from_text("hmac-sha256") HMAC_SHA256_128 = dns.name.from_text("hmac-sha256-128") HMAC_SHA384 = dns.name.from_text("hmac-sha384") HMAC_SHA384_192 = dns.name.from_text("hmac-sha384-192") HMAC_SHA512 = dns.name.from_text("hmac-sha512") HMAC_SHA512_256 = dns.name.from_text("hmac-sha512-256") GSS_TSIG = dns.name.from_text("gss-tsig") default_algorithm = HMAC_SHA256 mac_sizes = { HMAC_SHA1: 20, HMAC_SHA224: 28, HMAC_SHA256: 32, HMAC_SHA256_128: 16, HMAC_SHA384: 48, HMAC_SHA384_192: 24, HMAC_SHA512: 64, HMAC_SHA512_256: 32, HMAC_MD5: 16, GSS_TSIG: 128, # This is what we assume to be the worst case! } class GSSTSig: """ GSS-TSIG TSIG implementation. This uses the GSS-API context established in the TKEY message handshake to sign messages using GSS-API message integrity codes, per the RFC. In order to avoid a direct GSSAPI dependency, the keyring holds a ref to the GSSAPI object required, rather than the key itself. """ def __init__(self, gssapi_context): self.gssapi_context = gssapi_context self.data = b"" self.name = "gss-tsig" def update(self, data): self.data += data def sign(self): # defer to the GSSAPI function to sign return self.gssapi_context.get_signature(self.data) def verify(self, expected): try: # defer to the GSSAPI function to verify return self.gssapi_context.verify_signature(self.data, expected) except Exception: # note the usage of a bare exception raise BadSignature class GSSTSigAdapter: def __init__(self, keyring): self.keyring = keyring def __call__(self, message, keyname): if keyname in self.keyring: key = self.keyring[keyname] if isinstance(key, Key) and key.algorithm == GSS_TSIG: if message: GSSTSigAdapter.parse_tkey_and_step(key, message, keyname) return key else: return None @classmethod def parse_tkey_and_step(cls, key, message, keyname): # if the message is a TKEY type, absorb the key material # into the context using step(); this is used to allow the # client to complete the GSSAPI negotiation before attempting # to verify the signed response to a TKEY message exchange try: rrset = message.find_rrset( message.answer, keyname, dns.rdataclass.ANY, dns.rdatatype.TKEY ) if rrset: token = rrset[0].key gssapi_context = key.secret return gssapi_context.step(token) except KeyError: pass class HMACTSig: """ HMAC TSIG implementation. This uses the HMAC python module to handle the sign/verify operations. """ _hashes = { HMAC_SHA1: hashlib.sha1, HMAC_SHA224: hashlib.sha224, HMAC_SHA256: hashlib.sha256, HMAC_SHA256_128: (hashlib.sha256, 128), HMAC_SHA384: hashlib.sha384, HMAC_SHA384_192: (hashlib.sha384, 192), HMAC_SHA512: hashlib.sha512, HMAC_SHA512_256: (hashlib.sha512, 256), HMAC_MD5: hashlib.md5, } def __init__(self, key, algorithm): try: hashinfo = self._hashes[algorithm] except KeyError: raise NotImplementedError(f"TSIG algorithm {algorithm} is not supported") # create the HMAC context if isinstance(hashinfo, tuple): self.hmac_context = hmac.new(key, digestmod=hashinfo[0]) self.size = hashinfo[1] else: self.hmac_context = hmac.new(key, digestmod=hashinfo) self.size = None self.name = self.hmac_context.name if self.size: self.name += f"-{self.size}" def update(self, data): return self.hmac_context.update(data) def sign(self): # defer to the HMAC digest() function for that digestmod digest = self.hmac_context.digest() if self.size: digest = digest[: (self.size // 8)] return digest def verify(self, expected): # re-digest and compare the results mac = self.sign() if not hmac.compare_digest(mac, expected): raise BadSignature def _digest(wire, key, rdata, time=None, request_mac=None, ctx=None, multi=None): """Return a context containing the TSIG rdata for the input parameters @rtype: dns.tsig.HMACTSig or dns.tsig.GSSTSig object @raises ValueError: I{other_data} is too long @raises NotImplementedError: I{algorithm} is not supported """ first = not (ctx and multi) if first: ctx = get_context(key) if request_mac: ctx.update(struct.pack("!H", len(request_mac))) ctx.update(request_mac) ctx.update(struct.pack("!H", rdata.original_id)) ctx.update(wire[2:]) if first: ctx.update(key.name.to_digestable()) ctx.update(struct.pack("!H", dns.rdataclass.ANY)) ctx.update(struct.pack("!I", 0)) if time is None: time = rdata.time_signed upper_time = (time >> 32) & 0xFFFF lower_time = time & 0xFFFFFFFF time_encoded = struct.pack("!HIH", upper_time, lower_time, rdata.fudge) other_len = len(rdata.other) if other_len > 65535: raise ValueError("TSIG Other Data is > 65535 bytes") if first: ctx.update(key.algorithm.to_digestable() + time_encoded) ctx.update(struct.pack("!HH", rdata.error, other_len) + rdata.other) else: ctx.update(time_encoded) return ctx def _maybe_start_digest(key, mac, multi): """If this is the first message in a multi-message sequence, start a new context. @rtype: dns.tsig.HMACTSig or dns.tsig.GSSTSig object """ if multi: ctx = get_context(key) ctx.update(struct.pack("!H", len(mac))) ctx.update(mac) return ctx else: return None def sign(wire, key, rdata, time=None, request_mac=None, ctx=None, multi=False): """Return a (tsig_rdata, mac, ctx) tuple containing the HMAC TSIG rdata for the input parameters, the HMAC MAC calculated by applying the TSIG signature algorithm, and the TSIG digest context. @rtype: (string, dns.tsig.HMACTSig or dns.tsig.GSSTSig object) @raises ValueError: I{other_data} is too long @raises NotImplementedError: I{algorithm} is not supported """ ctx = _digest(wire, key, rdata, time, request_mac, ctx, multi) mac = ctx.sign() tsig = rdata.replace(time_signed=time, mac=mac) return (tsig, _maybe_start_digest(key, mac, multi)) def validate( wire, key, owner, rdata, now, request_mac, tsig_start, ctx=None, multi=False ): """Validate the specified TSIG rdata against the other input parameters. @raises FormError: The TSIG is badly formed. @raises BadTime: There is too much time skew between the client and the server. @raises BadSignature: The TSIG signature did not validate @rtype: dns.tsig.HMACTSig or dns.tsig.GSSTSig object""" (adcount,) = struct.unpack("!H", wire[10:12]) if adcount == 0: raise dns.exception.FormError adcount -= 1 new_wire = wire[0:10] + struct.pack("!H", adcount) + wire[12:tsig_start] if rdata.error != 0: if rdata.error == dns.rcode.BADSIG: raise PeerBadSignature elif rdata.error == dns.rcode.BADKEY: raise PeerBadKey elif rdata.error == dns.rcode.BADTIME: raise PeerBadTime elif rdata.error == dns.rcode.BADTRUNC: raise PeerBadTruncation else: raise PeerError("unknown TSIG error code %d" % rdata.error) if abs(rdata.time_signed - now) > rdata.fudge: raise BadTime if key.name != owner: raise BadKey if key.algorithm != rdata.algorithm: raise BadAlgorithm ctx = _digest(new_wire, key, rdata, None, request_mac, ctx, multi) ctx.verify(rdata.mac) return _maybe_start_digest(key, rdata.mac, multi) def get_context(key): """Returns an HMAC context for the specified key. @rtype: HMAC context @raises NotImplementedError: I{algorithm} is not supported """ if key.algorithm == GSS_TSIG: return GSSTSig(key.secret) else: return HMACTSig(key.secret, key.algorithm) class Key: def __init__(self, name, secret, algorithm=default_algorithm): if isinstance(name, str): name = dns.name.from_text(name) self.name = name if isinstance(secret, str): secret = base64.decodebytes(secret.encode()) self.secret = secret if isinstance(algorithm, str): algorithm = dns.name.from_text(algorithm) self.algorithm = algorithm def __eq__(self, other): return ( isinstance(other, Key) and self.name == other.name and self.secret == other.secret and self.algorithm == other.algorithm ) def __repr__(self): r = f" Dict[dns.name.Name, dns.tsig.Key]: """Convert a dictionary containing (textual DNS name, base64 secret) pairs into a binary keyring which has (dns.name.Name, bytes) pairs, or a dictionary containing (textual DNS name, (algorithm, base64 secret)) pairs into a binary keyring which has (dns.name.Name, dns.tsig.Key) pairs. @rtype: dict""" keyring = {} for name, value in textring.items(): kname = dns.name.from_text(name) if isinstance(value, str): keyring[kname] = dns.tsig.Key(kname, value).secret else: (algorithm, secret) = value keyring[kname] = dns.tsig.Key(kname, secret, algorithm) return keyring def to_text(keyring: Dict[dns.name.Name, Any]) -> Dict[str, Any]: """Convert a dictionary containing (dns.name.Name, dns.tsig.Key) pairs into a text keyring which has (textual DNS name, (textual algorithm, base64 secret)) pairs, or a dictionary containing (dns.name.Name, bytes) pairs into a text keyring which has (textual DNS name, base64 secret) pairs. @rtype: dict""" textring = {} def b64encode(secret): return base64.encodebytes(secret).decode().rstrip() for name, key in keyring.items(): tname = name.to_text() if isinstance(key, bytes): textring[tname] = b64encode(key) else: if isinstance(key.secret, bytes): text_secret = b64encode(key.secret) else: text_secret = str(key.secret) textring[tname] = (key.algorithm.to_text(), text_secret) return textring dnspython-2.7.0/dns/ttl.py0000644000000000000000000000564113615410400012435 0ustar00# 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 # 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. """DNS TTL conversion.""" from typing import Union import dns.exception # Technically TTLs are supposed to be between 0 and 2**31 - 1, with values # greater than that interpreted as 0, but we do not impose this policy here # as values > 2**31 - 1 occur in real world data. # # We leave it to applications to impose tighter bounds if desired. MAX_TTL = 2**32 - 1 class BadTTL(dns.exception.SyntaxError): """DNS TTL value is not well-formed.""" def from_text(text: str) -> int: """Convert the text form of a TTL to an integer. The BIND 8 units syntax for TTLs (e.g. '1w6d4h3m10s') is supported. *text*, a ``str``, the textual TTL. Raises ``dns.ttl.BadTTL`` if the TTL is not well-formed. Returns an ``int``. """ if text.isdigit(): total = int(text) elif len(text) == 0: raise BadTTL else: total = 0 current = 0 need_digit = True for c in text: if c.isdigit(): current *= 10 current += int(c) need_digit = False else: if need_digit: raise BadTTL c = c.lower() if c == "w": total += current * 604800 elif c == "d": total += current * 86400 elif c == "h": total += current * 3600 elif c == "m": total += current * 60 elif c == "s": total += current else: raise BadTTL(f"unknown unit '{c}'") current = 0 need_digit = True if not current == 0: raise BadTTL("trailing integer") if total < 0 or total > MAX_TTL: raise BadTTL("TTL should be between 0 and 2**32 - 1 (inclusive)") return total def make(value: Union[int, str]) -> int: if isinstance(value, int): return value elif isinstance(value, str): return dns.ttl.from_text(value) else: raise ValueError("cannot convert value to TTL") dnspython-2.7.0/dns/update.py0000644000000000000000000002772313615410400013121 0ustar00# 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. """DNS Dynamic Update Support""" from typing import Any, List, Optional, Union import dns.message import dns.name import dns.opcode import dns.rdata import dns.rdataclass import dns.rdataset import dns.rdatatype import dns.tsig class UpdateSection(dns.enum.IntEnum): """Update sections""" ZONE = 0 PREREQ = 1 UPDATE = 2 ADDITIONAL = 3 @classmethod def _maximum(cls): return 3 class UpdateMessage(dns.message.Message): # lgtm[py/missing-equals] # ignore the mypy error here as we mean to use a different enum _section_enum = UpdateSection # type: ignore def __init__( self, zone: Optional[Union[dns.name.Name, str]] = None, rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, keyring: Optional[Any] = None, keyname: Optional[dns.name.Name] = None, keyalgorithm: Union[dns.name.Name, str] = dns.tsig.default_algorithm, id: Optional[int] = None, ): """Initialize a new DNS Update object. See the documentation of the Message class for a complete description of the keyring dictionary. *zone*, a ``dns.name.Name``, ``str``, or ``None``, the zone which is being updated. ``None`` should only be used by dnspython's message constructors, as a zone is required for the convenience methods like ``add()``, ``replace()``, etc. *rdclass*, an ``int`` or ``str``, the class of the zone. The *keyring*, *keyname*, and *keyalgorithm* parameters are passed to ``use_tsig()``; see its documentation for details. """ super().__init__(id=id) self.flags |= dns.opcode.to_flags(dns.opcode.UPDATE) if isinstance(zone, str): zone = dns.name.from_text(zone) self.origin = zone rdclass = dns.rdataclass.RdataClass.make(rdclass) self.zone_rdclass = rdclass if self.origin: self.find_rrset( self.zone, self.origin, rdclass, dns.rdatatype.SOA, create=True, force_unique=True, ) if keyring is not None: self.use_tsig(keyring, keyname, algorithm=keyalgorithm) @property def zone(self) -> List[dns.rrset.RRset]: """The zone section.""" return self.sections[0] @zone.setter def zone(self, v): self.sections[0] = v @property def prerequisite(self) -> List[dns.rrset.RRset]: """The prerequisite section.""" return self.sections[1] @prerequisite.setter def prerequisite(self, v): self.sections[1] = v @property def update(self) -> List[dns.rrset.RRset]: """The update section.""" return self.sections[2] @update.setter def update(self, v): self.sections[2] = v def _add_rr(self, name, ttl, rd, deleting=None, section=None): """Add a single RR to the update section.""" if section is None: section = self.update covers = rd.covers() rrset = self.find_rrset( section, name, self.zone_rdclass, rd.rdtype, covers, deleting, True, True ) rrset.add(rd, ttl) def _add(self, replace, section, name, *args): """Add records. *replace* is the replacement mode. If ``False``, RRs are added to an existing RRset; if ``True``, the RRset is replaced with the specified contents. The second argument is the section to add to. The third argument is always a name. The other arguments can be: - rdataset... - ttl, rdata... - ttl, rdtype, string... """ if isinstance(name, str): name = dns.name.from_text(name, None) if isinstance(args[0], dns.rdataset.Rdataset): for rds in args: if replace: self.delete(name, rds.rdtype) for rd in rds: self._add_rr(name, rds.ttl, rd, section=section) else: args = list(args) ttl = int(args.pop(0)) if isinstance(args[0], dns.rdata.Rdata): if replace: self.delete(name, args[0].rdtype) for rd in args: self._add_rr(name, ttl, rd, section=section) else: rdtype = dns.rdatatype.RdataType.make(args.pop(0)) if replace: self.delete(name, rdtype) for s in args: rd = dns.rdata.from_text(self.zone_rdclass, rdtype, s, self.origin) self._add_rr(name, ttl, rd, section=section) def add(self, name: Union[dns.name.Name, str], *args: Any) -> None: """Add records. The first argument is always a name. The other arguments can be: - rdataset... - ttl, rdata... - ttl, rdtype, string... """ self._add(False, self.update, name, *args) def delete(self, name: Union[dns.name.Name, str], *args: Any) -> None: """Delete records. The first argument is always a name. The other arguments can be: - *empty* - rdataset... - rdata... - rdtype, [string...] """ if isinstance(name, str): name = dns.name.from_text(name, None) if len(args) == 0: self.find_rrset( self.update, name, dns.rdataclass.ANY, dns.rdatatype.ANY, dns.rdatatype.NONE, dns.rdataclass.ANY, True, True, ) elif isinstance(args[0], dns.rdataset.Rdataset): for rds in args: for rd in rds: self._add_rr(name, 0, rd, dns.rdataclass.NONE) else: largs = list(args) if isinstance(largs[0], dns.rdata.Rdata): for rd in largs: self._add_rr(name, 0, rd, dns.rdataclass.NONE) else: rdtype = dns.rdatatype.RdataType.make(largs.pop(0)) if len(largs) == 0: self.find_rrset( self.update, name, self.zone_rdclass, rdtype, dns.rdatatype.NONE, dns.rdataclass.ANY, True, True, ) else: for s in largs: rd = dns.rdata.from_text( self.zone_rdclass, rdtype, s, # type: ignore[arg-type] self.origin, ) self._add_rr(name, 0, rd, dns.rdataclass.NONE) def replace(self, name: Union[dns.name.Name, str], *args: Any) -> None: """Replace records. The first argument is always a name. The other arguments can be: - rdataset... - ttl, rdata... - ttl, rdtype, string... Note that if you want to replace the entire node, you should do a delete of the name followed by one or more calls to add. """ self._add(True, self.update, name, *args) def present(self, name: Union[dns.name.Name, str], *args: Any) -> None: """Require that an owner name (and optionally an rdata type, or specific rdataset) exists as a prerequisite to the execution of the update. The first argument is always a name. The other arguments can be: - rdataset... - rdata... - rdtype, string... """ if isinstance(name, str): name = dns.name.from_text(name, None) if len(args) == 0: self.find_rrset( self.prerequisite, name, dns.rdataclass.ANY, dns.rdatatype.ANY, dns.rdatatype.NONE, None, True, True, ) elif ( isinstance(args[0], dns.rdataset.Rdataset) or isinstance(args[0], dns.rdata.Rdata) or len(args) > 1 ): if not isinstance(args[0], dns.rdataset.Rdataset): # Add a 0 TTL largs = list(args) largs.insert(0, 0) # type: ignore[arg-type] self._add(False, self.prerequisite, name, *largs) else: self._add(False, self.prerequisite, name, *args) else: rdtype = dns.rdatatype.RdataType.make(args[0]) self.find_rrset( self.prerequisite, name, dns.rdataclass.ANY, rdtype, dns.rdatatype.NONE, None, True, True, ) def absent( self, name: Union[dns.name.Name, str], rdtype: Optional[Union[dns.rdatatype.RdataType, str]] = None, ) -> None: """Require that an owner name (and optionally an rdata type) does not exist as a prerequisite to the execution of the update.""" if isinstance(name, str): name = dns.name.from_text(name, None) if rdtype is None: self.find_rrset( self.prerequisite, name, dns.rdataclass.NONE, dns.rdatatype.ANY, dns.rdatatype.NONE, None, True, True, ) else: rdtype = dns.rdatatype.RdataType.make(rdtype) self.find_rrset( self.prerequisite, name, dns.rdataclass.NONE, rdtype, dns.rdatatype.NONE, None, True, True, ) def _get_one_rr_per_rrset(self, value): # Updates are always one_rr_per_rrset return True def _parse_rr_header(self, section, name, rdclass, rdtype): deleting = None empty = False if section == UpdateSection.ZONE: if ( dns.rdataclass.is_metaclass(rdclass) or rdtype != dns.rdatatype.SOA or self.zone ): raise dns.exception.FormError else: if not self.zone: raise dns.exception.FormError if rdclass in (dns.rdataclass.ANY, dns.rdataclass.NONE): deleting = rdclass rdclass = self.zone[0].rdclass empty = ( deleting == dns.rdataclass.ANY or section == UpdateSection.PREREQ ) return (rdclass, rdtype, deleting, empty) # backwards compatibility Update = UpdateMessage ### BEGIN generated UpdateSection constants ZONE = UpdateSection.ZONE PREREQ = UpdateSection.PREREQ UPDATE = UpdateSection.UPDATE ADDITIONAL = UpdateSection.ADDITIONAL ### END generated UpdateSection constants dnspython-2.7.0/dns/version.py0000644000000000000000000000360613615410400013316 0ustar00# 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 # 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. """dnspython release version information.""" #: MAJOR MAJOR = 2 #: MINOR MINOR = 7 #: MICRO MICRO = 0 #: RELEASELEVEL RELEASELEVEL = 0x0F #: SERIAL SERIAL = 0 if RELEASELEVEL == 0x0F: # pragma: no cover lgtm[py/unreachable-statement] #: version version = "%d.%d.%d" % (MAJOR, MINOR, MICRO) # lgtm[py/unreachable-statement] elif RELEASELEVEL == 0x00: # pragma: no cover lgtm[py/unreachable-statement] version = "%d.%d.%ddev%d" % ( MAJOR, MINOR, MICRO, SERIAL, ) # lgtm[py/unreachable-statement] elif RELEASELEVEL == 0x0C: # pragma: no cover lgtm[py/unreachable-statement] version = "%d.%d.%drc%d" % ( MAJOR, MINOR, MICRO, SERIAL, ) # lgtm[py/unreachable-statement] else: # pragma: no cover lgtm[py/unreachable-statement] version = "%d.%d.%d%x%d" % ( MAJOR, MINOR, MICRO, RELEASELEVEL, SERIAL, ) # lgtm[py/unreachable-statement] #: hexversion hexversion = MAJOR << 24 | MINOR << 16 | MICRO << 8 | RELEASELEVEL << 4 | SERIAL dnspython-2.7.0/dns/versioned.py0000644000000000000000000002676513615410400013642 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license """DNS Versioned Zones.""" import collections import threading from typing import Callable, Deque, Optional, Set, Union import dns.exception import dns.immutable import dns.name import dns.node import dns.rdataclass import dns.rdataset import dns.rdatatype import dns.rdtypes.ANY.SOA import dns.zone class UseTransaction(dns.exception.DNSException): """To alter a versioned zone, use a transaction.""" # Backwards compatibility Node = dns.zone.VersionedNode ImmutableNode = dns.zone.ImmutableVersionedNode Version = dns.zone.Version WritableVersion = dns.zone.WritableVersion ImmutableVersion = dns.zone.ImmutableVersion Transaction = dns.zone.Transaction class Zone(dns.zone.Zone): # lgtm[py/missing-equals] __slots__ = [ "_versions", "_versions_lock", "_write_txn", "_write_waiters", "_write_event", "_pruning_policy", "_readers", ] node_factory = Node def __init__( self, origin: Optional[Union[dns.name.Name, str]], rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, relativize: bool = True, pruning_policy: Optional[Callable[["Zone", Version], Optional[bool]]] = None, ): """Initialize a versioned zone object. *origin* is the origin of the zone. It may be a ``dns.name.Name``, a ``str``, or ``None``. If ``None``, then the zone's origin will be set by the first ``$ORIGIN`` line in a zone file. *rdclass*, an ``int``, the zone's rdata class; the default is class IN. *relativize*, a ``bool``, determine's whether domain names are relativized to the zone's origin. The default is ``True``. *pruning policy*, a function taking a ``Zone`` and a ``Version`` and returning a ``bool``, or ``None``. Should the version be pruned? If ``None``, the default policy, which retains one version is used. """ super().__init__(origin, rdclass, relativize) self._versions: Deque[Version] = collections.deque() self._version_lock = threading.Lock() if pruning_policy is None: self._pruning_policy = self._default_pruning_policy else: self._pruning_policy = pruning_policy self._write_txn: Optional[Transaction] = None self._write_event: Optional[threading.Event] = None self._write_waiters: Deque[threading.Event] = collections.deque() self._readers: Set[Transaction] = set() self._commit_version_unlocked( None, WritableVersion(self, replacement=True), origin ) def reader( self, id: Optional[int] = None, serial: Optional[int] = None ) -> Transaction: # pylint: disable=arguments-differ if id is not None and serial is not None: raise ValueError("cannot specify both id and serial") with self._version_lock: if id is not None: version = None for v in reversed(self._versions): if v.id == id: version = v break if version is None: raise KeyError("version not found") elif serial is not None: if self.relativize: oname = dns.name.empty else: assert self.origin is not None oname = self.origin version = None for v in reversed(self._versions): n = v.nodes.get(oname) if n: rds = n.get_rdataset(self.rdclass, dns.rdatatype.SOA) if rds and rds[0].serial == serial: version = v break if version is None: raise KeyError("serial not found") else: version = self._versions[-1] txn = Transaction(self, False, version) self._readers.add(txn) return txn def writer(self, replacement: bool = False) -> Transaction: event = None while True: with self._version_lock: # Checking event == self._write_event ensures that either # no one was waiting before we got lucky and found no write # txn, or we were the one who was waiting and got woken up. # This prevents "taking cuts" when creating a write txn. if self._write_txn is None and event == self._write_event: # Creating the transaction defers version setup # (i.e. copying the nodes dictionary) until we # give up the lock, so that we hold the lock as # short a time as possible. This is why we call # _setup_version() below. self._write_txn = Transaction( self, replacement, make_immutable=True ) # give up our exclusive right to make a Transaction self._write_event = None break # Someone else is writing already, so we will have to # wait, but we want to do the actual wait outside the # lock. event = threading.Event() self._write_waiters.append(event) # wait (note we gave up the lock!) # # We only wake one sleeper at a time, so it's important # that no event waiter can exit this method (e.g. via # cancellation) without returning a transaction or waking # someone else up. # # This is not a problem with Threading module threads as # they cannot be canceled, but could be an issue with trio # tasks when we do the async version of writer(). # I.e. we'd need to do something like: # # try: # event.wait() # except trio.Cancelled: # with self._version_lock: # self._maybe_wakeup_one_waiter_unlocked() # raise # event.wait() # Do the deferred version setup. self._write_txn._setup_version() return self._write_txn def _maybe_wakeup_one_waiter_unlocked(self): if len(self._write_waiters) > 0: self._write_event = self._write_waiters.popleft() self._write_event.set() # pylint: disable=unused-argument def _default_pruning_policy(self, zone, version): return True # pylint: enable=unused-argument def _prune_versions_unlocked(self): assert len(self._versions) > 0 # Don't ever prune a version greater than or equal to one that # a reader has open. This pins versions in memory while the # reader is open, and importantly lets the reader open a txn on # a successor version (e.g. if generating an IXFR). # # Note our definition of least_kept also ensures we do not try to # delete the greatest version. if len(self._readers) > 0: least_kept = min(txn.version.id for txn in self._readers) else: least_kept = self._versions[-1].id while self._versions[0].id < least_kept and self._pruning_policy( self, self._versions[0] ): self._versions.popleft() def set_max_versions(self, max_versions: Optional[int]) -> None: """Set a pruning policy that retains up to the specified number of versions """ if max_versions is not None and max_versions < 1: raise ValueError("max versions must be at least 1") if max_versions is None: def policy(zone, _): # pylint: disable=unused-argument return False else: def policy(zone, _): return len(zone._versions) > max_versions self.set_pruning_policy(policy) def set_pruning_policy( self, policy: Optional[Callable[["Zone", Version], Optional[bool]]] ) -> None: """Set the pruning policy for the zone. The *policy* function takes a `Version` and returns `True` if the version should be pruned, and `False` otherwise. `None` may also be specified for policy, in which case the default policy is used. Pruning checking proceeds from the least version and the first time the function returns `False`, the checking stops. I.e. the retained versions are always a consecutive sequence. """ if policy is None: policy = self._default_pruning_policy with self._version_lock: self._pruning_policy = policy self._prune_versions_unlocked() def _end_read(self, txn): with self._version_lock: self._readers.remove(txn) self._prune_versions_unlocked() def _end_write_unlocked(self, txn): assert self._write_txn == txn self._write_txn = None self._maybe_wakeup_one_waiter_unlocked() def _end_write(self, txn): with self._version_lock: self._end_write_unlocked(txn) def _commit_version_unlocked(self, txn, version, origin): self._versions.append(version) self._prune_versions_unlocked() self.nodes = version.nodes if self.origin is None: self.origin = origin # txn can be None in __init__ when we make the empty version. if txn is not None: self._end_write_unlocked(txn) def _commit_version(self, txn, version, origin): with self._version_lock: self._commit_version_unlocked(txn, version, origin) def _get_next_version_id(self): if len(self._versions) > 0: id = self._versions[-1].id + 1 else: id = 1 return id def find_node( self, name: Union[dns.name.Name, str], create: bool = False ) -> dns.node.Node: if create: raise UseTransaction return super().find_node(name) def delete_node(self, name: Union[dns.name.Name, str]) -> None: raise UseTransaction def find_rdataset( self, name: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str], covers: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.NONE, create: bool = False, ) -> dns.rdataset.Rdataset: if create: raise UseTransaction rdataset = super().find_rdataset(name, rdtype, covers) return dns.rdataset.ImmutableRdataset(rdataset) def get_rdataset( self, name: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str], covers: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.NONE, create: bool = False, ) -> Optional[dns.rdataset.Rdataset]: if create: raise UseTransaction rdataset = super().get_rdataset(name, rdtype, covers) if rdataset is not None: return dns.rdataset.ImmutableRdataset(rdataset) else: return None def delete_rdataset( self, name: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str], covers: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.NONE, ) -> None: raise UseTransaction def replace_rdataset( self, name: Union[dns.name.Name, str], replacement: dns.rdataset.Rdataset ) -> None: raise UseTransaction dnspython-2.7.0/dns/win32util.py0000644000000000000000000002125213615410400013466 0ustar00import sys import dns._features if sys.platform == "win32": from typing import Any import dns.name _prefer_wmi = True import winreg # pylint: disable=import-error # Keep pylint quiet on non-windows. try: _ = WindowsError # pylint: disable=used-before-assignment except NameError: WindowsError = Exception if dns._features.have("wmi"): import threading import pythoncom # pylint: disable=import-error import wmi # pylint: disable=import-error _have_wmi = True else: _have_wmi = False def _config_domain(domain): # Sometimes DHCP servers add a '.' prefix to the default domain, and # Windows just stores such values in the registry (see #687). # Check for this and fix it. if domain.startswith("."): domain = domain[1:] return dns.name.from_text(domain) class DnsInfo: def __init__(self): self.domain = None self.nameservers = [] self.search = [] if _have_wmi: class _WMIGetter(threading.Thread): # pylint: disable=possibly-used-before-assignment def __init__(self): super().__init__() self.info = DnsInfo() def run(self): pythoncom.CoInitialize() try: system = wmi.WMI() for interface in system.Win32_NetworkAdapterConfiguration(): if interface.IPEnabled and interface.DNSServerSearchOrder: self.info.nameservers = list(interface.DNSServerSearchOrder) if interface.DNSDomain: self.info.domain = _config_domain(interface.DNSDomain) if interface.DNSDomainSuffixSearchOrder: self.info.search = [ _config_domain(x) for x in interface.DNSDomainSuffixSearchOrder ] break finally: pythoncom.CoUninitialize() def get(self): # We always run in a separate thread to avoid any issues with # the COM threading model. self.start() self.join() return self.info else: class _WMIGetter: # type: ignore pass class _RegistryGetter: def __init__(self): self.info = DnsInfo() def _split(self, text): # The windows registry has used both " " and "," as a delimiter, and while # it is currently using "," in Windows 10 and later, updates can seemingly # leave a space in too, e.g. "a, b". So we just convert all commas to # spaces, and use split() in its default configuration, which splits on # all whitespace and ignores empty strings. return text.replace(",", " ").split() def _config_nameservers(self, nameservers): for ns in self._split(nameservers): if ns not in self.info.nameservers: self.info.nameservers.append(ns) def _config_search(self, search): for s in self._split(search): s = _config_domain(s) if s not in self.info.search: self.info.search.append(s) def _config_fromkey(self, key, always_try_domain): try: servers, _ = winreg.QueryValueEx(key, "NameServer") except WindowsError: servers = None if servers: self._config_nameservers(servers) if servers or always_try_domain: try: dom, _ = winreg.QueryValueEx(key, "Domain") if dom: self.info.domain = _config_domain(dom) except WindowsError: pass else: try: servers, _ = winreg.QueryValueEx(key, "DhcpNameServer") except WindowsError: servers = None if servers: self._config_nameservers(servers) try: dom, _ = winreg.QueryValueEx(key, "DhcpDomain") if dom: self.info.domain = _config_domain(dom) except WindowsError: pass try: search, _ = winreg.QueryValueEx(key, "SearchList") except WindowsError: search = None if search is None: try: search, _ = winreg.QueryValueEx(key, "DhcpSearchList") except WindowsError: search = None if search: self._config_search(search) def _is_nic_enabled(self, lm, guid): # Look in the Windows Registry to determine whether the network # interface corresponding to the given guid is enabled. # # (Code contributed by Paul Marks, thanks!) # try: # This hard-coded location seems to be consistent, at least # from Windows 2000 through Vista. connection_key = winreg.OpenKey( lm, r"SYSTEM\CurrentControlSet\Control\Network" r"\{4D36E972-E325-11CE-BFC1-08002BE10318}" rf"\{guid}\Connection", ) try: # The PnpInstanceID points to a key inside Enum (pnp_id, ttype) = winreg.QueryValueEx( connection_key, "PnpInstanceID" ) if ttype != winreg.REG_SZ: raise ValueError # pragma: no cover device_key = winreg.OpenKey( lm, rf"SYSTEM\CurrentControlSet\Enum\{pnp_id}" ) try: # Get ConfigFlags for this device (flags, ttype) = winreg.QueryValueEx(device_key, "ConfigFlags") if ttype != winreg.REG_DWORD: raise ValueError # pragma: no cover # Based on experimentation, bit 0x1 indicates that the # device is disabled. # # XXXRTH I suspect we really want to & with 0x03 so # that CONFIGFLAGS_REMOVED devices are also ignored, # but we're shifting to WMI as ConfigFlags is not # supposed to be used. return not flags & 0x1 finally: device_key.Close() finally: connection_key.Close() except Exception: # pragma: no cover return False def get(self): """Extract resolver configuration from the Windows registry.""" lm = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) try: tcp_params = winreg.OpenKey( lm, r"SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" ) try: self._config_fromkey(tcp_params, True) finally: tcp_params.Close() interfaces = winreg.OpenKey( lm, r"SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces", ) try: i = 0 while True: try: guid = winreg.EnumKey(interfaces, i) i += 1 key = winreg.OpenKey(interfaces, guid) try: if not self._is_nic_enabled(lm, guid): continue self._config_fromkey(key, False) finally: key.Close() except OSError: break finally: interfaces.Close() finally: lm.Close() return self.info _getter_class: Any if _have_wmi and _prefer_wmi: _getter_class = _WMIGetter else: _getter_class = _RegistryGetter def get_dns_info(): """Extract resolver configuration.""" getter = _getter_class() return getter.get() dnspython-2.7.0/dns/wire.py0000644000000000000000000000541613615410400012600 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import contextlib import struct from typing import Iterator, Optional, Tuple import dns.exception import dns.name class Parser: def __init__(self, wire: bytes, current: int = 0): self.wire = wire self.current = 0 self.end = len(self.wire) if current: self.seek(current) self.furthest = current def remaining(self) -> int: return self.end - self.current def get_bytes(self, size: int) -> bytes: assert size >= 0 if size > self.remaining(): raise dns.exception.FormError output = self.wire[self.current : self.current + size] self.current += size self.furthest = max(self.furthest, self.current) return output def get_counted_bytes(self, length_size: int = 1) -> bytes: length = int.from_bytes(self.get_bytes(length_size), "big") return self.get_bytes(length) def get_remaining(self) -> bytes: return self.get_bytes(self.remaining()) def get_uint8(self) -> int: return struct.unpack("!B", self.get_bytes(1))[0] def get_uint16(self) -> int: return struct.unpack("!H", self.get_bytes(2))[0] def get_uint32(self) -> int: return struct.unpack("!I", self.get_bytes(4))[0] def get_uint48(self) -> int: return int.from_bytes(self.get_bytes(6), "big") def get_struct(self, format: str) -> Tuple: return struct.unpack(format, self.get_bytes(struct.calcsize(format))) def get_name(self, origin: Optional["dns.name.Name"] = None) -> "dns.name.Name": name = dns.name.from_wire_parser(self) if origin: name = name.relativize(origin) return name def seek(self, where: int) -> None: # Note that seeking to the end is OK! (If you try to read # after such a seek, you'll get an exception as expected.) if where < 0 or where > self.end: raise dns.exception.FormError self.current = where @contextlib.contextmanager def restrict_to(self, size: int) -> Iterator: assert size >= 0 if size > self.remaining(): raise dns.exception.FormError saved_end = self.end try: self.end = self.current + size yield # We make this check here and not in the finally as we # don't want to raise if we're already raising for some # other reason. if self.current != self.end: raise dns.exception.FormError finally: self.end = saved_end @contextlib.contextmanager def restore_furthest(self) -> Iterator: try: yield None finally: self.current = self.furthest dnspython-2.7.0/dns/xfr.py0000644000000000000000000003172713615410400012435 0ustar00# 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 # 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. from typing import Any, List, Optional, Tuple, Union import dns.exception import dns.message import dns.name import dns.rcode import dns.rdataset import dns.rdatatype import dns.serial import dns.transaction import dns.tsig import dns.zone class TransferError(dns.exception.DNSException): """A zone transfer response got a non-zero rcode.""" def __init__(self, rcode): message = f"Zone transfer error: {dns.rcode.to_text(rcode)}" super().__init__(message) self.rcode = rcode class SerialWentBackwards(dns.exception.FormError): """The current serial number is less than the serial we know.""" class UseTCP(dns.exception.DNSException): """This IXFR cannot be completed with UDP.""" class Inbound: """ State machine for zone transfers. """ def __init__( self, txn_manager: dns.transaction.TransactionManager, rdtype: dns.rdatatype.RdataType = dns.rdatatype.AXFR, serial: Optional[int] = None, is_udp: bool = False, ): """Initialize an inbound zone transfer. *txn_manager* is a :py:class:`dns.transaction.TransactionManager`. *rdtype* can be `dns.rdatatype.AXFR` or `dns.rdatatype.IXFR` *serial* is the base serial number for IXFRs, and is required in that case. *is_udp*, a ``bool`` indidicates if UDP is being used for this XFR. """ self.txn_manager = txn_manager self.txn: Optional[dns.transaction.Transaction] = None self.rdtype = rdtype if rdtype == dns.rdatatype.IXFR: if serial is None: raise ValueError("a starting serial must be supplied for IXFRs") elif is_udp: raise ValueError("is_udp specified for AXFR") self.serial = serial self.is_udp = is_udp (_, _, self.origin) = txn_manager.origin_information() self.soa_rdataset: Optional[dns.rdataset.Rdataset] = None self.done = False self.expecting_SOA = False self.delete_mode = False def process_message(self, message: dns.message.Message) -> bool: """Process one message in the transfer. The message should have the same relativization as was specified when the `dns.xfr.Inbound` was created. The message should also have been created with `one_rr_per_rrset=True` because order matters. Returns `True` if the transfer is complete, and `False` otherwise. """ if self.txn is None: replacement = self.rdtype == dns.rdatatype.AXFR self.txn = self.txn_manager.writer(replacement) rcode = message.rcode() if rcode != dns.rcode.NOERROR: raise TransferError(rcode) # # We don't require a question section, but if it is present is # should be correct. # if len(message.question) > 0: if message.question[0].name != self.origin: raise dns.exception.FormError("wrong question name") if message.question[0].rdtype != self.rdtype: raise dns.exception.FormError("wrong question rdatatype") answer_index = 0 if self.soa_rdataset is None: # # This is the first message. We're expecting an SOA at # the origin. # if not message.answer or message.answer[0].name != self.origin: raise dns.exception.FormError("No answer or RRset not for zone origin") rrset = message.answer[0] rdataset = rrset if rdataset.rdtype != dns.rdatatype.SOA: raise dns.exception.FormError("first RRset is not an SOA") answer_index = 1 self.soa_rdataset = rdataset.copy() if self.rdtype == dns.rdatatype.IXFR: if self.soa_rdataset[0].serial == self.serial: # # We're already up-to-date. # self.done = True elif dns.serial.Serial(self.soa_rdataset[0].serial) < self.serial: # It went backwards! raise SerialWentBackwards else: if self.is_udp and len(message.answer[answer_index:]) == 0: # # There are no more records, so this is the # "truncated" response. Say to use TCP # raise UseTCP # # Note we're expecting another SOA so we can detect # if this IXFR response is an AXFR-style response. # self.expecting_SOA = True # # Process the answer section (other than the initial SOA in # the first message). # for rrset in message.answer[answer_index:]: name = rrset.name rdataset = rrset if self.done: raise dns.exception.FormError("answers after final SOA") assert self.txn is not None # for mypy if rdataset.rdtype == dns.rdatatype.SOA and name == self.origin: # # Every time we see an origin SOA delete_mode inverts # if self.rdtype == dns.rdatatype.IXFR: self.delete_mode = not self.delete_mode # # If this SOA Rdataset is equal to the first we saw # then we're finished. If this is an IXFR we also # check that we're seeing the record in the expected # part of the response. # if rdataset == self.soa_rdataset and ( self.rdtype == dns.rdatatype.AXFR or (self.rdtype == dns.rdatatype.IXFR and self.delete_mode) ): # # This is the final SOA # if self.expecting_SOA: # We got an empty IXFR sequence! raise dns.exception.FormError("empty IXFR sequence") if ( self.rdtype == dns.rdatatype.IXFR and self.serial != rdataset[0].serial ): raise dns.exception.FormError("unexpected end of IXFR sequence") self.txn.replace(name, rdataset) self.txn.commit() self.txn = None self.done = True else: # # This is not the final SOA # self.expecting_SOA = False if self.rdtype == dns.rdatatype.IXFR: if self.delete_mode: # This is the start of an IXFR deletion set if rdataset[0].serial != self.serial: raise dns.exception.FormError( "IXFR base serial mismatch" ) else: # This is the start of an IXFR addition set self.serial = rdataset[0].serial self.txn.replace(name, rdataset) else: # We saw a non-final SOA for the origin in an AXFR. raise dns.exception.FormError("unexpected origin SOA in AXFR") continue if self.expecting_SOA: # # We made an IXFR request and are expecting another # SOA RR, but saw something else, so this must be an # AXFR response. # self.rdtype = dns.rdatatype.AXFR self.expecting_SOA = False self.delete_mode = False self.txn.rollback() self.txn = self.txn_manager.writer(True) # # Note we are falling through into the code below # so whatever rdataset this was gets written. # # Add or remove the data if self.delete_mode: self.txn.delete_exact(name, rdataset) else: self.txn.add(name, rdataset) if self.is_udp and not self.done: # # This is a UDP IXFR and we didn't get to done, and we didn't # get the proper "truncated" response # raise dns.exception.FormError("unexpected end of UDP IXFR") return self.done # # Inbounds are context managers. # def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): if self.txn: self.txn.rollback() return False def make_query( txn_manager: dns.transaction.TransactionManager, serial: Optional[int] = 0, use_edns: Optional[Union[int, bool]] = None, ednsflags: Optional[int] = None, payload: Optional[int] = None, request_payload: Optional[int] = None, options: Optional[List[dns.edns.Option]] = None, keyring: Any = None, keyname: Optional[dns.name.Name] = None, keyalgorithm: Union[dns.name.Name, str] = dns.tsig.default_algorithm, ) -> Tuple[dns.message.QueryMessage, Optional[int]]: """Make an AXFR or IXFR query. *txn_manager* is a ``dns.transaction.TransactionManager``, typically a ``dns.zone.Zone``. *serial* is an ``int`` or ``None``. If 0, then IXFR will be attempted using the most recent serial number from the *txn_manager*; it is the caller's responsibility to ensure there are no write transactions active that could invalidate the retrieved serial. If a serial cannot be determined, AXFR will be forced. Other integer values are the starting serial to use. ``None`` forces an AXFR. Please see the documentation for :py:func:`dns.message.make_query` and :py:func:`dns.message.Message.use_tsig` for details on the other parameters to this function. Returns a `(query, serial)` tuple. """ (zone_origin, _, origin) = txn_manager.origin_information() if zone_origin is None: raise ValueError("no zone origin") if serial is None: rdtype = dns.rdatatype.AXFR elif not isinstance(serial, int): raise ValueError("serial is not an integer") elif serial == 0: with txn_manager.reader() as txn: rdataset = txn.get(origin, "SOA") if rdataset: serial = rdataset[0].serial rdtype = dns.rdatatype.IXFR else: serial = None rdtype = dns.rdatatype.AXFR elif serial > 0 and serial < 4294967296: rdtype = dns.rdatatype.IXFR else: raise ValueError("serial out-of-range") rdclass = txn_manager.get_class() q = dns.message.make_query( zone_origin, rdtype, rdclass, use_edns, False, ednsflags, payload, request_payload, options, ) if serial is not None: rdata = dns.rdata.from_text(rdclass, "SOA", f". . {serial} 0 0 0 0") rrset = q.find_rrset( q.authority, zone_origin, rdclass, dns.rdatatype.SOA, create=True ) rrset.add(rdata, 0) if keyring is not None: q.use_tsig(keyring, keyname, algorithm=keyalgorithm) return (q, serial) def extract_serial_from_query(query: dns.message.Message) -> Optional[int]: """Extract the SOA serial number from query if it is an IXFR and return it, otherwise return None. *query* is a dns.message.QueryMessage that is an IXFR or AXFR request. Raises if the query is not an IXFR or AXFR, or if an IXFR doesn't have an appropriate SOA RRset in the authority section. """ if not isinstance(query, dns.message.QueryMessage): raise ValueError("query not a QueryMessage") question = query.question[0] if question.rdtype == dns.rdatatype.AXFR: return None elif question.rdtype != dns.rdatatype.IXFR: raise ValueError("query is not an AXFR or IXFR") soa = query.find_rrset( query.authority, question.name, question.rdclass, dns.rdatatype.SOA ) return soa[0].serial dnspython-2.7.0/dns/zone.py0000644000000000000000000014556613615410400012620 0ustar00# 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. """DNS Zones.""" import contextlib import io import os import struct from typing import ( Any, Callable, Iterable, Iterator, List, MutableMapping, Optional, Set, Tuple, Union, ) import dns.exception import dns.grange import dns.immutable import dns.name import dns.node import dns.rdata import dns.rdataclass import dns.rdataset import dns.rdatatype import dns.rdtypes.ANY.SOA import dns.rdtypes.ANY.ZONEMD import dns.rrset import dns.tokenizer import dns.transaction import dns.ttl import dns.zonefile from dns.zonetypes import DigestHashAlgorithm, DigestScheme, _digest_hashers class BadZone(dns.exception.DNSException): """The DNS zone is malformed.""" class NoSOA(BadZone): """The DNS zone has no SOA RR at its origin.""" class NoNS(BadZone): """The DNS zone has no NS RRset at its origin.""" class UnknownOrigin(BadZone): """The DNS zone's origin is unknown.""" class UnsupportedDigestScheme(dns.exception.DNSException): """The zone digest's scheme is unsupported.""" class UnsupportedDigestHashAlgorithm(dns.exception.DNSException): """The zone digest's origin is unsupported.""" class NoDigest(dns.exception.DNSException): """The DNS zone has no ZONEMD RRset at its origin.""" class DigestVerificationFailure(dns.exception.DNSException): """The ZONEMD digest failed to verify.""" def _validate_name( name: dns.name.Name, origin: Optional[dns.name.Name], relativize: bool, ) -> dns.name.Name: # This name validation code is shared by Zone and Version if origin is None: # This should probably never happen as other code (e.g. # _rr_line) will notice the lack of an origin before us, but # we check just in case! raise KeyError("no zone origin is defined") if name.is_absolute(): if not name.is_subdomain(origin): raise KeyError("name parameter must be a subdomain of the zone origin") if relativize: name = name.relativize(origin) else: # We have a relative name. Make sure that the derelativized name is # not too long. try: abs_name = name.derelativize(origin) except dns.name.NameTooLong: # We map dns.name.NameTooLong to KeyError to be consistent with # the other exceptions above. raise KeyError("relative name too long for zone") if not relativize: # We have a relative name in a non-relative zone, so use the # derelativized name. name = abs_name return name class Zone(dns.transaction.TransactionManager): """A DNS zone. A ``Zone`` is a mapping from names to nodes. The zone object may be treated like a Python dictionary, e.g. ``zone[name]`` will retrieve the node associated with that name. The *name* may be a ``dns.name.Name object``, or it may be a string. In either case, if the name is relative it is treated as relative to the origin of the zone. """ node_factory: Callable[[], dns.node.Node] = dns.node.Node map_factory: Callable[[], MutableMapping[dns.name.Name, dns.node.Node]] = dict writable_version_factory: Optional[Callable[[], "WritableVersion"]] = None immutable_version_factory: Optional[Callable[[], "ImmutableVersion"]] = None __slots__ = ["rdclass", "origin", "nodes", "relativize"] def __init__( self, origin: Optional[Union[dns.name.Name, str]], rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, relativize: bool = True, ): """Initialize a zone object. *origin* is the origin of the zone. It may be a ``dns.name.Name``, a ``str``, or ``None``. If ``None``, then the zone's origin will be set by the first ``$ORIGIN`` line in a zone file. *rdclass*, an ``int``, the zone's rdata class; the default is class IN. *relativize*, a ``bool``, determine's whether domain names are relativized to the zone's origin. The default is ``True``. """ if origin is not None: if isinstance(origin, str): origin = dns.name.from_text(origin) elif not isinstance(origin, dns.name.Name): raise ValueError("origin parameter must be convertible to a DNS name") if not origin.is_absolute(): raise ValueError("origin parameter must be an absolute name") self.origin = origin self.rdclass = rdclass self.nodes: MutableMapping[dns.name.Name, dns.node.Node] = self.map_factory() self.relativize = relativize def __eq__(self, other): """Two zones are equal if they have the same origin, class, and nodes. Returns a ``bool``. """ if not isinstance(other, Zone): return False if ( self.rdclass != other.rdclass or self.origin != other.origin or self.nodes != other.nodes ): return False return True def __ne__(self, other): """Are two zones not equal? Returns a ``bool``. """ return not self.__eq__(other) def _validate_name(self, name: Union[dns.name.Name, str]) -> dns.name.Name: # Note that any changes in this method should have corresponding changes # made in the Version _validate_name() method. if isinstance(name, str): name = dns.name.from_text(name, None) elif not isinstance(name, dns.name.Name): raise KeyError("name parameter must be convertible to a DNS name") return _validate_name(name, self.origin, self.relativize) def __getitem__(self, key): key = self._validate_name(key) return self.nodes[key] def __setitem__(self, key, value): key = self._validate_name(key) self.nodes[key] = value def __delitem__(self, key): key = self._validate_name(key) del self.nodes[key] def __iter__(self): return self.nodes.__iter__() def keys(self): return self.nodes.keys() def values(self): return self.nodes.values() def items(self): return self.nodes.items() def get(self, key): key = self._validate_name(key) return self.nodes.get(key) def __contains__(self, key): key = self._validate_name(key) return key in self.nodes def find_node( self, name: Union[dns.name.Name, str], create: bool = False ) -> dns.node.Node: """Find a node in the zone, possibly creating it. *name*: the name of the node to find. The value may be a ``dns.name.Name`` or a ``str``. If absolute, the name must be a subdomain of the zone's origin. If ``zone.relativize`` is ``True``, then the name will be relativized. *create*, a ``bool``. If true, the node will be created if it does not exist. Raises ``KeyError`` if the name is not known and create was not specified, or if the name was not a subdomain of the origin. Returns a ``dns.node.Node``. """ name = self._validate_name(name) node = self.nodes.get(name) if node is None: if not create: raise KeyError node = self.node_factory() self.nodes[name] = node return node def get_node( self, name: Union[dns.name.Name, str], create: bool = False ) -> Optional[dns.node.Node]: """Get a node in the zone, possibly creating it. This method is like ``find_node()``, except it returns None instead of raising an exception if the node does not exist and creation has not been requested. *name*: the name of the node to find. The value may be a ``dns.name.Name`` or a ``str``. If absolute, the name must be a subdomain of the zone's origin. If ``zone.relativize`` is ``True``, then the name will be relativized. *create*, a ``bool``. If true, the node will be created if it does not exist. Returns a ``dns.node.Node`` or ``None``. """ try: node = self.find_node(name, create) except KeyError: node = None return node def delete_node(self, name: Union[dns.name.Name, str]) -> None: """Delete the specified node if it exists. *name*: the name of the node to find. The value may be a ``dns.name.Name`` or a ``str``. If absolute, the name must be a subdomain of the zone's origin. If ``zone.relativize`` is ``True``, then the name will be relativized. It is not an error if the node does not exist. """ name = self._validate_name(name) if name in self.nodes: del self.nodes[name] def find_rdataset( self, name: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str], covers: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.NONE, create: bool = False, ) -> dns.rdataset.Rdataset: """Look for an rdataset with the specified name and type in the zone, and return an rdataset encapsulating it. The rdataset returned is not a copy; changes to it will change the zone. KeyError is raised if the name or type are not found. *name*: the name of the node to find. The value may be a ``dns.name.Name`` or a ``str``. If absolute, the name must be a subdomain of the zone's origin. If ``zone.relativize`` is ``True``, then the name will be relativized. *rdtype*, a ``dns.rdatatype.RdataType`` or ``str``, the rdata type desired. *covers*, a ``dns.rdatatype.RdataType`` or ``str`` 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. *create*, a ``bool``. If true, the node will be created if it does not exist. Raises ``KeyError`` if the name is not known and create was not specified, or if the name was not a subdomain of the origin. Returns a ``dns.rdataset.Rdataset``. """ name = self._validate_name(name) rdtype = dns.rdatatype.RdataType.make(rdtype) covers = dns.rdatatype.RdataType.make(covers) node = self.find_node(name, create) return node.find_rdataset(self.rdclass, rdtype, covers, create) def get_rdataset( self, name: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str], covers: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.NONE, create: bool = False, ) -> Optional[dns.rdataset.Rdataset]: """Look for an rdataset with the specified name and type in the zone. This method is like ``find_rdataset()``, except it returns None instead of raising an exception if the rdataset does not exist and creation has not been requested. The rdataset returned is not a copy; changes to it will change the zone. *name*: the name of the node to find. The value may be a ``dns.name.Name`` or a ``str``. If absolute, the name must be a subdomain of the zone's origin. If ``zone.relativize`` is ``True``, then the name will be relativized. *rdtype*, a ``dns.rdatatype.RdataType`` or ``str``, the rdata type desired. *covers*, a ``dns.rdatatype.RdataType`` or ``str``, 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. *create*, a ``bool``. If true, the node will be created if it does not exist. Raises ``KeyError`` if the name is not known and create was not specified, or if the name was not a subdomain of the origin. Returns a ``dns.rdataset.Rdataset`` or ``None``. """ try: rdataset = self.find_rdataset(name, rdtype, covers, create) except KeyError: rdataset = None return rdataset def delete_rdataset( self, name: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str], covers: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.NONE, ) -> None: """Delete the rdataset matching *rdtype* and *covers*, if it exists at the node specified by *name*. It is not an error if the node does not exist, or if there is no matching rdataset at the node. If the node has no rdatasets after the deletion, it will itself be deleted. *name*: the name of the node to find. The value may be a ``dns.name.Name`` or a ``str``. If absolute, the name must be a subdomain of the zone's origin. If ``zone.relativize`` is ``True``, then the name will be relativized. *rdtype*, a ``dns.rdatatype.RdataType`` or ``str``, the rdata type desired. *covers*, a ``dns.rdatatype.RdataType`` or ``str`` or ``None``, 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. """ name = self._validate_name(name) rdtype = dns.rdatatype.RdataType.make(rdtype) covers = dns.rdatatype.RdataType.make(covers) node = self.get_node(name) if node is not None: node.delete_rdataset(self.rdclass, rdtype, covers) if len(node) == 0: self.delete_node(name) def replace_rdataset( self, name: Union[dns.name.Name, str], replacement: dns.rdataset.Rdataset ) -> None: """Replace an rdataset at name. It is not an error if there is no rdataset matching I{replacement}. Ownership of the *replacement* object is transferred to the zone; in other words, this method does not store a copy of *replacement* at the node, it stores *replacement* itself. If the node does not exist, it is created. *name*: the name of the node to find. The value may be a ``dns.name.Name`` or a ``str``. If absolute, the name must be a subdomain of the zone's origin. If ``zone.relativize`` is ``True``, then the name will be relativized. *replacement*, a ``dns.rdataset.Rdataset``, the replacement rdataset. """ if replacement.rdclass != self.rdclass: raise ValueError("replacement.rdclass != zone.rdclass") node = self.find_node(name, True) node.replace_rdataset(replacement) def find_rrset( self, name: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str], covers: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.NONE, ) -> dns.rrset.RRset: """Look for an rdataset with the specified name and type in the zone, and return an RRset encapsulating it. This method is less efficient than the similar ``find_rdataset()`` because it creates an RRset instead of returning the matching rdataset. It may be more convenient for some uses since it returns an object which binds the owner name to the rdataset. This method may not be used to create new nodes or rdatasets; use ``find_rdataset`` instead. *name*: the name of the node to find. The value may be a ``dns.name.Name`` or a ``str``. If absolute, the name must be a subdomain of the zone's origin. If ``zone.relativize`` is ``True``, then the name will be relativized. *rdtype*, a ``dns.rdatatype.RdataType`` or ``str``, the rdata type desired. *covers*, a ``dns.rdatatype.RdataType`` or ``str``, 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. *create*, a ``bool``. If true, the node will be created if it does not exist. Raises ``KeyError`` if the name is not known and create was not specified, or if the name was not a subdomain of the origin. Returns a ``dns.rrset.RRset`` or ``None``. """ vname = self._validate_name(name) rdtype = dns.rdatatype.RdataType.make(rdtype) covers = dns.rdatatype.RdataType.make(covers) rdataset = self.nodes[vname].find_rdataset(self.rdclass, rdtype, covers) rrset = dns.rrset.RRset(vname, self.rdclass, rdtype, covers) rrset.update(rdataset) return rrset def get_rrset( self, name: Union[dns.name.Name, str], rdtype: Union[dns.rdatatype.RdataType, str], covers: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.NONE, ) -> Optional[dns.rrset.RRset]: """Look for an rdataset with the specified name and type in the zone, and return an RRset encapsulating it. This method is less efficient than the similar ``get_rdataset()`` because it creates an RRset instead of returning the matching rdataset. It may be more convenient for some uses since it returns an object which binds the owner name to the rdataset. This method may not be used to create new nodes or rdatasets; use ``get_rdataset()`` instead. *name*: the name of the node to find. The value may be a ``dns.name.Name`` or a ``str``. If absolute, the name must be a subdomain of the zone's origin. If ``zone.relativize`` is ``True``, then the name will be relativized. *rdtype*, a ``dns.rdataset.Rdataset`` or ``str``, the rdata type desired. *covers*, a ``dns.rdataset.Rdataset`` or ``str``, 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. *create*, a ``bool``. If true, the node will be created if it does not exist. Returns a ``dns.rrset.RRset`` or ``None``. """ try: rrset = self.find_rrset(name, rdtype, covers) except KeyError: rrset = None return rrset def iterate_rdatasets( self, rdtype: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.ANY, covers: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.NONE, ) -> Iterator[Tuple[dns.name.Name, dns.rdataset.Rdataset]]: """Return a generator which yields (name, rdataset) tuples for all rdatasets in the zone which have the specified *rdtype* and *covers*. If *rdtype* is ``dns.rdatatype.ANY``, the default, then all rdatasets will be matched. *rdtype*, a ``dns.rdataset.Rdataset`` or ``str``, the rdata type desired. *covers*, a ``dns.rdataset.Rdataset`` or ``str``, 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. """ rdtype = dns.rdatatype.RdataType.make(rdtype) covers = dns.rdatatype.RdataType.make(covers) for name, node in self.items(): for rds in node: if rdtype == dns.rdatatype.ANY or ( rds.rdtype == rdtype and rds.covers == covers ): yield (name, rds) def iterate_rdatas( self, rdtype: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.ANY, covers: Union[dns.rdatatype.RdataType, str] = dns.rdatatype.NONE, ) -> Iterator[Tuple[dns.name.Name, int, dns.rdata.Rdata]]: """Return a generator which yields (name, ttl, rdata) tuples for all rdatas in the zone which have the specified *rdtype* and *covers*. If *rdtype* is ``dns.rdatatype.ANY``, the default, then all rdatas will be matched. *rdtype*, a ``dns.rdataset.Rdataset`` or ``str``, the rdata type desired. *covers*, a ``dns.rdataset.Rdataset`` or ``str``, 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. """ rdtype = dns.rdatatype.RdataType.make(rdtype) covers = dns.rdatatype.RdataType.make(covers) for name, node in self.items(): for rds in node: if rdtype == dns.rdatatype.ANY or ( rds.rdtype == rdtype and rds.covers == covers ): for rdata in rds: yield (name, rds.ttl, rdata) def to_file( self, f: Any, sorted: bool = True, relativize: bool = True, nl: Optional[str] = None, want_comments: bool = False, want_origin: bool = False, ) -> None: """Write a zone to a file. *f*, a file or `str`. If *f* is a string, it is treated as the name of a file to open. *sorted*, a ``bool``. If True, the default, then the file will be written with the names sorted in DNSSEC order from least to greatest. Otherwise the names will be written in whatever order they happen to have in the zone's dictionary. *relativize*, a ``bool``. If True, the default, then domain names in the output will be relativized to the zone's origin if possible. *nl*, a ``str`` or None. The end of line string. If not ``None``, the output will use the platform's native end-of-line marker (i.e. LF on POSIX, CRLF on Windows). *want_comments*, a ``bool``. If ``True``, emit end-of-line comments as part of writing the file. If ``False``, the default, do not emit them. *want_origin*, a ``bool``. If ``True``, emit a $ORIGIN line at the start of the file. If ``False``, the default, do not emit one. """ if isinstance(f, str): cm: contextlib.AbstractContextManager = open(f, "wb") else: cm = contextlib.nullcontext(f) with cm as f: # must be in this way, f.encoding may contain None, or even # attribute may not be there file_enc = getattr(f, "encoding", None) if file_enc is None: file_enc = "utf-8" if nl is None: # binary mode, '\n' is not enough nl_b = os.linesep.encode(file_enc) nl = "\n" elif isinstance(nl, str): nl_b = nl.encode(file_enc) else: nl_b = nl nl = nl.decode() if want_origin: assert self.origin is not None l = "$ORIGIN " + self.origin.to_text() l_b = l.encode(file_enc) try: f.write(l_b) f.write(nl_b) except TypeError: # textual mode f.write(l) f.write(nl) if sorted: names = list(self.keys()) names.sort() else: names = self.keys() for n in names: l = self[n].to_text( n, origin=self.origin, relativize=relativize, want_comments=want_comments, ) l_b = l.encode(file_enc) try: f.write(l_b) f.write(nl_b) except TypeError: # textual mode f.write(l) f.write(nl) def to_text( self, sorted: bool = True, relativize: bool = True, nl: Optional[str] = None, want_comments: bool = False, want_origin: bool = False, ) -> str: """Return a zone's text as though it were written to a file. *sorted*, a ``bool``. If True, the default, then the file will be written with the names sorted in DNSSEC order from least to greatest. Otherwise the names will be written in whatever order they happen to have in the zone's dictionary. *relativize*, a ``bool``. If True, the default, then domain names in the output will be relativized to the zone's origin if possible. *nl*, a ``str`` or None. The end of line string. If not ``None``, the output will use the platform's native end-of-line marker (i.e. LF on POSIX, CRLF on Windows). *want_comments*, a ``bool``. If ``True``, emit end-of-line comments as part of writing the file. If ``False``, the default, do not emit them. *want_origin*, a ``bool``. If ``True``, emit a $ORIGIN line at the start of the output. If ``False``, the default, do not emit one. Returns a ``str``. """ temp_buffer = io.StringIO() self.to_file(temp_buffer, sorted, relativize, nl, want_comments, want_origin) return_value = temp_buffer.getvalue() temp_buffer.close() return return_value def check_origin(self) -> None: """Do some simple checking of the zone's origin. Raises ``dns.zone.NoSOA`` if there is no SOA RRset. Raises ``dns.zone.NoNS`` if there is no NS RRset. Raises ``KeyError`` if there is no origin node. """ if self.relativize: name = dns.name.empty else: assert self.origin is not None name = self.origin if self.get_rdataset(name, dns.rdatatype.SOA) is None: raise NoSOA if self.get_rdataset(name, dns.rdatatype.NS) is None: raise NoNS def get_soa( self, txn: Optional[dns.transaction.Transaction] = None ) -> dns.rdtypes.ANY.SOA.SOA: """Get the zone SOA rdata. Raises ``dns.zone.NoSOA`` if there is no SOA RRset. Returns a ``dns.rdtypes.ANY.SOA.SOA`` Rdata. """ if self.relativize: origin_name = dns.name.empty else: if self.origin is None: # get_soa() has been called very early, and there must not be # an SOA if there is no origin. raise NoSOA origin_name = self.origin soa: Optional[dns.rdataset.Rdataset] if txn: soa = txn.get(origin_name, dns.rdatatype.SOA) else: soa = self.get_rdataset(origin_name, dns.rdatatype.SOA) if soa is None: raise NoSOA return soa[0] def _compute_digest( self, hash_algorithm: DigestHashAlgorithm, scheme: DigestScheme = DigestScheme.SIMPLE, ) -> bytes: hashinfo = _digest_hashers.get(hash_algorithm) if not hashinfo: raise UnsupportedDigestHashAlgorithm if scheme != DigestScheme.SIMPLE: raise UnsupportedDigestScheme if self.relativize: origin_name = dns.name.empty else: assert self.origin is not None origin_name = self.origin hasher = hashinfo() for name, node in sorted(self.items()): rrnamebuf = name.to_digestable(self.origin) for rdataset in sorted(node, key=lambda rds: (rds.rdtype, rds.covers)): if name == origin_name and dns.rdatatype.ZONEMD in ( rdataset.rdtype, rdataset.covers, ): continue rrfixed = struct.pack( "!HHI", rdataset.rdtype, rdataset.rdclass, rdataset.ttl ) rdatas = [rdata.to_digestable(self.origin) for rdata in rdataset] for rdata in sorted(rdatas): rrlen = struct.pack("!H", len(rdata)) hasher.update(rrnamebuf + rrfixed + rrlen + rdata) return hasher.digest() def compute_digest( self, hash_algorithm: DigestHashAlgorithm, scheme: DigestScheme = DigestScheme.SIMPLE, ) -> dns.rdtypes.ANY.ZONEMD.ZONEMD: serial = self.get_soa().serial digest = self._compute_digest(hash_algorithm, scheme) return dns.rdtypes.ANY.ZONEMD.ZONEMD( self.rdclass, dns.rdatatype.ZONEMD, serial, scheme, hash_algorithm, digest ) def verify_digest( self, zonemd: Optional[dns.rdtypes.ANY.ZONEMD.ZONEMD] = None ) -> None: digests: Union[dns.rdataset.Rdataset, List[dns.rdtypes.ANY.ZONEMD.ZONEMD]] if zonemd: digests = [zonemd] else: assert self.origin is not None rds = self.get_rdataset(self.origin, dns.rdatatype.ZONEMD) if rds is None: raise NoDigest digests = rds for digest in digests: try: computed = self._compute_digest(digest.hash_algorithm, digest.scheme) if computed == digest.digest: return except Exception: pass raise DigestVerificationFailure # TransactionManager methods def reader(self) -> "Transaction": return Transaction(self, False, Version(self, 1, self.nodes, self.origin)) def writer(self, replacement: bool = False) -> "Transaction": txn = Transaction(self, replacement) txn._setup_version() return txn def origin_information( self, ) -> Tuple[Optional[dns.name.Name], bool, Optional[dns.name.Name]]: effective: Optional[dns.name.Name] if self.relativize: effective = dns.name.empty else: effective = self.origin return (self.origin, self.relativize, effective) def get_class(self): return self.rdclass # Transaction methods def _end_read(self, txn): pass def _end_write(self, txn): pass def _commit_version(self, _, version, origin): self.nodes = version.nodes if self.origin is None: self.origin = origin def _get_next_version_id(self): # Versions are ephemeral and all have id 1 return 1 # These classes used to be in dns.versioned, but have moved here so we can use # the copy-on-write transaction mechanism for both kinds of zones. In a # regular zone, the version only exists during the transaction, and the nodes # are regular dns.node.Nodes. # A node with a version id. class VersionedNode(dns.node.Node): # lgtm[py/missing-equals] __slots__ = ["id"] def __init__(self): super().__init__() # A proper id will get set by the Version self.id = 0 @dns.immutable.immutable class ImmutableVersionedNode(VersionedNode): def __init__(self, node): super().__init__() self.id = node.id self.rdatasets = tuple( [dns.rdataset.ImmutableRdataset(rds) for rds in node.rdatasets] ) def find_rdataset( self, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, create: bool = False, ) -> dns.rdataset.Rdataset: if create: raise TypeError("immutable") return super().find_rdataset(rdclass, rdtype, covers, False) def get_rdataset( self, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, create: bool = False, ) -> Optional[dns.rdataset.Rdataset]: if create: raise TypeError("immutable") return super().get_rdataset(rdclass, rdtype, covers, False) def delete_rdataset( self, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, ) -> None: raise TypeError("immutable") def replace_rdataset(self, replacement: dns.rdataset.Rdataset) -> None: raise TypeError("immutable") def is_immutable(self) -> bool: return True class Version: def __init__( self, zone: Zone, id: int, nodes: Optional[MutableMapping[dns.name.Name, dns.node.Node]] = None, origin: Optional[dns.name.Name] = None, ): self.zone = zone self.id = id if nodes is not None: self.nodes = nodes else: self.nodes = zone.map_factory() self.origin = origin def _validate_name(self, name: dns.name.Name) -> dns.name.Name: return _validate_name(name, self.origin, self.zone.relativize) def get_node(self, name: dns.name.Name) -> Optional[dns.node.Node]: name = self._validate_name(name) return self.nodes.get(name) def get_rdataset( self, name: dns.name.Name, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType, ) -> Optional[dns.rdataset.Rdataset]: node = self.get_node(name) if node is None: return None return node.get_rdataset(self.zone.rdclass, rdtype, covers) def keys(self): return self.nodes.keys() def items(self): return self.nodes.items() class WritableVersion(Version): def __init__(self, zone: Zone, replacement: bool = False): # The zone._versions_lock must be held by our caller in a versioned # zone. id = zone._get_next_version_id() super().__init__(zone, id) if not replacement: # We copy the map, because that gives us a simple and thread-safe # way of doing versions, and we have a garbage collector to help # us. We only make new node objects if we actually change the # node. self.nodes.update(zone.nodes) # We have to copy the zone origin as it may be None in the first # version, and we don't want to mutate the zone until we commit. self.origin = zone.origin self.changed: Set[dns.name.Name] = set() def _maybe_cow(self, name: dns.name.Name) -> dns.node.Node: name = self._validate_name(name) node = self.nodes.get(name) if node is None or name not in self.changed: new_node = self.zone.node_factory() if hasattr(new_node, "id"): # We keep doing this for backwards compatibility, as earlier # code used new_node.id != self.id for the "do we need to CoW?" # test. Now we use the changed set as this works with both # regular zones and versioned zones. # # We ignore the mypy error as this is safe but it doesn't see it. new_node.id = self.id # type: ignore if node is not None: # moo! copy on write! new_node.rdatasets.extend(node.rdatasets) self.nodes[name] = new_node self.changed.add(name) return new_node else: return node def delete_node(self, name: dns.name.Name) -> None: name = self._validate_name(name) if name in self.nodes: del self.nodes[name] self.changed.add(name) def put_rdataset( self, name: dns.name.Name, rdataset: dns.rdataset.Rdataset ) -> None: node = self._maybe_cow(name) node.replace_rdataset(rdataset) def delete_rdataset( self, name: dns.name.Name, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType, ) -> None: node = self._maybe_cow(name) node.delete_rdataset(self.zone.rdclass, rdtype, covers) if len(node) == 0: del self.nodes[name] @dns.immutable.immutable class ImmutableVersion(Version): def __init__(self, version: WritableVersion): # We tell super() that it's a replacement as we don't want it # to copy the nodes, as we're about to do that with an # immutable Dict. super().__init__(version.zone, True) # set the right id! self.id = version.id # keep the origin self.origin = version.origin # Make changed nodes immutable for name in version.changed: node = version.nodes.get(name) # it might not exist if we deleted it in the version if node: version.nodes[name] = ImmutableVersionedNode(node) # We're changing the type of the nodes dictionary here on purpose, so # we ignore the mypy error. self.nodes = dns.immutable.Dict( version.nodes, True, self.zone.map_factory ) # type: ignore class Transaction(dns.transaction.Transaction): def __init__(self, zone, replacement, version=None, make_immutable=False): read_only = version is not None super().__init__(zone, replacement, read_only) self.version = version self.make_immutable = make_immutable @property def zone(self): return self.manager def _setup_version(self): assert self.version is None factory = self.manager.writable_version_factory if factory is None: factory = WritableVersion self.version = factory(self.zone, self.replacement) def _get_rdataset(self, name, rdtype, covers): return self.version.get_rdataset(name, rdtype, covers) def _put_rdataset(self, name, rdataset): assert not self.read_only self.version.put_rdataset(name, rdataset) def _delete_name(self, name): assert not self.read_only self.version.delete_node(name) def _delete_rdataset(self, name, rdtype, covers): assert not self.read_only self.version.delete_rdataset(name, rdtype, covers) def _name_exists(self, name): return self.version.get_node(name) is not None def _changed(self): if self.read_only: return False else: return len(self.version.changed) > 0 def _end_transaction(self, commit): if self.read_only: self.zone._end_read(self) elif commit and len(self.version.changed) > 0: if self.make_immutable: factory = self.manager.immutable_version_factory if factory is None: factory = ImmutableVersion version = factory(self.version) else: version = self.version self.zone._commit_version(self, version, self.version.origin) else: # rollback self.zone._end_write(self) def _set_origin(self, origin): if self.version.origin is None: self.version.origin = origin def _iterate_rdatasets(self): for name, node in self.version.items(): for rdataset in node: yield (name, rdataset) def _iterate_names(self): return self.version.keys() def _get_node(self, name): return self.version.get_node(name) def _origin_information(self): (absolute, relativize, effective) = self.manager.origin_information() if absolute is None and self.version.origin is not None: # No origin has been committed yet, but we've learned one as part of # this txn. Use it. absolute = self.version.origin if relativize: effective = dns.name.empty else: effective = absolute return (absolute, relativize, effective) def _from_text( text: Any, origin: Optional[Union[dns.name.Name, str]] = None, rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, relativize: bool = True, zone_factory: Any = Zone, filename: Optional[str] = None, allow_include: bool = False, check_origin: bool = True, idna_codec: Optional[dns.name.IDNACodec] = None, allow_directives: Union[bool, Iterable[str]] = True, ) -> Zone: # See the comments for the public APIs from_text() and from_file() for # details. # 'text' can also be a file, but we don't publish that fact # since it's an implementation detail. The official file # interface is from_file(). if filename is None: filename = "" zone = zone_factory(origin, rdclass, relativize=relativize) with zone.writer(True) as txn: tok = dns.tokenizer.Tokenizer(text, filename, idna_codec=idna_codec) reader = dns.zonefile.Reader( tok, rdclass, txn, allow_include=allow_include, allow_directives=allow_directives, ) try: reader.read() except dns.zonefile.UnknownOrigin: # for backwards compatibility raise dns.zone.UnknownOrigin # Now that we're done reading, do some basic checking of the zone. if check_origin: zone.check_origin() return zone def from_text( text: str, origin: Optional[Union[dns.name.Name, str]] = None, rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, relativize: bool = True, zone_factory: Any = Zone, filename: Optional[str] = None, allow_include: bool = False, check_origin: bool = True, idna_codec: Optional[dns.name.IDNACodec] = None, allow_directives: Union[bool, Iterable[str]] = True, ) -> Zone: """Build a zone object from a zone file format string. *text*, a ``str``, the zone file format input. *origin*, a ``dns.name.Name``, a ``str``, or ``None``. The origin of the zone; if not specified, the first ``$ORIGIN`` statement in the zone file will determine the origin of the zone. *rdclass*, a ``dns.rdataclass.RdataClass``, the zone's rdata class; the default is class IN. *relativize*, a ``bool``, determine's whether domain names are relativized to the zone's origin. The default is ``True``. *zone_factory*, the zone factory to use or ``None``. If ``None``, then ``dns.zone.Zone`` will be used. The value may be any class or callable that returns a subclass of ``dns.zone.Zone``. *filename*, a ``str`` or ``None``, the filename to emit when describing where an error occurred; the default is ``''``. *allow_include*, a ``bool``. If ``True``, the default, then ``$INCLUDE`` directives are permitted. If ``False``, then encoutering a ``$INCLUDE`` will raise a ``SyntaxError`` exception. *check_origin*, a ``bool``. If ``True``, the default, then sanity checks of the origin node will be made by calling the zone's ``check_origin()`` method. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder is used. *allow_directives*, a ``bool`` or an iterable of `str`. If ``True``, the default, then directives are permitted, and the *allow_include* parameter controls whether ``$INCLUDE`` is permitted. If ``False`` or an empty iterable, then no directive processing is done and any directive-like text will be treated as a regular owner name. If a non-empty iterable, then only the listed directives (including the ``$``) are allowed. Raises ``dns.zone.NoSOA`` if there is no SOA RRset. Raises ``dns.zone.NoNS`` if there is no NS RRset. Raises ``KeyError`` if there is no origin node. Returns a subclass of ``dns.zone.Zone``. """ return _from_text( text, origin, rdclass, relativize, zone_factory, filename, allow_include, check_origin, idna_codec, allow_directives, ) def from_file( f: Any, origin: Optional[Union[dns.name.Name, str]] = None, rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, relativize: bool = True, zone_factory: Any = Zone, filename: Optional[str] = None, allow_include: bool = True, check_origin: bool = True, idna_codec: Optional[dns.name.IDNACodec] = None, allow_directives: Union[bool, Iterable[str]] = True, ) -> Zone: """Read a zone file and build a zone object. *f*, a file or ``str``. If *f* is a string, it is treated as the name of a file to open. *origin*, a ``dns.name.Name``, a ``str``, or ``None``. The origin of the zone; if not specified, the first ``$ORIGIN`` statement in the zone file will determine the origin of the zone. *rdclass*, an ``int``, the zone's rdata class; the default is class IN. *relativize*, a ``bool``, determine's whether domain names are relativized to the zone's origin. The default is ``True``. *zone_factory*, the zone factory to use or ``None``. If ``None``, then ``dns.zone.Zone`` will be used. The value may be any class or callable that returns a subclass of ``dns.zone.Zone``. *filename*, a ``str`` or ``None``, the filename to emit when describing where an error occurred; the default is ``''``. *allow_include*, a ``bool``. If ``True``, the default, then ``$INCLUDE`` directives are permitted. If ``False``, then encoutering a ``$INCLUDE`` will raise a ``SyntaxError`` exception. *check_origin*, a ``bool``. If ``True``, the default, then sanity checks of the origin node will be made by calling the zone's ``check_origin()`` method. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder is used. *allow_directives*, a ``bool`` or an iterable of `str`. If ``True``, the default, then directives are permitted, and the *allow_include* parameter controls whether ``$INCLUDE`` is permitted. If ``False`` or an empty iterable, then no directive processing is done and any directive-like text will be treated as a regular owner name. If a non-empty iterable, then only the listed directives (including the ``$``) are allowed. Raises ``dns.zone.NoSOA`` if there is no SOA RRset. Raises ``dns.zone.NoNS`` if there is no NS RRset. Raises ``KeyError`` if there is no origin node. Returns a subclass of ``dns.zone.Zone``. """ if isinstance(f, str): if filename is None: filename = f cm: contextlib.AbstractContextManager = open(f) else: cm = contextlib.nullcontext(f) with cm as f: return _from_text( f, origin, rdclass, relativize, zone_factory, filename, allow_include, check_origin, idna_codec, allow_directives, ) assert False # make mypy happy lgtm[py/unreachable-statement] def from_xfr( xfr: Any, zone_factory: Any = Zone, relativize: bool = True, check_origin: bool = True, ) -> Zone: """Convert the output of a zone transfer generator into a zone object. *xfr*, a generator of ``dns.message.Message`` objects, typically ``dns.query.xfr()``. *relativize*, a ``bool``, determine's whether domain names are relativized to the zone's origin. The default is ``True``. It is essential that the relativize setting matches the one specified to the generator. *check_origin*, a ``bool``. If ``True``, the default, then sanity checks of the origin node will be made by calling the zone's ``check_origin()`` method. Raises ``dns.zone.NoSOA`` if there is no SOA RRset. Raises ``dns.zone.NoNS`` if there is no NS RRset. Raises ``KeyError`` if there is no origin node. Raises ``ValueError`` if no messages are yielded by the generator. Returns a subclass of ``dns.zone.Zone``. """ z = None for r in xfr: if z is None: if relativize: origin = r.origin else: origin = r.answer[0].name rdclass = r.answer[0].rdclass z = zone_factory(origin, rdclass, relativize=relativize) for rrset in r.answer: znode = z.nodes.get(rrset.name) if not znode: znode = z.node_factory() z.nodes[rrset.name] = znode zrds = znode.find_rdataset(rrset.rdclass, rrset.rdtype, rrset.covers, True) zrds.update_ttl(rrset.ttl) for rd in rrset: zrds.add(rd) if z is None: raise ValueError("empty transfer") if check_origin: z.check_origin() return z dnspython-2.7.0/dns/zonefile.py0000644000000000000000000006642613615410400013455 0ustar00# 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. """DNS Zones.""" import re import sys from typing import Any, Iterable, List, Optional, Set, Tuple, Union import dns.exception import dns.grange import dns.name import dns.node import dns.rdata import dns.rdataclass import dns.rdatatype import dns.rdtypes.ANY.SOA import dns.rrset import dns.tokenizer import dns.transaction import dns.ttl class UnknownOrigin(dns.exception.DNSException): """Unknown origin""" class CNAMEAndOtherData(dns.exception.DNSException): """A node has a CNAME and other data""" def _check_cname_and_other_data(txn, name, rdataset): rdataset_kind = dns.node.NodeKind.classify_rdataset(rdataset) node = txn.get_node(name) if node is None: # empty nodes are neutral. return node_kind = node.classify() if ( node_kind == dns.node.NodeKind.CNAME and rdataset_kind == dns.node.NodeKind.REGULAR ): raise CNAMEAndOtherData("rdataset type is not compatible with a CNAME node") elif ( node_kind == dns.node.NodeKind.REGULAR and rdataset_kind == dns.node.NodeKind.CNAME ): raise CNAMEAndOtherData( "CNAME rdataset is not compatible with a regular data node" ) # Otherwise at least one of the node and the rdataset is neutral, so # adding the rdataset is ok SavedStateType = Tuple[ dns.tokenizer.Tokenizer, Optional[dns.name.Name], # current_origin Optional[dns.name.Name], # last_name Optional[Any], # current_file int, # last_ttl bool, # last_ttl_known int, # default_ttl bool, ] # default_ttl_known def _upper_dollarize(s): s = s.upper() if not s.startswith("$"): s = "$" + s return s class Reader: """Read a DNS zone file into a transaction.""" def __init__( self, tok: dns.tokenizer.Tokenizer, rdclass: dns.rdataclass.RdataClass, txn: dns.transaction.Transaction, allow_include: bool = False, allow_directives: Union[bool, Iterable[str]] = True, force_name: Optional[dns.name.Name] = None, force_ttl: Optional[int] = None, force_rdclass: Optional[dns.rdataclass.RdataClass] = None, force_rdtype: Optional[dns.rdatatype.RdataType] = None, default_ttl: Optional[int] = None, ): self.tok = tok (self.zone_origin, self.relativize, _) = txn.manager.origin_information() self.current_origin = self.zone_origin self.last_ttl = 0 self.last_ttl_known = False if force_ttl is not None: default_ttl = force_ttl if default_ttl is None: self.default_ttl = 0 self.default_ttl_known = False else: self.default_ttl = default_ttl self.default_ttl_known = True self.last_name = self.current_origin self.zone_rdclass = rdclass self.txn = txn self.saved_state: List[SavedStateType] = [] self.current_file: Optional[Any] = None self.allowed_directives: Set[str] if allow_directives is True: self.allowed_directives = {"$GENERATE", "$ORIGIN", "$TTL"} if allow_include: self.allowed_directives.add("$INCLUDE") elif allow_directives is False: # allow_include was ignored in earlier releases if allow_directives was # False, so we continue that. self.allowed_directives = set() else: # Note that if directives are explicitly specified, then allow_include # is ignored. self.allowed_directives = set(_upper_dollarize(d) for d in allow_directives) self.force_name = force_name self.force_ttl = force_ttl self.force_rdclass = force_rdclass self.force_rdtype = force_rdtype self.txn.check_put_rdataset(_check_cname_and_other_data) def _eat_line(self): while 1: token = self.tok.get() if token.is_eol_or_eof(): break def _get_identifier(self): token = self.tok.get() if not token.is_identifier(): raise dns.exception.SyntaxError return token def _rr_line(self): """Process one line from a DNS zone file.""" token = None # Name if self.force_name is not None: name = self.force_name else: if self.current_origin is None: raise UnknownOrigin token = self.tok.get(want_leading=True) if not token.is_whitespace(): self.last_name = self.tok.as_name(token, self.current_origin) else: token = self.tok.get() if token.is_eol_or_eof(): # treat leading WS followed by EOL/EOF as if they were EOL/EOF. return self.tok.unget(token) name = self.last_name if not name.is_subdomain(self.zone_origin): self._eat_line() return if self.relativize: name = name.relativize(self.zone_origin) # TTL if self.force_ttl is not None: ttl = self.force_ttl self.last_ttl = ttl self.last_ttl_known = True else: token = self._get_identifier() ttl = None try: ttl = dns.ttl.from_text(token.value) self.last_ttl = ttl self.last_ttl_known = True token = None except dns.ttl.BadTTL: self.tok.unget(token) # Class if self.force_rdclass is not None: rdclass = self.force_rdclass else: token = self._get_identifier() try: rdclass = dns.rdataclass.from_text(token.value) except dns.exception.SyntaxError: raise except Exception: rdclass = self.zone_rdclass self.tok.unget(token) if rdclass != self.zone_rdclass: raise dns.exception.SyntaxError("RR class is not zone's class") if ttl is None: # support for syntax token = self._get_identifier() ttl = None try: ttl = dns.ttl.from_text(token.value) self.last_ttl = ttl self.last_ttl_known = True token = None except dns.ttl.BadTTL: if self.default_ttl_known: ttl = self.default_ttl elif self.last_ttl_known: ttl = self.last_ttl self.tok.unget(token) # Type if self.force_rdtype is not None: rdtype = self.force_rdtype else: token = self._get_identifier() try: rdtype = dns.rdatatype.from_text(token.value) except Exception: raise dns.exception.SyntaxError(f"unknown rdatatype '{token.value}'") try: rd = dns.rdata.from_text( rdclass, rdtype, self.tok, self.current_origin, self.relativize, self.zone_origin, ) except dns.exception.SyntaxError: # Catch and reraise. raise except Exception: # All exceptions that occur in the processing of rdata # are treated as syntax errors. This is not strictly # correct, but it is correct almost all of the time. # We convert them to syntax errors so that we can emit # helpful filename:line info. (ty, va) = sys.exc_info()[:2] raise dns.exception.SyntaxError(f"caught exception {str(ty)}: {str(va)}") if not self.default_ttl_known and rdtype == dns.rdatatype.SOA: # The pre-RFC2308 and pre-BIND9 behavior inherits the zone default # TTL from the SOA minttl if no $TTL statement is present before the # SOA is parsed. self.default_ttl = rd.minimum self.default_ttl_known = True if ttl is None: # if we didn't have a TTL on the SOA, set it! ttl = rd.minimum # TTL check. We had to wait until now to do this as the SOA RR's # own TTL can be inferred from its minimum. if ttl is None: raise dns.exception.SyntaxError("Missing default TTL value") self.txn.add(name, ttl, rd) def _parse_modify(self, side: str) -> Tuple[str, str, int, int, str]: # Here we catch everything in '{' '}' in a group so we can replace it # with ''. is_generate1 = re.compile(r"^.*\$({(\+|-?)(\d+),(\d+),(.)}).*$") is_generate2 = re.compile(r"^.*\$({(\+|-?)(\d+)}).*$") is_generate3 = re.compile(r"^.*\$({(\+|-?)(\d+),(\d+)}).*$") # Sometimes there are modifiers in the hostname. These come after # the dollar sign. They are in the form: ${offset[,width[,base]]}. # Make names mod = "" sign = "+" offset = "0" width = "0" base = "d" g1 = is_generate1.match(side) if g1: mod, sign, offset, width, base = g1.groups() if sign == "": sign = "+" else: g2 = is_generate2.match(side) if g2: mod, sign, offset = g2.groups() if sign == "": sign = "+" width = "0" base = "d" else: g3 = is_generate3.match(side) if g3: mod, sign, offset, width = g3.groups() if sign == "": sign = "+" base = "d" ioffset = int(offset) iwidth = int(width) if sign not in ["+", "-"]: raise dns.exception.SyntaxError(f"invalid offset sign {sign}") if base not in ["d", "o", "x", "X", "n", "N"]: raise dns.exception.SyntaxError(f"invalid type {base}") return mod, sign, ioffset, iwidth, base def _generate_line(self): # range lhs [ttl] [class] type rhs [ comment ] """Process one line containing the GENERATE statement from a DNS zone file.""" if self.current_origin is None: raise UnknownOrigin token = self.tok.get() # Range (required) try: start, stop, step = dns.grange.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise dns.exception.SyntaxError except Exception: raise dns.exception.SyntaxError # lhs (required) try: lhs = token.value token = self.tok.get() if not token.is_identifier(): raise dns.exception.SyntaxError except Exception: raise dns.exception.SyntaxError # TTL try: ttl = dns.ttl.from_text(token.value) self.last_ttl = ttl self.last_ttl_known = True token = self.tok.get() if not token.is_identifier(): raise dns.exception.SyntaxError except dns.ttl.BadTTL: if not (self.last_ttl_known or self.default_ttl_known): raise dns.exception.SyntaxError("Missing default TTL value") if self.default_ttl_known: ttl = self.default_ttl elif self.last_ttl_known: ttl = self.last_ttl # Class try: rdclass = dns.rdataclass.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise dns.exception.SyntaxError except dns.exception.SyntaxError: raise dns.exception.SyntaxError except Exception: rdclass = self.zone_rdclass if rdclass != self.zone_rdclass: raise dns.exception.SyntaxError("RR class is not zone's class") # Type try: rdtype = dns.rdatatype.from_text(token.value) token = self.tok.get() if not token.is_identifier(): raise dns.exception.SyntaxError except Exception: raise dns.exception.SyntaxError(f"unknown rdatatype '{token.value}'") # rhs (required) rhs = token.value def _calculate_index(counter: int, offset_sign: str, offset: int) -> int: """Calculate the index from the counter and offset.""" if offset_sign == "-": offset *= -1 return counter + offset def _format_index(index: int, base: str, width: int) -> str: """Format the index with the given base, and zero-fill it to the given width.""" if base in ["d", "o", "x", "X"]: return format(index, base).zfill(width) # base can only be n or N here hexa = _format_index(index, "x", width) nibbles = ".".join(hexa[::-1])[:width] if base == "N": nibbles = nibbles.upper() return nibbles lmod, lsign, loffset, lwidth, lbase = self._parse_modify(lhs) rmod, rsign, roffset, rwidth, rbase = self._parse_modify(rhs) for i in range(start, stop + 1, step): # +1 because bind is inclusive and python is exclusive lindex = _calculate_index(i, lsign, loffset) rindex = _calculate_index(i, rsign, roffset) lzfindex = _format_index(lindex, lbase, lwidth) rzfindex = _format_index(rindex, rbase, rwidth) name = lhs.replace(f"${lmod}", lzfindex) rdata = rhs.replace(f"${rmod}", rzfindex) self.last_name = dns.name.from_text( name, self.current_origin, self.tok.idna_codec ) name = self.last_name if not name.is_subdomain(self.zone_origin): self._eat_line() return if self.relativize: name = name.relativize(self.zone_origin) try: rd = dns.rdata.from_text( rdclass, rdtype, rdata, self.current_origin, self.relativize, self.zone_origin, ) except dns.exception.SyntaxError: # Catch and reraise. raise except Exception: # All exceptions that occur in the processing of rdata # are treated as syntax errors. This is not strictly # correct, but it is correct almost all of the time. # We convert them to syntax errors so that we can emit # helpful filename:line info. (ty, va) = sys.exc_info()[:2] raise dns.exception.SyntaxError( f"caught exception {str(ty)}: {str(va)}" ) self.txn.add(name, ttl, rd) def read(self) -> None: """Read a DNS zone file and build a zone object. @raises dns.zone.NoSOA: No SOA RR was found at the zone origin @raises dns.zone.NoNS: No NS RRset was found at the zone origin """ try: while 1: token = self.tok.get(True, True) if token.is_eof(): if self.current_file is not None: self.current_file.close() if len(self.saved_state) > 0: ( self.tok, self.current_origin, self.last_name, self.current_file, self.last_ttl, self.last_ttl_known, self.default_ttl, self.default_ttl_known, ) = self.saved_state.pop(-1) continue break elif token.is_eol(): continue elif token.is_comment(): self.tok.get_eol() continue elif token.value[0] == "$" and len(self.allowed_directives) > 0: # Note that we only run directive processing code if at least # one directive is allowed in order to be backwards compatible c = token.value.upper() if c not in self.allowed_directives: raise dns.exception.SyntaxError( f"zone file directive '{c}' is not allowed" ) if c == "$TTL": token = self.tok.get() if not token.is_identifier(): raise dns.exception.SyntaxError("bad $TTL") self.default_ttl = dns.ttl.from_text(token.value) self.default_ttl_known = True self.tok.get_eol() elif c == "$ORIGIN": self.current_origin = self.tok.get_name() self.tok.get_eol() if self.zone_origin is None: self.zone_origin = self.current_origin self.txn._set_origin(self.current_origin) elif c == "$INCLUDE": token = self.tok.get() filename = token.value token = self.tok.get() new_origin: Optional[dns.name.Name] if token.is_identifier(): new_origin = dns.name.from_text( token.value, self.current_origin, self.tok.idna_codec ) self.tok.get_eol() elif not token.is_eol_or_eof(): raise dns.exception.SyntaxError("bad origin in $INCLUDE") else: new_origin = self.current_origin self.saved_state.append( ( self.tok, self.current_origin, self.last_name, self.current_file, self.last_ttl, self.last_ttl_known, self.default_ttl, self.default_ttl_known, ) ) self.current_file = open(filename) self.tok = dns.tokenizer.Tokenizer(self.current_file, filename) self.current_origin = new_origin elif c == "$GENERATE": self._generate_line() else: raise dns.exception.SyntaxError( f"Unknown zone file directive '{c}'" ) continue self.tok.unget(token) self._rr_line() except dns.exception.SyntaxError as detail: (filename, line_number) = self.tok.where() if detail is None: detail = "syntax error" ex = dns.exception.SyntaxError( "%s:%d: %s" % (filename, line_number, detail) ) tb = sys.exc_info()[2] raise ex.with_traceback(tb) from None class RRsetsReaderTransaction(dns.transaction.Transaction): def __init__(self, manager, replacement, read_only): assert not read_only super().__init__(manager, replacement, read_only) self.rdatasets = {} def _get_rdataset(self, name, rdtype, covers): return self.rdatasets.get((name, rdtype, covers)) def _get_node(self, name): rdatasets = [] for (rdataset_name, _, _), rdataset in self.rdatasets.items(): if name == rdataset_name: rdatasets.append(rdataset) if len(rdatasets) == 0: return None node = dns.node.Node() node.rdatasets = rdatasets return node def _put_rdataset(self, name, rdataset): self.rdatasets[(name, rdataset.rdtype, rdataset.covers)] = rdataset def _delete_name(self, name): # First remove any changes involving the name remove = [] for key in self.rdatasets: if key[0] == name: remove.append(key) if len(remove) > 0: for key in remove: del self.rdatasets[key] def _delete_rdataset(self, name, rdtype, covers): try: del self.rdatasets[(name, rdtype, covers)] except KeyError: pass def _name_exists(self, name): for n, _, _ in self.rdatasets: if n == name: return True return False def _changed(self): return len(self.rdatasets) > 0 def _end_transaction(self, commit): if commit and self._changed(): rrsets = [] for (name, _, _), rdataset in self.rdatasets.items(): rrset = dns.rrset.RRset( name, rdataset.rdclass, rdataset.rdtype, rdataset.covers ) rrset.update(rdataset) rrsets.append(rrset) self.manager.set_rrsets(rrsets) def _set_origin(self, origin): pass def _iterate_rdatasets(self): raise NotImplementedError # pragma: no cover def _iterate_names(self): raise NotImplementedError # pragma: no cover class RRSetsReaderManager(dns.transaction.TransactionManager): def __init__( self, origin=dns.name.root, relativize=False, rdclass=dns.rdataclass.IN ): self.origin = origin self.relativize = relativize self.rdclass = rdclass self.rrsets = [] def reader(self): # pragma: no cover raise NotImplementedError def writer(self, replacement=False): assert replacement is True return RRsetsReaderTransaction(self, True, False) def get_class(self): return self.rdclass def origin_information(self): if self.relativize: effective = dns.name.empty else: effective = self.origin return (self.origin, self.relativize, effective) def set_rrsets(self, rrsets): self.rrsets = rrsets def read_rrsets( text: Any, name: Optional[Union[dns.name.Name, str]] = None, ttl: Optional[int] = None, rdclass: Optional[Union[dns.rdataclass.RdataClass, str]] = dns.rdataclass.IN, default_rdclass: Union[dns.rdataclass.RdataClass, str] = dns.rdataclass.IN, rdtype: Optional[Union[dns.rdatatype.RdataType, str]] = None, default_ttl: Optional[Union[int, str]] = None, idna_codec: Optional[dns.name.IDNACodec] = None, origin: Optional[Union[dns.name.Name, str]] = dns.name.root, relativize: bool = False, ) -> List[dns.rrset.RRset]: """Read one or more rrsets from the specified text, possibly subject to restrictions. *text*, a file object or a string, is the input to process. *name*, a string, ``dns.name.Name``, or ``None``, is the owner name of the rrset. If not ``None``, then the owner name is "forced", and the input must not specify an owner name. If ``None``, then any owner names are allowed and must be present in the input. *ttl*, an ``int``, string, or None. If not ``None``, the the TTL is forced to be the specified value and the input must not specify a TTL. If ``None``, then a TTL may be specified in the input. If it is not specified, then the *default_ttl* will be used. *rdclass*, a ``dns.rdataclass.RdataClass``, string, or ``None``. If not ``None``, then the class is forced to the specified value, and the input must not specify a class. If ``None``, then the input may specify a class that matches *default_rdclass*. Note that it is not possible to return rrsets with differing classes; specifying ``None`` for the class simply allows the user to optionally type a class as that may be convenient when cutting and pasting. *default_rdclass*, a ``dns.rdataclass.RdataClass`` or string. The class of the returned rrsets. *rdtype*, a ``dns.rdatatype.RdataType``, string, or ``None``. If not ``None``, then the type is forced to the specified value, and the input must not specify a type. If ``None``, then a type must be present for each RR. *default_ttl*, an ``int``, string, or ``None``. If not ``None``, then if the TTL is not forced and is not specified, then this value will be used. if ``None``, then if the TTL is not forced an error will occur if the TTL is not specified. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder is used. Note that codecs only apply to the owner name; dnspython does not do IDNA for names in rdata, as there is no IDNA zonefile format. *origin*, a string, ``dns.name.Name``, or ``None``, is the origin for any relative names in the input, and also the origin to relativize to if *relativize* is ``True``. *relativize*, a bool. If ``True``, names are relativized to the *origin*; if ``False`` then any relative names in the input are made absolute by appending the *origin*. """ if isinstance(origin, str): origin = dns.name.from_text(origin, dns.name.root, idna_codec) if isinstance(name, str): name = dns.name.from_text(name, origin, idna_codec) if isinstance(ttl, str): ttl = dns.ttl.from_text(ttl) if isinstance(default_ttl, str): default_ttl = dns.ttl.from_text(default_ttl) if rdclass is not None: rdclass = dns.rdataclass.RdataClass.make(rdclass) else: rdclass = None default_rdclass = dns.rdataclass.RdataClass.make(default_rdclass) if rdtype is not None: rdtype = dns.rdatatype.RdataType.make(rdtype) else: rdtype = None manager = RRSetsReaderManager(origin, relativize, default_rdclass) with manager.writer(True) as txn: tok = dns.tokenizer.Tokenizer(text, "", idna_codec=idna_codec) reader = Reader( tok, default_rdclass, txn, allow_directives=False, force_name=name, force_ttl=ttl, force_rdclass=rdclass, force_rdtype=rdtype, default_ttl=default_ttl, ) reader.read() return manager.rrsets dnspython-2.7.0/dns/zonetypes.py0000644000000000000000000000126213615410400013665 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license """Common zone-related types.""" # This is a separate file to avoid import circularity between dns.zone and # the implementation of the ZONEMD type. import hashlib import dns.enum class DigestScheme(dns.enum.IntEnum): """ZONEMD Scheme""" SIMPLE = 1 @classmethod def _maximum(cls): return 255 class DigestHashAlgorithm(dns.enum.IntEnum): """ZONEMD Hash Algorithm""" SHA384 = 1 SHA512 = 2 @classmethod def _maximum(cls): return 255 _digest_hashers = { DigestHashAlgorithm.SHA384: hashlib.sha384, DigestHashAlgorithm.SHA512: hashlib.sha512, } dnspython-2.7.0/dns/dnssecalgs/__init__.py0000644000000000000000000001035313615410400015513 0ustar00from typing import Dict, Optional, Tuple, Type, Union import dns.name from dns.dnssecalgs.base import GenericPrivateKey from dns.dnssectypes import Algorithm from dns.exception import UnsupportedAlgorithm from dns.rdtypes.ANY.DNSKEY import DNSKEY if dns._features.have("dnssec"): from dns.dnssecalgs.dsa import PrivateDSA, PrivateDSANSEC3SHA1 from dns.dnssecalgs.ecdsa import PrivateECDSAP256SHA256, PrivateECDSAP384SHA384 from dns.dnssecalgs.eddsa import PrivateED448, PrivateED25519 from dns.dnssecalgs.rsa import ( PrivateRSAMD5, PrivateRSASHA1, PrivateRSASHA1NSEC3SHA1, PrivateRSASHA256, PrivateRSASHA512, ) _have_cryptography = True else: _have_cryptography = False AlgorithmPrefix = Optional[Union[bytes, dns.name.Name]] algorithms: Dict[Tuple[Algorithm, AlgorithmPrefix], Type[GenericPrivateKey]] = {} if _have_cryptography: # pylint: disable=possibly-used-before-assignment algorithms.update( { (Algorithm.RSAMD5, None): PrivateRSAMD5, (Algorithm.DSA, None): PrivateDSA, (Algorithm.RSASHA1, None): PrivateRSASHA1, (Algorithm.DSANSEC3SHA1, None): PrivateDSANSEC3SHA1, (Algorithm.RSASHA1NSEC3SHA1, None): PrivateRSASHA1NSEC3SHA1, (Algorithm.RSASHA256, None): PrivateRSASHA256, (Algorithm.RSASHA512, None): PrivateRSASHA512, (Algorithm.ECDSAP256SHA256, None): PrivateECDSAP256SHA256, (Algorithm.ECDSAP384SHA384, None): PrivateECDSAP384SHA384, (Algorithm.ED25519, None): PrivateED25519, (Algorithm.ED448, None): PrivateED448, } ) def get_algorithm_cls( algorithm: Union[int, str], prefix: AlgorithmPrefix = None ) -> Type[GenericPrivateKey]: """Get Private Key class from Algorithm. *algorithm*, a ``str`` or ``int`` specifying the DNSKEY algorithm. Raises ``UnsupportedAlgorithm`` if the algorithm is unknown. Returns a ``dns.dnssecalgs.GenericPrivateKey`` """ algorithm = Algorithm.make(algorithm) cls = algorithms.get((algorithm, prefix)) if cls: return cls raise UnsupportedAlgorithm( f'algorithm "{Algorithm.to_text(algorithm)}" not supported by dnspython' ) def get_algorithm_cls_from_dnskey(dnskey: DNSKEY) -> Type[GenericPrivateKey]: """Get Private Key class from DNSKEY. *dnskey*, a ``DNSKEY`` to get Algorithm class for. Raises ``UnsupportedAlgorithm`` if the algorithm is unknown. Returns a ``dns.dnssecalgs.GenericPrivateKey`` """ prefix: AlgorithmPrefix = None if dnskey.algorithm == Algorithm.PRIVATEDNS: prefix, _ = dns.name.from_wire(dnskey.key, 0) elif dnskey.algorithm == Algorithm.PRIVATEOID: length = int(dnskey.key[0]) prefix = dnskey.key[0 : length + 1] return get_algorithm_cls(dnskey.algorithm, prefix) def register_algorithm_cls( algorithm: Union[int, str], algorithm_cls: Type[GenericPrivateKey], name: Optional[Union[dns.name.Name, str]] = None, oid: Optional[bytes] = None, ) -> None: """Register Algorithm Private Key class. *algorithm*, a ``str`` or ``int`` specifying the DNSKEY algorithm. *algorithm_cls*: A `GenericPrivateKey` class. *name*, an optional ``dns.name.Name`` or ``str``, for for PRIVATEDNS algorithms. *oid*: an optional BER-encoded `bytes` for PRIVATEOID algorithms. Raises ``ValueError`` if a name or oid is specified incorrectly. """ if not issubclass(algorithm_cls, GenericPrivateKey): raise TypeError("Invalid algorithm class") algorithm = Algorithm.make(algorithm) prefix: AlgorithmPrefix = None if algorithm == Algorithm.PRIVATEDNS: if name is None: raise ValueError("Name required for PRIVATEDNS algorithms") if isinstance(name, str): name = dns.name.from_text(name) prefix = name elif algorithm == Algorithm.PRIVATEOID: if oid is None: raise ValueError("OID required for PRIVATEOID algorithms") prefix = bytes([len(oid)]) + oid elif name: raise ValueError("Name only supported for PRIVATEDNS algorithm") elif oid: raise ValueError("OID only supported for PRIVATEOID algorithm") algorithms[(algorithm, prefix)] = algorithm_cls dnspython-2.7.0/dns/dnssecalgs/base.py0000644000000000000000000000472113615410400014670 0ustar00from abc import ABC, abstractmethod # pylint: disable=no-name-in-module from typing import Any, Optional, Type import dns.rdataclass import dns.rdatatype from dns.dnssectypes import Algorithm from dns.exception import AlgorithmKeyMismatch from dns.rdtypes.ANY.DNSKEY import DNSKEY from dns.rdtypes.dnskeybase import Flag class GenericPublicKey(ABC): algorithm: Algorithm @abstractmethod def __init__(self, key: Any) -> None: pass @abstractmethod def verify(self, signature: bytes, data: bytes) -> None: """Verify signed DNSSEC data""" @abstractmethod def encode_key_bytes(self) -> bytes: """Encode key as bytes for DNSKEY""" @classmethod def _ensure_algorithm_key_combination(cls, key: DNSKEY) -> None: if key.algorithm != cls.algorithm: raise AlgorithmKeyMismatch def to_dnskey(self, flags: int = Flag.ZONE, protocol: int = 3) -> DNSKEY: """Return public key as DNSKEY""" return DNSKEY( rdclass=dns.rdataclass.IN, rdtype=dns.rdatatype.DNSKEY, flags=flags, protocol=protocol, algorithm=self.algorithm, key=self.encode_key_bytes(), ) @classmethod @abstractmethod def from_dnskey(cls, key: DNSKEY) -> "GenericPublicKey": """Create public key from DNSKEY""" @classmethod @abstractmethod def from_pem(cls, public_pem: bytes) -> "GenericPublicKey": """Create public key from PEM-encoded SubjectPublicKeyInfo as specified in RFC 5280""" @abstractmethod def to_pem(self) -> bytes: """Return public-key as PEM-encoded SubjectPublicKeyInfo as specified in RFC 5280""" class GenericPrivateKey(ABC): public_cls: Type[GenericPublicKey] @abstractmethod def __init__(self, key: Any) -> None: pass @abstractmethod def sign( self, data: bytes, verify: bool = False, deterministic: bool = True, ) -> bytes: """Sign DNSSEC data""" @abstractmethod def public_key(self) -> "GenericPublicKey": """Return public key instance""" @classmethod @abstractmethod def from_pem( cls, private_pem: bytes, password: Optional[bytes] = None ) -> "GenericPrivateKey": """Create private key from PEM-encoded PKCS#8""" @abstractmethod def to_pem(self, password: Optional[bytes] = None) -> bytes: """Return private key as PEM-encoded PKCS#8""" dnspython-2.7.0/dns/dnssecalgs/cryptography.py0000644000000000000000000000457113615410400016514 0ustar00from typing import Any, Optional, Type from cryptography.hazmat.primitives import serialization from dns.dnssecalgs.base import GenericPrivateKey, GenericPublicKey from dns.exception import AlgorithmKeyMismatch class CryptographyPublicKey(GenericPublicKey): key: Any = None key_cls: Any = None def __init__(self, key: Any) -> None: # pylint: disable=super-init-not-called if self.key_cls is None: raise TypeError("Undefined private key class") if not isinstance( # pylint: disable=isinstance-second-argument-not-valid-type key, self.key_cls ): raise AlgorithmKeyMismatch self.key = key @classmethod def from_pem(cls, public_pem: bytes) -> "GenericPublicKey": key = serialization.load_pem_public_key(public_pem) return cls(key=key) def to_pem(self) -> bytes: return self.key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, ) class CryptographyPrivateKey(GenericPrivateKey): key: Any = None key_cls: Any = None public_cls: Type[CryptographyPublicKey] def __init__(self, key: Any) -> None: # pylint: disable=super-init-not-called if self.key_cls is None: raise TypeError("Undefined private key class") if not isinstance( # pylint: disable=isinstance-second-argument-not-valid-type key, self.key_cls ): raise AlgorithmKeyMismatch self.key = key def public_key(self) -> "CryptographyPublicKey": return self.public_cls(key=self.key.public_key()) @classmethod def from_pem( cls, private_pem: bytes, password: Optional[bytes] = None ) -> "GenericPrivateKey": key = serialization.load_pem_private_key(private_pem, password=password) return cls(key=key) def to_pem(self, password: Optional[bytes] = None) -> bytes: encryption_algorithm: serialization.KeySerializationEncryption if password: encryption_algorithm = serialization.BestAvailableEncryption(password) else: encryption_algorithm = serialization.NoEncryption() return self.key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=encryption_algorithm, ) dnspython-2.7.0/dns/dnssecalgs/dsa.py0000644000000000000000000000675413615410400014535 0ustar00import struct from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import dsa, utils from dns.dnssecalgs.cryptography import CryptographyPrivateKey, CryptographyPublicKey from dns.dnssectypes import Algorithm from dns.rdtypes.ANY.DNSKEY import DNSKEY class PublicDSA(CryptographyPublicKey): key: dsa.DSAPublicKey key_cls = dsa.DSAPublicKey algorithm = Algorithm.DSA chosen_hash = hashes.SHA1() def verify(self, signature: bytes, data: bytes) -> None: sig_r = signature[1:21] sig_s = signature[21:] sig = utils.encode_dss_signature( int.from_bytes(sig_r, "big"), int.from_bytes(sig_s, "big") ) self.key.verify(sig, data, self.chosen_hash) def encode_key_bytes(self) -> bytes: """Encode a public key per RFC 2536, section 2.""" pn = self.key.public_numbers() dsa_t = (self.key.key_size // 8 - 64) // 8 if dsa_t > 8: raise ValueError("unsupported DSA key size") octets = 64 + dsa_t * 8 res = struct.pack("!B", dsa_t) res += pn.parameter_numbers.q.to_bytes(20, "big") res += pn.parameter_numbers.p.to_bytes(octets, "big") res += pn.parameter_numbers.g.to_bytes(octets, "big") res += pn.y.to_bytes(octets, "big") return res @classmethod def from_dnskey(cls, key: DNSKEY) -> "PublicDSA": cls._ensure_algorithm_key_combination(key) keyptr = key.key (t,) = struct.unpack("!B", keyptr[0:1]) keyptr = keyptr[1:] octets = 64 + t * 8 dsa_q = keyptr[0:20] keyptr = keyptr[20:] dsa_p = keyptr[0:octets] keyptr = keyptr[octets:] dsa_g = keyptr[0:octets] keyptr = keyptr[octets:] dsa_y = keyptr[0:octets] return cls( key=dsa.DSAPublicNumbers( # type: ignore int.from_bytes(dsa_y, "big"), dsa.DSAParameterNumbers( int.from_bytes(dsa_p, "big"), int.from_bytes(dsa_q, "big"), int.from_bytes(dsa_g, "big"), ), ).public_key(default_backend()), ) class PrivateDSA(CryptographyPrivateKey): key: dsa.DSAPrivateKey key_cls = dsa.DSAPrivateKey public_cls = PublicDSA def sign( self, data: bytes, verify: bool = False, deterministic: bool = True, ) -> bytes: """Sign using a private key per RFC 2536, section 3.""" public_dsa_key = self.key.public_key() if public_dsa_key.key_size > 1024: raise ValueError("DSA key size overflow") der_signature = self.key.sign(data, self.public_cls.chosen_hash) dsa_r, dsa_s = utils.decode_dss_signature(der_signature) dsa_t = (public_dsa_key.key_size // 8 - 64) // 8 octets = 20 signature = ( struct.pack("!B", dsa_t) + int.to_bytes(dsa_r, length=octets, byteorder="big") + int.to_bytes(dsa_s, length=octets, byteorder="big") ) if verify: self.public_key().verify(signature, data) return signature @classmethod def generate(cls, key_size: int) -> "PrivateDSA": return cls( key=dsa.generate_private_key(key_size=key_size), ) class PublicDSANSEC3SHA1(PublicDSA): algorithm = Algorithm.DSANSEC3SHA1 class PrivateDSANSEC3SHA1(PrivateDSA): public_cls = PublicDSANSEC3SHA1 dnspython-2.7.0/dns/dnssecalgs/ecdsa.py0000644000000000000000000000614413615410400015036 0ustar00from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec, utils from dns.dnssecalgs.cryptography import CryptographyPrivateKey, CryptographyPublicKey from dns.dnssectypes import Algorithm from dns.rdtypes.ANY.DNSKEY import DNSKEY class PublicECDSA(CryptographyPublicKey): key: ec.EllipticCurvePublicKey key_cls = ec.EllipticCurvePublicKey algorithm: Algorithm chosen_hash: hashes.HashAlgorithm curve: ec.EllipticCurve octets: int def verify(self, signature: bytes, data: bytes) -> None: sig_r = signature[0 : self.octets] sig_s = signature[self.octets :] sig = utils.encode_dss_signature( int.from_bytes(sig_r, "big"), int.from_bytes(sig_s, "big") ) self.key.verify(sig, data, ec.ECDSA(self.chosen_hash)) def encode_key_bytes(self) -> bytes: """Encode a public key per RFC 6605, section 4.""" pn = self.key.public_numbers() return pn.x.to_bytes(self.octets, "big") + pn.y.to_bytes(self.octets, "big") @classmethod def from_dnskey(cls, key: DNSKEY) -> "PublicECDSA": cls._ensure_algorithm_key_combination(key) ecdsa_x = key.key[0 : cls.octets] ecdsa_y = key.key[cls.octets : cls.octets * 2] return cls( key=ec.EllipticCurvePublicNumbers( curve=cls.curve, x=int.from_bytes(ecdsa_x, "big"), y=int.from_bytes(ecdsa_y, "big"), ).public_key(default_backend()), ) class PrivateECDSA(CryptographyPrivateKey): key: ec.EllipticCurvePrivateKey key_cls = ec.EllipticCurvePrivateKey public_cls = PublicECDSA def sign( self, data: bytes, verify: bool = False, deterministic: bool = True, ) -> bytes: """Sign using a private key per RFC 6605, section 4.""" algorithm = ec.ECDSA( self.public_cls.chosen_hash, deterministic_signing=deterministic ) der_signature = self.key.sign(data, algorithm) dsa_r, dsa_s = utils.decode_dss_signature(der_signature) signature = int.to_bytes( dsa_r, length=self.public_cls.octets, byteorder="big" ) + int.to_bytes(dsa_s, length=self.public_cls.octets, byteorder="big") if verify: self.public_key().verify(signature, data) return signature @classmethod def generate(cls) -> "PrivateECDSA": return cls( key=ec.generate_private_key( curve=cls.public_cls.curve, backend=default_backend() ), ) class PublicECDSAP256SHA256(PublicECDSA): algorithm = Algorithm.ECDSAP256SHA256 chosen_hash = hashes.SHA256() curve = ec.SECP256R1() octets = 32 class PrivateECDSAP256SHA256(PrivateECDSA): public_cls = PublicECDSAP256SHA256 class PublicECDSAP384SHA384(PublicECDSA): algorithm = Algorithm.ECDSAP384SHA384 chosen_hash = hashes.SHA384() curve = ec.SECP384R1() octets = 48 class PrivateECDSAP384SHA384(PrivateECDSA): public_cls = PublicECDSAP384SHA384 dnspython-2.7.0/dns/dnssecalgs/eddsa.py0000644000000000000000000000367513615410400015045 0ustar00from typing import Type from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ed448, ed25519 from dns.dnssecalgs.cryptography import CryptographyPrivateKey, CryptographyPublicKey from dns.dnssectypes import Algorithm from dns.rdtypes.ANY.DNSKEY import DNSKEY class PublicEDDSA(CryptographyPublicKey): def verify(self, signature: bytes, data: bytes) -> None: self.key.verify(signature, data) def encode_key_bytes(self) -> bytes: """Encode a public key per RFC 8080, section 3.""" return self.key.public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw ) @classmethod def from_dnskey(cls, key: DNSKEY) -> "PublicEDDSA": cls._ensure_algorithm_key_combination(key) return cls( key=cls.key_cls.from_public_bytes(key.key), ) class PrivateEDDSA(CryptographyPrivateKey): public_cls: Type[PublicEDDSA] def sign( self, data: bytes, verify: bool = False, deterministic: bool = True, ) -> bytes: """Sign using a private key per RFC 8080, section 4.""" signature = self.key.sign(data) if verify: self.public_key().verify(signature, data) return signature @classmethod def generate(cls) -> "PrivateEDDSA": return cls(key=cls.key_cls.generate()) class PublicED25519(PublicEDDSA): key: ed25519.Ed25519PublicKey key_cls = ed25519.Ed25519PublicKey algorithm = Algorithm.ED25519 class PrivateED25519(PrivateEDDSA): key: ed25519.Ed25519PrivateKey key_cls = ed25519.Ed25519PrivateKey public_cls = PublicED25519 class PublicED448(PublicEDDSA): key: ed448.Ed448PublicKey key_cls = ed448.Ed448PublicKey algorithm = Algorithm.ED448 class PrivateED448(PrivateEDDSA): key: ed448.Ed448PrivateKey key_cls = ed448.Ed448PrivateKey public_cls = PublicED448 dnspython-2.7.0/dns/dnssecalgs/rsa.py0000644000000000000000000000704613615410400014546 0ustar00import math import struct from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding, rsa from dns.dnssecalgs.cryptography import CryptographyPrivateKey, CryptographyPublicKey from dns.dnssectypes import Algorithm from dns.rdtypes.ANY.DNSKEY import DNSKEY class PublicRSA(CryptographyPublicKey): key: rsa.RSAPublicKey key_cls = rsa.RSAPublicKey algorithm: Algorithm chosen_hash: hashes.HashAlgorithm def verify(self, signature: bytes, data: bytes) -> None: self.key.verify(signature, data, padding.PKCS1v15(), self.chosen_hash) def encode_key_bytes(self) -> bytes: """Encode a public key per RFC 3110, section 2.""" pn = self.key.public_numbers() _exp_len = math.ceil(int.bit_length(pn.e) / 8) exp = int.to_bytes(pn.e, length=_exp_len, byteorder="big") if _exp_len > 255: exp_header = b"\0" + struct.pack("!H", _exp_len) else: exp_header = struct.pack("!B", _exp_len) if pn.n.bit_length() < 512 or pn.n.bit_length() > 4096: raise ValueError("unsupported RSA key length") return exp_header + exp + pn.n.to_bytes((pn.n.bit_length() + 7) // 8, "big") @classmethod def from_dnskey(cls, key: DNSKEY) -> "PublicRSA": cls._ensure_algorithm_key_combination(key) keyptr = key.key (bytes_,) = struct.unpack("!B", keyptr[0:1]) keyptr = keyptr[1:] if bytes_ == 0: (bytes_,) = struct.unpack("!H", keyptr[0:2]) keyptr = keyptr[2:] rsa_e = keyptr[0:bytes_] rsa_n = keyptr[bytes_:] return cls( key=rsa.RSAPublicNumbers( int.from_bytes(rsa_e, "big"), int.from_bytes(rsa_n, "big") ).public_key(default_backend()) ) class PrivateRSA(CryptographyPrivateKey): key: rsa.RSAPrivateKey key_cls = rsa.RSAPrivateKey public_cls = PublicRSA default_public_exponent = 65537 def sign( self, data: bytes, verify: bool = False, deterministic: bool = True, ) -> bytes: """Sign using a private key per RFC 3110, section 3.""" signature = self.key.sign(data, padding.PKCS1v15(), self.public_cls.chosen_hash) if verify: self.public_key().verify(signature, data) return signature @classmethod def generate(cls, key_size: int) -> "PrivateRSA": return cls( key=rsa.generate_private_key( public_exponent=cls.default_public_exponent, key_size=key_size, backend=default_backend(), ) ) class PublicRSAMD5(PublicRSA): algorithm = Algorithm.RSAMD5 chosen_hash = hashes.MD5() class PrivateRSAMD5(PrivateRSA): public_cls = PublicRSAMD5 class PublicRSASHA1(PublicRSA): algorithm = Algorithm.RSASHA1 chosen_hash = hashes.SHA1() class PrivateRSASHA1(PrivateRSA): public_cls = PublicRSASHA1 class PublicRSASHA1NSEC3SHA1(PublicRSA): algorithm = Algorithm.RSASHA1NSEC3SHA1 chosen_hash = hashes.SHA1() class PrivateRSASHA1NSEC3SHA1(PrivateRSA): public_cls = PublicRSASHA1NSEC3SHA1 class PublicRSASHA256(PublicRSA): algorithm = Algorithm.RSASHA256 chosen_hash = hashes.SHA256() class PrivateRSASHA256(PrivateRSA): public_cls = PublicRSASHA256 class PublicRSASHA512(PublicRSA): algorithm = Algorithm.RSASHA512 chosen_hash = hashes.SHA512() class PrivateRSASHA512(PrivateRSA): public_cls = PublicRSASHA512 dnspython-2.7.0/dns/quic/__init__.py0000644000000000000000000000434013615410400014325 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license from typing import List, Tuple import dns._features import dns.asyncbackend if dns._features.have("doq"): import aioquic.quic.configuration # type: ignore from dns._asyncbackend import NullContext from dns.quic._asyncio import ( AsyncioQuicConnection, AsyncioQuicManager, AsyncioQuicStream, ) from dns.quic._common import AsyncQuicConnection, AsyncQuicManager from dns.quic._sync import SyncQuicConnection, SyncQuicManager, SyncQuicStream have_quic = True def null_factory( *args, # pylint: disable=unused-argument **kwargs, # pylint: disable=unused-argument ): return NullContext(None) def _asyncio_manager_factory( context, *args, **kwargs # pylint: disable=unused-argument ): return AsyncioQuicManager(*args, **kwargs) # We have a context factory and a manager factory as for trio we need to have # a nursery. _async_factories = {"asyncio": (null_factory, _asyncio_manager_factory)} if dns._features.have("trio"): import trio from dns.quic._trio import ( # pylint: disable=ungrouped-imports TrioQuicConnection, TrioQuicManager, TrioQuicStream, ) def _trio_context_factory(): return trio.open_nursery() def _trio_manager_factory(context, *args, **kwargs): return TrioQuicManager(context, *args, **kwargs) _async_factories["trio"] = (_trio_context_factory, _trio_manager_factory) def factories_for_backend(backend=None): if backend is None: backend = dns.asyncbackend.get_default_backend() return _async_factories[backend.name()] else: # pragma: no cover have_quic = False from typing import Any class AsyncQuicStream: # type: ignore pass class AsyncQuicConnection: # type: ignore async def make_stream(self) -> Any: raise NotImplementedError class SyncQuicStream: # type: ignore pass class SyncQuicConnection: # type: ignore def make_stream(self) -> Any: raise NotImplementedError Headers = List[Tuple[bytes, bytes]] dnspython-2.7.0/dns/quic/_asyncio.py0000644000000000000000000002321613615410400014375 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import asyncio import socket import ssl import struct import time import aioquic.quic.configuration # type: ignore import aioquic.quic.connection # type: ignore import aioquic.quic.events # type: ignore import dns.asyncbackend import dns.exception import dns.inet from dns.quic._common import ( QUIC_MAX_DATAGRAM, AsyncQuicConnection, AsyncQuicManager, BaseQuicStream, UnexpectedEOF, ) class AsyncioQuicStream(BaseQuicStream): def __init__(self, connection, stream_id): super().__init__(connection, stream_id) self._wake_up = asyncio.Condition() async def _wait_for_wake_up(self): async with self._wake_up: await self._wake_up.wait() async def wait_for(self, amount, expiration): while True: timeout = self._timeout_from_expiration(expiration) if self._buffer.have(amount): return self._expecting = amount try: await asyncio.wait_for(self._wait_for_wake_up(), timeout) except TimeoutError: raise dns.exception.Timeout self._expecting = 0 async def wait_for_end(self, expiration): while True: timeout = self._timeout_from_expiration(expiration) if self._buffer.seen_end(): return try: await asyncio.wait_for(self._wait_for_wake_up(), timeout) except TimeoutError: raise dns.exception.Timeout async def receive(self, timeout=None): expiration = self._expiration_from_timeout(timeout) if self._connection.is_h3(): await self.wait_for_end(expiration) return self._buffer.get_all() else: await self.wait_for(2, expiration) (size,) = struct.unpack("!H", self._buffer.get(2)) await self.wait_for(size, expiration) return self._buffer.get(size) async def send(self, datagram, is_end=False): data = self._encapsulate(datagram) await self._connection.write(self._stream_id, data, is_end) async def _add_input(self, data, is_end): if self._common_add_input(data, is_end): async with self._wake_up: self._wake_up.notify() async def close(self): self._close() # Streams are async context managers async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): await self.close() async with self._wake_up: self._wake_up.notify() return False class AsyncioQuicConnection(AsyncQuicConnection): def __init__(self, connection, address, port, source, source_port, manager=None): super().__init__(connection, address, port, source, source_port, manager) self._socket = None self._handshake_complete = asyncio.Event() self._socket_created = asyncio.Event() self._wake_timer = asyncio.Condition() self._receiver_task = None self._sender_task = None self._wake_pending = False async def _receiver(self): try: af = dns.inet.af_for_address(self._address) backend = dns.asyncbackend.get_backend("asyncio") # Note that peer is a low-level address tuple, but make_socket() wants # a high-level address tuple, so we convert. self._socket = await backend.make_socket( af, socket.SOCK_DGRAM, 0, self._source, (self._peer[0], self._peer[1]) ) self._socket_created.set() async with self._socket: while not self._done: (datagram, address) = await self._socket.recvfrom( QUIC_MAX_DATAGRAM, None ) if address[0] != self._peer[0] or address[1] != self._peer[1]: continue self._connection.receive_datagram(datagram, address, time.time()) # Wake up the timer in case the sender is sleeping, as there may be # stuff to send now. await self._wakeup() except Exception: pass finally: self._done = True await self._wakeup() self._handshake_complete.set() async def _wakeup(self): self._wake_pending = True async with self._wake_timer: self._wake_timer.notify_all() async def _wait_for_wake_timer(self): async with self._wake_timer: if not self._wake_pending: await self._wake_timer.wait() self._wake_pending = False async def _sender(self): await self._socket_created.wait() while not self._done: datagrams = self._connection.datagrams_to_send(time.time()) for datagram, address in datagrams: assert address == self._peer await self._socket.sendto(datagram, self._peer, None) (expiration, interval) = self._get_timer_values() try: await asyncio.wait_for(self._wait_for_wake_timer(), interval) except Exception: pass self._handle_timer(expiration) await self._handle_events() async def _handle_events(self): count = 0 while True: event = self._connection.next_event() if event is None: return if isinstance(event, aioquic.quic.events.StreamDataReceived): if self.is_h3(): h3_events = self._h3_conn.handle_event(event) for h3_event in h3_events: if isinstance(h3_event, aioquic.h3.events.HeadersReceived): stream = self._streams.get(event.stream_id) if stream: if stream._headers is None: stream._headers = h3_event.headers elif stream._trailers is None: stream._trailers = h3_event.headers if h3_event.stream_ended: await stream._add_input(b"", True) elif isinstance(h3_event, aioquic.h3.events.DataReceived): stream = self._streams.get(event.stream_id) if stream: await stream._add_input( h3_event.data, h3_event.stream_ended ) else: stream = self._streams.get(event.stream_id) if stream: await stream._add_input(event.data, event.end_stream) elif isinstance(event, aioquic.quic.events.HandshakeCompleted): self._handshake_complete.set() elif isinstance(event, aioquic.quic.events.ConnectionTerminated): self._done = True self._receiver_task.cancel() elif isinstance(event, aioquic.quic.events.StreamReset): stream = self._streams.get(event.stream_id) if stream: await stream._add_input(b"", True) count += 1 if count > 10: # yield count = 0 await asyncio.sleep(0) async def write(self, stream, data, is_end=False): self._connection.send_stream_data(stream, data, is_end) await self._wakeup() def run(self): if self._closed: return self._receiver_task = asyncio.Task(self._receiver()) self._sender_task = asyncio.Task(self._sender()) async def make_stream(self, timeout=None): try: await asyncio.wait_for(self._handshake_complete.wait(), timeout) except TimeoutError: raise dns.exception.Timeout if self._done: raise UnexpectedEOF stream_id = self._connection.get_next_available_stream_id(False) stream = AsyncioQuicStream(self, stream_id) self._streams[stream_id] = stream return stream async def close(self): if not self._closed: self._manager.closed(self._peer[0], self._peer[1]) self._closed = True self._connection.close() # sender might be blocked on this, so set it self._socket_created.set() await self._wakeup() try: await self._receiver_task except asyncio.CancelledError: pass try: await self._sender_task except asyncio.CancelledError: pass await self._socket.close() class AsyncioQuicManager(AsyncQuicManager): def __init__( self, conf=None, verify_mode=ssl.CERT_REQUIRED, server_name=None, h3=False ): super().__init__(conf, verify_mode, AsyncioQuicConnection, server_name, h3) def connect( self, address, port=853, source=None, source_port=0, want_session_ticket=True ): (connection, start) = self._connect( address, port, source, source_port, want_session_ticket ) if start: connection.run() return connection async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): # Copy the iterator into a list as exiting things will mutate the connections # table. connections = list(self._connections.values()) for connection in connections: await connection.close() return False dnspython-2.7.0/dns/quic/_common.py0000644000000000000000000002515113615410400014220 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import base64 import copy import functools import socket import struct import time import urllib from typing import Any, Optional import aioquic.h3.connection # type: ignore import aioquic.h3.events # type: ignore import aioquic.quic.configuration # type: ignore import aioquic.quic.connection # type: ignore import dns.inet QUIC_MAX_DATAGRAM = 2048 MAX_SESSION_TICKETS = 8 # If we hit the max sessions limit we will delete this many of the oldest connections. # The value must be a integer > 0 and <= MAX_SESSION_TICKETS. SESSIONS_TO_DELETE = MAX_SESSION_TICKETS // 4 class UnexpectedEOF(Exception): pass class Buffer: def __init__(self): self._buffer = b"" self._seen_end = False def put(self, data, is_end): if self._seen_end: return self._buffer += data if is_end: self._seen_end = True def have(self, amount): if len(self._buffer) >= amount: return True if self._seen_end: raise UnexpectedEOF return False def seen_end(self): return self._seen_end def get(self, amount): assert self.have(amount) data = self._buffer[:amount] self._buffer = self._buffer[amount:] return data def get_all(self): assert self.seen_end() data = self._buffer self._buffer = b"" return data class BaseQuicStream: def __init__(self, connection, stream_id): self._connection = connection self._stream_id = stream_id self._buffer = Buffer() self._expecting = 0 self._headers = None self._trailers = None def id(self): return self._stream_id def headers(self): return self._headers def trailers(self): return self._trailers def _expiration_from_timeout(self, timeout): if timeout is not None: expiration = time.time() + timeout else: expiration = None return expiration def _timeout_from_expiration(self, expiration): if expiration is not None: timeout = max(expiration - time.time(), 0.0) else: timeout = None return timeout # Subclass must implement receive() as sync / async and which returns a message # or raises. # Subclass must implement send() as sync / async and which takes a message and # an EOF indicator. def send_h3(self, url, datagram, post=True): if not self._connection.is_h3(): raise SyntaxError("cannot send H3 to a non-H3 connection") url_parts = urllib.parse.urlparse(url) path = url_parts.path.encode() if post: method = b"POST" else: method = b"GET" path += b"?dns=" + base64.urlsafe_b64encode(datagram).rstrip(b"=") headers = [ (b":method", method), (b":scheme", url_parts.scheme.encode()), (b":authority", url_parts.netloc.encode()), (b":path", path), (b"accept", b"application/dns-message"), ] if post: headers.extend( [ (b"content-type", b"application/dns-message"), (b"content-length", str(len(datagram)).encode()), ] ) self._connection.send_headers(self._stream_id, headers, not post) if post: self._connection.send_data(self._stream_id, datagram, True) def _encapsulate(self, datagram): if self._connection.is_h3(): return datagram l = len(datagram) return struct.pack("!H", l) + datagram def _common_add_input(self, data, is_end): self._buffer.put(data, is_end) try: return ( self._expecting > 0 and self._buffer.have(self._expecting) ) or self._buffer.seen_end except UnexpectedEOF: return True def _close(self): self._connection.close_stream(self._stream_id) self._buffer.put(b"", True) # send EOF in case we haven't seen it. class BaseQuicConnection: def __init__( self, connection, address, port, source=None, source_port=0, manager=None, ): self._done = False self._connection = connection self._address = address self._port = port self._closed = False self._manager = manager self._streams = {} if manager.is_h3(): self._h3_conn = aioquic.h3.connection.H3Connection(connection, False) else: self._h3_conn = None self._af = dns.inet.af_for_address(address) self._peer = dns.inet.low_level_address_tuple((address, port)) if source is None and source_port != 0: if self._af == socket.AF_INET: source = "0.0.0.0" elif self._af == socket.AF_INET6: source = "::" else: raise NotImplementedError if source: self._source = (source, source_port) else: self._source = None def is_h3(self): return self._h3_conn is not None def close_stream(self, stream_id): del self._streams[stream_id] def send_headers(self, stream_id, headers, is_end=False): self._h3_conn.send_headers(stream_id, headers, is_end) def send_data(self, stream_id, data, is_end=False): self._h3_conn.send_data(stream_id, data, is_end) def _get_timer_values(self, closed_is_special=True): now = time.time() expiration = self._connection.get_timer() if expiration is None: expiration = now + 3600 # arbitrary "big" value interval = max(expiration - now, 0) if self._closed and closed_is_special: # lower sleep interval to avoid a race in the closing process # which can lead to higher latency closing due to sleeping when # we have events. interval = min(interval, 0.05) return (expiration, interval) def _handle_timer(self, expiration): now = time.time() if expiration <= now: self._connection.handle_timer(now) class AsyncQuicConnection(BaseQuicConnection): async def make_stream(self, timeout: Optional[float] = None) -> Any: pass class BaseQuicManager: def __init__( self, conf, verify_mode, connection_factory, server_name=None, h3=False ): self._connections = {} self._connection_factory = connection_factory self._session_tickets = {} self._tokens = {} self._h3 = h3 if conf is None: verify_path = None if isinstance(verify_mode, str): verify_path = verify_mode verify_mode = True if h3: alpn_protocols = ["h3"] else: alpn_protocols = ["doq", "doq-i03"] conf = aioquic.quic.configuration.QuicConfiguration( alpn_protocols=alpn_protocols, verify_mode=verify_mode, server_name=server_name, ) if verify_path is not None: conf.load_verify_locations(verify_path) self._conf = conf def _connect( self, address, port=853, source=None, source_port=0, want_session_ticket=True, want_token=True, ): connection = self._connections.get((address, port)) if connection is not None: return (connection, False) conf = self._conf if want_session_ticket: try: session_ticket = self._session_tickets.pop((address, port)) # We found a session ticket, so make a configuration that uses it. conf = copy.copy(conf) conf.session_ticket = session_ticket except KeyError: # No session ticket. pass # Whether or not we found a session ticket, we want a handler to save # one. session_ticket_handler = functools.partial( self.save_session_ticket, address, port ) else: session_ticket_handler = None if want_token: try: token = self._tokens.pop((address, port)) # We found a token, so make a configuration that uses it. conf = copy.copy(conf) conf.token = token except KeyError: # No token pass # Whether or not we found a token, we want a handler to save # one. token_handler = functools.partial(self.save_token, address, port) else: token_handler = None qconn = aioquic.quic.connection.QuicConnection( configuration=conf, session_ticket_handler=session_ticket_handler, token_handler=token_handler, ) lladdress = dns.inet.low_level_address_tuple((address, port)) qconn.connect(lladdress, time.time()) connection = self._connection_factory( qconn, address, port, source, source_port, self ) self._connections[(address, port)] = connection return (connection, True) def closed(self, address, port): try: del self._connections[(address, port)] except KeyError: pass def is_h3(self): return self._h3 def save_session_ticket(self, address, port, ticket): # We rely on dictionaries keys() being in insertion order here. We # can't just popitem() as that would be LIFO which is the opposite of # what we want. l = len(self._session_tickets) if l >= MAX_SESSION_TICKETS: keys_to_delete = list(self._session_tickets.keys())[0:SESSIONS_TO_DELETE] for key in keys_to_delete: del self._session_tickets[key] self._session_tickets[(address, port)] = ticket def save_token(self, address, port, token): # We rely on dictionaries keys() being in insertion order here. We # can't just popitem() as that would be LIFO which is the opposite of # what we want. l = len(self._tokens) if l >= MAX_SESSION_TICKETS: keys_to_delete = list(self._tokens.keys())[0:SESSIONS_TO_DELETE] for key in keys_to_delete: del self._tokens[key] self._tokens[(address, port)] = token class AsyncQuicManager(BaseQuicManager): def connect(self, address, port=853, source=None, source_port=0): raise NotImplementedError dnspython-2.7.0/dns/quic/_sync.py0000644000000000000000000002430413615410400013703 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import selectors import socket import ssl import struct import threading import time import aioquic.quic.configuration # type: ignore import aioquic.quic.connection # type: ignore import aioquic.quic.events # type: ignore import dns.exception import dns.inet from dns.quic._common import ( QUIC_MAX_DATAGRAM, BaseQuicConnection, BaseQuicManager, BaseQuicStream, UnexpectedEOF, ) # Function used to create a socket. Can be overridden if needed in special # situations. socket_factory = socket.socket class SyncQuicStream(BaseQuicStream): def __init__(self, connection, stream_id): super().__init__(connection, stream_id) self._wake_up = threading.Condition() self._lock = threading.Lock() def wait_for(self, amount, expiration): while True: timeout = self._timeout_from_expiration(expiration) with self._lock: if self._buffer.have(amount): return self._expecting = amount with self._wake_up: if not self._wake_up.wait(timeout): raise dns.exception.Timeout self._expecting = 0 def wait_for_end(self, expiration): while True: timeout = self._timeout_from_expiration(expiration) with self._lock: if self._buffer.seen_end(): return with self._wake_up: if not self._wake_up.wait(timeout): raise dns.exception.Timeout def receive(self, timeout=None): expiration = self._expiration_from_timeout(timeout) if self._connection.is_h3(): self.wait_for_end(expiration) with self._lock: return self._buffer.get_all() else: self.wait_for(2, expiration) with self._lock: (size,) = struct.unpack("!H", self._buffer.get(2)) self.wait_for(size, expiration) with self._lock: return self._buffer.get(size) def send(self, datagram, is_end=False): data = self._encapsulate(datagram) self._connection.write(self._stream_id, data, is_end) def _add_input(self, data, is_end): if self._common_add_input(data, is_end): with self._wake_up: self._wake_up.notify() def close(self): with self._lock: self._close() def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.close() with self._wake_up: self._wake_up.notify() return False class SyncQuicConnection(BaseQuicConnection): def __init__(self, connection, address, port, source, source_port, manager): super().__init__(connection, address, port, source, source_port, manager) self._socket = socket_factory(self._af, socket.SOCK_DGRAM, 0) if self._source is not None: try: self._socket.bind( dns.inet.low_level_address_tuple(self._source, self._af) ) except Exception: self._socket.close() raise self._socket.connect(self._peer) (self._send_wakeup, self._receive_wakeup) = socket.socketpair() self._receive_wakeup.setblocking(False) self._socket.setblocking(False) self._handshake_complete = threading.Event() self._worker_thread = None self._lock = threading.Lock() def _read(self): count = 0 while count < 10: count += 1 try: datagram = self._socket.recv(QUIC_MAX_DATAGRAM) except BlockingIOError: return with self._lock: self._connection.receive_datagram(datagram, self._peer, time.time()) def _drain_wakeup(self): while True: try: self._receive_wakeup.recv(32) except BlockingIOError: return def _worker(self): try: sel = selectors.DefaultSelector() sel.register(self._socket, selectors.EVENT_READ, self._read) sel.register(self._receive_wakeup, selectors.EVENT_READ, self._drain_wakeup) while not self._done: (expiration, interval) = self._get_timer_values(False) items = sel.select(interval) for key, _ in items: key.data() with self._lock: self._handle_timer(expiration) self._handle_events() with self._lock: datagrams = self._connection.datagrams_to_send(time.time()) for datagram, _ in datagrams: try: self._socket.send(datagram) except BlockingIOError: # we let QUIC handle any lossage pass finally: with self._lock: self._done = True self._socket.close() # Ensure anyone waiting for this gets woken up. self._handshake_complete.set() def _handle_events(self): while True: with self._lock: event = self._connection.next_event() if event is None: return if isinstance(event, aioquic.quic.events.StreamDataReceived): if self.is_h3(): h3_events = self._h3_conn.handle_event(event) for h3_event in h3_events: if isinstance(h3_event, aioquic.h3.events.HeadersReceived): with self._lock: stream = self._streams.get(event.stream_id) if stream: if stream._headers is None: stream._headers = h3_event.headers elif stream._trailers is None: stream._trailers = h3_event.headers if h3_event.stream_ended: stream._add_input(b"", True) elif isinstance(h3_event, aioquic.h3.events.DataReceived): with self._lock: stream = self._streams.get(event.stream_id) if stream: stream._add_input(h3_event.data, h3_event.stream_ended) else: with self._lock: stream = self._streams.get(event.stream_id) if stream: stream._add_input(event.data, event.end_stream) elif isinstance(event, aioquic.quic.events.HandshakeCompleted): self._handshake_complete.set() elif isinstance(event, aioquic.quic.events.ConnectionTerminated): with self._lock: self._done = True elif isinstance(event, aioquic.quic.events.StreamReset): with self._lock: stream = self._streams.get(event.stream_id) if stream: stream._add_input(b"", True) def write(self, stream, data, is_end=False): with self._lock: self._connection.send_stream_data(stream, data, is_end) self._send_wakeup.send(b"\x01") def send_headers(self, stream_id, headers, is_end=False): with self._lock: super().send_headers(stream_id, headers, is_end) if is_end: self._send_wakeup.send(b"\x01") def send_data(self, stream_id, data, is_end=False): with self._lock: super().send_data(stream_id, data, is_end) if is_end: self._send_wakeup.send(b"\x01") def run(self): if self._closed: return self._worker_thread = threading.Thread(target=self._worker) self._worker_thread.start() def make_stream(self, timeout=None): if not self._handshake_complete.wait(timeout): raise dns.exception.Timeout with self._lock: if self._done: raise UnexpectedEOF stream_id = self._connection.get_next_available_stream_id(False) stream = SyncQuicStream(self, stream_id) self._streams[stream_id] = stream return stream def close_stream(self, stream_id): with self._lock: super().close_stream(stream_id) def close(self): with self._lock: if self._closed: return self._manager.closed(self._peer[0], self._peer[1]) self._closed = True self._connection.close() self._send_wakeup.send(b"\x01") self._worker_thread.join() class SyncQuicManager(BaseQuicManager): def __init__( self, conf=None, verify_mode=ssl.CERT_REQUIRED, server_name=None, h3=False ): super().__init__(conf, verify_mode, SyncQuicConnection, server_name, h3) self._lock = threading.Lock() def connect( self, address, port=853, source=None, source_port=0, want_session_ticket=True, want_token=True, ): with self._lock: (connection, start) = self._connect( address, port, source, source_port, want_session_ticket, want_token ) if start: connection.run() return connection def closed(self, address, port): with self._lock: super().closed(address, port) def save_session_ticket(self, address, port, ticket): with self._lock: super().save_session_ticket(address, port, ticket) def save_token(self, address, port, token): with self._lock: super().save_token(address, port, token) def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): # Copy the iterator into a list as exiting things will mutate the connections # table. connections = list(self._connections.values()) for connection in connections: connection.close() return False dnspython-2.7.0/dns/quic/_trio.py0000644000000000000000000002204013615410400013677 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import socket import ssl import struct import time import aioquic.quic.configuration # type: ignore import aioquic.quic.connection # type: ignore import aioquic.quic.events # type: ignore import trio import dns.exception import dns.inet from dns._asyncbackend import NullContext from dns.quic._common import ( QUIC_MAX_DATAGRAM, AsyncQuicConnection, AsyncQuicManager, BaseQuicStream, UnexpectedEOF, ) class TrioQuicStream(BaseQuicStream): def __init__(self, connection, stream_id): super().__init__(connection, stream_id) self._wake_up = trio.Condition() async def wait_for(self, amount): while True: if self._buffer.have(amount): return self._expecting = amount async with self._wake_up: await self._wake_up.wait() self._expecting = 0 async def wait_for_end(self): while True: if self._buffer.seen_end(): return async with self._wake_up: await self._wake_up.wait() async def receive(self, timeout=None): if timeout is None: context = NullContext(None) else: context = trio.move_on_after(timeout) with context: if self._connection.is_h3(): await self.wait_for_end() return self._buffer.get_all() else: await self.wait_for(2) (size,) = struct.unpack("!H", self._buffer.get(2)) await self.wait_for(size) return self._buffer.get(size) raise dns.exception.Timeout async def send(self, datagram, is_end=False): data = self._encapsulate(datagram) await self._connection.write(self._stream_id, data, is_end) async def _add_input(self, data, is_end): if self._common_add_input(data, is_end): async with self._wake_up: self._wake_up.notify() async def close(self): self._close() # Streams are async context managers async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): await self.close() async with self._wake_up: self._wake_up.notify() return False class TrioQuicConnection(AsyncQuicConnection): def __init__(self, connection, address, port, source, source_port, manager=None): super().__init__(connection, address, port, source, source_port, manager) self._socket = trio.socket.socket(self._af, socket.SOCK_DGRAM, 0) self._handshake_complete = trio.Event() self._run_done = trio.Event() self._worker_scope = None self._send_pending = False async def _worker(self): try: if self._source: await self._socket.bind( dns.inet.low_level_address_tuple(self._source, self._af) ) await self._socket.connect(self._peer) while not self._done: (expiration, interval) = self._get_timer_values(False) if self._send_pending: # Do not block forever if sends are pending. Even though we # have a wake-up mechanism if we've already started the blocking # read, the possibility of context switching in send means that # more writes can happen while we have no wake up context, so # we need self._send_pending to avoid (effectively) a "lost wakeup" # race. interval = 0.0 with trio.CancelScope( deadline=trio.current_time() + interval ) as self._worker_scope: datagram = await self._socket.recv(QUIC_MAX_DATAGRAM) self._connection.receive_datagram(datagram, self._peer, time.time()) self._worker_scope = None self._handle_timer(expiration) await self._handle_events() # We clear this now, before sending anything, as sending can cause # context switches that do more sends. We want to know if that # happens so we don't block a long time on the recv() above. self._send_pending = False datagrams = self._connection.datagrams_to_send(time.time()) for datagram, _ in datagrams: await self._socket.send(datagram) finally: self._done = True self._socket.close() self._handshake_complete.set() async def _handle_events(self): count = 0 while True: event = self._connection.next_event() if event is None: return if isinstance(event, aioquic.quic.events.StreamDataReceived): if self.is_h3(): h3_events = self._h3_conn.handle_event(event) for h3_event in h3_events: if isinstance(h3_event, aioquic.h3.events.HeadersReceived): stream = self._streams.get(event.stream_id) if stream: if stream._headers is None: stream._headers = h3_event.headers elif stream._trailers is None: stream._trailers = h3_event.headers if h3_event.stream_ended: await stream._add_input(b"", True) elif isinstance(h3_event, aioquic.h3.events.DataReceived): stream = self._streams.get(event.stream_id) if stream: await stream._add_input( h3_event.data, h3_event.stream_ended ) else: stream = self._streams.get(event.stream_id) if stream: await stream._add_input(event.data, event.end_stream) elif isinstance(event, aioquic.quic.events.HandshakeCompleted): self._handshake_complete.set() elif isinstance(event, aioquic.quic.events.ConnectionTerminated): self._done = True self._socket.close() elif isinstance(event, aioquic.quic.events.StreamReset): stream = self._streams.get(event.stream_id) if stream: await stream._add_input(b"", True) count += 1 if count > 10: # yield count = 0 await trio.sleep(0) async def write(self, stream, data, is_end=False): self._connection.send_stream_data(stream, data, is_end) self._send_pending = True if self._worker_scope is not None: self._worker_scope.cancel() async def run(self): if self._closed: return async with trio.open_nursery() as nursery: nursery.start_soon(self._worker) self._run_done.set() async def make_stream(self, timeout=None): if timeout is None: context = NullContext(None) else: context = trio.move_on_after(timeout) with context: await self._handshake_complete.wait() if self._done: raise UnexpectedEOF stream_id = self._connection.get_next_available_stream_id(False) stream = TrioQuicStream(self, stream_id) self._streams[stream_id] = stream return stream raise dns.exception.Timeout async def close(self): if not self._closed: self._manager.closed(self._peer[0], self._peer[1]) self._closed = True self._connection.close() self._send_pending = True if self._worker_scope is not None: self._worker_scope.cancel() await self._run_done.wait() class TrioQuicManager(AsyncQuicManager): def __init__( self, nursery, conf=None, verify_mode=ssl.CERT_REQUIRED, server_name=None, h3=False, ): super().__init__(conf, verify_mode, TrioQuicConnection, server_name, h3) self._nursery = nursery def connect( self, address, port=853, source=None, source_port=0, want_session_ticket=True ): (connection, start) = self._connect( address, port, source, source_port, want_session_ticket ) if start: self._nursery.start_soon(connection.run) return connection async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): # Copy the iterator into a list as exiting things will mutate the connections # table. connections = list(self._connections.values()) for connection in connections: await connection.close() return False dnspython-2.7.0/dns/rdtypes/__init__.py0000644000000000000000000000206113615410400015054 0ustar00# 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. """DNS rdata type classes""" __all__ = [ "ANY", "IN", "CH", "dnskeybase", "dsbase", "euibase", "mxbase", "nsbase", "svcbbase", "tlsabase", "txtbase", "util", ] dnspython-2.7.0/dns/rdtypes/dnskeybase.py0000644000000000000000000000545013615410400015452 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2004-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 base64 import enum import struct import dns.dnssectypes import dns.exception import dns.immutable import dns.rdata # wildcard import __all__ = ["SEP", "REVOKE", "ZONE"] # noqa: F822 class Flag(enum.IntFlag): SEP = 0x0001 REVOKE = 0x0080 ZONE = 0x0100 @dns.immutable.immutable class DNSKEYBase(dns.rdata.Rdata): """Base class for rdata that is like a DNSKEY record""" __slots__ = ["flags", "protocol", "algorithm", "key"] def __init__(self, rdclass, rdtype, flags, protocol, algorithm, key): super().__init__(rdclass, rdtype) self.flags = Flag(self._as_uint16(flags)) self.protocol = self._as_uint8(protocol) self.algorithm = dns.dnssectypes.Algorithm.make(algorithm) self.key = self._as_bytes(key) def to_text(self, origin=None, relativize=True, **kw): return "%d %d %d %s" % ( self.flags, self.protocol, self.algorithm, dns.rdata._base64ify(self.key, **kw), ) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): flags = tok.get_uint16() protocol = tok.get_uint8() algorithm = tok.get_string() b64 = tok.concatenate_remaining_identifiers().encode() key = base64.b64decode(b64) return cls(rdclass, rdtype, flags, protocol, algorithm, key) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): header = struct.pack("!HBB", self.flags, self.protocol, self.algorithm) file.write(header) file.write(self.key) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): header = parser.get_struct("!HBB") key = parser.get_remaining() return cls(rdclass, rdtype, header[0], header[1], header[2], key) ### BEGIN generated Flag constants SEP = Flag.SEP REVOKE = Flag.REVOKE ZONE = Flag.ZONE ### END generated Flag constants dnspython-2.7.0/dns/rdtypes/dsbase.py0000644000000000000000000000654313615410400014567 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2010, 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 binascii import struct import dns.dnssectypes import dns.immutable import dns.rdata import dns.rdatatype @dns.immutable.immutable class DSBase(dns.rdata.Rdata): """Base class for rdata that is like a DS record""" __slots__ = ["key_tag", "algorithm", "digest_type", "digest"] # Digest types registry: # https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml _digest_length_by_type = { 1: 20, # SHA-1, RFC 3658 Sec. 2.4 2: 32, # SHA-256, RFC 4509 Sec. 2.2 3: 32, # GOST R 34.11-94, RFC 5933 Sec. 4 in conjunction with RFC 4490 Sec. 2.1 4: 48, # SHA-384, RFC 6605 Sec. 2 } def __init__(self, rdclass, rdtype, key_tag, algorithm, digest_type, digest): super().__init__(rdclass, rdtype) self.key_tag = self._as_uint16(key_tag) self.algorithm = dns.dnssectypes.Algorithm.make(algorithm) self.digest_type = dns.dnssectypes.DSDigest.make(self._as_uint8(digest_type)) self.digest = self._as_bytes(digest) try: if len(self.digest) != self._digest_length_by_type[self.digest_type]: raise ValueError("digest length inconsistent with digest type") except KeyError: if self.digest_type == 0: # reserved, RFC 3658 Sec. 2.4 raise ValueError("digest type 0 is reserved") def to_text(self, origin=None, relativize=True, **kw): kw = kw.copy() chunksize = kw.pop("chunksize", 128) return "%d %d %d %s" % ( self.key_tag, self.algorithm, self.digest_type, dns.rdata._hexify(self.digest, chunksize=chunksize, **kw), ) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): key_tag = tok.get_uint16() algorithm = tok.get_string() digest_type = tok.get_uint8() digest = tok.concatenate_remaining_identifiers().encode() digest = binascii.unhexlify(digest) return cls(rdclass, rdtype, key_tag, algorithm, digest_type, digest) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): header = struct.pack("!HBB", self.key_tag, self.algorithm, self.digest_type) file.write(header) file.write(self.digest) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): header = parser.get_struct("!HBB") digest = parser.get_remaining() return cls(rdclass, rdtype, header[0], header[1], header[2], digest) dnspython-2.7.0/dns/rdtypes/euibase.py0000644000000000000000000000507213615410400014737 0ustar00# Copyright (C) 2015 Red Hat, Inc. # Author: Petr Spacek # # 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 RED HAT 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 binascii import dns.immutable import dns.rdata @dns.immutable.immutable class EUIBase(dns.rdata.Rdata): """EUIxx record""" # see: rfc7043.txt __slots__ = ["eui"] # define these in subclasses # byte_len = 6 # 0123456789ab (in hex) # text_len = byte_len * 3 - 1 # 01-23-45-67-89-ab def __init__(self, rdclass, rdtype, eui): super().__init__(rdclass, rdtype) self.eui = self._as_bytes(eui) if len(self.eui) != self.byte_len: raise dns.exception.FormError( f"EUI{self.byte_len * 8} rdata has to have {self.byte_len} bytes" ) def to_text(self, origin=None, relativize=True, **kw): return dns.rdata._hexify(self.eui, chunksize=2, separator=b"-", **kw) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): text = tok.get_string() if len(text) != cls.text_len: raise dns.exception.SyntaxError( f"Input text must have {cls.text_len} characters" ) for i in range(2, cls.byte_len * 3 - 1, 3): if text[i] != "-": raise dns.exception.SyntaxError(f"Dash expected at position {i}") text = text.replace("-", "") try: data = binascii.unhexlify(text.encode()) except (ValueError, TypeError) as ex: raise dns.exception.SyntaxError(f"Hex decoding error: {str(ex)}") return cls(rdclass, rdtype, data) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): file.write(self.eui) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): eui = parser.get_bytes(cls.byte_len) return cls(rdclass, rdtype, eui) dnspython-2.7.0/dns/rdtypes/mxbase.py0000644000000000000000000000617413615410400014605 0ustar00# 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. """MX-like base classes.""" import struct import dns.exception import dns.immutable import dns.name import dns.rdata import dns.rdtypes.util @dns.immutable.immutable class MXBase(dns.rdata.Rdata): """Base class for rdata that is like an MX record.""" __slots__ = ["preference", "exchange"] def __init__(self, rdclass, rdtype, preference, exchange): super().__init__(rdclass, rdtype) self.preference = self._as_uint16(preference) self.exchange = self._as_name(exchange) def to_text(self, origin=None, relativize=True, **kw): exchange = self.exchange.choose_relativity(origin, relativize) return "%d %s" % (self.preference, exchange) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): preference = tok.get_uint16() exchange = tok.get_name(origin, relativize, relativize_to) return cls(rdclass, rdtype, preference, exchange) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): pref = struct.pack("!H", self.preference) file.write(pref) self.exchange.to_wire(file, compress, origin, canonicalize) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): preference = parser.get_uint16() exchange = parser.get_name(origin) return cls(rdclass, rdtype, preference, exchange) def _processing_priority(self): return self.preference @classmethod def _processing_order(cls, iterable): return dns.rdtypes.util.priority_processing_order(iterable) @dns.immutable.immutable class UncompressedMX(MXBase): """Base class for rdata that is like an MX record, but whose name is not compressed when converted to DNS wire format, and whose digestable form is not downcased.""" def _to_wire(self, file, compress=None, origin=None, canonicalize=False): super()._to_wire(file, None, origin, False) @dns.immutable.immutable class UncompressedDowncasingMX(MXBase): """Base class for rdata that is like an MX record, but whose name is not compressed when convert to DNS wire format.""" def _to_wire(self, file, compress=None, origin=None, canonicalize=False): super()._to_wire(file, None, origin, canonicalize) dnspython-2.7.0/dns/rdtypes/nsbase.py0000644000000000000000000000442313615410400014574 0ustar00# 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. """NS-like base classes.""" import dns.exception import dns.immutable import dns.name import dns.rdata @dns.immutable.immutable class NSBase(dns.rdata.Rdata): """Base class for rdata that is like an NS record.""" __slots__ = ["target"] def __init__(self, rdclass, rdtype, target): super().__init__(rdclass, rdtype) self.target = self._as_name(target) def to_text(self, origin=None, relativize=True, **kw): target = self.target.choose_relativity(origin, relativize) return str(target) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): target = tok.get_name(origin, relativize, relativize_to) return cls(rdclass, rdtype, target) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): self.target.to_wire(file, compress, origin, canonicalize) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): target = parser.get_name(origin) return cls(rdclass, rdtype, target) @dns.immutable.immutable class UncompressedNS(NSBase): """Base class for rdata that is like an NS record, but whose name is not compressed when convert to DNS wire format, and whose digestable form is not downcased.""" def _to_wire(self, file, compress=None, origin=None, canonicalize=False): self.target.to_wire(file, None, origin, False) dnspython-2.7.0/dns/rdtypes/svcbbase.py0000644000000000000000000004231413615410400015112 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import base64 import enum import struct import dns.enum import dns.exception import dns.immutable import dns.ipv4 import dns.ipv6 import dns.name import dns.rdata import dns.rdtypes.util import dns.renderer import dns.tokenizer import dns.wire # Until there is an RFC, this module is experimental and may be changed in # incompatible ways. class UnknownParamKey(dns.exception.DNSException): """Unknown SVCB ParamKey""" class ParamKey(dns.enum.IntEnum): """SVCB ParamKey""" MANDATORY = 0 ALPN = 1 NO_DEFAULT_ALPN = 2 PORT = 3 IPV4HINT = 4 ECH = 5 IPV6HINT = 6 DOHPATH = 7 OHTTP = 8 @classmethod def _maximum(cls): return 65535 @classmethod def _short_name(cls): return "SVCBParamKey" @classmethod def _prefix(cls): return "KEY" @classmethod def _unknown_exception_class(cls): return UnknownParamKey class Emptiness(enum.IntEnum): NEVER = 0 ALWAYS = 1 ALLOWED = 2 def _validate_key(key): force_generic = False if isinstance(key, bytes): # We decode to latin-1 so we get 0-255 as valid and do NOT interpret # UTF-8 sequences key = key.decode("latin-1") if isinstance(key, str): if key.lower().startswith("key"): force_generic = True if key[3:].startswith("0") and len(key) != 4: # key has leading zeros raise ValueError("leading zeros in key") key = key.replace("-", "_") return (ParamKey.make(key), force_generic) def key_to_text(key): return ParamKey.to_text(key).replace("_", "-").lower() # Like rdata escapify, but escapes ',' too. _escaped = b'",\\' def _escapify(qstring): text = "" for c in qstring: if c in _escaped: text += "\\" + chr(c) elif c >= 0x20 and c < 0x7F: text += chr(c) else: text += "\\%03d" % c return text def _unescape(value): if value == "": return value unescaped = b"" l = len(value) i = 0 while i < l: c = value[i] i += 1 if c == "\\": if i >= l: # pragma: no cover (can't happen via tokenizer get()) raise dns.exception.UnexpectedEnd c = value[i] i += 1 if c.isdigit(): if i >= l: raise dns.exception.UnexpectedEnd c2 = value[i] i += 1 if i >= l: raise dns.exception.UnexpectedEnd c3 = value[i] i += 1 if not (c2.isdigit() and c3.isdigit()): raise dns.exception.SyntaxError codepoint = int(c) * 100 + int(c2) * 10 + int(c3) if codepoint > 255: raise dns.exception.SyntaxError unescaped += b"%c" % (codepoint) continue unescaped += c.encode() return unescaped def _split(value): l = len(value) i = 0 items = [] unescaped = b"" while i < l: c = value[i] i += 1 if c == ord("\\"): if i >= l: # pragma: no cover (can't happen via tokenizer get()) raise dns.exception.UnexpectedEnd c = value[i] i += 1 unescaped += b"%c" % (c) elif c == ord(","): items.append(unescaped) unescaped = b"" else: unescaped += b"%c" % (c) items.append(unescaped) return items @dns.immutable.immutable class Param: """Abstract base class for SVCB parameters""" @classmethod def emptiness(cls): return Emptiness.NEVER @dns.immutable.immutable class GenericParam(Param): """Generic SVCB parameter""" def __init__(self, value): self.value = dns.rdata.Rdata._as_bytes(value, True) @classmethod def emptiness(cls): return Emptiness.ALLOWED @classmethod def from_value(cls, value): if value is None or len(value) == 0: return None else: return cls(_unescape(value)) def to_text(self): return '"' + dns.rdata._escapify(self.value) + '"' @classmethod def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613 value = parser.get_bytes(parser.remaining()) if len(value) == 0: return None else: return cls(value) def to_wire(self, file, origin=None): # pylint: disable=W0613 file.write(self.value) @dns.immutable.immutable class MandatoryParam(Param): def __init__(self, keys): # check for duplicates keys = sorted([_validate_key(key)[0] for key in keys]) prior_k = None for k in keys: if k == prior_k: raise ValueError(f"duplicate key {k:d}") prior_k = k if k == ParamKey.MANDATORY: raise ValueError("listed the mandatory key as mandatory") self.keys = tuple(keys) @classmethod def from_value(cls, value): keys = [k.encode() for k in value.split(",")] return cls(keys) def to_text(self): return '"' + ",".join([key_to_text(key) for key in self.keys]) + '"' @classmethod def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613 keys = [] last_key = -1 while parser.remaining() > 0: key = parser.get_uint16() if key < last_key: raise dns.exception.FormError("manadatory keys not ascending") last_key = key keys.append(key) return cls(keys) def to_wire(self, file, origin=None): # pylint: disable=W0613 for key in self.keys: file.write(struct.pack("!H", key)) @dns.immutable.immutable class ALPNParam(Param): def __init__(self, ids): self.ids = dns.rdata.Rdata._as_tuple( ids, lambda x: dns.rdata.Rdata._as_bytes(x, True, 255, False) ) @classmethod def from_value(cls, value): return cls(_split(_unescape(value))) def to_text(self): value = ",".join([_escapify(id) for id in self.ids]) return '"' + dns.rdata._escapify(value.encode()) + '"' @classmethod def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613 ids = [] while parser.remaining() > 0: id = parser.get_counted_bytes() ids.append(id) return cls(ids) def to_wire(self, file, origin=None): # pylint: disable=W0613 for id in self.ids: file.write(struct.pack("!B", len(id))) file.write(id) @dns.immutable.immutable class NoDefaultALPNParam(Param): # We don't ever expect to instantiate this class, but we need # a from_value() and a from_wire_parser(), so we just return None # from the class methods when things are OK. @classmethod def emptiness(cls): return Emptiness.ALWAYS @classmethod def from_value(cls, value): if value is None or value == "": return None else: raise ValueError("no-default-alpn with non-empty value") def to_text(self): raise NotImplementedError # pragma: no cover @classmethod def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613 if parser.remaining() != 0: raise dns.exception.FormError return None def to_wire(self, file, origin=None): # pylint: disable=W0613 raise NotImplementedError # pragma: no cover @dns.immutable.immutable class PortParam(Param): def __init__(self, port): self.port = dns.rdata.Rdata._as_uint16(port) @classmethod def from_value(cls, value): value = int(value) return cls(value) def to_text(self): return f'"{self.port}"' @classmethod def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613 port = parser.get_uint16() return cls(port) def to_wire(self, file, origin=None): # pylint: disable=W0613 file.write(struct.pack("!H", self.port)) @dns.immutable.immutable class IPv4HintParam(Param): def __init__(self, addresses): self.addresses = dns.rdata.Rdata._as_tuple( addresses, dns.rdata.Rdata._as_ipv4_address ) @classmethod def from_value(cls, value): addresses = value.split(",") return cls(addresses) def to_text(self): return '"' + ",".join(self.addresses) + '"' @classmethod def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613 addresses = [] while parser.remaining() > 0: ip = parser.get_bytes(4) addresses.append(dns.ipv4.inet_ntoa(ip)) return cls(addresses) def to_wire(self, file, origin=None): # pylint: disable=W0613 for address in self.addresses: file.write(dns.ipv4.inet_aton(address)) @dns.immutable.immutable class IPv6HintParam(Param): def __init__(self, addresses): self.addresses = dns.rdata.Rdata._as_tuple( addresses, dns.rdata.Rdata._as_ipv6_address ) @classmethod def from_value(cls, value): addresses = value.split(",") return cls(addresses) def to_text(self): return '"' + ",".join(self.addresses) + '"' @classmethod def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613 addresses = [] while parser.remaining() > 0: ip = parser.get_bytes(16) addresses.append(dns.ipv6.inet_ntoa(ip)) return cls(addresses) def to_wire(self, file, origin=None): # pylint: disable=W0613 for address in self.addresses: file.write(dns.ipv6.inet_aton(address)) @dns.immutable.immutable class ECHParam(Param): def __init__(self, ech): self.ech = dns.rdata.Rdata._as_bytes(ech, True) @classmethod def from_value(cls, value): if "\\" in value: raise ValueError("escape in ECH value") value = base64.b64decode(value.encode()) return cls(value) def to_text(self): b64 = base64.b64encode(self.ech).decode("ascii") return f'"{b64}"' @classmethod def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613 value = parser.get_bytes(parser.remaining()) return cls(value) def to_wire(self, file, origin=None): # pylint: disable=W0613 file.write(self.ech) @dns.immutable.immutable class OHTTPParam(Param): # We don't ever expect to instantiate this class, but we need # a from_value() and a from_wire_parser(), so we just return None # from the class methods when things are OK. @classmethod def emptiness(cls): return Emptiness.ALWAYS @classmethod def from_value(cls, value): if value is None or value == "": return None else: raise ValueError("ohttp with non-empty value") def to_text(self): raise NotImplementedError # pragma: no cover @classmethod def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613 if parser.remaining() != 0: raise dns.exception.FormError return None def to_wire(self, file, origin=None): # pylint: disable=W0613 raise NotImplementedError # pragma: no cover _class_for_key = { ParamKey.MANDATORY: MandatoryParam, ParamKey.ALPN: ALPNParam, ParamKey.NO_DEFAULT_ALPN: NoDefaultALPNParam, ParamKey.PORT: PortParam, ParamKey.IPV4HINT: IPv4HintParam, ParamKey.ECH: ECHParam, ParamKey.IPV6HINT: IPv6HintParam, ParamKey.OHTTP: OHTTPParam, } def _validate_and_define(params, key, value): (key, force_generic) = _validate_key(_unescape(key)) if key in params: raise SyntaxError(f'duplicate key "{key:d}"') cls = _class_for_key.get(key, GenericParam) emptiness = cls.emptiness() if value is None: if emptiness == Emptiness.NEVER: raise SyntaxError("value cannot be empty") value = cls.from_value(value) else: if force_generic: value = cls.from_wire_parser(dns.wire.Parser(_unescape(value))) else: value = cls.from_value(value) params[key] = value @dns.immutable.immutable class SVCBBase(dns.rdata.Rdata): """Base class for SVCB-like records""" # see: draft-ietf-dnsop-svcb-https-11 __slots__ = ["priority", "target", "params"] def __init__(self, rdclass, rdtype, priority, target, params): super().__init__(rdclass, rdtype) self.priority = self._as_uint16(priority) self.target = self._as_name(target) for k, v in params.items(): k = ParamKey.make(k) if not isinstance(v, Param) and v is not None: raise ValueError(f"{k:d} not a Param") self.params = dns.immutable.Dict(params) # Make sure any parameter listed as mandatory is present in the # record. mandatory = params.get(ParamKey.MANDATORY) if mandatory: for key in mandatory.keys: # Note we have to say "not in" as we have None as a value # so a get() and a not None test would be wrong. if key not in params: raise ValueError(f"key {key:d} declared mandatory but not present") # The no-default-alpn parameter requires the alpn parameter. if ParamKey.NO_DEFAULT_ALPN in params: if ParamKey.ALPN not in params: raise ValueError("no-default-alpn present, but alpn missing") def to_text(self, origin=None, relativize=True, **kw): target = self.target.choose_relativity(origin, relativize) params = [] for key in sorted(self.params.keys()): value = self.params[key] if value is None: params.append(key_to_text(key)) else: kv = key_to_text(key) + "=" + value.to_text() params.append(kv) if len(params) > 0: space = " " else: space = "" return "%d %s%s%s" % (self.priority, target, space, " ".join(params)) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): priority = tok.get_uint16() target = tok.get_name(origin, relativize, relativize_to) if priority == 0: token = tok.get() if not token.is_eol_or_eof(): raise SyntaxError("parameters in AliasMode") tok.unget(token) params = {} while True: token = tok.get() if token.is_eol_or_eof(): tok.unget(token) break if token.ttype != dns.tokenizer.IDENTIFIER: raise SyntaxError("parameter is not an identifier") equals = token.value.find("=") if equals == len(token.value) - 1: # 'key=', so next token should be a quoted string without # any intervening whitespace. key = token.value[:-1] token = tok.get(want_leading=True) if token.ttype != dns.tokenizer.QUOTED_STRING: raise SyntaxError("whitespace after =") value = token.value elif equals > 0: # key=value key = token.value[:equals] value = token.value[equals + 1 :] elif equals == 0: # =key raise SyntaxError('parameter cannot start with "="') else: # key key = token.value value = None _validate_and_define(params, key, value) return cls(rdclass, rdtype, priority, target, params) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): file.write(struct.pack("!H", self.priority)) self.target.to_wire(file, None, origin, False) for key in sorted(self.params): file.write(struct.pack("!H", key)) value = self.params[key] with dns.renderer.prefixed_length(file, 2): # Note that we're still writing a length of zero if the value is None if value is not None: value.to_wire(file, origin) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): priority = parser.get_uint16() target = parser.get_name(origin) if priority == 0 and parser.remaining() != 0: raise dns.exception.FormError("parameters in AliasMode") params = {} prior_key = -1 while parser.remaining() > 0: key = parser.get_uint16() if key < prior_key: raise dns.exception.FormError("keys not in order") prior_key = key vlen = parser.get_uint16() pcls = _class_for_key.get(key, GenericParam) with parser.restrict_to(vlen): value = pcls.from_wire_parser(parser, origin) params[key] = value return cls(rdclass, rdtype, priority, target, params) def _processing_priority(self): return self.priority @classmethod def _processing_order(cls, iterable): return dns.rdtypes.util.priority_processing_order(iterable) dnspython-2.7.0/dns/rdtypes/tlsabase.py0000644000000000000000000000504413615410400015117 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2005-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 binascii import struct import dns.immutable import dns.rdata import dns.rdatatype @dns.immutable.immutable class TLSABase(dns.rdata.Rdata): """Base class for TLSA and SMIMEA records""" # see: RFC 6698 __slots__ = ["usage", "selector", "mtype", "cert"] def __init__(self, rdclass, rdtype, usage, selector, mtype, cert): super().__init__(rdclass, rdtype) self.usage = self._as_uint8(usage) self.selector = self._as_uint8(selector) self.mtype = self._as_uint8(mtype) self.cert = self._as_bytes(cert) def to_text(self, origin=None, relativize=True, **kw): kw = kw.copy() chunksize = kw.pop("chunksize", 128) return "%d %d %d %s" % ( self.usage, self.selector, self.mtype, dns.rdata._hexify(self.cert, chunksize=chunksize, **kw), ) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): usage = tok.get_uint8() selector = tok.get_uint8() mtype = tok.get_uint8() cert = tok.concatenate_remaining_identifiers().encode() cert = binascii.unhexlify(cert) return cls(rdclass, rdtype, usage, selector, mtype, cert) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): header = struct.pack("!BBB", self.usage, self.selector, self.mtype) file.write(header) file.write(self.cert) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): header = parser.get_struct("BBB") cert = parser.get_remaining() return cls(rdclass, rdtype, header[0], header[1], header[2], cert) dnspython-2.7.0/dns/rdtypes/txtbase.py0000644000000000000000000000716013615410400014774 0ustar00# 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 # 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. """TXT-like base class.""" from typing import Any, Dict, Iterable, Optional, Tuple, Union import dns.exception import dns.immutable import dns.rdata import dns.renderer import dns.tokenizer @dns.immutable.immutable class TXTBase(dns.rdata.Rdata): """Base class for rdata that is like a TXT record (see RFC 1035).""" __slots__ = ["strings"] def __init__( self, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, strings: Iterable[Union[bytes, str]], ): """Initialize a TXT-like rdata. *rdclass*, an ``int`` is the rdataclass of the Rdata. *rdtype*, an ``int`` is the rdatatype of the Rdata. *strings*, a tuple of ``bytes`` """ super().__init__(rdclass, rdtype) self.strings: Tuple[bytes] = self._as_tuple( strings, lambda x: self._as_bytes(x, True, 255) ) if len(self.strings) == 0: raise ValueError("the list of strings must not be empty") def to_text( self, origin: Optional[dns.name.Name] = None, relativize: bool = True, **kw: Dict[str, Any], ) -> str: txt = "" prefix = "" for s in self.strings: txt += f'{prefix}"{dns.rdata._escapify(s)}"' prefix = " " return txt @classmethod def from_text( cls, rdclass: dns.rdataclass.RdataClass, rdtype: dns.rdatatype.RdataType, tok: dns.tokenizer.Tokenizer, origin: Optional[dns.name.Name] = None, relativize: bool = True, relativize_to: Optional[dns.name.Name] = None, ) -> dns.rdata.Rdata: strings = [] for token in tok.get_remaining(): token = token.unescape_to_bytes() # The 'if' below is always true in the current code, but we # are leaving this check in in case things change some day. if not ( token.is_quoted_string() or token.is_identifier() ): # pragma: no cover raise dns.exception.SyntaxError("expected a string") if len(token.value) > 255: raise dns.exception.SyntaxError("string too long") strings.append(token.value) if len(strings) == 0: raise dns.exception.UnexpectedEnd return cls(rdclass, rdtype, strings) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): for s in self.strings: with dns.renderer.prefixed_length(file, 1): file.write(s) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): strings = [] while parser.remaining() > 0: s = parser.get_counted_bytes() strings.append(s) return cls(rdclass, rdtype, strings) dnspython-2.7.0/dns/rdtypes/util.py0000644000000000000000000002147113615410400014300 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2006, 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 collections import random import struct from typing import Any, List import dns.exception import dns.ipv4 import dns.ipv6 import dns.name import dns.rdata class Gateway: """A helper class for the IPSECKEY gateway and AMTRELAY relay fields""" name = "" def __init__(self, type, gateway=None): self.type = dns.rdata.Rdata._as_uint8(type) self.gateway = gateway self._check() @classmethod def _invalid_type(cls, gateway_type): return f"invalid {cls.name} type: {gateway_type}" def _check(self): if self.type == 0: if self.gateway not in (".", None): raise SyntaxError(f"invalid {self.name} for type 0") self.gateway = None elif self.type == 1: # check that it's OK dns.ipv4.inet_aton(self.gateway) elif self.type == 2: # check that it's OK dns.ipv6.inet_aton(self.gateway) elif self.type == 3: if not isinstance(self.gateway, dns.name.Name): raise SyntaxError(f"invalid {self.name}; not a name") else: raise SyntaxError(self._invalid_type(self.type)) def to_text(self, origin=None, relativize=True): if self.type == 0: return "." elif self.type in (1, 2): return self.gateway elif self.type == 3: return str(self.gateway.choose_relativity(origin, relativize)) else: raise ValueError(self._invalid_type(self.type)) # pragma: no cover @classmethod def from_text( cls, gateway_type, tok, origin=None, relativize=True, relativize_to=None ): if gateway_type in (0, 1, 2): gateway = tok.get_string() elif gateway_type == 3: gateway = tok.get_name(origin, relativize, relativize_to) else: raise dns.exception.SyntaxError( cls._invalid_type(gateway_type) ) # pragma: no cover return cls(gateway_type, gateway) # pylint: disable=unused-argument def to_wire(self, file, compress=None, origin=None, canonicalize=False): if self.type == 0: pass elif self.type == 1: file.write(dns.ipv4.inet_aton(self.gateway)) elif self.type == 2: file.write(dns.ipv6.inet_aton(self.gateway)) elif self.type == 3: self.gateway.to_wire(file, None, origin, False) else: raise ValueError(self._invalid_type(self.type)) # pragma: no cover # pylint: enable=unused-argument @classmethod def from_wire_parser(cls, gateway_type, parser, origin=None): if gateway_type == 0: gateway = None elif gateway_type == 1: gateway = dns.ipv4.inet_ntoa(parser.get_bytes(4)) elif gateway_type == 2: gateway = dns.ipv6.inet_ntoa(parser.get_bytes(16)) elif gateway_type == 3: gateway = parser.get_name(origin) else: raise dns.exception.FormError(cls._invalid_type(gateway_type)) return cls(gateway_type, gateway) class Bitmap: """A helper class for the NSEC/NSEC3/CSYNC type bitmaps""" type_name = "" def __init__(self, windows=None): last_window = -1 self.windows = windows for window, bitmap in self.windows: if not isinstance(window, int): raise ValueError(f"bad {self.type_name} window type") if window <= last_window: raise ValueError(f"bad {self.type_name} window order") if window > 256: raise ValueError(f"bad {self.type_name} window number") last_window = window if not isinstance(bitmap, bytes): raise ValueError(f"bad {self.type_name} octets type") if len(bitmap) == 0 or len(bitmap) > 32: raise ValueError(f"bad {self.type_name} octets") def to_text(self) -> str: text = "" for window, bitmap in self.windows: bits = [] for i, byte in enumerate(bitmap): for j in range(0, 8): if byte & (0x80 >> j): rdtype = window * 256 + i * 8 + j bits.append(dns.rdatatype.to_text(rdtype)) text += " " + " ".join(bits) return text @classmethod def from_text(cls, tok: "dns.tokenizer.Tokenizer") -> "Bitmap": rdtypes = [] for token in tok.get_remaining(): rdtype = dns.rdatatype.from_text(token.unescape().value) if rdtype == 0: raise dns.exception.SyntaxError(f"{cls.type_name} with bit 0") rdtypes.append(rdtype) return cls.from_rdtypes(rdtypes) @classmethod def from_rdtypes(cls, rdtypes: List[dns.rdatatype.RdataType]) -> "Bitmap": rdtypes = sorted(rdtypes) window = 0 octets = 0 prior_rdtype = 0 bitmap = bytearray(b"\0" * 32) windows = [] for rdtype in rdtypes: if rdtype == prior_rdtype: continue prior_rdtype = rdtype new_window = rdtype // 256 if new_window != window: if octets != 0: windows.append((window, bytes(bitmap[0:octets]))) bitmap = bytearray(b"\0" * 32) window = new_window offset = rdtype % 256 byte = offset // 8 bit = offset % 8 octets = byte + 1 bitmap[byte] = bitmap[byte] | (0x80 >> bit) if octets != 0: windows.append((window, bytes(bitmap[0:octets]))) return cls(windows) def to_wire(self, file: Any) -> None: for window, bitmap in self.windows: file.write(struct.pack("!BB", window, len(bitmap))) file.write(bitmap) @classmethod def from_wire_parser(cls, parser: "dns.wire.Parser") -> "Bitmap": windows = [] while parser.remaining() > 0: window = parser.get_uint8() bitmap = parser.get_counted_bytes() windows.append((window, bitmap)) return cls(windows) def _priority_table(items): by_priority = collections.defaultdict(list) for rdata in items: by_priority[rdata._processing_priority()].append(rdata) return by_priority def priority_processing_order(iterable): items = list(iterable) if len(items) == 1: return items by_priority = _priority_table(items) ordered = [] for k in sorted(by_priority.keys()): rdatas = by_priority[k] random.shuffle(rdatas) ordered.extend(rdatas) return ordered _no_weight = 0.1 def weighted_processing_order(iterable): items = list(iterable) if len(items) == 1: return items by_priority = _priority_table(items) ordered = [] for k in sorted(by_priority.keys()): rdatas = by_priority[k] total = sum(rdata._processing_weight() or _no_weight for rdata in rdatas) while len(rdatas) > 1: r = random.uniform(0, total) for n, rdata in enumerate(rdatas): # noqa: B007 weight = rdata._processing_weight() or _no_weight if weight > r: break r -= weight total -= weight ordered.append(rdata) # pylint: disable=undefined-loop-variable del rdatas[n] # pylint: disable=undefined-loop-variable ordered.append(rdatas[0]) return ordered def parse_formatted_hex(formatted, num_chunks, chunk_size, separator): if len(formatted) != num_chunks * (chunk_size + 1) - 1: raise ValueError("invalid formatted hex string") value = b"" for _ in range(num_chunks): chunk = formatted[0:chunk_size] value += int(chunk, 16).to_bytes(chunk_size // 2, "big") formatted = formatted[chunk_size:] if len(formatted) > 0 and formatted[0] != separator: raise ValueError("invalid formatted hex string") formatted = formatted[1:] return value dnspython-2.7.0/dns/rdtypes/ANY/AFSDB.py0000644000000000000000000000317513615410400014572 0ustar00# 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.immutable import dns.rdtypes.mxbase @dns.immutable.immutable class AFSDB(dns.rdtypes.mxbase.UncompressedDowncasingMX): """AFSDB record""" # Use the property mechanism to make "subtype" an alias for the # "preference" attribute, and "hostname" an alias for the "exchange" # attribute. # # This lets us inherit the UncompressedMX implementation but lets # the caller use appropriate attribute names for the rdata type. # # We probably lose some performance vs. a cut-and-paste # implementation, but this way we don't copy code, and that's # good. @property def subtype(self): "the AFSDB subtype" return self.preference @property def hostname(self): "the AFSDB hostname" return self.exchange dnspython-2.7.0/dns/rdtypes/ANY/AMTRELAY.py0000644000000000000000000000646513615410400015176 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2006, 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 struct import dns.exception import dns.immutable import dns.rdtypes.util class Relay(dns.rdtypes.util.Gateway): name = "AMTRELAY relay" @property def relay(self): return self.gateway @dns.immutable.immutable class AMTRELAY(dns.rdata.Rdata): """AMTRELAY record""" # see: RFC 8777 __slots__ = ["precedence", "discovery_optional", "relay_type", "relay"] def __init__( self, rdclass, rdtype, precedence, discovery_optional, relay_type, relay ): super().__init__(rdclass, rdtype) relay = Relay(relay_type, relay) self.precedence = self._as_uint8(precedence) self.discovery_optional = self._as_bool(discovery_optional) self.relay_type = relay.type self.relay = relay.relay def to_text(self, origin=None, relativize=True, **kw): relay = Relay(self.relay_type, self.relay).to_text(origin, relativize) return "%d %d %d %s" % ( self.precedence, self.discovery_optional, self.relay_type, relay, ) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): precedence = tok.get_uint8() discovery_optional = tok.get_uint8() if discovery_optional > 1: raise dns.exception.SyntaxError("expecting 0 or 1") discovery_optional = bool(discovery_optional) relay_type = tok.get_uint8() if relay_type > 0x7F: raise dns.exception.SyntaxError("expecting an integer <= 127") relay = Relay.from_text(relay_type, tok, origin, relativize, relativize_to) return cls( rdclass, rdtype, precedence, discovery_optional, relay_type, relay.relay ) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): relay_type = self.relay_type | (self.discovery_optional << 7) header = struct.pack("!BB", self.precedence, relay_type) file.write(header) Relay(self.relay_type, self.relay).to_wire(file, compress, origin, canonicalize) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): (precedence, relay_type) = parser.get_struct("!BB") discovery_optional = bool(relay_type >> 7) relay_type &= 0x7F relay = Relay.from_wire_parser(relay_type, parser, origin) return cls( rdclass, rdtype, precedence, discovery_optional, relay_type, relay.relay ) dnspython-2.7.0/dns/rdtypes/ANY/AVC.py0000644000000000000000000000200013615410400014346 0ustar00# 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 dns.immutable import dns.rdtypes.txtbase @dns.immutable.immutable class AVC(dns.rdtypes.txtbase.TXTBase): """AVC record""" # See: IANA dns parameters for AVC dnspython-2.7.0/dns/rdtypes/ANY/CAA.py0000644000000000000000000000471713615410400014342 0ustar00# 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 struct import dns.exception import dns.immutable import dns.rdata import dns.tokenizer @dns.immutable.immutable class CAA(dns.rdata.Rdata): """CAA (Certification Authority Authorization) record""" # see: RFC 6844 __slots__ = ["flags", "tag", "value"] def __init__(self, rdclass, rdtype, flags, tag, value): super().__init__(rdclass, rdtype) self.flags = self._as_uint8(flags) self.tag = self._as_bytes(tag, True, 255) if not tag.isalnum(): raise ValueError("tag is not alphanumeric") self.value = self._as_bytes(value) def to_text(self, origin=None, relativize=True, **kw): return '%u %s "%s"' % ( self.flags, dns.rdata._escapify(self.tag), dns.rdata._escapify(self.value), ) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): flags = tok.get_uint8() tag = tok.get_string().encode() value = tok.get_string().encode() return cls(rdclass, rdtype, flags, tag, value) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): file.write(struct.pack("!B", self.flags)) l = len(self.tag) assert l < 256 file.write(struct.pack("!B", l)) file.write(self.tag) file.write(self.value) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): flags = parser.get_uint8() tag = parser.get_counted_bytes() value = parser.get_remaining() return cls(rdclass, rdtype, flags, tag, value) dnspython-2.7.0/dns/rdtypes/ANY/CDNSKEY.py0000644000000000000000000000231113615410400015042 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2004-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.immutable import dns.rdtypes.dnskeybase # lgtm[py/import-and-import-from] # pylint: disable=unused-import from dns.rdtypes.dnskeybase import ( # noqa: F401 lgtm[py/unused-import] REVOKE, SEP, ZONE, ) # pylint: enable=unused-import @dns.immutable.immutable class CDNSKEY(dns.rdtypes.dnskeybase.DNSKEYBase): """CDNSKEY record""" dnspython-2.7.0/dns/rdtypes/ANY/CDS.py0000644000000000000000000000221313615410400014354 0ustar00# 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.immutable import dns.rdtypes.dsbase @dns.immutable.immutable class CDS(dns.rdtypes.dsbase.DSBase): """CDS record""" _digest_length_by_type = { **dns.rdtypes.dsbase.DSBase._digest_length_by_type, 0: 1, # delete, RFC 8078 Sec. 4 (including Errata ID 5049) } dnspython-2.7.0/dns/rdtypes/ANY/CERT.py0000644000000000000000000000671513615410400014513 0ustar00# 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 base64 import struct import dns.dnssectypes import dns.exception import dns.immutable import dns.rdata import dns.tokenizer _ctype_by_value = { 1: "PKIX", 2: "SPKI", 3: "PGP", 4: "IPKIX", 5: "ISPKI", 6: "IPGP", 7: "ACPKIX", 8: "IACPKIX", 253: "URI", 254: "OID", } _ctype_by_name = { "PKIX": 1, "SPKI": 2, "PGP": 3, "IPKIX": 4, "ISPKI": 5, "IPGP": 6, "ACPKIX": 7, "IACPKIX": 8, "URI": 253, "OID": 254, } def _ctype_from_text(what): v = _ctype_by_name.get(what) if v is not None: return v return int(what) def _ctype_to_text(what): v = _ctype_by_value.get(what) if v is not None: return v return str(what) @dns.immutable.immutable class CERT(dns.rdata.Rdata): """CERT record""" # see RFC 4398 __slots__ = ["certificate_type", "key_tag", "algorithm", "certificate"] def __init__( self, rdclass, rdtype, certificate_type, key_tag, algorithm, certificate ): super().__init__(rdclass, rdtype) self.certificate_type = self._as_uint16(certificate_type) self.key_tag = self._as_uint16(key_tag) self.algorithm = self._as_uint8(algorithm) self.certificate = self._as_bytes(certificate) def to_text(self, origin=None, relativize=True, **kw): certificate_type = _ctype_to_text(self.certificate_type) return "%s %d %s %s" % ( certificate_type, self.key_tag, dns.dnssectypes.Algorithm.to_text(self.algorithm), dns.rdata._base64ify(self.certificate, **kw), ) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): certificate_type = _ctype_from_text(tok.get_string()) key_tag = tok.get_uint16() algorithm = dns.dnssectypes.Algorithm.from_text(tok.get_string()) b64 = tok.concatenate_remaining_identifiers().encode() certificate = base64.b64decode(b64) return cls(rdclass, rdtype, certificate_type, key_tag, algorithm, certificate) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): prefix = struct.pack( "!HHB", self.certificate_type, self.key_tag, self.algorithm ) file.write(prefix) file.write(self.certificate) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): (certificate_type, key_tag, algorithm) = parser.get_struct("!HHB") certificate = parser.get_remaining() return cls(rdclass, rdtype, certificate_type, key_tag, algorithm, certificate) dnspython-2.7.0/dns/rdtypes/ANY/CNAME.py0000644000000000000000000000226613615410400014576 0ustar00# 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.immutable import dns.rdtypes.nsbase @dns.immutable.immutable class CNAME(dns.rdtypes.nsbase.NSBase): """CNAME record Note: although CNAME is officially a singleton type, dnspython allows non-singleton CNAME rdatasets because such sets have been commonly used by BIND and other nameservers for load balancing.""" dnspython-2.7.0/dns/rdtypes/ANY/CSYNC.py0000644000000000000000000000460713615410400014633 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2004-2007, 2009-2011, 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 struct import dns.exception import dns.immutable import dns.name import dns.rdata import dns.rdatatype import dns.rdtypes.util @dns.immutable.immutable class Bitmap(dns.rdtypes.util.Bitmap): type_name = "CSYNC" @dns.immutable.immutable class CSYNC(dns.rdata.Rdata): """CSYNC record""" __slots__ = ["serial", "flags", "windows"] def __init__(self, rdclass, rdtype, serial, flags, windows): super().__init__(rdclass, rdtype) self.serial = self._as_uint32(serial) self.flags = self._as_uint16(flags) if not isinstance(windows, Bitmap): windows = Bitmap(windows) self.windows = tuple(windows.windows) def to_text(self, origin=None, relativize=True, **kw): text = Bitmap(self.windows).to_text() return "%d %d%s" % (self.serial, self.flags, text) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): serial = tok.get_uint32() flags = tok.get_uint16() bitmap = Bitmap.from_text(tok) return cls(rdclass, rdtype, serial, flags, bitmap) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): file.write(struct.pack("!IH", self.serial, self.flags)) Bitmap(self.windows).to_wire(file) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): (serial, flags) = parser.get_struct("!IH") bitmap = Bitmap.from_wire_parser(parser) return cls(rdclass, rdtype, serial, flags, bitmap) dnspython-2.7.0/dns/rdtypes/ANY/DLV.py0000644000000000000000000000173213615410400014375 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 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.immutable import dns.rdtypes.dsbase @dns.immutable.immutable class DLV(dns.rdtypes.dsbase.DSBase): """DLV record""" dnspython-2.7.0/dns/rdtypes/ANY/DNAME.py0000644000000000000000000000217613615410400014577 0ustar00# 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.immutable import dns.rdtypes.nsbase @dns.immutable.immutable class DNAME(dns.rdtypes.nsbase.UncompressedNS): """DNAME record""" def _to_wire(self, file, compress=None, origin=None, canonicalize=False): self.target.to_wire(file, None, origin, canonicalize) dnspython-2.7.0/dns/rdtypes/ANY/DNSKEY.py0000644000000000000000000000230713615410400014744 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2004-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.immutable import dns.rdtypes.dnskeybase # lgtm[py/import-and-import-from] # pylint: disable=unused-import from dns.rdtypes.dnskeybase import ( # noqa: F401 lgtm[py/unused-import] REVOKE, SEP, ZONE, ) # pylint: enable=unused-import @dns.immutable.immutable class DNSKEY(dns.rdtypes.dnskeybase.DNSKEYBase): """DNSKEY record""" dnspython-2.7.0/dns/rdtypes/ANY/DS.py0000644000000000000000000000174313615410400014260 0ustar00# 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.immutable import dns.rdtypes.dsbase @dns.immutable.immutable class DS(dns.rdtypes.dsbase.DSBase): """DS record""" dnspython-2.7.0/dns/rdtypes/ANY/EUI48.py0000644000000000000000000000217713615410400014552 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2015 Red Hat, Inc. # Author: Petr Spacek # # 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 RED HAT 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.immutable import dns.rdtypes.euibase @dns.immutable.immutable class EUI48(dns.rdtypes.euibase.EUIBase): """EUI48 record""" # see: rfc7043.txt byte_len = 6 # 0123456789ab (in hex) text_len = byte_len * 3 - 1 # 01-23-45-67-89-ab dnspython-2.7.0/dns/rdtypes/ANY/EUI64.py0000644000000000000000000000221113615410400014535 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2015 Red Hat, Inc. # Author: Petr Spacek # # 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 RED HAT 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.immutable import dns.rdtypes.euibase @dns.immutable.immutable class EUI64(dns.rdtypes.euibase.EUIBase): """EUI64 record""" # see: rfc7043.txt byte_len = 8 # 0123456789abcdef (in hex) text_len = byte_len * 3 - 1 # 01-23-45-67-89-ab-cd-ef dnspython-2.7.0/dns/rdtypes/ANY/GPOS.py0000644000000000000000000001052713615410400014522 0ustar00# 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 struct import dns.exception import dns.immutable import dns.rdata import dns.tokenizer def _validate_float_string(what): if len(what) == 0: raise dns.exception.FormError if what[0] == b"-"[0] or what[0] == b"+"[0]: what = what[1:] if what.isdigit(): return try: (left, right) = what.split(b".") except ValueError: raise dns.exception.FormError if left == b"" and right == b"": raise dns.exception.FormError if not left == b"" and not left.decode().isdigit(): raise dns.exception.FormError if not right == b"" and not right.decode().isdigit(): raise dns.exception.FormError @dns.immutable.immutable class GPOS(dns.rdata.Rdata): """GPOS record""" # see: RFC 1712 __slots__ = ["latitude", "longitude", "altitude"] def __init__(self, rdclass, rdtype, latitude, longitude, altitude): super().__init__(rdclass, rdtype) if isinstance(latitude, float) or isinstance(latitude, int): latitude = str(latitude) if isinstance(longitude, float) or isinstance(longitude, int): longitude = str(longitude) if isinstance(altitude, float) or isinstance(altitude, int): altitude = str(altitude) latitude = self._as_bytes(latitude, True, 255) longitude = self._as_bytes(longitude, True, 255) altitude = self._as_bytes(altitude, True, 255) _validate_float_string(latitude) _validate_float_string(longitude) _validate_float_string(altitude) self.latitude = latitude self.longitude = longitude self.altitude = altitude flat = self.float_latitude if flat < -90.0 or flat > 90.0: raise dns.exception.FormError("bad latitude") flong = self.float_longitude if flong < -180.0 or flong > 180.0: raise dns.exception.FormError("bad longitude") def to_text(self, origin=None, relativize=True, **kw): return ( f"{self.latitude.decode()} {self.longitude.decode()} " f"{self.altitude.decode()}" ) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): latitude = tok.get_string() longitude = tok.get_string() altitude = tok.get_string() return cls(rdclass, rdtype, latitude, longitude, altitude) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): l = len(self.latitude) assert l < 256 file.write(struct.pack("!B", l)) file.write(self.latitude) l = len(self.longitude) assert l < 256 file.write(struct.pack("!B", l)) file.write(self.longitude) l = len(self.altitude) assert l < 256 file.write(struct.pack("!B", l)) file.write(self.altitude) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): latitude = parser.get_counted_bytes() longitude = parser.get_counted_bytes() altitude = parser.get_counted_bytes() return cls(rdclass, rdtype, latitude, longitude, altitude) @property def float_latitude(self): "latitude as a floating point value" return float(self.latitude) @property def float_longitude(self): "longitude as a floating point value" return float(self.longitude) @property def float_altitude(self): "altitude as a floating point value" return float(self.altitude) dnspython-2.7.0/dns/rdtypes/ANY/HINFO.py0000644000000000000000000000425113615410400014612 0ustar00# 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 struct import dns.exception import dns.immutable import dns.rdata import dns.tokenizer @dns.immutable.immutable class HINFO(dns.rdata.Rdata): """HINFO record""" # see: RFC 1035 __slots__ = ["cpu", "os"] def __init__(self, rdclass, rdtype, cpu, os): super().__init__(rdclass, rdtype) self.cpu = self._as_bytes(cpu, True, 255) self.os = self._as_bytes(os, True, 255) def to_text(self, origin=None, relativize=True, **kw): return f'"{dns.rdata._escapify(self.cpu)}" "{dns.rdata._escapify(self.os)}"' @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): cpu = tok.get_string(max_length=255) os = tok.get_string(max_length=255) return cls(rdclass, rdtype, cpu, os) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): l = len(self.cpu) assert l < 256 file.write(struct.pack("!B", l)) file.write(self.cpu) l = len(self.os) assert l < 256 file.write(struct.pack("!B", l)) file.write(self.os) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): cpu = parser.get_counted_bytes() os = parser.get_counted_bytes() return cls(rdclass, rdtype, cpu, os) dnspython-2.7.0/dns/rdtypes/ANY/HIP.py0000644000000000000000000000623213615410400014370 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2010, 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 base64 import binascii import struct import dns.exception import dns.immutable import dns.rdata import dns.rdatatype @dns.immutable.immutable class HIP(dns.rdata.Rdata): """HIP record""" # see: RFC 5205 __slots__ = ["hit", "algorithm", "key", "servers"] def __init__(self, rdclass, rdtype, hit, algorithm, key, servers): super().__init__(rdclass, rdtype) self.hit = self._as_bytes(hit, True, 255) self.algorithm = self._as_uint8(algorithm) self.key = self._as_bytes(key, True) self.servers = self._as_tuple(servers, self._as_name) def to_text(self, origin=None, relativize=True, **kw): hit = binascii.hexlify(self.hit).decode() key = base64.b64encode(self.key).replace(b"\n", b"").decode() text = "" servers = [] for server in self.servers: servers.append(server.choose_relativity(origin, relativize)) if len(servers) > 0: text += " " + " ".join(x.to_unicode() for x in servers) return "%u %s %s%s" % (self.algorithm, hit, key, text) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): algorithm = tok.get_uint8() hit = binascii.unhexlify(tok.get_string().encode()) key = base64.b64decode(tok.get_string().encode()) servers = [] for token in tok.get_remaining(): server = tok.as_name(token, origin, relativize, relativize_to) servers.append(server) return cls(rdclass, rdtype, hit, algorithm, key, servers) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): lh = len(self.hit) lk = len(self.key) file.write(struct.pack("!BBH", lh, self.algorithm, lk)) file.write(self.hit) file.write(self.key) for server in self.servers: server.to_wire(file, None, origin, False) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): (lh, algorithm, lk) = parser.get_struct("!BBH") hit = parser.get_bytes(lh) key = parser.get_bytes(lk) servers = [] while parser.remaining() > 0: server = parser.get_name(origin) servers.append(server) return cls(rdclass, rdtype, hit, algorithm, key, servers) dnspython-2.7.0/dns/rdtypes/ANY/ISDN.py0000644000000000000000000000524313615410400014506 0ustar00# 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 struct import dns.exception import dns.immutable import dns.rdata import dns.tokenizer @dns.immutable.immutable class ISDN(dns.rdata.Rdata): """ISDN record""" # see: RFC 1183 __slots__ = ["address", "subaddress"] def __init__(self, rdclass, rdtype, address, subaddress): super().__init__(rdclass, rdtype) self.address = self._as_bytes(address, True, 255) self.subaddress = self._as_bytes(subaddress, True, 255) def to_text(self, origin=None, relativize=True, **kw): if self.subaddress: return ( f'"{dns.rdata._escapify(self.address)}" ' f'"{dns.rdata._escapify(self.subaddress)}"' ) else: return f'"{dns.rdata._escapify(self.address)}"' @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): address = tok.get_string() tokens = tok.get_remaining(max_tokens=1) if len(tokens) >= 1: subaddress = tokens[0].unescape().value else: subaddress = "" return cls(rdclass, rdtype, address, subaddress) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): l = len(self.address) assert l < 256 file.write(struct.pack("!B", l)) file.write(self.address) l = len(self.subaddress) if l > 0: assert l < 256 file.write(struct.pack("!B", l)) file.write(self.subaddress) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): address = parser.get_counted_bytes() if parser.remaining() > 0: subaddress = parser.get_counted_bytes() else: subaddress = b"" return cls(rdclass, rdtype, address, subaddress) dnspython-2.7.0/dns/rdtypes/ANY/L32.py0000644000000000000000000000240613615410400014307 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import struct import dns.immutable import dns.rdata @dns.immutable.immutable class L32(dns.rdata.Rdata): """L32 record""" # see: rfc6742.txt __slots__ = ["preference", "locator32"] def __init__(self, rdclass, rdtype, preference, locator32): super().__init__(rdclass, rdtype) self.preference = self._as_uint16(preference) self.locator32 = self._as_ipv4_address(locator32) def to_text(self, origin=None, relativize=True, **kw): return f"{self.preference} {self.locator32}" @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): preference = tok.get_uint16() nodeid = tok.get_identifier() return cls(rdclass, rdtype, preference, nodeid) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): file.write(struct.pack("!H", self.preference)) file.write(dns.ipv4.inet_aton(self.locator32)) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): preference = parser.get_uint16() locator32 = parser.get_remaining() return cls(rdclass, rdtype, preference, locator32) dnspython-2.7.0/dns/rdtypes/ANY/L64.py0000644000000000000000000000307013615410400014312 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import struct import dns.immutable import dns.rdtypes.util @dns.immutable.immutable class L64(dns.rdata.Rdata): """L64 record""" # see: rfc6742.txt __slots__ = ["preference", "locator64"] def __init__(self, rdclass, rdtype, preference, locator64): super().__init__(rdclass, rdtype) self.preference = self._as_uint16(preference) if isinstance(locator64, bytes): if len(locator64) != 8: raise ValueError("invalid locator64") self.locator64 = dns.rdata._hexify(locator64, 4, b":") else: dns.rdtypes.util.parse_formatted_hex(locator64, 4, 4, ":") self.locator64 = locator64 def to_text(self, origin=None, relativize=True, **kw): return f"{self.preference} {self.locator64}" @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): preference = tok.get_uint16() locator64 = tok.get_identifier() return cls(rdclass, rdtype, preference, locator64) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): file.write(struct.pack("!H", self.preference)) file.write(dns.rdtypes.util.parse_formatted_hex(self.locator64, 4, 4, ":")) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): preference = parser.get_uint16() locator64 = parser.get_remaining() return cls(rdclass, rdtype, preference, locator64) dnspython-2.7.0/dns/rdtypes/ANY/LOC.py0000644000000000000000000002733313615410400014372 0ustar00# 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 struct import dns.exception import dns.immutable import dns.rdata _pows = tuple(10**i for i in range(0, 11)) # default values are in centimeters _default_size = 100.0 _default_hprec = 1000000.0 _default_vprec = 1000.0 # for use by from_wire() _MAX_LATITUDE = 0x80000000 + 90 * 3600000 _MIN_LATITUDE = 0x80000000 - 90 * 3600000 _MAX_LONGITUDE = 0x80000000 + 180 * 3600000 _MIN_LONGITUDE = 0x80000000 - 180 * 3600000 def _exponent_of(what, desc): if what == 0: return 0 exp = None for i, pow in enumerate(_pows): if what < pow: exp = i - 1 break if exp is None or exp < 0: raise dns.exception.SyntaxError(f"{desc} value out of bounds") return exp def _float_to_tuple(what): if what < 0: sign = -1 what *= -1 else: sign = 1 what = round(what * 3600000) degrees = int(what // 3600000) what -= degrees * 3600000 minutes = int(what // 60000) what -= minutes * 60000 seconds = int(what // 1000) what -= int(seconds * 1000) what = int(what) return (degrees, minutes, seconds, what, sign) def _tuple_to_float(what): value = float(what[0]) value += float(what[1]) / 60.0 value += float(what[2]) / 3600.0 value += float(what[3]) / 3600000.0 return float(what[4]) * value def _encode_size(what, desc): what = int(what) exponent = _exponent_of(what, desc) & 0xF base = what // pow(10, exponent) & 0xF return base * 16 + exponent def _decode_size(what, desc): exponent = what & 0x0F if exponent > 9: raise dns.exception.FormError(f"bad {desc} exponent") base = (what & 0xF0) >> 4 if base > 9: raise dns.exception.FormError(f"bad {desc} base") return base * pow(10, exponent) def _check_coordinate_list(value, low, high): if value[0] < low or value[0] > high: raise ValueError(f"not in range [{low}, {high}]") if value[1] < 0 or value[1] > 59: raise ValueError("bad minutes value") if value[2] < 0 or value[2] > 59: raise ValueError("bad seconds value") if value[3] < 0 or value[3] > 999: raise ValueError("bad milliseconds value") if value[4] != 1 and value[4] != -1: raise ValueError("bad hemisphere value") @dns.immutable.immutable class LOC(dns.rdata.Rdata): """LOC record""" # see: RFC 1876 __slots__ = [ "latitude", "longitude", "altitude", "size", "horizontal_precision", "vertical_precision", ] def __init__( self, rdclass, rdtype, latitude, longitude, altitude, size=_default_size, hprec=_default_hprec, vprec=_default_vprec, ): """Initialize a LOC record instance. The parameters I{latitude} and I{longitude} may be either a 4-tuple of integers specifying (degrees, minutes, seconds, milliseconds), or they may be floating point values specifying the number of degrees. The other parameters are floats. Size, horizontal precision, and vertical precision are specified in centimeters.""" super().__init__(rdclass, rdtype) if isinstance(latitude, int): latitude = float(latitude) if isinstance(latitude, float): latitude = _float_to_tuple(latitude) _check_coordinate_list(latitude, -90, 90) self.latitude = tuple(latitude) if isinstance(longitude, int): longitude = float(longitude) if isinstance(longitude, float): longitude = _float_to_tuple(longitude) _check_coordinate_list(longitude, -180, 180) self.longitude = tuple(longitude) self.altitude = float(altitude) self.size = float(size) self.horizontal_precision = float(hprec) self.vertical_precision = float(vprec) def to_text(self, origin=None, relativize=True, **kw): if self.latitude[4] > 0: lat_hemisphere = "N" else: lat_hemisphere = "S" if self.longitude[4] > 0: long_hemisphere = "E" else: long_hemisphere = "W" text = "%d %d %d.%03d %s %d %d %d.%03d %s %0.2fm" % ( self.latitude[0], self.latitude[1], self.latitude[2], self.latitude[3], lat_hemisphere, self.longitude[0], self.longitude[1], self.longitude[2], self.longitude[3], long_hemisphere, self.altitude / 100.0, ) # do not print default values if ( self.size != _default_size or self.horizontal_precision != _default_hprec or self.vertical_precision != _default_vprec ): text += ( f" {self.size / 100.0:0.2f}m {self.horizontal_precision / 100.0:0.2f}m" f" {self.vertical_precision / 100.0:0.2f}m" ) return text @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): latitude = [0, 0, 0, 0, 1] longitude = [0, 0, 0, 0, 1] size = _default_size hprec = _default_hprec vprec = _default_vprec latitude[0] = tok.get_int() t = tok.get_string() if t.isdigit(): latitude[1] = int(t) t = tok.get_string() if "." in t: (seconds, milliseconds) = t.split(".") if not seconds.isdigit(): raise dns.exception.SyntaxError("bad latitude seconds value") latitude[2] = int(seconds) l = len(milliseconds) if l == 0 or l > 3 or not milliseconds.isdigit(): raise dns.exception.SyntaxError("bad latitude milliseconds value") if l == 1: m = 100 elif l == 2: m = 10 else: m = 1 latitude[3] = m * int(milliseconds) t = tok.get_string() elif t.isdigit(): latitude[2] = int(t) t = tok.get_string() if t == "S": latitude[4] = -1 elif t != "N": raise dns.exception.SyntaxError("bad latitude hemisphere value") longitude[0] = tok.get_int() t = tok.get_string() if t.isdigit(): longitude[1] = int(t) t = tok.get_string() if "." in t: (seconds, milliseconds) = t.split(".") if not seconds.isdigit(): raise dns.exception.SyntaxError("bad longitude seconds value") longitude[2] = int(seconds) l = len(milliseconds) if l == 0 or l > 3 or not milliseconds.isdigit(): raise dns.exception.SyntaxError("bad longitude milliseconds value") if l == 1: m = 100 elif l == 2: m = 10 else: m = 1 longitude[3] = m * int(milliseconds) t = tok.get_string() elif t.isdigit(): longitude[2] = int(t) t = tok.get_string() if t == "W": longitude[4] = -1 elif t != "E": raise dns.exception.SyntaxError("bad longitude hemisphere value") t = tok.get_string() if t[-1] == "m": t = t[0:-1] altitude = float(t) * 100.0 # m -> cm tokens = tok.get_remaining(max_tokens=3) if len(tokens) >= 1: value = tokens[0].unescape().value if value[-1] == "m": value = value[0:-1] size = float(value) * 100.0 # m -> cm if len(tokens) >= 2: value = tokens[1].unescape().value if value[-1] == "m": value = value[0:-1] hprec = float(value) * 100.0 # m -> cm if len(tokens) >= 3: value = tokens[2].unescape().value if value[-1] == "m": value = value[0:-1] vprec = float(value) * 100.0 # m -> cm # Try encoding these now so we raise if they are bad _encode_size(size, "size") _encode_size(hprec, "horizontal precision") _encode_size(vprec, "vertical precision") return cls(rdclass, rdtype, latitude, longitude, altitude, size, hprec, vprec) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): milliseconds = ( self.latitude[0] * 3600000 + self.latitude[1] * 60000 + self.latitude[2] * 1000 + self.latitude[3] ) * self.latitude[4] latitude = 0x80000000 + milliseconds milliseconds = ( self.longitude[0] * 3600000 + self.longitude[1] * 60000 + self.longitude[2] * 1000 + self.longitude[3] ) * self.longitude[4] longitude = 0x80000000 + milliseconds altitude = int(self.altitude) + 10000000 size = _encode_size(self.size, "size") hprec = _encode_size(self.horizontal_precision, "horizontal precision") vprec = _encode_size(self.vertical_precision, "vertical precision") wire = struct.pack( "!BBBBIII", 0, size, hprec, vprec, latitude, longitude, altitude ) file.write(wire) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): ( version, size, hprec, vprec, latitude, longitude, altitude, ) = parser.get_struct("!BBBBIII") if version != 0: raise dns.exception.FormError("LOC version not zero") if latitude < _MIN_LATITUDE or latitude > _MAX_LATITUDE: raise dns.exception.FormError("bad latitude") if latitude > 0x80000000: latitude = (latitude - 0x80000000) / 3600000 else: latitude = -1 * (0x80000000 - latitude) / 3600000 if longitude < _MIN_LONGITUDE or longitude > _MAX_LONGITUDE: raise dns.exception.FormError("bad longitude") if longitude > 0x80000000: longitude = (longitude - 0x80000000) / 3600000 else: longitude = -1 * (0x80000000 - longitude) / 3600000 altitude = float(altitude) - 10000000.0 size = _decode_size(size, "size") hprec = _decode_size(hprec, "horizontal precision") vprec = _decode_size(vprec, "vertical precision") return cls(rdclass, rdtype, latitude, longitude, altitude, size, hprec, vprec) @property def float_latitude(self): "latitude as a floating point value" return _tuple_to_float(self.latitude) @property def float_longitude(self): "longitude as a floating point value" return _tuple_to_float(self.longitude) dnspython-2.7.0/dns/rdtypes/ANY/LP.py0000644000000000000000000000247213615410400014265 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import struct import dns.immutable import dns.rdata @dns.immutable.immutable class LP(dns.rdata.Rdata): """LP record""" # see: rfc6742.txt __slots__ = ["preference", "fqdn"] def __init__(self, rdclass, rdtype, preference, fqdn): super().__init__(rdclass, rdtype) self.preference = self._as_uint16(preference) self.fqdn = self._as_name(fqdn) def to_text(self, origin=None, relativize=True, **kw): fqdn = self.fqdn.choose_relativity(origin, relativize) return "%d %s" % (self.preference, fqdn) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): preference = tok.get_uint16() fqdn = tok.get_name(origin, relativize, relativize_to) return cls(rdclass, rdtype, preference, fqdn) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): file.write(struct.pack("!H", self.preference)) self.fqdn.to_wire(file, compress, origin, canonicalize) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): preference = parser.get_uint16() fqdn = parser.get_name(origin) return cls(rdclass, rdtype, preference, fqdn) dnspython-2.7.0/dns/rdtypes/ANY/MX.py0000644000000000000000000000174313615410400014276 0ustar00# 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.immutable import dns.rdtypes.mxbase @dns.immutable.immutable class MX(dns.rdtypes.mxbase.MXBase): """MX record""" dnspython-2.7.0/dns/rdtypes/ANY/NID.py0000644000000000000000000000301013615410400014351 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import struct import dns.immutable import dns.rdtypes.util @dns.immutable.immutable class NID(dns.rdata.Rdata): """NID record""" # see: rfc6742.txt __slots__ = ["preference", "nodeid"] def __init__(self, rdclass, rdtype, preference, nodeid): super().__init__(rdclass, rdtype) self.preference = self._as_uint16(preference) if isinstance(nodeid, bytes): if len(nodeid) != 8: raise ValueError("invalid nodeid") self.nodeid = dns.rdata._hexify(nodeid, 4, b":") else: dns.rdtypes.util.parse_formatted_hex(nodeid, 4, 4, ":") self.nodeid = nodeid def to_text(self, origin=None, relativize=True, **kw): return f"{self.preference} {self.nodeid}" @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): preference = tok.get_uint16() nodeid = tok.get_identifier() return cls(rdclass, rdtype, preference, nodeid) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): file.write(struct.pack("!H", self.preference)) file.write(dns.rdtypes.util.parse_formatted_hex(self.nodeid, 4, 4, ":")) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): preference = parser.get_uint16() nodeid = parser.get_remaining() return cls(rdclass, rdtype, preference, nodeid) dnspython-2.7.0/dns/rdtypes/ANY/NINFO.py0000644000000000000000000000202113615410400014611 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2006, 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.immutable import dns.rdtypes.txtbase @dns.immutable.immutable class NINFO(dns.rdtypes.txtbase.TXTBase): """NINFO record""" # see: draft-reid-dnsext-zs-01 dnspython-2.7.0/dns/rdtypes/ANY/NS.py0000644000000000000000000000174313615410400014272 0ustar00# 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.immutable import dns.rdtypes.nsbase @dns.immutable.immutable class NS(dns.rdtypes.nsbase.NSBase): """NS record""" dnspython-2.7.0/dns/rdtypes/ANY/NSEC.py0000644000000000000000000000464113615410400014502 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2004-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.exception import dns.immutable import dns.name import dns.rdata import dns.rdatatype import dns.rdtypes.util @dns.immutable.immutable class Bitmap(dns.rdtypes.util.Bitmap): type_name = "NSEC" @dns.immutable.immutable class NSEC(dns.rdata.Rdata): """NSEC record""" __slots__ = ["next", "windows"] def __init__(self, rdclass, rdtype, next, windows): super().__init__(rdclass, rdtype) self.next = self._as_name(next) if not isinstance(windows, Bitmap): windows = Bitmap(windows) self.windows = tuple(windows.windows) def to_text(self, origin=None, relativize=True, **kw): next = self.next.choose_relativity(origin, relativize) text = Bitmap(self.windows).to_text() return f"{next}{text}" @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): next = tok.get_name(origin, relativize, relativize_to) windows = Bitmap.from_text(tok) return cls(rdclass, rdtype, next, windows) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): # Note that NSEC downcasing, originally mandated by RFC 4034 # section 6.2 was removed by RFC 6840 section 5.1. self.next.to_wire(file, None, origin, False) Bitmap(self.windows).to_wire(file) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): next = parser.get_name(origin) bitmap = Bitmap.from_wire_parser(parser) return cls(rdclass, rdtype, next, bitmap) dnspython-2.7.0/dns/rdtypes/ANY/NSEC3.py0000644000000000000000000001035313615410400014562 0ustar00# 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 # 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 binascii import struct import dns.exception import dns.immutable import dns.rdata import dns.rdatatype import dns.rdtypes.util b32_hex_to_normal = bytes.maketrans( b"0123456789ABCDEFGHIJKLMNOPQRSTUV", b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" ) b32_normal_to_hex = bytes.maketrans( b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", b"0123456789ABCDEFGHIJKLMNOPQRSTUV" ) # hash algorithm constants SHA1 = 1 # flag constants OPTOUT = 1 @dns.immutable.immutable class Bitmap(dns.rdtypes.util.Bitmap): type_name = "NSEC3" @dns.immutable.immutable class NSEC3(dns.rdata.Rdata): """NSEC3 record""" __slots__ = ["algorithm", "flags", "iterations", "salt", "next", "windows"] def __init__( self, rdclass, rdtype, algorithm, flags, iterations, salt, next, windows ): super().__init__(rdclass, rdtype) self.algorithm = self._as_uint8(algorithm) self.flags = self._as_uint8(flags) self.iterations = self._as_uint16(iterations) self.salt = self._as_bytes(salt, True, 255) self.next = self._as_bytes(next, True, 255) if not isinstance(windows, Bitmap): windows = Bitmap(windows) self.windows = tuple(windows.windows) def _next_text(self): next = base64.b32encode(self.next).translate(b32_normal_to_hex).lower().decode() next = next.rstrip("=") return next def to_text(self, origin=None, relativize=True, **kw): next = self._next_text() if self.salt == b"": salt = "-" else: salt = binascii.hexlify(self.salt).decode() text = Bitmap(self.windows).to_text() return "%u %u %u %s %s%s" % ( self.algorithm, self.flags, self.iterations, salt, next, text, ) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): algorithm = tok.get_uint8() flags = tok.get_uint8() iterations = tok.get_uint16() salt = tok.get_string() if salt == "-": salt = b"" else: salt = binascii.unhexlify(salt.encode("ascii")) next = tok.get_string().encode("ascii").upper().translate(b32_hex_to_normal) if next.endswith(b"="): raise binascii.Error("Incorrect padding") if len(next) % 8 != 0: next += b"=" * (8 - len(next) % 8) next = base64.b32decode(next) bitmap = Bitmap.from_text(tok) return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next, bitmap) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): l = len(self.salt) file.write(struct.pack("!BBHB", self.algorithm, self.flags, self.iterations, l)) file.write(self.salt) l = len(self.next) file.write(struct.pack("!B", l)) file.write(self.next) Bitmap(self.windows).to_wire(file) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): (algorithm, flags, iterations) = parser.get_struct("!BBH") salt = parser.get_counted_bytes() next = parser.get_counted_bytes() bitmap = Bitmap.from_wire_parser(parser) return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next, bitmap) def next_name(self, origin=None): return dns.name.from_text(self._next_text(), origin) dnspython-2.7.0/dns/rdtypes/ANY/NSEC3PARAM.py0000644000000000000000000000511313615410400015341 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2004-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 binascii import struct import dns.exception import dns.immutable import dns.rdata @dns.immutable.immutable class NSEC3PARAM(dns.rdata.Rdata): """NSEC3PARAM record""" __slots__ = ["algorithm", "flags", "iterations", "salt"] def __init__(self, rdclass, rdtype, algorithm, flags, iterations, salt): super().__init__(rdclass, rdtype) self.algorithm = self._as_uint8(algorithm) self.flags = self._as_uint8(flags) self.iterations = self._as_uint16(iterations) self.salt = self._as_bytes(salt, True, 255) def to_text(self, origin=None, relativize=True, **kw): if self.salt == b"": salt = "-" else: salt = binascii.hexlify(self.salt).decode() return "%u %u %u %s" % (self.algorithm, self.flags, self.iterations, salt) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): algorithm = tok.get_uint8() flags = tok.get_uint8() iterations = tok.get_uint16() salt = tok.get_string() if salt == "-": salt = "" else: salt = binascii.unhexlify(salt.encode()) return cls(rdclass, rdtype, algorithm, flags, iterations, salt) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): l = len(self.salt) file.write(struct.pack("!BBHB", self.algorithm, self.flags, self.iterations, l)) file.write(self.salt) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): (algorithm, flags, iterations) = parser.get_struct("!BBH") salt = parser.get_counted_bytes() return cls(rdclass, rdtype, algorithm, flags, iterations, salt) dnspython-2.7.0/dns/rdtypes/ANY/OPENPGPKEY.py0000644000000000000000000000347313615410400015435 0ustar00# 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.immutable import dns.rdata import dns.tokenizer @dns.immutable.immutable class OPENPGPKEY(dns.rdata.Rdata): """OPENPGPKEY record""" # see: RFC 7929 def __init__(self, rdclass, rdtype, key): super().__init__(rdclass, rdtype) self.key = self._as_bytes(key) def to_text(self, origin=None, relativize=True, **kw): return dns.rdata._base64ify(self.key, chunksize=None, **kw) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): b64 = tok.concatenate_remaining_identifiers().encode() key = base64.b64decode(b64) return cls(rdclass, rdtype, key) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): file.write(self.key) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): key = parser.get_remaining() return cls(rdclass, rdtype, key) dnspython-2.7.0/dns/rdtypes/ANY/OPT.py0000644000000000000000000000500113615410400014403 0ustar00# 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 # 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 struct import dns.edns import dns.exception import dns.immutable import dns.rdata # We don't implement from_text, and that's ok. # pylint: disable=abstract-method @dns.immutable.immutable class OPT(dns.rdata.Rdata): """OPT record""" __slots__ = ["options"] def __init__(self, rdclass, rdtype, options): """Initialize an OPT rdata. *rdclass*, an ``int`` is the rdataclass of the Rdata, which is also the payload size. *rdtype*, an ``int`` is the rdatatype of the Rdata. *options*, a tuple of ``bytes`` """ super().__init__(rdclass, rdtype) def as_option(option): if not isinstance(option, dns.edns.Option): raise ValueError("option is not a dns.edns.option") return option self.options = self._as_tuple(options, as_option) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): for opt in self.options: owire = opt.to_wire() file.write(struct.pack("!HH", opt.otype, len(owire))) file.write(owire) def to_text(self, origin=None, relativize=True, **kw): return " ".join(opt.to_text() for opt in self.options) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): options = [] while parser.remaining() > 0: (otype, olen) = parser.get_struct("!HH") with parser.restrict_to(olen): opt = dns.edns.option_from_wire_parser(otype, parser) options.append(opt) return cls(rdclass, rdtype, options) @property def payload(self): "payload size" return self.rdclass dnspython-2.7.0/dns/rdtypes/ANY/PTR.py0000644000000000000000000000174513615410400014421 0ustar00# 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.immutable import dns.rdtypes.nsbase @dns.immutable.immutable class PTR(dns.rdtypes.nsbase.NSBase): """PTR record""" dnspython-2.7.0/dns/rdtypes/ANY/RESINFO.py0000644000000000000000000000176013615410400015056 0ustar00# 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.immutable import dns.rdtypes.txtbase @dns.immutable.immutable class RESINFO(dns.rdtypes.txtbase.TXTBase): """RESINFO record""" dnspython-2.7.0/dns/rdtypes/ANY/RP.py0000644000000000000000000000417613615410400014276 0ustar00# 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.exception import dns.immutable import dns.name import dns.rdata @dns.immutable.immutable class RP(dns.rdata.Rdata): """RP record""" # see: RFC 1183 __slots__ = ["mbox", "txt"] def __init__(self, rdclass, rdtype, mbox, txt): super().__init__(rdclass, rdtype) self.mbox = self._as_name(mbox) self.txt = self._as_name(txt) def to_text(self, origin=None, relativize=True, **kw): mbox = self.mbox.choose_relativity(origin, relativize) txt = self.txt.choose_relativity(origin, relativize) return f"{str(mbox)} {str(txt)}" @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): mbox = tok.get_name(origin, relativize, relativize_to) txt = tok.get_name(origin, relativize, relativize_to) return cls(rdclass, rdtype, mbox, txt) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): self.mbox.to_wire(file, None, origin, canonicalize) self.txt.to_wire(file, None, origin, canonicalize) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): mbox = parser.get_name(origin) txt = parser.get_name(origin) return cls(rdclass, rdtype, mbox, txt) dnspython-2.7.0/dns/rdtypes/ANY/RRSIG.py0000644000000000000000000001147213615410400014640 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2004-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 base64 import calendar import struct import time import dns.dnssectypes import dns.exception import dns.immutable import dns.rdata import dns.rdatatype class BadSigTime(dns.exception.DNSException): """Time in DNS SIG or RRSIG resource record cannot be parsed.""" def sigtime_to_posixtime(what): if len(what) <= 10 and what.isdigit(): return int(what) if len(what) != 14: raise BadSigTime year = int(what[0:4]) month = int(what[4:6]) day = int(what[6:8]) hour = int(what[8:10]) minute = int(what[10:12]) second = int(what[12:14]) return calendar.timegm((year, month, day, hour, minute, second, 0, 0, 0)) def posixtime_to_sigtime(what): return time.strftime("%Y%m%d%H%M%S", time.gmtime(what)) @dns.immutable.immutable class RRSIG(dns.rdata.Rdata): """RRSIG record""" __slots__ = [ "type_covered", "algorithm", "labels", "original_ttl", "expiration", "inception", "key_tag", "signer", "signature", ] def __init__( self, rdclass, rdtype, type_covered, algorithm, labels, original_ttl, expiration, inception, key_tag, signer, signature, ): super().__init__(rdclass, rdtype) self.type_covered = self._as_rdatatype(type_covered) self.algorithm = dns.dnssectypes.Algorithm.make(algorithm) self.labels = self._as_uint8(labels) self.original_ttl = self._as_ttl(original_ttl) self.expiration = self._as_uint32(expiration) self.inception = self._as_uint32(inception) self.key_tag = self._as_uint16(key_tag) self.signer = self._as_name(signer) self.signature = self._as_bytes(signature) def covers(self): return self.type_covered def to_text(self, origin=None, relativize=True, **kw): return "%s %d %d %d %s %s %d %s %s" % ( dns.rdatatype.to_text(self.type_covered), self.algorithm, self.labels, self.original_ttl, posixtime_to_sigtime(self.expiration), posixtime_to_sigtime(self.inception), self.key_tag, self.signer.choose_relativity(origin, relativize), dns.rdata._base64ify(self.signature, **kw), ) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): type_covered = dns.rdatatype.from_text(tok.get_string()) algorithm = dns.dnssectypes.Algorithm.from_text(tok.get_string()) labels = tok.get_int() original_ttl = tok.get_ttl() expiration = sigtime_to_posixtime(tok.get_string()) inception = sigtime_to_posixtime(tok.get_string()) key_tag = tok.get_int() signer = tok.get_name(origin, relativize, relativize_to) b64 = tok.concatenate_remaining_identifiers().encode() signature = base64.b64decode(b64) return cls( rdclass, rdtype, type_covered, algorithm, labels, original_ttl, expiration, inception, key_tag, signer, signature, ) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): header = struct.pack( "!HBBIIIH", self.type_covered, self.algorithm, self.labels, self.original_ttl, self.expiration, self.inception, self.key_tag, ) file.write(header) self.signer.to_wire(file, None, origin, canonicalize) file.write(self.signature) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): header = parser.get_struct("!HBBIIIH") signer = parser.get_name(origin) signature = parser.get_remaining() return cls(rdclass, rdtype, *header, signer, signature) dnspython-2.7.0/dns/rdtypes/ANY/RT.py0000644000000000000000000000176513615410400014303 0ustar00# 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.immutable import dns.rdtypes.mxbase @dns.immutable.immutable class RT(dns.rdtypes.mxbase.UncompressedDowncasingMX): """RT record""" dnspython-2.7.0/dns/rdtypes/ANY/SMIMEA.py0000644000000000000000000000033613615410400014722 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import dns.immutable import dns.rdtypes.tlsabase @dns.immutable.immutable class SMIMEA(dns.rdtypes.tlsabase.TLSABase): """SMIMEA record""" dnspython-2.7.0/dns/rdtypes/ANY/SOA.py0000644000000000000000000000611113615410400014366 0ustar00# 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 struct import dns.exception import dns.immutable import dns.name import dns.rdata @dns.immutable.immutable class SOA(dns.rdata.Rdata): """SOA record""" # see: RFC 1035 __slots__ = ["mname", "rname", "serial", "refresh", "retry", "expire", "minimum"] def __init__( self, rdclass, rdtype, mname, rname, serial, refresh, retry, expire, minimum ): super().__init__(rdclass, rdtype) self.mname = self._as_name(mname) self.rname = self._as_name(rname) self.serial = self._as_uint32(serial) self.refresh = self._as_ttl(refresh) self.retry = self._as_ttl(retry) self.expire = self._as_ttl(expire) self.minimum = self._as_ttl(minimum) def to_text(self, origin=None, relativize=True, **kw): mname = self.mname.choose_relativity(origin, relativize) rname = self.rname.choose_relativity(origin, relativize) return "%s %s %d %d %d %d %d" % ( mname, rname, self.serial, self.refresh, self.retry, self.expire, self.minimum, ) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): mname = tok.get_name(origin, relativize, relativize_to) rname = tok.get_name(origin, relativize, relativize_to) serial = tok.get_uint32() refresh = tok.get_ttl() retry = tok.get_ttl() expire = tok.get_ttl() minimum = tok.get_ttl() return cls( rdclass, rdtype, mname, rname, serial, refresh, retry, expire, minimum ) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): self.mname.to_wire(file, compress, origin, canonicalize) self.rname.to_wire(file, compress, origin, canonicalize) five_ints = struct.pack( "!IIIII", self.serial, self.refresh, self.retry, self.expire, self.minimum ) file.write(five_ints) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): mname = parser.get_name(origin) rname = parser.get_name(origin) return cls(rdclass, rdtype, mname, rname, *parser.get_struct("!IIIII")) dnspython-2.7.0/dns/rdtypes/ANY/SPF.py0000644000000000000000000000177613615410400014410 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2006, 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.immutable import dns.rdtypes.txtbase @dns.immutable.immutable class SPF(dns.rdtypes.txtbase.TXTBase): """SPF record""" # see: RFC 4408 dnspython-2.7.0/dns/rdtypes/ANY/SSHFP.py0000644000000000000000000000474213615410400014637 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2005-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 binascii import struct import dns.immutable import dns.rdata import dns.rdatatype @dns.immutable.immutable class SSHFP(dns.rdata.Rdata): """SSHFP record""" # See RFC 4255 __slots__ = ["algorithm", "fp_type", "fingerprint"] def __init__(self, rdclass, rdtype, algorithm, fp_type, fingerprint): super().__init__(rdclass, rdtype) self.algorithm = self._as_uint8(algorithm) self.fp_type = self._as_uint8(fp_type) self.fingerprint = self._as_bytes(fingerprint, True) def to_text(self, origin=None, relativize=True, **kw): kw = kw.copy() chunksize = kw.pop("chunksize", 128) return "%d %d %s" % ( self.algorithm, self.fp_type, dns.rdata._hexify(self.fingerprint, chunksize=chunksize, **kw), ) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): algorithm = tok.get_uint8() fp_type = tok.get_uint8() fingerprint = tok.concatenate_remaining_identifiers().encode() fingerprint = binascii.unhexlify(fingerprint) return cls(rdclass, rdtype, algorithm, fp_type, fingerprint) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): header = struct.pack("!BB", self.algorithm, self.fp_type) file.write(header) file.write(self.fingerprint) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): header = parser.get_struct("BB") fingerprint = parser.get_remaining() return cls(rdclass, rdtype, header[0], header[1], fingerprint) dnspython-2.7.0/dns/rdtypes/ANY/TKEY.py0000644000000000000000000001147713615410400014533 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2004-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 base64 import struct import dns.exception import dns.immutable import dns.rdata @dns.immutable.immutable class TKEY(dns.rdata.Rdata): """TKEY Record""" __slots__ = [ "algorithm", "inception", "expiration", "mode", "error", "key", "other", ] def __init__( self, rdclass, rdtype, algorithm, inception, expiration, mode, error, key, other=b"", ): super().__init__(rdclass, rdtype) self.algorithm = self._as_name(algorithm) self.inception = self._as_uint32(inception) self.expiration = self._as_uint32(expiration) self.mode = self._as_uint16(mode) self.error = self._as_uint16(error) self.key = self._as_bytes(key) self.other = self._as_bytes(other) def to_text(self, origin=None, relativize=True, **kw): _algorithm = self.algorithm.choose_relativity(origin, relativize) text = "%s %u %u %u %u %s" % ( str(_algorithm), self.inception, self.expiration, self.mode, self.error, dns.rdata._base64ify(self.key, 0), ) if len(self.other) > 0: text += f" {dns.rdata._base64ify(self.other, 0)}" return text @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): algorithm = tok.get_name(relativize=False) inception = tok.get_uint32() expiration = tok.get_uint32() mode = tok.get_uint16() error = tok.get_uint16() key_b64 = tok.get_string().encode() key = base64.b64decode(key_b64) other_b64 = tok.concatenate_remaining_identifiers(True).encode() other = base64.b64decode(other_b64) return cls( rdclass, rdtype, algorithm, inception, expiration, mode, error, key, other ) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): self.algorithm.to_wire(file, compress, origin) file.write( struct.pack("!IIHH", self.inception, self.expiration, self.mode, self.error) ) file.write(struct.pack("!H", len(self.key))) file.write(self.key) file.write(struct.pack("!H", len(self.other))) if len(self.other) > 0: file.write(self.other) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): algorithm = parser.get_name(origin) inception, expiration, mode, error = parser.get_struct("!IIHH") key = parser.get_counted_bytes(2) other = parser.get_counted_bytes(2) return cls( rdclass, rdtype, algorithm, inception, expiration, mode, error, key, other ) # Constants for the mode field - from RFC 2930: # 2.5 The Mode Field # # The mode field specifies the general scheme for key agreement or # the purpose of the TKEY DNS message. Servers and resolvers # supporting this specification MUST implement the Diffie-Hellman key # agreement mode and the key deletion mode for queries. All other # modes are OPTIONAL. A server supporting TKEY that receives a TKEY # request with a mode it does not support returns the BADMODE error. # The following values of the Mode octet are defined, available, or # reserved: # # Value Description # ----- ----------- # 0 - reserved, see section 7 # 1 server assignment # 2 Diffie-Hellman exchange # 3 GSS-API negotiation # 4 resolver assignment # 5 key deletion # 6-65534 - available, see section 7 # 65535 - reserved, see section 7 SERVER_ASSIGNMENT = 1 DIFFIE_HELLMAN_EXCHANGE = 2 GSSAPI_NEGOTIATION = 3 RESOLVER_ASSIGNMENT = 4 KEY_DELETION = 5 dnspython-2.7.0/dns/rdtypes/ANY/TLSA.py0000644000000000000000000000033213615410400014506 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import dns.immutable import dns.rdtypes.tlsabase @dns.immutable.immutable class TLSA(dns.rdtypes.tlsabase.TLSABase): """TLSA record""" dnspython-2.7.0/dns/rdtypes/ANY/TSIG.py0000644000000000000000000001121613615410400014514 0ustar00# 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 # 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 struct import dns.exception import dns.immutable import dns.rcode import dns.rdata @dns.immutable.immutable class TSIG(dns.rdata.Rdata): """TSIG record""" __slots__ = [ "algorithm", "time_signed", "fudge", "mac", "original_id", "error", "other", ] def __init__( self, rdclass, rdtype, algorithm, time_signed, fudge, mac, original_id, error, other, ): """Initialize a TSIG rdata. *rdclass*, an ``int`` is the rdataclass of the Rdata. *rdtype*, an ``int`` is the rdatatype of the Rdata. *algorithm*, a ``dns.name.Name``. *time_signed*, an ``int``. *fudge*, an ``int`. *mac*, a ``bytes`` *original_id*, an ``int`` *error*, an ``int`` *other*, a ``bytes`` """ super().__init__(rdclass, rdtype) self.algorithm = self._as_name(algorithm) self.time_signed = self._as_uint48(time_signed) self.fudge = self._as_uint16(fudge) self.mac = self._as_bytes(mac) self.original_id = self._as_uint16(original_id) self.error = dns.rcode.Rcode.make(error) self.other = self._as_bytes(other) def to_text(self, origin=None, relativize=True, **kw): algorithm = self.algorithm.choose_relativity(origin, relativize) error = dns.rcode.to_text(self.error, True) text = ( f"{algorithm} {self.time_signed} {self.fudge} " + f"{len(self.mac)} {dns.rdata._base64ify(self.mac, 0)} " + f"{self.original_id} {error} {len(self.other)}" ) if self.other: text += f" {dns.rdata._base64ify(self.other, 0)}" return text @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): algorithm = tok.get_name(relativize=False) time_signed = tok.get_uint48() fudge = tok.get_uint16() mac_len = tok.get_uint16() mac = base64.b64decode(tok.get_string()) if len(mac) != mac_len: raise SyntaxError("invalid MAC") original_id = tok.get_uint16() error = dns.rcode.from_text(tok.get_string()) other_len = tok.get_uint16() if other_len > 0: other = base64.b64decode(tok.get_string()) if len(other) != other_len: raise SyntaxError("invalid other data") else: other = b"" return cls( rdclass, rdtype, algorithm, time_signed, fudge, mac, original_id, error, other, ) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): self.algorithm.to_wire(file, None, origin, False) file.write( struct.pack( "!HIHH", (self.time_signed >> 32) & 0xFFFF, self.time_signed & 0xFFFFFFFF, self.fudge, len(self.mac), ) ) file.write(self.mac) file.write(struct.pack("!HHH", self.original_id, self.error, len(self.other))) file.write(self.other) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): algorithm = parser.get_name() time_signed = parser.get_uint48() fudge = parser.get_uint16() mac = parser.get_counted_bytes(2) (original_id, error) = parser.get_struct("!HH") other = parser.get_counted_bytes(2) return cls( rdclass, rdtype, algorithm, time_signed, fudge, mac, original_id, error, other, ) dnspython-2.7.0/dns/rdtypes/ANY/TXT.py0000644000000000000000000000175013615410400014427 0ustar00# 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.immutable import dns.rdtypes.txtbase @dns.immutable.immutable class TXT(dns.rdtypes.txtbase.TXTBase): """TXT record""" dnspython-2.7.0/dns/rdtypes/ANY/URI.py0000644000000000000000000000555113615410400014412 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. # Copyright (C) 2015 Red Hat, 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 struct import dns.exception import dns.immutable import dns.name import dns.rdata import dns.rdtypes.util @dns.immutable.immutable class URI(dns.rdata.Rdata): """URI record""" # see RFC 7553 __slots__ = ["priority", "weight", "target"] def __init__(self, rdclass, rdtype, priority, weight, target): super().__init__(rdclass, rdtype) self.priority = self._as_uint16(priority) self.weight = self._as_uint16(weight) self.target = self._as_bytes(target, True) if len(self.target) == 0: raise dns.exception.SyntaxError("URI target cannot be empty") def to_text(self, origin=None, relativize=True, **kw): return '%d %d "%s"' % (self.priority, self.weight, self.target.decode()) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): priority = tok.get_uint16() weight = tok.get_uint16() target = tok.get().unescape() if not (target.is_quoted_string() or target.is_identifier()): raise dns.exception.SyntaxError("URI target must be a string") return cls(rdclass, rdtype, priority, weight, target.value) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): two_ints = struct.pack("!HH", self.priority, self.weight) file.write(two_ints) file.write(self.target) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): (priority, weight) = parser.get_struct("!HH") target = parser.get_remaining() if len(target) == 0: raise dns.exception.FormError("URI target may not be empty") return cls(rdclass, rdtype, priority, weight, target) def _processing_priority(self): return self.priority def _processing_weight(self): return self.weight @classmethod def _processing_order(cls, iterable): return dns.rdtypes.util.weighted_processing_order(iterable) dnspython-2.7.0/dns/rdtypes/ANY/WALLET.py0000644000000000000000000000033313615410400014734 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import dns.immutable import dns.rdtypes.txtbase @dns.immutable.immutable class WALLET(dns.rdtypes.txtbase.TXTBase): """WALLET record""" dnspython-2.7.0/dns/rdtypes/ANY/X25.py0000644000000000000000000000362613615410400014332 0ustar00# 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 struct import dns.exception import dns.immutable import dns.rdata import dns.tokenizer @dns.immutable.immutable class X25(dns.rdata.Rdata): """X25 record""" # see RFC 1183 __slots__ = ["address"] def __init__(self, rdclass, rdtype, address): super().__init__(rdclass, rdtype) self.address = self._as_bytes(address, True, 255) def to_text(self, origin=None, relativize=True, **kw): return f'"{dns.rdata._escapify(self.address)}"' @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): address = tok.get_string() return cls(rdclass, rdtype, address) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): l = len(self.address) assert l < 256 file.write(struct.pack("!B", l)) file.write(self.address) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): address = parser.get_counted_bytes() return cls(rdclass, rdtype, address) dnspython-2.7.0/dns/rdtypes/ANY/ZONEMD.py0000644000000000000000000000453113615410400014744 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import binascii import struct import dns.immutable import dns.rdata import dns.rdatatype import dns.zonetypes @dns.immutable.immutable class ZONEMD(dns.rdata.Rdata): """ZONEMD record""" # See RFC 8976 __slots__ = ["serial", "scheme", "hash_algorithm", "digest"] def __init__(self, rdclass, rdtype, serial, scheme, hash_algorithm, digest): super().__init__(rdclass, rdtype) self.serial = self._as_uint32(serial) self.scheme = dns.zonetypes.DigestScheme.make(scheme) self.hash_algorithm = dns.zonetypes.DigestHashAlgorithm.make(hash_algorithm) self.digest = self._as_bytes(digest) if self.scheme == 0: # reserved, RFC 8976 Sec. 5.2 raise ValueError("scheme 0 is reserved") if self.hash_algorithm == 0: # reserved, RFC 8976 Sec. 5.3 raise ValueError("hash_algorithm 0 is reserved") hasher = dns.zonetypes._digest_hashers.get(self.hash_algorithm) if hasher and hasher().digest_size != len(self.digest): raise ValueError("digest length inconsistent with hash algorithm") def to_text(self, origin=None, relativize=True, **kw): kw = kw.copy() chunksize = kw.pop("chunksize", 128) return "%d %d %d %s" % ( self.serial, self.scheme, self.hash_algorithm, dns.rdata._hexify(self.digest, chunksize=chunksize, **kw), ) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): serial = tok.get_uint32() scheme = tok.get_uint8() hash_algorithm = tok.get_uint8() digest = tok.concatenate_remaining_identifiers().encode() digest = binascii.unhexlify(digest) return cls(rdclass, rdtype, serial, scheme, hash_algorithm, digest) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): header = struct.pack("!IBB", self.serial, self.scheme, self.hash_algorithm) file.write(header) file.write(self.digest) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): header = parser.get_struct("!IBB") digest = parser.get_remaining() return cls(rdclass, rdtype, header[0], header[1], header[2], digest) dnspython-2.7.0/dns/rdtypes/ANY/__init__.py0000644000000000000000000000276613615410400015517 0ustar00# 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 ANY (generic) rdata type classes.""" __all__ = [ "AFSDB", "AMTRELAY", "AVC", "CAA", "CDNSKEY", "CDS", "CERT", "CNAME", "CSYNC", "DLV", "DNAME", "DNSKEY", "DS", "EUI48", "EUI64", "GPOS", "HINFO", "HIP", "ISDN", "L32", "L64", "LOC", "LP", "MX", "NID", "NINFO", "NS", "NSEC", "NSEC3", "NSEC3PARAM", "OPENPGPKEY", "OPT", "PTR", "RESINFO", "RP", "RRSIG", "RT", "SMIMEA", "SOA", "SPF", "SSHFP", "TKEY", "TLSA", "TSIG", "TXT", "URI", "WALLET", "X25", "ZONEMD", ] dnspython-2.7.0/dns/rdtypes/CH/A.py0000644000000000000000000000424413615410400013774 0ustar00# 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 struct import dns.immutable import dns.rdtypes.mxbase @dns.immutable.immutable class A(dns.rdata.Rdata): """A record for Chaosnet""" # domain: the domain of the address # address: the 16-bit address __slots__ = ["domain", "address"] def __init__(self, rdclass, rdtype, domain, address): super().__init__(rdclass, rdtype) self.domain = self._as_name(domain) self.address = self._as_uint16(address) def to_text(self, origin=None, relativize=True, **kw): domain = self.domain.choose_relativity(origin, relativize) return f"{domain} {self.address:o}" @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): domain = tok.get_name(origin, relativize, relativize_to) address = tok.get_uint16(base=8) return cls(rdclass, rdtype, domain, address) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): self.domain.to_wire(file, compress, origin, canonicalize) pref = struct.pack("!H", self.address) file.write(pref) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): domain = parser.get_name(origin) address = parser.get_uint16() return cls(rdclass, rdtype, domain, address) dnspython-2.7.0/dns/rdtypes/CH/__init__.py0000644000000000000000000000163313615410400015352 0ustar00# 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", ] dnspython-2.7.0/dns/rdtypes/IN/A.py0000644000000000000000000000342613615410400014011 0ustar00# 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.exception import dns.immutable import dns.ipv4 import dns.rdata import dns.tokenizer @dns.immutable.immutable class A(dns.rdata.Rdata): """A record.""" __slots__ = ["address"] def __init__(self, rdclass, rdtype, address): super().__init__(rdclass, rdtype) self.address = self._as_ipv4_address(address) def to_text(self, origin=None, relativize=True, **kw): return self.address @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): address = tok.get_identifier() return cls(rdclass, rdtype, address) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): file.write(dns.ipv4.inet_aton(self.address)) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): address = parser.get_remaining() return cls(rdclass, rdtype, address) dnspython-2.7.0/dns/rdtypes/IN/AAAA.py0000644000000000000000000000343413615410400014313 0ustar00# 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.exception import dns.immutable import dns.ipv6 import dns.rdata import dns.tokenizer @dns.immutable.immutable class AAAA(dns.rdata.Rdata): """AAAA record.""" __slots__ = ["address"] def __init__(self, rdclass, rdtype, address): super().__init__(rdclass, rdtype) self.address = self._as_ipv6_address(address) def to_text(self, origin=None, relativize=True, **kw): return self.address @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): address = tok.get_identifier() return cls(rdclass, rdtype, address) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): file.write(dns.ipv6.inet_aton(self.address)) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): address = parser.get_remaining() return cls(rdclass, rdtype, address) dnspython-2.7.0/dns/rdtypes/IN/APL.py0000644000000000000000000001175113615410400014245 0ustar00# 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 # 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 binascii import codecs import struct import dns.exception import dns.immutable import dns.ipv4 import dns.ipv6 import dns.rdata import dns.tokenizer @dns.immutable.immutable class APLItem: """An APL list item.""" __slots__ = ["family", "negation", "address", "prefix"] def __init__(self, family, negation, address, prefix): self.family = dns.rdata.Rdata._as_uint16(family) self.negation = dns.rdata.Rdata._as_bool(negation) if self.family == 1: self.address = dns.rdata.Rdata._as_ipv4_address(address) self.prefix = dns.rdata.Rdata._as_int(prefix, 0, 32) elif self.family == 2: self.address = dns.rdata.Rdata._as_ipv6_address(address) self.prefix = dns.rdata.Rdata._as_int(prefix, 0, 128) else: self.address = dns.rdata.Rdata._as_bytes(address, max_length=127) self.prefix = dns.rdata.Rdata._as_uint8(prefix) def __str__(self): if self.negation: return "!%d:%s/%s" % (self.family, self.address, self.prefix) else: return "%d:%s/%s" % (self.family, self.address, self.prefix) def to_wire(self, file): if self.family == 1: address = dns.ipv4.inet_aton(self.address) elif self.family == 2: address = dns.ipv6.inet_aton(self.address) else: address = binascii.unhexlify(self.address) # # Truncate least significant zero bytes. # last = 0 for i in range(len(address) - 1, -1, -1): if address[i] != 0: last = i + 1 break address = address[0:last] l = len(address) assert l < 128 if self.negation: l |= 0x80 header = struct.pack("!HBB", self.family, self.prefix, l) file.write(header) file.write(address) @dns.immutable.immutable class APL(dns.rdata.Rdata): """APL record.""" # see: RFC 3123 __slots__ = ["items"] def __init__(self, rdclass, rdtype, items): super().__init__(rdclass, rdtype) for item in items: if not isinstance(item, APLItem): raise ValueError("item not an APLItem") self.items = tuple(items) def to_text(self, origin=None, relativize=True, **kw): return " ".join(map(str, self.items)) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): items = [] for token in tok.get_remaining(): item = token.unescape().value if item[0] == "!": negation = True item = item[1:] else: negation = False (family, rest) = item.split(":", 1) family = int(family) (address, prefix) = rest.split("/", 1) prefix = int(prefix) item = APLItem(family, negation, address, prefix) items.append(item) return cls(rdclass, rdtype, items) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): for item in self.items: item.to_wire(file) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): items = [] while parser.remaining() > 0: header = parser.get_struct("!HBB") afdlen = header[2] if afdlen > 127: negation = True afdlen -= 128 else: negation = False address = parser.get_bytes(afdlen) l = len(address) if header[0] == 1: if l < 4: address += b"\x00" * (4 - l) elif header[0] == 2: if l < 16: address += b"\x00" * (16 - l) else: # # This isn't really right according to the RFC, but it # seems better than throwing an exception # address = codecs.encode(address, "hex_codec") item = APLItem(header[0], negation, address, header[1]) items.append(item) return cls(rdclass, rdtype, items) dnspython-2.7.0/dns/rdtypes/IN/DHCID.py0000644000000000000000000000350013615410400014435 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2006, 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 base64 import dns.exception import dns.immutable import dns.rdata @dns.immutable.immutable class DHCID(dns.rdata.Rdata): """DHCID record""" # see: RFC 4701 __slots__ = ["data"] def __init__(self, rdclass, rdtype, data): super().__init__(rdclass, rdtype) self.data = self._as_bytes(data) def to_text(self, origin=None, relativize=True, **kw): return dns.rdata._base64ify(self.data, **kw) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): b64 = tok.concatenate_remaining_identifiers().encode() data = base64.b64decode(b64) return cls(rdclass, rdtype, data) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): file.write(self.data) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): data = parser.get_remaining() return cls(rdclass, rdtype, data) dnspython-2.7.0/dns/rdtypes/IN/HTTPS.py0000644000000000000000000000033413615410400014526 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import dns.immutable import dns.rdtypes.svcbbase @dns.immutable.immutable class HTTPS(dns.rdtypes.svcbbase.SVCBBase): """HTTPS record""" dnspython-2.7.0/dns/rdtypes/IN/IPSECKEY.py0000644000000000000000000000633213615410400015044 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2006, 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 base64 import struct import dns.exception import dns.immutable import dns.rdtypes.util class Gateway(dns.rdtypes.util.Gateway): name = "IPSECKEY gateway" @dns.immutable.immutable class IPSECKEY(dns.rdata.Rdata): """IPSECKEY record""" # see: RFC 4025 __slots__ = ["precedence", "gateway_type", "algorithm", "gateway", "key"] def __init__( self, rdclass, rdtype, precedence, gateway_type, algorithm, gateway, key ): super().__init__(rdclass, rdtype) gateway = Gateway(gateway_type, gateway) self.precedence = self._as_uint8(precedence) self.gateway_type = gateway.type self.algorithm = self._as_uint8(algorithm) self.gateway = gateway.gateway self.key = self._as_bytes(key) def to_text(self, origin=None, relativize=True, **kw): gateway = Gateway(self.gateway_type, self.gateway).to_text(origin, relativize) return "%d %d %d %s %s" % ( self.precedence, self.gateway_type, self.algorithm, gateway, dns.rdata._base64ify(self.key, **kw), ) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): precedence = tok.get_uint8() gateway_type = tok.get_uint8() algorithm = tok.get_uint8() gateway = Gateway.from_text( gateway_type, tok, origin, relativize, relativize_to ) b64 = tok.concatenate_remaining_identifiers().encode() key = base64.b64decode(b64) return cls( rdclass, rdtype, precedence, gateway_type, algorithm, gateway.gateway, key ) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): header = struct.pack("!BBB", self.precedence, self.gateway_type, self.algorithm) file.write(header) Gateway(self.gateway_type, self.gateway).to_wire( file, compress, origin, canonicalize ) file.write(self.key) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): header = parser.get_struct("!BBB") gateway_type = header[1] gateway = Gateway.from_wire_parser(gateway_type, parser, origin) key = parser.get_remaining() return cls( rdclass, rdtype, header[0], gateway_type, header[2], gateway.gateway, key ) dnspython-2.7.0/dns/rdtypes/IN/KX.py0000644000000000000000000000176513615410400014157 0ustar00# 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.immutable import dns.rdtypes.mxbase @dns.immutable.immutable class KX(dns.rdtypes.mxbase.UncompressedDowncasingMX): """KX record""" dnspython-2.7.0/dns/rdtypes/IN/NAPTR.py0000644000000000000000000000724613615410400014521 0ustar00# 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 struct import dns.exception import dns.immutable import dns.name import dns.rdata import dns.rdtypes.util def _write_string(file, s): l = len(s) assert l < 256 file.write(struct.pack("!B", l)) file.write(s) @dns.immutable.immutable class NAPTR(dns.rdata.Rdata): """NAPTR record""" # see: RFC 3403 __slots__ = ["order", "preference", "flags", "service", "regexp", "replacement"] def __init__( self, rdclass, rdtype, order, preference, flags, service, regexp, replacement ): super().__init__(rdclass, rdtype) self.flags = self._as_bytes(flags, True, 255) self.service = self._as_bytes(service, True, 255) self.regexp = self._as_bytes(regexp, True, 255) self.order = self._as_uint16(order) self.preference = self._as_uint16(preference) self.replacement = self._as_name(replacement) def to_text(self, origin=None, relativize=True, **kw): replacement = self.replacement.choose_relativity(origin, relativize) return '%d %d "%s" "%s" "%s" %s' % ( self.order, self.preference, dns.rdata._escapify(self.flags), dns.rdata._escapify(self.service), dns.rdata._escapify(self.regexp), replacement, ) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): order = tok.get_uint16() preference = tok.get_uint16() flags = tok.get_string() service = tok.get_string() regexp = tok.get_string() replacement = tok.get_name(origin, relativize, relativize_to) return cls( rdclass, rdtype, order, preference, flags, service, regexp, replacement ) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): two_ints = struct.pack("!HH", self.order, self.preference) file.write(two_ints) _write_string(file, self.flags) _write_string(file, self.service) _write_string(file, self.regexp) self.replacement.to_wire(file, compress, origin, canonicalize) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): (order, preference) = parser.get_struct("!HH") strings = [] for _ in range(3): s = parser.get_counted_bytes() strings.append(s) replacement = parser.get_name(origin) return cls( rdclass, rdtype, order, preference, strings[0], strings[1], strings[2], replacement, ) def _processing_priority(self): return (self.order, self.preference) @classmethod def _processing_order(cls, iterable): return dns.rdtypes.util.priority_processing_order(iterable) dnspython-2.7.0/dns/rdtypes/IN/NSAP.py0000644000000000000000000000416313615410400014371 0ustar00# 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 binascii import dns.exception import dns.immutable import dns.rdata import dns.tokenizer @dns.immutable.immutable class NSAP(dns.rdata.Rdata): """NSAP record.""" # see: RFC 1706 __slots__ = ["address"] def __init__(self, rdclass, rdtype, address): super().__init__(rdclass, rdtype) self.address = self._as_bytes(address) def to_text(self, origin=None, relativize=True, **kw): return f"0x{binascii.hexlify(self.address).decode()}" @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): address = tok.get_string() if address[0:2] != "0x": raise dns.exception.SyntaxError("string does not start with 0x") address = address[2:].replace(".", "") if len(address) % 2 != 0: raise dns.exception.SyntaxError("hexstring has odd length") address = binascii.unhexlify(address.encode()) return cls(rdclass, rdtype, address) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): file.write(self.address) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): address = parser.get_remaining() return cls(rdclass, rdtype, address) dnspython-2.7.0/dns/rdtypes/IN/NSAP_PTR.py0000644000000000000000000000176713615410400015125 0ustar00# 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.immutable import dns.rdtypes.nsbase @dns.immutable.immutable class NSAP_PTR(dns.rdtypes.nsbase.UncompressedNS): """NSAP-PTR record""" dnspython-2.7.0/dns/rdtypes/IN/PX.py0000644000000000000000000000530413615410400014155 0ustar00# 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 struct import dns.exception import dns.immutable import dns.name import dns.rdata import dns.rdtypes.util @dns.immutable.immutable class PX(dns.rdata.Rdata): """PX record.""" # see: RFC 2163 __slots__ = ["preference", "map822", "mapx400"] def __init__(self, rdclass, rdtype, preference, map822, mapx400): super().__init__(rdclass, rdtype) self.preference = self._as_uint16(preference) self.map822 = self._as_name(map822) self.mapx400 = self._as_name(mapx400) def to_text(self, origin=None, relativize=True, **kw): map822 = self.map822.choose_relativity(origin, relativize) mapx400 = self.mapx400.choose_relativity(origin, relativize) return "%d %s %s" % (self.preference, map822, mapx400) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): preference = tok.get_uint16() map822 = tok.get_name(origin, relativize, relativize_to) mapx400 = tok.get_name(origin, relativize, relativize_to) return cls(rdclass, rdtype, preference, map822, mapx400) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): pref = struct.pack("!H", self.preference) file.write(pref) self.map822.to_wire(file, None, origin, canonicalize) self.mapx400.to_wire(file, None, origin, canonicalize) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): preference = parser.get_uint16() map822 = parser.get_name(origin) mapx400 = parser.get_name(origin) return cls(rdclass, rdtype, preference, map822, mapx400) def _processing_priority(self): return self.preference @classmethod def _processing_order(cls, iterable): return dns.rdtypes.util.priority_processing_order(iterable) dnspython-2.7.0/dns/rdtypes/IN/SRV.py0000644000000000000000000000532113615410400014277 0ustar00# 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 struct import dns.exception import dns.immutable import dns.name import dns.rdata import dns.rdtypes.util @dns.immutable.immutable class SRV(dns.rdata.Rdata): """SRV record""" # see: RFC 2782 __slots__ = ["priority", "weight", "port", "target"] def __init__(self, rdclass, rdtype, priority, weight, port, target): super().__init__(rdclass, rdtype) self.priority = self._as_uint16(priority) self.weight = self._as_uint16(weight) self.port = self._as_uint16(port) self.target = self._as_name(target) def to_text(self, origin=None, relativize=True, **kw): target = self.target.choose_relativity(origin, relativize) return "%d %d %d %s" % (self.priority, self.weight, self.port, target) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): priority = tok.get_uint16() weight = tok.get_uint16() port = tok.get_uint16() target = tok.get_name(origin, relativize, relativize_to) return cls(rdclass, rdtype, priority, weight, port, target) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): three_ints = struct.pack("!HHH", self.priority, self.weight, self.port) file.write(three_ints) self.target.to_wire(file, compress, origin, canonicalize) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): (priority, weight, port) = parser.get_struct("!HHH") target = parser.get_name(origin) return cls(rdclass, rdtype, priority, weight, port, target) def _processing_priority(self): return self.priority def _processing_weight(self): return self.weight @classmethod def _processing_order(cls, iterable): return dns.rdtypes.util.weighted_processing_order(iterable) dnspython-2.7.0/dns/rdtypes/IN/SVCB.py0000644000000000000000000000033213615410400014357 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import dns.immutable import dns.rdtypes.svcbbase @dns.immutable.immutable class SVCB(dns.rdtypes.svcbbase.SVCBBase): """SVCB record""" dnspython-2.7.0/dns/rdtypes/IN/WKS.py0000644000000000000000000000710413615410400014272 0ustar00# 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 socket import struct import dns.immutable import dns.ipv4 import dns.rdata try: _proto_tcp = socket.getprotobyname("tcp") _proto_udp = socket.getprotobyname("udp") except OSError: # Fall back to defaults in case /etc/protocols is unavailable. _proto_tcp = 6 _proto_udp = 17 @dns.immutable.immutable class WKS(dns.rdata.Rdata): """WKS record""" # see: RFC 1035 __slots__ = ["address", "protocol", "bitmap"] def __init__(self, rdclass, rdtype, address, protocol, bitmap): super().__init__(rdclass, rdtype) self.address = self._as_ipv4_address(address) self.protocol = self._as_uint8(protocol) self.bitmap = self._as_bytes(bitmap) def to_text(self, origin=None, relativize=True, **kw): bits = [] for i, byte in enumerate(self.bitmap): for j in range(0, 8): if byte & (0x80 >> j): bits.append(str(i * 8 + j)) text = " ".join(bits) return "%s %d %s" % (self.address, self.protocol, text) @classmethod def from_text( cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None ): address = tok.get_string() protocol = tok.get_string() if protocol.isdigit(): protocol = int(protocol) else: protocol = socket.getprotobyname(protocol) bitmap = bytearray() for token in tok.get_remaining(): value = token.unescape().value if value.isdigit(): serv = int(value) else: if protocol != _proto_udp and protocol != _proto_tcp: raise NotImplementedError("protocol must be TCP or UDP") if protocol == _proto_udp: protocol_text = "udp" else: protocol_text = "tcp" serv = socket.getservbyname(value, protocol_text) i = serv // 8 l = len(bitmap) if l < i + 1: for _ in range(l, i + 1): bitmap.append(0) bitmap[i] = bitmap[i] | (0x80 >> (serv % 8)) bitmap = dns.rdata._truncate_bitmap(bitmap) return cls(rdclass, rdtype, address, protocol, bitmap) def _to_wire(self, file, compress=None, origin=None, canonicalize=False): file.write(dns.ipv4.inet_aton(self.address)) protocol = struct.pack("!B", self.protocol) file.write(protocol) file.write(self.bitmap) @classmethod def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): address = parser.get_bytes(4) protocol = parser.get_uint8() bitmap = parser.get_remaining() return cls(rdclass, rdtype, address, protocol, bitmap) dnspython-2.7.0/dns/rdtypes/IN/__init__.py0000644000000000000000000000207313615410400015365 0ustar00# 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 IN rdata type classes.""" __all__ = [ "A", "AAAA", "APL", "DHCID", "HTTPS", "IPSECKEY", "KX", "NAPTR", "NSAP", "NSAP_PTR", "PX", "SRV", "SVCB", "WKS", ] dnspython-2.7.0/examples/async_dns.py0000644000000000000000000000155213615410400014642 0ustar00import sys import trio import dns.asyncquery import dns.asyncresolver import dns.message async def main(): if len(sys.argv) > 1: host = sys.argv[0] else: host = "www.dnspython.org" q = dns.message.make_query(host, "A") r = await dns.asyncquery.udp(q, "8.8.8.8") print(r) q = dns.message.make_query(host, "A") r = await dns.asyncquery.tcp(q, "8.8.8.8") print(r) q = dns.message.make_query(host, "A") r = await dns.asyncquery.tls(q, "8.8.8.8") print(r) a = await dns.asyncresolver.resolve(host, "A") print(a.response) zn = await dns.asyncresolver.zone_for_name(host) print(zn) answer = await dns.asyncresolver.resolve_at("8.8.8.8", "amazon.com", "NS") print("The amazon.com nameservers are:") for rr in answer: print(rr.target) if __name__ == "__main__": trio.run(main) dnspython-2.7.0/examples/ddns.py0000755000000000000000000000225213615410400013612 0ustar00#!/usr/bin/env python3 # # Use a TSIG-signed DDNS update to update our hostname-to-address # mapping. # # usage: ddns.py # # On linux systems, you can automatically update your DNS any time an # interface comes up by adding an ifup-local script that invokes this # python code. # # E.g. on my systems I have this # # #!/bin/sh # # DEVICE=$1 # # if [ "X${DEVICE}" == "Xeth0" ]; then # IPADDR=`LANG= LC_ALL= ifconfig ${DEVICE} | grep 'inet addr' | # awk -F: '{ print $2 } ' | awk '{ print $1 }'` # /usr/local/sbin/ddns.py $IPADDR # fi # # in /etc/ifup-local. # import sys import dns.query import dns.tsigkeyring import dns.update # # Replace the keyname and secret with appropriate values for your # configuration. # keyring = dns.tsigkeyring.from_text({"keyname.": "NjHwPsMKjdN++dOfE5iAiQ=="}) # # Replace "example." with your domain, and "host" with your hostname. # update = dns.update.Update("example.", keyring=keyring) update.replace("host", 300, "A", sys.argv[1]) # # Replace "10.0.0.1" with the IP address of your master server. # response = dns.query.tcp(update, "10.0.0.1", timeout=10) dnspython-2.7.0/examples/ddr.py0000644000000000000000000000166613615410400013440 0ustar00#!/usr/bin/env python3 # Using Discovery of Designated Resolvers (synchronous I/O) import dns.resolver res = dns.resolver.Resolver(configure=False) res.nameservers = ["1.1.1.1"] # Invoke try_ddr() to attempt to upgrade the connection via DDR res.try_ddr() # Do a sample resolution for rr in res.resolve("www.google.com", "A"): print(rr.address) # Note that the nameservers have been upgraded print(res.nameservers) # Using Discovery of Designated Resolvers (asynchronous I/O) # We show using asyncio, but if you comment out asyncio lines # and uncomment the trio lines, it will work with trio too. import asyncio import dns.asyncresolver # import trio async def amain(): res = dns.asyncresolver.Resolver(configure=False) res.nameservers = ["8.8.8.8"] await res.try_ddr() for rr in await res.resolve("www.google.com", "A"): print(rr.address) print(res.nameservers) asyncio.run(amain()) # trio.run(amain) dnspython-2.7.0/examples/doh-json.py0000755000000000000000000000567213615410400014414 0ustar00#!/usr/bin/env python3 import copy import json import httpx import dns.flags import dns.message import dns.rdataclass import dns.rdatatype import dns.resolver # This shows how to convert to/from dnspython's message object and the # DNS-over-HTTPS (DoH) JSON form used by Google and Cloudflare, and # described here: # # https://developers.google.com/speed/public-dns/docs/doh/json # # There's no need to do this for DoH as dnspython supports the # standard RFC 8484 protocol which all DoH providers implement. The # conversion to/from JSON is useful, however, so we show a way to do # it. # # "simple" below means "simple python data types", i.e. things made of # combinations of dictionaries, lists, strings, and numbers. def make_rr(simple, rdata): csimple = copy.copy(simple) csimple["data"] = rdata.to_text() return csimple def flatten_rrset(rrs): simple = { "name": str(rrs.name), "type": rrs.rdtype, } if len(rrs) > 0: simple["TTL"] = rrs.ttl return [make_rr(simple, rdata) for rdata in rrs] else: return [simple] def to_doh_simple(message): simple = {"Status": message.rcode()} for f in dns.flags.Flag: if f != dns.flags.Flag.AA and f != dns.flags.Flag.QR: # DoH JSON doesn't need AA and omits it. DoH JSON is only # used in replies so the QR flag is implied. simple[f.name] = (message.flags & f) != 0 for i, s in enumerate(message.sections): k = dns.message.MessageSection.to_text(i).title() simple[k] = [] for rrs in s: simple[k].extend(flatten_rrset(rrs)) # we don't encode the ecs_client_subnet field return simple def from_doh_simple(simple, add_qr=False): message = dns.message.QueryMessage() flags = 0 for f in dns.flags.Flag: if simple.get(f.name, False): flags |= f if add_qr: # QR is implied flags |= dns.flags.QR message.flags = flags message.set_rcode(simple.get("Status", 0)) for i, sn in enumerate(dns.message.MessageSection): rr_list = simple.get(sn.name.title(), []) for rr in rr_list: rdtype = dns.rdatatype.RdataType(rr["type"]) rrs = message.find_rrset( i, dns.name.from_text(rr["name"]), dns.rdataclass.IN, rdtype, create=True, ) if "data" in rr: rrs.add( dns.rdata.from_text(dns.rdataclass.IN, rdtype, rr["data"]), rr.get("TTL", 0), ) # we don't decode the ecs_client_subnet field return message a = dns.resolver.resolve("www.dnspython.org", "a") p = to_doh_simple(a.response) print(json.dumps(p, indent=4)) response = httpx.get( "https://dns.google/resolve?", verify=True, params={"name": "www.dnspython.org", "type": 1}, ) p = json.loads(response.text) m = from_doh_simple(p, True) print(m) dnspython-2.7.0/examples/doh.py0000755000000000000000000000102613615410400013432 0ustar00#!/usr/bin/env python3 # # This is an example of sending DNS queries over HTTPS (DoH) with dnspython. import httpx import dns.message import dns.query import dns.rdatatype def main(): where = "https://dns.google/dns-query" qname = "example.com." with httpx.Client() as client: q = dns.message.make_query(qname, dns.rdatatype.A) r = dns.query.https(q, where, session=client) for answer in r.answer: print(answer) # ... do more lookups if __name__ == "__main__": main() dnspython-2.7.0/examples/doq.py0000644000000000000000000000610713615410400013445 0ustar00import asyncio import threading import dns.asyncbackend import dns.asyncquery import dns.message import dns.query import dns.quic import dns.rdatatype try: import trio have_trio = True except ImportError: have_trio = False # This demo assumes you have the aioquic example doq_server.py running on localhost # on port 4784 on localhost. peer_address = "127.0.0.1" peer_port = 4784 query_name = "www.dnspython.org" tls_verify_mode = False def squery(rdtype="A", connection=None): q = dns.message.make_query(query_name, rdtype) r = dns.query.quic( q, peer_address, port=peer_port, connection=connection, verify=tls_verify_mode ) print(r) def srun(): squery() def smultirun(): with dns.quic.SyncQuicManager(verify_mode=tls_verify_mode) as manager: connection = manager.connect(peer_address, peer_port) t1 = threading.Thread(target=squery, args=["A", connection]) t1.start() t2 = threading.Thread(target=squery, args=["AAAA", connection]) t2.start() t1.join() t2.join() async def aquery(rdtype="A", connection=None): q = dns.message.make_query(query_name, rdtype) r = await dns.asyncquery.quic( q, peer_address, port=peer_port, connection=connection, verify=tls_verify_mode ) print(r) def arun(): asyncio.run(aquery()) async def amulti(): async with dns.quic.AsyncioQuicManager(verify_mode=tls_verify_mode) as manager: connection = manager.connect(peer_address, peer_port) t1 = asyncio.Task(aquery("A", connection)) t2 = asyncio.Task(aquery("AAAA", connection)) await t1 await t2 def amultirun(): asyncio.run(amulti()) if have_trio: def trun(): trio.run(aquery) async def tmulti(): async with trio.open_nursery() as nursery: async with dns.quic.TrioQuicManager( nursery, verify_mode=tls_verify_mode ) as manager: async with trio.open_nursery() as query_nursery: # We run queries in a separate nursery so we can demonstrate # waiting for them all to exit without waiting for the manager to # exit as well. connection = manager.connect(peer_address, peer_port) query_nursery.start_soon(aquery, "A", connection) query_nursery.start_soon(aquery, "AAAA", connection) def tmultirun(): trio.run(tmulti) def main(): print("*** Single Queries ***") print("--- Sync ---") srun() print("--- Asyncio ---") dns.asyncbackend.set_default_backend("asyncio") arun() if have_trio: print("--- Trio ---") dns.asyncbackend.set_default_backend("trio") trun() print("*** Multi-connection Queries ***") print("--- Sync ---") smultirun() print("--- Asyncio ---") dns.asyncbackend.set_default_backend("asyncio") amultirun() if have_trio: print("--- Trio ---") dns.asyncbackend.set_default_backend("trio") tmultirun() if __name__ == "__main__": main() dnspython-2.7.0/examples/dot.py0000644000000000000000000000070313615410400013444 0ustar00#!/usr/bin/env python3 # # This is an example of sending DNS queries over TLS (DoT) with dnspython. import dns.message import dns.query import dns.rdatatype def main(): where = "1.1.1.1" qname = "example.com." q = dns.message.make_query(qname, dns.rdatatype.A) r = dns.query.tls(q, where) for answer in r.answer: print(answer) # ... do more lookups if __name__ == "__main__": main() dnspython-2.7.0/examples/e164.py0000755000000000000000000000016313615410400013340 0ustar00#!/usr/bin/env python3 import dns.e164 n = dns.e164.from_e164("+1 555 1212") print(n) print(dns.e164.to_e164(n)) dnspython-2.7.0/examples/ecs.py0000755000000000000000000000070213615410400013432 0ustar00#!/usr/bin/env python3 import dns.edns import dns.message import dns.query # This example demonstrates how to use the EDNS client subnet option ADDRESS = "0.0.0.0" # replace this with the address you want to check PREFIX = 0 # replace this with a prefix length (typically 24 for IPv4) ecs = dns.edns.ECSOption(ADDRESS, PREFIX) q = dns.message.make_query("www.google.com", "A", use_edns=0, options=[ecs]) r = dns.query.udp(q, "8.8.8.8") print(r) dnspython-2.7.0/examples/edns.py0000755000000000000000000000331313615410400013612 0ustar00#!/usr/bin/env python3 import dns.edns import dns.message import dns.query import dns.resolver n = "." t = dns.rdatatype.SOA l = "199.7.83.42" # Address of l.root-servers.net i = "149.20.1.73" # Address of ns1.isc.org, for COOKIEs q_list = [] # A query without EDNS0 q_list.append((l, dns.message.make_query(n, t))) # The same query, but with EDNS0 turned on with no options q_list.append((l, dns.message.make_query(n, t, use_edns=0))) # Use use_edns() to specify EDNS0 options, such as buffer size this_q = dns.message.make_query(n, t) this_q.use_edns(0, payload=2000) q_list.append((l, this_q)) # With an NSID option # use_edns=0 is not needed if options are specified) q_list.append( ( l, dns.message.make_query( n, t, options=[dns.edns.GenericOption(dns.edns.OptionType.NSID, b"")] ), ) ) # With an NSID option, but with use_edns() to specify the options this_q = dns.message.make_query(n, t) this_q.use_edns(0, options=[dns.edns.GenericOption(dns.edns.OptionType.NSID, b"")]) q_list.append((l, this_q)) # With a COOKIE q_list.append( ( i, dns.message.make_query( n, t, options=[ dns.edns.GenericOption( dns.edns.OptionType.COOKIE, b"0xfe11ac99bebe3322" ) ], ), ) ) # With an ECS option using dns.edns.ECSOption to form the option q_list.append( (l, dns.message.make_query(n, t, options=[dns.edns.ECSOption("192.168.0.0", 20)])) ) for addr, q in q_list: r = dns.query.udp(q, addr) if not r.options: print("No EDNS options returned") else: for o in r.options: print(o.otype.value, o.data) print() dnspython-2.7.0/examples/edns_resolver.py0000644000000000000000000000277413615410400015542 0ustar00#!/usr/bin/env python3 import dns.edns import dns.message import dns.query import dns.resolver n = "." t = dns.rdatatype.SOA l = "google.com" # Address of l.root-servers.net, '199.7.83.42' i = "ns1.isc.org" # Address of ns1.isc.org, for COOKIEs, '149.20.1.73' o_list = [] # A query without options o_list.append((l, dict())) # The same query, but with empty options list o_list.append((l, dict(options=[]))) # Use use_edns() to specify EDNS0 options, such as buffer size o_list.append((l, dict(payload=2000))) # With an NSID option, but with use_edns() to specify the options edns_kwargs = dict( edns=0, options=[dns.edns.GenericOption(dns.edns.OptionType.NSID, b"")] ) o_list.append((l, edns_kwargs)) # With a COOKIE o_list.append( ( i, dict( options=[ dns.edns.GenericOption( dns.edns.OptionType.COOKIE, b"0xfe11ac99bebe3322" ) ] ), ) ) # With an ECS option using cloudflare dns address o_list.append((l, dict(options=[dns.edns.ECSOption("1.1.1.1", 24)]))) # With an ECS option using the current machine address import urllib.request external_ip = urllib.request.urlopen("https://ident.me").read().decode("utf8") o_list.append((l, dict(options=[dns.edns.ECSOption(external_ip, 24)]))) aresolver = dns.resolver.Resolver() for addr, edns_kwargs in o_list: if edns_kwargs: aresolver.use_edns(**edns_kwargs) aresolver.nameservers = ["8.8.8.8"] print(list(aresolver.resolve(addr, "A"))) dnspython-2.7.0/examples/mx.py0000755000000000000000000000027513615410400013311 0ustar00#!/usr/bin/env python3 import dns.resolver answers = dns.resolver.resolve("nominum.com", "MX") for rdata in answers: print("Host", rdata.exchange, "has preference", rdata.preference) dnspython-2.7.0/examples/name.py0000755000000000000000000000054413615410400013604 0ustar00#!/usr/bin/env python3 import dns.name n = dns.name.from_text("www.dnspython.org") o = dns.name.from_text("dnspython.org") print(n.is_subdomain(o)) # True print(n.is_superdomain(o)) # False print(n > o) # True rel = n.relativize(o) # rel is the relative name www n2 = rel + o print(n2 == n) # True print(n.labels) # ['www', 'dnspython', 'org', ''] dnspython-2.7.0/examples/query_specific.py0000644000000000000000000000316713615410400015677 0ustar00#!/usr/bin/env python3 # Two ways of querying a specific nameserver. import dns.message import dns.query import dns.rdataclass import dns.rdatatype # This way is just like nslookup/dig: qname = dns.name.from_text("amazon.com") q = dns.message.make_query(qname, dns.rdatatype.NS) print("The query is:") print(q) print("") r = dns.query.udp(q, "8.8.8.8") print("The response is:") print(r) print("") print("The nameservers are:") ns_rrset = r.find_rrset(r.answer, qname, dns.rdataclass.IN, dns.rdatatype.NS) for rr in ns_rrset: print(rr.target) print("") print("") # A higher-level way: import dns.resolver answer = dns.resolver.resolve_at("8.8.8.8", "amazon.com", "NS") print("The nameservers are:") for rr in answer: print(rr.target) print("") print("") # If you're going to make a bunch of queries to the server, make the resolver once # and then use it multiple times: res = dns.resolver.make_resolver_at("dns.google") answer = res.resolve("amazon.com", "NS") print("The amazon.com nameservers are:") for rr in answer: print(rr.target) answer = res.resolve("google.com", "NS") print("The google.com nameservers are:") for rr in answer: print(rr.target) print("") print("") # Sending a query with the all flags set to 0. This is the easiest way # to make a query with the RD flag off. # # This sends a query with RD=0 for the root SOA RRset to the IP address # for l.root-servers.net. q = dns.message.make_query(".", dns.rdatatype.SOA, flags=0) r = dns.query.udp(q, "199.7.83.42") print("\nThe flags in the response are {}".format(dns.flags.to_text(r.flags))) print('The SOA in the response is "{}"'.format((r.answer)[0][0])) dnspython-2.7.0/examples/receive_notify.py0000644000000000000000000000177513615410400015702 0ustar00#!/usr/bin/env python3 # This is just a toy, real code would check that the received message # really was a NOTIFY, and otherwise handle errors. import socket from typing import cast import dns.flags import dns.message import dns.name import dns.rdataclass import dns.rdatatype address = "127.0.0.1" port = 53535 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind((address, port)) while True: (wire, address) = s.recvfrom(512) notify = dns.message.from_wire(wire) try: soa = notify.find_rrset( notify.answer, notify.question[0].name, dns.rdataclass.IN, dns.rdatatype.SOA ) # Do something with the SOA RR here print("The serial number for", soa.name, "is", soa[0].serial) except KeyError: # No SOA RR in the answer section. pass response = dns.message.make_response(notify) # type: dns.message.Message response.flags |= dns.flags.AA wire = response.to_wire(cast(dns.name.Name, response)) s.sendto(wire, address) dnspython-2.7.0/examples/reverse.py0000755000000000000000000000255613615410400014344 0ustar00#!/usr/bin/env python3 # Usage: reverse.py ... # # This demo script will load in all of the zones specified by the # filenames on the command line, find all the A RRs in them, and # construct a reverse mapping table that maps each IP address used to # the list of names mapping to that address. The table is then sorted # nicely and printed. # # Note! The zone name is taken from the basename of the filename, so # you must use filenames like "/wherever/you/like/dnspython.org" and # not something like "/wherever/you/like/foo.db" (unless you're # working with the ".db" GTLD, of course :)). # # If this weren't a demo script, there'd be a way of specifying the # origin for each zone instead of constructing it from the filename. import os.path import sys from typing import Dict, List # pylint: disable=unused-import import dns.ipv4 import dns.zone reverse_map = {} # type: Dict[str, List[str]] for filename in sys.argv[1:]: zone = dns.zone.from_file(filename, os.path.basename(filename), relativize=False) for name, ttl, rdata in zone.iterate_rdatas("A"): print(type(rdata)) try: reverse_map[rdata.address].append(name.to_text()) except KeyError: reverse_map[rdata.address] = [name.to_text()] for k in sorted(reverse_map.keys(), key=dns.ipv4.inet_aton): v = reverse_map[k] v.sort() print(k, v) dnspython-2.7.0/examples/reverse_name.py0000755000000000000000000000021413615410400015331 0ustar00#!/usr/bin/env python3 import dns.reversename n = dns.reversename.from_address("127.0.0.1") print(n) print(dns.reversename.to_address(n)) dnspython-2.7.0/examples/send_notify.py0000755000000000000000000000463613615410400015213 0ustar00#!/usr/bin/python """ Minimalistic RFC 1996-like NOTIFY sender. """ import argparse import ipaddress import socket import dns.flags import dns.inet import dns.message import dns.name import dns.opcode import dns.query import dns.rdataclass import dns.rdatatype import dns.rrset def main(): """Also prints all inputs and intermediate values""" parser = argparse.ArgumentParser( description="Send DNS NOTIFY mesage via UDP, optionally with synthetized SOA RR " "in ANSWER section. No checks. It's not RFC 1996 sect 3.6 compliant sender." ) parser.add_argument("--source", type=ipaddress.ip_address, help="source IP address") parser.add_argument("--port", type=int, help="target port", default=53) parser.add_argument("target", type=ipaddress.ip_address, help="target IP address") parser.add_argument("zone", type=dns.name.from_text) parser.add_argument( "serial", nargs="?", type=int, help="optional serial - adds SOA RR into ANSWER section", ) parser.add_argument( "--rdclass", default=dns.rdataclass.IN, type=dns.rdataclass.from_text, help="DNS class, defaults to IN", ) args = parser.parse_args() if args.source: if dns.inet.af_for_address(str(args.target)) != dns.inet.af_for_address( str(args.source) ): parser.error("address family for source and target must be the same") print(args) msg = construct_msg(args) print(msg) udp_send(msg, args) def construct_msg(args): """if args.serial is specified it creates fake SOA RR with given serial""" msg = dns.message.make_query( args.zone, dns.rdatatype.SOA, rdclass=args.rdclass, flags=dns.flags.AA ) msg.set_opcode(dns.opcode.NOTIFY) if args.serial: soa = dns.rrset.from_text_list( name=args.zone, ttl=0, rdclass=args.rdclass, rdtype=dns.rdatatype.SOA, text_rdatas=[f". . {args.serial} 0 0 0 0"], ) msg.answer.append(soa) return msg def udp_send(msg, args): """ignores checks prescribed by RFC 1996 sect 3.6""" afam = dns.inet.af_for_address(str(args.target)) sock = socket.socket(afam, socket.SOCK_DGRAM) if args.source: sock.bind((str(args.source), 0)) dns.query.send_udp(sock, what=msg, destination=(str(args.target), args.port)) if __name__ == "__main__": main() dnspython-2.7.0/examples/tsig.py0000644000000000000000000000062413615410400013626 0ustar00#!/usr/bin/env python3 import dns.message import dns.query import dns.tsig key = dns.tsig.Key( "keyname.", "bnp6+y85UcBfsieuB/Uhx3EUsjc8wAFyyCSS5rhScb0=", algorithm=dns.tsig.HMAC_SHA256, ) q = dns.message.make_query("example.", "SOA") q.use_tsig(keyring=key) r = dns.query.udp(q, "127.0.0.1") # your authority address here soa = r.find_rrset(r.answer, "example", "IN", "SOA") print(soa) dnspython-2.7.0/examples/wire_read_tcp.py0000755000000000000000000000317313615410400015474 0ustar00#!/usr/bin/python import argparse import sys import dns.exception import dns.message def main(): parser = argparse.ArgumentParser( description="Read sequence of DNS wire formats prefixed with 2-byte " "length field - like from TCP socket - and print the messages. This " "format is used e.g. by dnsperf -B option and dnsgen." ) parser.add_argument("infile") args = parser.parse_args() ok_msgs = 0 bad_msgs = 0 with open(args.infile, "rb") as infile: while True: offset = infile.tell() len_wire = infile.read(2) if len(len_wire) == 0: # end of file - expected break if len(len_wire) == 1: raise ValueError("incomplete length preamble, offset", offset) len_msg = int.from_bytes(len_wire, byteorder="big", signed=False) print(f"; msg offset 0x{offset + 2:x}, length {len_msg} bytes") msg_wire = infile.read(len_msg) if len(msg_wire) != len_msg: raise ValueError( f"incomplete message: expected {len_msg} != got {len(msg_wire)}, " f"length field offset 0x{offset:x}", ) try: msg = dns.message.from_wire(msg_wire) ok_msgs += 1 print(msg) except dns.exception.DNSException as ex: print(f"; invalid message, skipping: {ex}") bad_msgs += 1 print(f"; read {ok_msgs} valid and {bad_msgs} invalid messages") if bad_msgs: sys.exit(1) if __name__ == "__main__": main() dnspython-2.7.0/examples/xfr.py0000755000000000000000000000052413615410400013461 0ustar00#!/usr/bin/env python3 import dns.query import dns.resolver import dns.zone soa_answer = dns.resolver.resolve("dnspython.org", "SOA") master_answer = dns.resolver.resolve(soa_answer[0].mname, "A") z = dns.zone.from_xfr(dns.query.xfr(master_answer[0].address, "dnspython.org")) for n in sorted(z.nodes.keys()): print(z[n].to_text(n)) dnspython-2.7.0/examples/zonediff.py0000755000000000000000000002761513615410400014500 0ustar00#!/usr/bin/env python3 # # Small library and commandline tool to do logical diffs of zonefiles # ./zonediff -h gives you help output # # Requires dnspython to do all the heavy lifting # # (c)2009 Dennis Kaarsemaker # # Permission to use, copy, modify, and distribute this software and its # documentation for any purpose with or without fee is hereby granted, # provided that the above copyright notice and this permission notice # appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. """See diff_zones.__doc__ for more information""" from typing import Any, Union, cast # pylint: disable=unused-import __all__ = ["diff_zones", "format_changes_plain", "format_changes_html"] try: import dns.node import dns.zone except ImportError: raise SystemExit("Please install dnspython") def diff_zones( zone1, # type: dns.zone.Zone zone2, # type: dns.zone.Zone ignore_ttl=False, ignore_soa=False, ): # type: (...) -> list """diff_zones(zone1, zone2, ignore_ttl=False, ignore_soa=False) -> changes Compares two dns.zone.Zone objects and returns a list of all changes in the format (name, oldnode, newnode). If ignore_ttl is true, a node will not be added to this list if the only change is its TTL. If ignore_soa is true, a node will not be added to this list if the only changes is a change in a SOA Rdata set. The returned nodes do include all Rdata sets, including unchanged ones. """ changes = [] for name in zone1: namestr = str(name) n1 = cast(dns.node.Node, zone1.get_node(namestr)) n2 = cast(dns.node.Node, zone2.get_node(namestr)) if not n2: changes.append((str(name), n1, n2)) elif _nodes_differ(n1, n2, ignore_ttl, ignore_soa): changes.append((str(name), n1, n2)) for name in zone2: n3 = cast(dns.node.Node, zone1.get_node(name)) if not n3: n4 = cast(dns.node.Node, zone2.get_node(name)) changes.append((str(name), n3, n4)) return changes def _nodes_differ( n1, # type: dns.node.Node n2, # type: dns.node.Node ignore_ttl, # type: bool ignore_soa, # type: bool ): # type: (...) -> bool if ignore_soa or not ignore_ttl: # Compare datasets directly for r in n1.rdatasets: if ignore_soa and r.rdtype == dns.rdatatype.SOA: continue if r not in n2.rdatasets: return True if not ignore_ttl: return r.ttl != n2.find_rdataset(r.rdclass, r.rdtype).ttl for r in n2.rdatasets: if ignore_soa and r.rdtype == dns.rdatatype.SOA: continue if r not in n1.rdatasets: return True assert False else: return n1 != n2 def format_changes_plain( oldf, # type: str newf, # type: str changes, # type: list ignore_ttl=False, ): # type: (...) -> str """format_changes(oldfile, newfile, changes, ignore_ttl=False) -> str Given 2 filenames and a list of changes from diff_zones, produce diff-like output. If ignore_ttl is True, TTL-only changes are not displayed""" ret = "--- {}\n+++ {}\n".format(oldf, newf) for name, old, new in changes: ret += "@ %s\n" % name if not old: for r in new.rdatasets: ret += "+ %s\n" % str(r).replace("\n", "\n+ ") elif not new: for r in old.rdatasets: ret += "- %s\n" % str(r).replace("\n", "\n+ ") else: for r in old.rdatasets: if r not in new.rdatasets or ( r.ttl != new.find_rdataset(r.rdclass, r.rdtype).ttl and not ignore_ttl ): ret += "- %s\n" % str(r).replace("\n", "\n+ ") for r in new.rdatasets: if r not in old.rdatasets or ( r.ttl != old.find_rdataset(r.rdclass, r.rdtype).ttl and not ignore_ttl ): ret += "+ %s\n" % str(r).replace("\n", "\n+ ") return ret def format_changes_html( oldf, # type: str newf, # type: str changes, # type: list ignore_ttl=False, ): # type: (...) -> str """format_changes(oldfile, newfile, changes, ignore_ttl=False) -> str Given 2 filenames and a list of changes from diff_zones, produce nice html output. If ignore_ttl is True, TTL-only changes are not displayed""" ret = """\n""" % ( oldf, newf, ) for name, old, new in changes: ret += ' \n \n' % name if not old: for r in new.rdatasets: ret += ( ' \n' ' \n' ) % str(r).replace("\n", "
") elif not new: for r in old.rdatasets: ret += ( ' \n' ' \n' ) % str(r).replace("\n", "
") else: ret += ' \n" ret += ' \n" ret += " \n" return ret + " \n
  %s %s
%s %s%s ' for r in old.rdatasets: if r not in new.rdatasets or ( r.ttl != new.find_rdataset(r.rdclass, r.rdtype).ttl and not ignore_ttl ): ret += str(r).replace("\n", "
") ret += "
' for r in new.rdatasets: if r not in old.rdatasets or ( r.ttl != old.find_rdataset(r.rdclass, r.rdtype).ttl and not ignore_ttl ): ret += str(r).replace("\n", "
") ret += "
" # Make this module usable as a script too. def main(): # type: () -> None import argparse import subprocess import sys import traceback usage = """%prog zonefile1 zonefile2 - Show differences between zones in a diff-like format %prog [--git|--bzr|--rcs] zonefile rev1 [rev2] - Show differences between two revisions of a zonefile The differences shown will be logical differences, not textual differences. """ p = argparse.ArgumentParser(usage=usage) p.add_argument( "-s", "--ignore-soa", action="store_true", default=False, dest="ignore_soa", help="Ignore SOA-only changes to records", ) p.add_argument( "-t", "--ignore-ttl", action="store_true", default=False, dest="ignore_ttl", help="Ignore TTL-only changes to Rdata", ) p.add_argument( "-T", "--traceback", action="store_true", default=False, dest="tracebacks", help="Show python tracebacks when errors occur", ) p.add_argument( "-H", "--html", action="store_true", default=False, dest="html", help="Print HTML output", ) p.add_argument( "-g", "--git", action="store_true", default=False, dest="use_git", help="Use git revisions instead of real files", ) p.add_argument( "-b", "--bzr", action="store_true", default=False, dest="use_bzr", help="Use bzr revisions instead of real files", ) p.add_argument( "-r", "--rcs", action="store_true", default=False, dest="use_rcs", help="Use rcs revisions instead of real files", ) opts, args = p.parse_args() opts.use_vc = opts.use_git or opts.use_bzr or opts.use_rcs def _open(what, err): # type: (Union[list,str], str) -> Any if isinstance(what, list): # Must be a list, open subprocess try: proc = subprocess.Popen(what, stdout=subprocess.PIPE) proc.wait() if proc.returncode == 0: return proc.stdout sys.stderr.write(err + "\n") except Exception: sys.stderr.write(err + "\n") if opts.tracebacks: traceback.print_exc() else: # Open as normal file try: return open(what, "rb") except IOError: sys.stderr.write(err + "\n") if opts.tracebacks: traceback.print_exc() if not opts.use_vc and len(args) != 2: p.print_help() sys.exit(64) if opts.use_vc and len(args) not in (2, 3): p.print_help() sys.exit(64) # Open file descriptors if not opts.use_vc: oldn, newn = args else: if len(args) == 3: filename, oldr, newr = args oldn = "{}:{}".format(oldr, filename) newn = "{}:{}".format(newr, filename) else: filename, oldr = args newr = None oldn = "{}:{}".format(oldr, filename) newn = filename old, new = None, None oldz, newz = None, None if opts.use_bzr: old = _open( ["bzr", "cat", "-r" + oldr, filename], "Unable to retrieve revision {} of {}".format(oldr, filename), ) if newr is not None: new = _open( ["bzr", "cat", "-r" + newr, filename], "Unable to retrieve revision {} of {}".format(newr, filename), ) elif opts.use_git: old = _open( ["git", "show", oldn], "Unable to retrieve revision {} of {}".format(oldr, filename), ) if newr is not None: new = _open( ["git", "show", newn], "Unable to retrieve revision {} of {}".format(newr, filename), ) elif opts.use_rcs: old = _open( ["co", "-q", "-p", "-r" + oldr, filename], "Unable to retrieve revision {} of {}".format(oldr, filename), ) if newr is not None: new = _open( ["co", "-q", "-p", "-r" + newr, filename], "Unable to retrieve revision {} of {}".format(newr, filename), ) if not opts.use_vc: old = _open(oldn, "Unable to open %s" % oldn) if not opts.use_vc or newr is None: new = _open(newn, "Unable to open %s" % newn) if not old or not new: sys.exit(65) # Parse the zones try: oldz = dns.zone.from_file(old, origin=".", check_origin=False) except dns.exception.DNSException: sys.stderr.write("Incorrect zonefile: %s\n" % old) if opts.tracebacks: traceback.print_exc() try: newz = dns.zone.from_file(new, origin=".", check_origin=False) except dns.exception.DNSException: sys.stderr.write("Incorrect zonefile: %s\n" % new) if opts.tracebacks: traceback.print_exc() if not oldz or not newz: sys.exit(65) changes = diff_zones(oldz, newz, opts.ignore_ttl, opts.ignore_soa) changes.sort() if not changes: sys.exit(0) if opts.html: print(format_changes_html(oldn, newn, changes, opts.ignore_ttl)) else: print(format_changes_plain(oldn, newn, changes, opts.ignore_ttl)) sys.exit(1) if __name__ == "__main__": main() dnspython-2.7.0/tests/__init__.py0000644000000000000000000000000013615410400013727 0ustar00dnspython-2.7.0/tests/doh.py0000644000000000000000000000414013615410400012753 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import base64 import functools import socket import hypercorn.config import hypercorn.trio import quart import quart_trio def setup(server, connection_type): name = f"{__name__}-{connection_type.name}" app = quart_trio.QuartTrio(name) app.logger.handlers = [] @app.route("/dns-query", methods=["GET", "POST"]) async def dns_query(): if quart.request.method == "POST": wire = await quart.request.body else: encoded = quart.request.args["dns"] remainder = len(encoded) % 4 if remainder != 0: encoded += "=" * (4 - remainder) wire = base64.urlsafe_b64decode(encoded) for body in server.handle_wire( wire, quart.request.remote_addr, quart.request.server, connection_type, ): if body is not None: return quart.Response(body, mimetype="application/dns-message") else: return quart.Response(status=500) return app def make_server(server, sock, connection_type, tls_chain, tls_key): doh_app = setup(server, connection_type) hconfig = hypercorn.config.Config() fd = sock.fileno() if sock.type == socket.SOCK_STREAM: # We put http/1.1 in the ALPN as we don't mind, but DoH is # supposed to be H2 officially. hconfig.alpn_protocols = ["h2", "http/1.1"] hconfig.bind = [f"fd://{fd}"] hconfig.quic_bind = [] else: hconfig.alpn_protocols = ["h3"] # We should be able to pass bind=[], but that triggers a bug in # hypercorn. So, create a dummy socket and bind to it. tmp_sock = socket.create_server(("127.0.0.1", 0)) hconfig.bind = [f"fd://{tmp_sock.fileno()}"] tmp_sock.detach() hconfig.quic_bind = [f"fd://{fd}"] sock.detach() hconfig.certfile = tls_chain hconfig.keyfile = tls_key hconfig.accesslog = None hconfig.errorlog = None return functools.partial(hypercorn.trio.serve, doh_app, hconfig) dnspython-2.7.0/tests/doq.py0000644000000000000000000003253313615410400012773 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Implement DNS-over-QUIC import secrets import struct import time from typing import Optional, Set, Tuple, Union import aioquic import aioquic.buffer import aioquic.quic.configuration import aioquic.quic.connection import aioquic.quic.events import aioquic.quic.logger import aioquic.quic.packet import aioquic.quic.retry import trio import dns.exception from dns._asyncbackend import NullContext from dns.quic._common import Buffer MAX_SAVED_SESSIONS = 100 class Stream: def __init__(self, connection, stream_id): self.connection = connection self.stream_id = stream_id self.buffer = Buffer() self.expecting = 0 self.wake_up = trio.Condition() self.headers = None self.trailers = None async def wait_for(self, amount: int): while True: if self.buffer.have(amount): return self.expecting = amount async with self.wake_up: await self.wake_up.wait() self.expecting = 0 async def receive(self, timeout: Optional[float] = None): context: Union[trio.CancelScope, NullContext] if timeout is None: context = NullContext(None) else: context = trio.move_on_after(timeout) with context: await self.wait_for(2) (size,) = struct.unpack("!H", self.buffer.get(2)) await self.wait_for(size) return self.buffer.get(size) raise dns.exception.Timeout async def send(self, datagram: bytes, is_end=False): l = len(datagram) data = struct.pack("!H", l) + datagram await self.connection.write(self.stream_id, data, is_end) async def add_input(self, data: bytes, is_end: bool): self.buffer.put(data, is_end) # Note it is important that we wake up if we're ending! if (self.expecting > 0 and self.buffer.have(self.expecting)) or is_end: async with self.wake_up: self.wake_up.notify() def seen_end(self) -> bool: return self.buffer.seen_end() async def run(self): try: wire = await self.receive() is_get = False path: Optional[bytes] for wire in self.connection.listener.server.handle_wire( wire, self.connection.peer, self.connection.listener.socket.getsockname(), self.connection.listener.connection_type, ): break await self.send(wire, True) except Exception: if not self.seen_end(): self.connection.reset(self.stream_id) finally: self.connection.stream_done(self) class Connection: def __init__(self, listener, cid, peer, retry_cid=None): self.original_cid: bytes = cid self.listener = listener self.cids: Set[bytes] = set() self.cids.add(cid) self.listener.connections[cid] = self self.peer = peer self.quic_connection = aioquic.quic.connection.QuicConnection( configuration=listener.quic_config, original_destination_connection_id=cid, retry_source_connection_id=retry_cid, session_ticket_fetcher=self.listener.pop_session_ticket, session_ticket_handler=self.listener.store_session_ticket, ) self.cids.add(self.quic_connection.host_cid) self.listener.connections[self.quic_connection.host_cid] = self self.send_channel: trio.MemorySendChannel self.receive_channel: trio.MemoryReceiveChannel self.send_channel, self.receive_channel = trio.open_memory_channel(100) self.send_pending = False self.done = False self.worker_scope = None self.streams = {} def get_timer_values(self, now: float) -> Tuple[float, float]: expiration = self.quic_connection.get_timer() if expiration is None: expiration = now + 3600 # arbitrary "big" value interval = max(expiration - now, 0) return (expiration, interval) async def close_open_streams(self): # We copy the list here as awaiting might let the dictionary change # due to the stream finishing. for stream in list(self.streams.values()): if not stream.seen_end(): await stream.add_input(b"", True) def create_stream(self, nursery: trio.Nursery, stream_id: int) -> Stream: stream = Stream(self, stream_id) self.streams[stream_id] = stream nursery.start_soon(stream.run) return stream async def handle_events(self, nursery: trio.Nursery): count = 0 while not self.done: event = self.quic_connection.next_event() if event is None: return if isinstance(event, aioquic.quic.events.StreamDataReceived): stream = self.streams.get(event.stream_id) if stream is None: stream = self.create_stream(nursery, event.stream_id) await stream.add_input(event.data, event.end_stream) elif isinstance(event, aioquic.quic.events.ConnectionTerminated): await self.close_open_streams() self.done = True elif isinstance(event, aioquic.quic.events.ConnectionIdIssued): cid = event.connection_id if cid not in self.cids: self.cids.add(cid) self.listener.connections[cid] = self else: self.done = True elif isinstance(event, aioquic.quic.events.ConnectionIdRetired): cid = event.connection_id if cid in self.cids: # These should not fail but we eat them just in case so we # don't crash the whole connection. self.cids.remove(cid) del self.listener.connections[cid] else: self.done = True count += 1 if count > 10: # yield count = 0 await trio.sleep(0) async def run(self): try: async with trio.open_nursery() as nursery: while not self.done: now = time.time() (expiration, interval) = self.get_timer_values(now) # Note it must be trio.current_time() and not now due to how # trio time works! if self.send_pending: interval = 0 self.send_pending = False with trio.CancelScope( deadline=trio.current_time() + interval ) as self.worker_scope: (datagram, peer) = await self.receive_channel.receive() self.quic_connection.receive_datagram(datagram, peer, now) self.worker_scope = None now = time.time() if expiration <= now: self.quic_connection.handle_timer(now) await self.handle_events(nursery) datagrams = self.quic_connection.datagrams_to_send(now) for datagram, _ in datagrams: await self.listener.socket.sendto(datagram, self.peer) finally: await self.close_open_streams() for cid in self.cids: try: del self.listener.connections[cid] except KeyError: pass def maybe_wake_up(self): self.send_pending = True if self.worker_scope is not None: self.worker_scope.cancel() async def write(self, stream: int, data: bytes, is_end=False): if not self.done: self.quic_connection.send_stream_data(stream, data, is_end) self.maybe_wake_up() def reset(self, stream: int, error=0): if not self.done: self.quic_connection.reset_stream(stream, error) self.maybe_wake_up() def stream_done(self, stream: Stream): try: del self.streams[stream.stream_id] except KeyError: pass class Listener: def __init__( self, server, socket, connection_type, tls_chain, tls_key, quic_log_directory=None, quic_retry=False, ): self.server = server self.socket = socket # note this is a trio socket self.connection_type = connection_type self.connections = {} self.session_tickets = {} self.done = False alpn_protocols = ["doq"] self.quic_config = aioquic.quic.configuration.QuicConfiguration( is_client=False, alpn_protocols=alpn_protocols ) if quic_log_directory is not None: self.quic_config.quic_logger = aioquic.quic.logger.QuicFileLogger( quic_log_directory ) self.quic_config.load_cert_chain(tls_chain, tls_key) self.retry: Optional[aioquic.quic.retry.QuicRetryTokenHandler] if quic_retry: self.retry = aioquic.quic.retry.QuicRetryTokenHandler() else: self.retry = None def pop_session_ticket(self, key): try: return self.session_tickets.pop(key) except KeyError: return None def store_session_ticket(self, session_ticket): self.session_tickets[session_ticket.ticket] = session_ticket while len(self.session_tickets) > MAX_SAVED_SESSIONS: # Grab the first key key = next(iter(self.session_tickets.keys())) del self.session_tickets[key] async def run(self): async with trio.open_nursery() as nursery: while True: data = None peer = None try: (data, peer) = await self.socket.recvfrom(65535) except Exception: continue buffer = aioquic.buffer.Buffer(data=data) try: header = aioquic.quic.packet.pull_quic_header( buffer, self.quic_config.connection_id_length ) except Exception: continue cid = header.destination_cid connection = self.connections.get(cid) if ( connection is None and header.version is not None and len(data) >= 1200 and header.version not in self.quic_config.supported_versions ): wire = aioquic.quic.packet.encode_quic_version_negotiation( source_cid=cid, destination_cid=header.source_cid, supported_versions=self.quic_config.supported_versions, ) await self.socket.sendto(wire, peer) continue if ( connection is None and len(data) >= 1200 and header.packet_type == aioquic.quic.packet.PACKET_TYPE_INITIAL ): retry_cid = None if self.retry is not None: if not header.token: if header.version is None: continue source_cid = secrets.token_bytes(8) wire = aioquic.quic.packet.encode_quic_retry( version=header.version, source_cid=source_cid, destination_cid=header.source_cid, original_destination_cid=header.destination_cid, retry_token=self.retry.create_token( peer, header.destination_cid, source_cid ), ) await self.socket.sendto(wire, peer) continue else: try: (cid, retry_cid) = self.retry.validate_token( peer, header.token ) # We need to recheck the cid here in case of duplicates, # as we don't want to kick off another connection! connection = self.connections.get(cid) if connection is not None: # duplicate! continue except ValueError: continue connection = Connection(self, cid, peer, retry_cid) nursery.start_soon(connection.run) if connection is not None: try: connection.send_channel.send_nowait((data, peer)) except trio.WouldBlock: pass # Listeners are async context managers async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): return False dnspython-2.7.0/tests/example0000644000000000000000000003174413615410400013217 0ustar00; Copyright (C) 2000, 2001 Internet Software Consortium. ; ; Permission to use, copy, modify, and distribute this software 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 INTERNET SOFTWARE CONSORTIUM ; DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL ; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ; INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, ; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING ; FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, ; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION ; WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ; $Id: example 265924 2016-05-20 18:26:42Z bwelling $ $ORIGIN . $TTL 300 ; 5 minutes example IN SOA ns1.example. hostmaster.example. 1 2 3 4 5 example. NS ns1.example. ns1.example. A 10.53.0.1 example. NS ns2.example. ns2.example. A 10.53.0.2 $ORIGIN example. @ NSEC3PARAM 1 1 12 aabbccdd @ NSEC3PARAM 1 1 12 - * MX 10 mail a TXT "foo foo foo" PTR foo.net. $TTL 3600 ; 1 hour a01 A 0.0.0.0 a02 A 255.255.255.255 ;; ;a601 A6 0 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff ; A6 64 ::ffff:ffff:ffff:ffff foo. ; A6 127 ::1 foo. ; A6 128 . aaaa01 AAAA ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff aaaa02 AAAA ::1 afsdb01 AFSDB 0 hostname afsdb02 AFSDB 65535 . $TTL 300 ; 5 minutes b CNAME foo.net. c A 73.80.65.49 $TTL 3600 ; 1 hour cert01 CERT 65534 65535 PRIVATEOID MxFcby9k/yvedMfQgKzhH5er0Mu/vILz45IkskceFGgi WCn/GxHhai6VAuHAoNUz4YoU1tVfSCSqQYn6//11U6Nl d80jEeC8aTrO+KKmCaY= cname01 CNAME cname-target. cname02 CNAME cname-target cname03 CNAME . dhcid01 DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69lOjxfNuVAA2kjEA= dhcid02 DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQdWL3b/NaiUDlW2No= dhcid03 DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6VytcKD//7es/deY= $TTL 300 ; 5 minutes d A 73.80.65.49 $TTL 3600 ; 1 hour dname01 DNAME dname-target. dname02 DNAME dname-target dname03 DNAME . $TTL 300 ; 5 minutes e MX 10 mail TXT "one" TXT "three" TXT "two" A 73.80.65.49 A 73.80.65.50 A 73.80.65.52 A 73.80.65.51 f A 73.80.65.52 $TTL 3600 ; 1 hour gpos01 GPOS "-22.6882" "116.8652" "250.0" ;; ;; XXXRTH I have commented out the following line because I don't think ;; it is a valid GPOS record. ;; ;;gpos02 GPOS "" "" "" hinfo01 HINFO "Generic PC clone" "NetBSD-1.4" hinfo02 HINFO "PC" "NetBSD" isdn01 ISDN "isdn-address" isdn02 ISDN "isdn-address" "subaddress" isdn03 ISDN isdn-address isdn04 ISDN isdn-address subaddress ;key01 KEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aR yzWZriO6i2odGWWQVucZqKVsENW91IOW4vqudngPZsY3 GvQ/xVA8/7pyFj6b7Esga60zyGW6LFe9r8n6paHrlG5o jqf0BaqHT+8= ;key02 KEY HOST|FLAG4 DNSSEC RSAMD5 AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aR yzWZriO6i2odGWWQVucZqKVsENW91IOW4vqudngPZsY3 GvQ/xVA8/7pyFj6b7Esga60zyGW6LFe9r8n6paHrlG5o jqf0BaqHT+8= kx01 KX 10 kdc kx02 KX 10 . loc01 LOC 60 9 N 24 39 E 10 20 2000 20 loc02 LOC 60 09 00.000 N 24 39 00.000 E 10.00m 20.00m 2000.00m 20.00m loc03 LOC 60 9 0.000 N 24 39 0.000 E 10.00m 90000000.00m 2000m 20m loc04 LOC 60 9 1.5 N 24 39 0.000 E 10.00m 90000000.00m 2000m 20m loc05 LOC 60 9 1.51 N 24 39 0.000 E 10.00m 90000000.00m 2000m 20m loc06 LOC 60 9 1 N 24 39 0.000 E 10.00m 90000000.00m 2000m 20m loc07 LOC 0 9 1 N 24 39 E 10 90000000 2000 20 loc08 LOC 0 9 1 S 24 39 0.000 E 10.00m 90000000.00m 2000m 20m ;; ;; XXXRTH These are all obsolete and unused. dnspython doesn't implement ;; them ;;mb01 MG madname ;;mb02 MG . ;;mg01 MG mgmname ;;mg02 MG . ;;minfo01 MINFO rmailbx emailbx ;;minfo02 MINFO . . ;;mr01 MR mrname ;;mr02 MR . mx01 MX 10 mail mx02 MX 10 . naptr01 NAPTR 0 0 "" "" "" . naptr02 NAPTR 65535 65535 "blurgh" "blorf" "blegh" foo. nsap-ptr01 NSAP-PTR foo. NSAP-PTR . nsap01 NSAP 0x47000580005a0000000001e133ffffff00016100 nsap02 NSAP 0x47.000580005a0000000001e133ffffff000161.00 ;nxt01 NXT a.secure ( NS SOA MX SIG KEY LOC NXT ) ;nxt02 NXT . ( NSAP-PTR NXT ) ;nxt03 NXT . ( A ) ;nxt04 NXT . ( 127 ) ptr01 PTR example. px01 PX 65535 foo. bar. px02 PX 65535 . . rp01 RP mbox-dname txt-dname rp02 RP . . rt01 RT 0 intermediate-host rt02 RT 65535 . $TTL 300 ; 5 minutes s NS ns.s $ORIGIN s.example. ns A 73.80.65.49 $ORIGIN example. $TTL 3600 ; 1 hour ;sig01 SIG NXT 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz45IkskceFGgi WCn/GxHhai6VAuHAoNUz4YoU1tVfSCSqQYn6//11U6Nl d80jEeC8aTrO+KKmCaY= srv01 SRV 0 0 0 . srv02 SRV 65535 65535 65535 old-slow-box.example.com. $TTL 301 ; 5 minutes 1 second t A 73.80.65.49 $TTL 3600 ; 1 hour tlsa1 TLSA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065 tlsa2 TLSA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955 tlsa3 TLSA 1 0 2 81ee7f6c0ecc6b09b7785a9418f54432de630dd54dc6ee9e3c49de547708d236d4c413c3e97e44f969e635958aa410495844127c04883503e5b024cf7a8f6a94 smimea1 SMIMEA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065 smimea2 SMIMEA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955 smimea3 SMIMEA 1 0 2 81ee7f6c0ecc6b09b7785a9418f54432de630dd54dc6ee9e3c49de547708d236d4c413c3e97e44f969e635958aa410495844127c04883503e5b024cf7a8f6a94 txt01 TXT "foo" txt02 TXT "foo" "bar" txt03 TXT foo txt04 TXT foo bar txt05 TXT "foo bar" txt06 TXT "foo\032bar" txt07 TXT foo\032bar txt08 TXT "foo\010bar" txt09 TXT foo\010bar txt10 TXT foo\ bar txt11 TXT "\"foo\"" txt12 TXT \"foo\" txt13 TXT "foo;" txt14 TXT "foo\;" txt15 TXT "bar\\;" $TTL 300 ; 5 minutes u TXT "txt-not-in-nxt" $ORIGIN u.example. a A 73.80.65.49 b A 73.80.65.49 $ORIGIN example. $TTL 3600 ; 1 hour wks01 WKS 10.0.0.1 6 ( 0 1 2 21 23 ) wks02 WKS 10.0.0.1 17 ( 0 1 2 53 ) wks03 WKS 10.0.0.2 6 ( 65535 ) x2501 X25 "123456789" ds01 DS 12345 3 1 123456789abcdef67890123456789abcdef67890 dlv01 DLV 12345 3 1 123456789abcdef67890123456789abcdef67890 apl01 APL 1:192.168.32.0/21 !1:192.168.38.0/28 apl02 APL 1:224.0.0.0/4 2:FF00:0:0:0:0:0:0:0/8 unknown2 TYPE999 \# 8 0a0000010a000001 unknown3 A \# 4 7f000002 rrsig01 RRSIG NSEC 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz45IkskceFGgi WCn/GxHhai6VAuHAoNUz4YoU1tVfSCSqQYn6//11U6Nl d80jEeC8aTrO+KKmCaY= rrsig02 RRSIG NSEC 1 3 3600 1577836800 1041379200 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz45IkskceFGgi WCn/GxHhai6VAuHAoNUz4YoU1tVfSCSqQYn6//11U6Nl d80jEeC8aTrO+KKmCaY= nsec01 NSEC a.secure A MX RRSIG NSEC TYPE1234 nsec02 NSEC . ( NSAP-PTR NSEC ) nsec03 NSEC . ( NSEC TYPE65535 ) nsec301 NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr MX DNSKEY NS SOA NSEC3PARAM RRSIG nsec302 NSEC3 1 1 12 - 2t7b4g4vsa5smi47k61mv5bv1a22bojr MX DNSKEY NS SOA NSEC3PARAM RRSIG nsec303 NSEC3 1 1 1 abcd alkmaao A dnskey01 DNSKEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aR yzWZriO6i2odGWWQVucZqKVsENW91IOW4vqudngPZsY3 GvQ/xVA8/7pyFj6b7Esga60zyGW6LFe9r8n6paHrlG5o jqf0BaqHT+8= dnskey02 DNSKEY 257 3 RSAMD5 ( AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aR yzWZriO6i2odGWWQVucZqKVsENW91IOW4vqudngPZsY3 GvQ/xVA8/7pyFj6b7Esga60zyGW6LFe9r8n6paHrlG5o jqf0BaqHT+8= ) sshfp1 SSHFP 1 1 aa549bfe898489c02d1715d97d79c57ba2fa76ab spf SPF "v=spf1 mx -all" ipseckey01 IPSECKEY 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== ipseckey02 IPSECKEY 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== ipseckey03 IPSECKEY 10 3 2 mygateway.example.com. AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== ipseckey04 IPSECKEY 10 2 2 2001:0DB8:0:8002::2000:1 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== ipseckey05 IPSECKEY 10 3 2 mygateway2 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== hip01 HIP 2 200100107B1A74DF365639CC39F1D578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D hip02 HIP 2 200100107B1A74DF365639CC39F1D578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com. hip03 HIP 2 200100107B1A74DF365639CC39F1D578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs1.example.com. rvs2.example.com. cds01 CDS 12345 3 1 123456789abcdef67890123456789abcdef67890 cdnskey01 CDNSKEY 256 3 8 ( AwEAAbmiLgh411Pz3v3XCSBrvYf52A/Gv55ItN1NbOLH Cqt3Ec3p+VB/kQ87VjjMrycanZFnZT4l9uCFuYh21Ccy xVpcxExbM0UuhX5rJoDyeFSXoQlkHrB01osPl5Vri5Ym KtcmqGxZ9An0VSunohkyiX1SrNRZSdQnk9/pIHDe/c8D ) nid01 NID 10 0014:4fff:ff20:ee64 nid02 NID 20 0015:5fff:ff21:ee65 nid03 NID 10 0016:6fff:ff22:ee66 l3201 L32 10 10.1.2.0 l3202 L32 20 10.1.4.0 l3203 L32 10 10.1.8.0 l6401 L64 10 2001:0DB8:1140:1000 l6402 L64 20 2001:0DB8:2140:2000 l6403 L64 10 2001:0DB8:4140:4000 lp01 LP 10 l64-subnet1.example.com. lp02 LP 10 l64-subnet2.example.com. lp03 LP 20 l32-subnet1.example.com. eui48 EUI48 00-00-5e-00-53-2a eui64 EUI64 00-00-5e-ef-10-00-00-2a uri01 URI 10 1 "ftp://ftp1.example.com/public" uri02 URI 10 1 "http://www.example.com/path" caa01 CAA 0 issue "ca.example.net" caa02 CAA 0 iodef "mailto:security@example.com" caa03 CAA 0 iodef "http://iodef.example.com/" caa04 CAA 0 issue "ca.example.net; account=230123" caa05 CAA 0 issue "ca.example.net; policy=ev" caa06 CAA 128 tbs "Unknown" openpgpkey OPENPGPKEY ( mQENBEteQDsBCADYnatn9+5t43AdJlVk9dZC2RM0idPQcmrrKcjeAWDnISqoJzkv Q8ifX6mefquTBsDZC279uXShyTffYzQtvP2r9ewkK7zmSv52Ar563TSULAMwiLpe 0gGQE0ex20mX5ggtYn6czdbEtcKpW0t+AfDqRk5YcpgqfZKXapKQ+A3CwWJKP9i3 ldx2Jz//kuru4YqROLBYyB8D6V2jNUFOdaP6j5C5prh9dxfYFp2O/xFeAKLWlWuH 9o96INUoIhgdEyj9PHPT3c821NMZu8tCvsZgUB+QPbHA/QYGa+aollcdGkJpVxXo Hhbu6aMx/B+pXg55WM5pqOxmoVjyViHIUYfPABEBAAG0IUJvYiBIYWxsZXkgPGhh bGxleUBkbnNweXRob24ub3JnPokBPgQTAQIAKAUCS15AOwIbAwUJA8JnAAYLCQgH AwIGFQgCCQoLBBYCAwECHgECF4AACgkQ6o6Gb8yUnXaflQgAhlhIqZGncRw3LV3d 24JmPD+UEcEGiVh2b/Ic/1TMec46Ts7ZqRXAcOATNteQmpzqexx+BRKDWU8ZgYx1 2J4GZmC06jABr2JDWxgvbMX9qjkUUgDGZZgAS/B2x5AmKgy2ZnCUlaKfePcKmtKT B9yNJ8v/WERlFdGaUveEUiFU8g75xp1Hj9Wp9sXCg9yeG1K2RwQ3RQd5tLudhyE6 7EQdFGgqQFynR53md7cmVhAGopKLwMkpCtToKUlxxlfnDfpKZhhXThmhA0PsUQUk JptfGwYwH3O2N3KzfUw3wXRvLa3hona3TlHk3kfg7Qyd7oP4AZGbJKp97YHnfqo1 kp8rObkBDQRLXkA7AQgA0ePG7g5GgZ/1SdtGZlJJiE2X15vTUc3KGfmx/kI5NaUD u4fXb+XK+yFy9I/X+UJ46JSkyhj6QvUxpoI+A7WWk9ThfjbynoZxRD820Kbqidqx BSgtFF36SRWzmX8DZfKKAskT9ZGU1odeSKDXLCJF7qAbZVRTuFRiDFGwtoVIICeE 6Xd65JO6ufhad+ELhgFt95vRwTiFvVrBRjwF7ZgN/nOXfYncxZ/2mpFqfwsnB2eu 0A2XZBm8IngsSmr/Wrz1RQ7+SNMqt77E7CKwBX7UIAZgyoJxIRxWirJoOt1rIm5V UqRR25ubXLuzx9PaHYiC5GiQIU45pWAd0IWcTI/MJQARAQABiQElBBgBAgAPBQJL XkA7AhsMBQkDwmcAAAoJEOqOhm/MlJ12HRsIAKrB9E++9X9W6VTXBfdkShCFv0yk ZVn2eVs6tkqzoub9s4f+Z5ylWw+a5nkMDMdGVe6bn4A3oIAbf0Tjykq1AetZLVPs Hl/QosTbSQluis/PEvJkTQXHaKHB3bFhwA90c/3HNhrLGugt9AmcfLf9LAynXDgN LV5eYdPYqfKE+27qjEBARf6PYh/8WQ8CPKS8DILFbwCZbRxUogyrZf/7AiHAGdJi 8dmpR1WPQYef2hF3kqGX6NngLBPzZ6CQRaHBhD4pHU1S/IRSlx9/3Ytww32PYD9A yO732NmCUcq3bmvqcOWy4Cc1NkEwU0Vg0qzwVBNGb84v/ex2MouwtAYScwc= ) amtrelay01 AMTRELAY 0 0 0 . amtrelay02 AMTRELAY 0 1 0 . amtrelay03 AMTRELAY 10 0 1 203.0.113.15 amtrelay04 AMTRELAY 10 0 2 2001:db8::15 amtrelay05 AMTRELAY 128 1 3 amtrelays.example.com. csync0 CSYNC 12345 0 A MX RRSIG NSEC TYPE1234 avc01 AVC "app-name:WOLFGANG|app-class:OAM|business=yes" zonemd01 ZONEMD 2018031900 1 1 62e6cf51b02e54b9 b5f967d547ce4313 6792901f9f88e637 493daaf401c92c27 9dd10f0edb1c56f8 080211f8480ee306 zonemd02 ZONEMD 2018031900 1 2 08cfa1115c7b948c 4163a901270395ea 226a930cd2cbcf2f a9a5e6eb85f37c8a 4e114d884e66f176 eab121cb02db7d65 2e0cc4827e7a3204 f166b47e5613fd27 zonemd03 ZONEMD 2018031900 1 240 e2d523f654b9422a 96c5a8f44607bbee zonemd04 ZONEMD 2018031900 241 1 e1846540e33a9e41 89792d18d5d131f6 05fc283e aaaaaaaa aaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaa aaaaaaaaaaaaaaa svcb01 SVCB ( 100 foo.com. mandatory="alpn,port" alpn="h2,h3" no-default-alpn port="12345" ech="abcd" ipv4hint=1.2.3.4,4.3.2.1 ipv6hint=1::2,3::4 key12345="foo" ) svcb02 SVCB 16 foo.example.org. alpn="foo\\,bar,h2" svcb03 SVCB 16 foo.example.org. alpn=foo\092,bar,h2 svcb04 SVCB 16 foo.example.org. dohpath=/dns-query{?dns} svcb05 SVCB 16 foo.example.org. ohttp https01 HTTPS 0 svc https02 HTTPS 1 . port=8002 ech="abcd" resinfo RESINFO qnamemin exterr=15,16,17 infourl=https://resolver.example.com/guide wallet WALLET EXAMPLE 01234567890abcdef dnspython-2.7.0/tests/example1.good0000644000000000000000000002616513615410400014230 0ustar00@ 300 IN SOA ns1 hostmaster 1 2 3 4 5 @ 300 IN NS ns1 @ 300 IN NS ns2 @ 300 IN NSEC3PARAM 1 1 12 aabbccdd @ 300 IN NSEC3PARAM 1 1 12 - * 300 IN MX 10 mail a 300 IN TXT "foo foo foo" a 300 IN PTR foo.net. a01 3600 IN A 0.0.0.0 a02 3600 IN A 255.255.255.255 aaaa01 3600 IN AAAA ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff aaaa02 3600 IN AAAA ::1 afsdb01 3600 IN AFSDB 0 hostname afsdb02 3600 IN AFSDB 65535 . amtrelay01 3600 IN AMTRELAY 0 0 0 . amtrelay02 3600 IN AMTRELAY 0 1 0 . amtrelay03 3600 IN AMTRELAY 10 0 1 203.0.113.15 amtrelay04 3600 IN AMTRELAY 10 0 2 2001:db8::15 amtrelay05 3600 IN AMTRELAY 128 1 3 amtrelays.example.com. apl01 3600 IN APL 1:192.168.32.0/21 !1:192.168.38.0/28 apl02 3600 IN APL 1:224.0.0.0/4 2:ff00::/8 avc01 3600 IN AVC "app-name:WOLFGANG|app-class:OAM|business=yes" b 300 IN CNAME foo.net. c 300 IN A 73.80.65.49 caa01 3600 IN CAA 0 issue "ca.example.net" caa02 3600 IN CAA 0 iodef "mailto:security@example.com" caa03 3600 IN CAA 0 iodef "http://iodef.example.com/" caa04 3600 IN CAA 0 issue "ca.example.net; account=230123" caa05 3600 IN CAA 0 issue "ca.example.net; policy=ev" caa06 3600 IN CAA 128 tbs "Unknown" cdnskey01 3600 IN CDNSKEY 256 3 8 AwEAAbmiLgh411Pz3v3XCSBrvYf52A/G v55ItN1NbOLHCqt3Ec3p+VB/kQ87VjjM rycanZFnZT4l9uCFuYh21CcyxVpcxExb M0UuhX5rJoDyeFSXoQlkHrB01osPl5Vr i5YmKtcmqGxZ9An0VSunohkyiX1SrNRZ SdQnk9/pIHDe/c8D cds01 3600 IN CDS 12345 3 1 123456789abcdef67890123456789abcdef67890 cert01 3600 IN CERT 65534 65535 PRIVATEOID MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= cname01 3600 IN CNAME cname-target. cname02 3600 IN CNAME cname-target cname03 3600 IN CNAME . csync0 3600 IN CSYNC 12345 0 A MX RRSIG NSEC TYPE1234 d 300 IN A 73.80.65.49 dhcid01 3600 IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69 lOjxfNuVAA2kjEA= dhcid02 3600 IN DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQd WL3b/NaiUDlW2No= dhcid03 3600 IN DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6 VytcKD//7es/deY= dlv01 3600 IN DLV 12345 3 1 123456789abcdef67890123456789abcdef67890 dname01 3600 IN DNAME dname-target. dname02 3600 IN DNAME dname-target dname03 3600 IN DNAME . dnskey01 3600 IN DNSKEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= dnskey02 3600 IN DNSKEY 257 3 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= ds01 3600 IN DS 12345 3 1 123456789abcdef67890123456789abcdef67890 e 300 IN MX 10 mail e 300 IN TXT "one" e 300 IN TXT "three" e 300 IN TXT "two" e 300 IN A 73.80.65.49 e 300 IN A 73.80.65.50 e 300 IN A 73.80.65.52 e 300 IN A 73.80.65.51 eui48 3600 IN EUI48 00-00-5e-00-53-2a eui64 3600 IN EUI64 00-00-5e-ef-10-00-00-2a f 300 IN A 73.80.65.52 gpos01 3600 IN GPOS -22.6882 116.8652 250.0 hinfo01 3600 IN HINFO "Generic PC clone" "NetBSD-1.4" hinfo02 3600 IN HINFO "PC" "NetBSD" hip01 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D hip02 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com. hip03 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs1.example.com. rvs2.example.com. https01 3600 IN HTTPS 0 svc https02 3600 IN HTTPS 1 . port="8002" ech="abcd" ipseckey01 3600 IN IPSECKEY 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== ipseckey02 3600 IN IPSECKEY 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== ipseckey03 3600 IN IPSECKEY 10 3 2 mygateway.example.com. AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== ipseckey04 3600 IN IPSECKEY 10 2 2 2001:0DB8:0:8002::2000:1 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== ipseckey05 3600 IN IPSECKEY 10 3 2 mygateway2 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== isdn01 3600 IN ISDN "isdn-address" isdn02 3600 IN ISDN "isdn-address" "subaddress" isdn03 3600 IN ISDN "isdn-address" isdn04 3600 IN ISDN "isdn-address" "subaddress" kx01 3600 IN KX 10 kdc kx02 3600 IN KX 10 . l3201 3600 IN L32 10 10.1.2.0 l3202 3600 IN L32 20 10.1.4.0 l3203 3600 IN L32 10 10.1.8.0 l6401 3600 IN L64 10 2001:0DB8:1140:1000 l6402 3600 IN L64 20 2001:0DB8:2140:2000 l6403 3600 IN L64 10 2001:0DB8:4140:4000 loc01 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m loc02 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m loc03 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc04 3600 IN LOC 60 9 1.500 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc05 3600 IN LOC 60 9 1.510 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc06 3600 IN LOC 60 9 1.000 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc07 3600 IN LOC 0 9 1.000 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc08 3600 IN LOC 0 9 1.000 S 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m lp01 3600 IN LP 10 l64-subnet1.example.com. lp02 3600 IN LP 10 l64-subnet2.example.com. lp03 3600 IN LP 20 l32-subnet1.example.com. mx01 3600 IN MX 10 mail mx02 3600 IN MX 10 . naptr01 3600 IN NAPTR 0 0 "" "" "" . naptr02 3600 IN NAPTR 65535 65535 "blurgh" "blorf" "blegh" foo. nid01 3600 IN NID 10 0014:4fff:ff20:ee64 nid02 3600 IN NID 20 0015:5fff:ff21:ee65 nid03 3600 IN NID 10 0016:6fff:ff22:ee66 ns1 300 IN A 10.53.0.1 ns2 300 IN A 10.53.0.2 nsap-ptr01 3600 IN NSAP-PTR foo. nsap-ptr01 3600 IN NSAP-PTR . nsap01 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 nsap02 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 nsec01 3600 IN NSEC a.secure A MX RRSIG NSEC TYPE1234 nsec02 3600 IN NSEC . NSAP-PTR NSEC nsec03 3600 IN NSEC . NSEC TYPE65535 nsec301 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM nsec302 3600 IN NSEC3 1 1 12 - 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM nsec303 3600 IN NSEC3 1 1 1 abcd alkmaao A openpgpkey 3600 IN OPENPGPKEY mQENBEteQDsBCADYnatn9+5t43AdJlVk9dZC2RM0idPQcmrrKcjeAWDnISqoJzkvQ8ifX6mefquTBsDZC279uXShyTffYzQtvP2r9ewkK7zmSv52Ar563TSULAMwiLpe0gGQE0ex20mX5ggtYn6czdbEtcKpW0t+AfDqRk5YcpgqfZKXapKQ+A3CwWJKP9i3ldx2Jz//kuru4YqROLBYyB8D6V2jNUFOdaP6j5C5prh9dxfYFp2O/xFeAKLWlWuH9o96INUoIhgdEyj9PHPT3c821NMZu8tCvsZgUB+QPbHA/QYGa+aollcdGkJpVxXoHhbu6aMx/B+pXg55WM5pqOxmoVjyViHIUYfPABEBAAG0IUJvYiBIYWxsZXkgPGhhbGxleUBkbnNweXRob24ub3JnPokBPgQTAQIAKAUCS15AOwIbAwUJA8JnAAYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ6o6Gb8yUnXaflQgAhlhIqZGncRw3LV3d24JmPD+UEcEGiVh2b/Ic/1TMec46Ts7ZqRXAcOATNteQmpzqexx+BRKDWU8ZgYx12J4GZmC06jABr2JDWxgvbMX9qjkUUgDGZZgAS/B2x5AmKgy2ZnCUlaKfePcKmtKTB9yNJ8v/WERlFdGaUveEUiFU8g75xp1Hj9Wp9sXCg9yeG1K2RwQ3RQd5tLudhyE67EQdFGgqQFynR53md7cmVhAGopKLwMkpCtToKUlxxlfnDfpKZhhXThmhA0PsUQUkJptfGwYwH3O2N3KzfUw3wXRvLa3hona3TlHk3kfg7Qyd7oP4AZGbJKp97YHnfqo1kp8rObkBDQRLXkA7AQgA0ePG7g5GgZ/1SdtGZlJJiE2X15vTUc3KGfmx/kI5NaUDu4fXb+XK+yFy9I/X+UJ46JSkyhj6QvUxpoI+A7WWk9ThfjbynoZxRD820KbqidqxBSgtFF36SRWzmX8DZfKKAskT9ZGU1odeSKDXLCJF7qAbZVRTuFRiDFGwtoVIICeE6Xd65JO6ufhad+ELhgFt95vRwTiFvVrBRjwF7ZgN/nOXfYncxZ/2mpFqfwsnB2eu0A2XZBm8IngsSmr/Wrz1RQ7+SNMqt77E7CKwBX7UIAZgyoJxIRxWirJoOt1rIm5VUqRR25ubXLuzx9PaHYiC5GiQIU45pWAd0IWcTI/MJQARAQABiQElBBgBAgAPBQJLXkA7AhsMBQkDwmcAAAoJEOqOhm/MlJ12HRsIAKrB9E++9X9W6VTXBfdkShCFv0ykZVn2eVs6tkqzoub9s4f+Z5ylWw+a5nkMDMdGVe6bn4A3oIAbf0Tjykq1AetZLVPsHl/QosTbSQluis/PEvJkTQXHaKHB3bFhwA90c/3HNhrLGugt9AmcfLf9LAynXDgNLV5eYdPYqfKE+27qjEBARf6PYh/8WQ8CPKS8DILFbwCZbRxUogyrZf/7AiHAGdJi8dmpR1WPQYef2hF3kqGX6NngLBPzZ6CQRaHBhD4pHU1S/IRSlx9/3Ytww32PYD9AyO732NmCUcq3bmvqcOWy4Cc1NkEwU0Vg0qzwVBNGb84v/ex2MouwtAYScwc= ptr01 3600 IN PTR @ px01 3600 IN PX 65535 foo. bar. px02 3600 IN PX 65535 . . resinfo 3600 IN RESINFO "qnamemin" "exterr=15,16,17" "infourl=https://resolver.example.com/guide" rp01 3600 IN RP mbox-dname txt-dname rp02 3600 IN RP . . rrsig01 3600 IN RRSIG NSEC 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= rrsig02 3600 IN RRSIG NSEC 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= rt01 3600 IN RT 0 intermediate-host rt02 3600 IN RT 65535 . s 300 IN NS ns.s ns.s 300 IN A 73.80.65.49 smimea1 3600 IN SMIMEA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065 smimea2 3600 IN SMIMEA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955 smimea3 3600 IN SMIMEA 1 0 2 81ee7f6c0ecc6b09b7785a9418f54432de630dd54dc6ee9e3c49de547708d236d4c413c3e97e44f969e635958aa410495844127c04883503e5b024cf7a8f6a94 spf 3600 IN SPF "v=spf1 mx -all" srv01 3600 IN SRV 0 0 0 . srv02 3600 IN SRV 65535 65535 65535 old-slow-box.example.com. sshfp1 3600 IN SSHFP 1 1 aa549bfe898489c02d1715d97d79c57ba2fa76ab svcb01 3600 IN SVCB 100 foo.com. mandatory="alpn,port" alpn="h2,h3" no-default-alpn port="12345" ipv4hint="1.2.3.4,4.3.2.1" ech="abcd" ipv6hint="1::2,3::4" key12345="foo" svcb02 3600 IN SVCB 16 foo.example.org. alpn="foo\\,bar,h2" svcb03 3600 IN SVCB 16 foo.example.org. alpn="foo\\,bar,h2" svcb04 3600 IN SVCB 16 foo.example.org. dohpath="/dns-query{?dns}" svcb05 3600 IN SVCB 16 foo.example.org. ohttp t 301 IN A 73.80.65.49 tlsa1 3600 IN TLSA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065 tlsa2 3600 IN TLSA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955 tlsa3 3600 IN TLSA 1 0 2 81ee7f6c0ecc6b09b7785a9418f54432de630dd54dc6ee9e3c49de547708d236d4c413c3e97e44f969e635958aa410495844127c04883503e5b024cf7a8f6a94 txt01 3600 IN TXT "foo" txt02 3600 IN TXT "foo" "bar" txt03 3600 IN TXT "foo" txt04 3600 IN TXT "foo" "bar" txt05 3600 IN TXT "foo bar" txt06 3600 IN TXT "foo bar" txt07 3600 IN TXT "foo bar" txt08 3600 IN TXT "foo\010bar" txt09 3600 IN TXT "foo\010bar" txt10 3600 IN TXT "foo bar" txt11 3600 IN TXT "\"foo\"" txt12 3600 IN TXT "\"foo\"" txt13 3600 IN TXT "foo;" txt14 3600 IN TXT "foo;" txt15 3600 IN TXT "bar\\;" u 300 IN TXT "txt-not-in-nxt" a.u 300 IN A 73.80.65.49 b.u 300 IN A 73.80.65.49 unknown2 3600 IN TYPE999 \# 8 0a0000010a000001 unknown3 3600 IN A 127.0.0.2 uri01 3600 IN URI 10 1 "ftp://ftp1.example.com/public" uri02 3600 IN URI 10 1 "http://www.example.com/path" wallet 3600 IN WALLET "EXAMPLE" "01234567890abcdef" wks01 3600 IN WKS 10.0.0.1 6 0 1 2 21 23 wks02 3600 IN WKS 10.0.0.1 17 0 1 2 53 wks03 3600 IN WKS 10.0.0.2 6 65535 x2501 3600 IN X25 "123456789" zonemd01 3600 IN ZONEMD 2018031900 1 1 62e6cf51b02e54b9b5f967d547ce43136792901f9f88e637493daaf401c92c279dd10f0edb1c56f8080211f8480ee306 zonemd02 3600 IN ZONEMD 2018031900 1 2 08cfa1115c7b948c4163a901270395ea226a930cd2cbcf2fa9a5e6eb85f37c8a4e114d884e66f176eab121cb02db7d652e0cc4827e7a3204f166b47e5613fd27 zonemd03 3600 IN ZONEMD 2018031900 1 240 e2d523f654b9422a96c5a8f44607bbee zonemd04 3600 IN ZONEMD 2018031900 241 1 e1846540e33a9e4189792d18d5d131f605fc283eaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa dnspython-2.7.0/tests/example2.good0000644000000000000000000003150413615410400014222 0ustar00example. 300 IN SOA ns1.example. hostmaster.example. 1 2 3 4 5 example. 300 IN NS ns1.example. example. 300 IN NS ns2.example. example. 300 IN NSEC3PARAM 1 1 12 aabbccdd example. 300 IN NSEC3PARAM 1 1 12 - *.example. 300 IN MX 10 mail.example. a.example. 300 IN TXT "foo foo foo" a.example. 300 IN PTR foo.net. a01.example. 3600 IN A 0.0.0.0 a02.example. 3600 IN A 255.255.255.255 aaaa01.example. 3600 IN AAAA ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff aaaa02.example. 3600 IN AAAA ::1 afsdb01.example. 3600 IN AFSDB 0 hostname.example. afsdb02.example. 3600 IN AFSDB 65535 . amtrelay01.example. 3600 IN AMTRELAY 0 0 0 . amtrelay02.example. 3600 IN AMTRELAY 0 1 0 . amtrelay03.example. 3600 IN AMTRELAY 10 0 1 203.0.113.15 amtrelay04.example. 3600 IN AMTRELAY 10 0 2 2001:db8::15 amtrelay05.example. 3600 IN AMTRELAY 128 1 3 amtrelays.example.com. apl01.example. 3600 IN APL 1:192.168.32.0/21 !1:192.168.38.0/28 apl02.example. 3600 IN APL 1:224.0.0.0/4 2:ff00::/8 avc01.example. 3600 IN AVC "app-name:WOLFGANG|app-class:OAM|business=yes" b.example. 300 IN CNAME foo.net. c.example. 300 IN A 73.80.65.49 caa01.example. 3600 IN CAA 0 issue "ca.example.net" caa02.example. 3600 IN CAA 0 iodef "mailto:security@example.com" caa03.example. 3600 IN CAA 0 iodef "http://iodef.example.com/" caa04.example. 3600 IN CAA 0 issue "ca.example.net; account=230123" caa05.example. 3600 IN CAA 0 issue "ca.example.net; policy=ev" caa06.example. 3600 IN CAA 128 tbs "Unknown" cdnskey01.example. 3600 IN CDNSKEY 256 3 8 AwEAAbmiLgh411Pz3v3XCSBrvYf52A/G v55ItN1NbOLHCqt3Ec3p+VB/kQ87VjjM rycanZFnZT4l9uCFuYh21CcyxVpcxExb M0UuhX5rJoDyeFSXoQlkHrB01osPl5Vr i5YmKtcmqGxZ9An0VSunohkyiX1SrNRZ SdQnk9/pIHDe/c8D cds01.example. 3600 IN CDS 12345 3 1 123456789abcdef67890123456789abcdef67890 cert01.example. 3600 IN CERT 65534 65535 PRIVATEOID MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= cname01.example. 3600 IN CNAME cname-target. cname02.example. 3600 IN CNAME cname-target.example. cname03.example. 3600 IN CNAME . csync0.example. 3600 IN CSYNC 12345 0 A MX RRSIG NSEC TYPE1234 d.example. 300 IN A 73.80.65.49 dhcid01.example. 3600 IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69 lOjxfNuVAA2kjEA= dhcid02.example. 3600 IN DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQd WL3b/NaiUDlW2No= dhcid03.example. 3600 IN DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6 VytcKD//7es/deY= dlv01.example. 3600 IN DLV 12345 3 1 123456789abcdef67890123456789abcdef67890 dname01.example. 3600 IN DNAME dname-target. dname02.example. 3600 IN DNAME dname-target.example. dname03.example. 3600 IN DNAME . dnskey01.example. 3600 IN DNSKEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= dnskey02.example. 3600 IN DNSKEY 257 3 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= ds01.example. 3600 IN DS 12345 3 1 123456789abcdef67890123456789abcdef67890 e.example. 300 IN MX 10 mail.example. e.example. 300 IN TXT "one" e.example. 300 IN TXT "three" e.example. 300 IN TXT "two" e.example. 300 IN A 73.80.65.49 e.example. 300 IN A 73.80.65.50 e.example. 300 IN A 73.80.65.52 e.example. 300 IN A 73.80.65.51 eui48.example. 3600 IN EUI48 00-00-5e-00-53-2a eui64.example. 3600 IN EUI64 00-00-5e-ef-10-00-00-2a f.example. 300 IN A 73.80.65.52 gpos01.example. 3600 IN GPOS -22.6882 116.8652 250.0 hinfo01.example. 3600 IN HINFO "Generic PC clone" "NetBSD-1.4" hinfo02.example. 3600 IN HINFO "PC" "NetBSD" hip01.example. 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D hip02.example. 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com. hip03.example. 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs1.example.com. rvs2.example.com. https01.example. 3600 IN HTTPS 0 svc.example. https02.example. 3600 IN HTTPS 1 . port="8002" ech="abcd" ipseckey01.example. 3600 IN IPSECKEY 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== ipseckey02.example. 3600 IN IPSECKEY 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== ipseckey03.example. 3600 IN IPSECKEY 10 3 2 mygateway.example.com. AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== ipseckey04.example. 3600 IN IPSECKEY 10 2 2 2001:0DB8:0:8002::2000:1 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== ipseckey05.example. 3600 IN IPSECKEY 10 3 2 mygateway2.example. AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== isdn01.example. 3600 IN ISDN "isdn-address" isdn02.example. 3600 IN ISDN "isdn-address" "subaddress" isdn03.example. 3600 IN ISDN "isdn-address" isdn04.example. 3600 IN ISDN "isdn-address" "subaddress" kx01.example. 3600 IN KX 10 kdc.example. kx02.example. 3600 IN KX 10 . l3201.example. 3600 IN L32 10 10.1.2.0 l3202.example. 3600 IN L32 20 10.1.4.0 l3203.example. 3600 IN L32 10 10.1.8.0 l6401.example. 3600 IN L64 10 2001:0DB8:1140:1000 l6402.example. 3600 IN L64 20 2001:0DB8:2140:2000 l6403.example. 3600 IN L64 10 2001:0DB8:4140:4000 loc01.example. 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m loc02.example. 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m loc03.example. 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc04.example. 3600 IN LOC 60 9 1.500 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc05.example. 3600 IN LOC 60 9 1.510 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc06.example. 3600 IN LOC 60 9 1.000 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc07.example. 3600 IN LOC 0 9 1.000 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc08.example. 3600 IN LOC 0 9 1.000 S 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m lp01.example. 3600 IN LP 10 l64-subnet1.example.com. lp02.example. 3600 IN LP 10 l64-subnet2.example.com. lp03.example. 3600 IN LP 20 l32-subnet1.example.com. mx01.example. 3600 IN MX 10 mail.example. mx02.example. 3600 IN MX 10 . naptr01.example. 3600 IN NAPTR 0 0 "" "" "" . naptr02.example. 3600 IN NAPTR 65535 65535 "blurgh" "blorf" "blegh" foo. nid01.example. 3600 IN NID 10 0014:4fff:ff20:ee64 nid02.example. 3600 IN NID 20 0015:5fff:ff21:ee65 nid03.example. 3600 IN NID 10 0016:6fff:ff22:ee66 ns1.example. 300 IN A 10.53.0.1 ns2.example. 300 IN A 10.53.0.2 nsap-ptr01.example. 3600 IN NSAP-PTR foo. nsap-ptr01.example. 3600 IN NSAP-PTR . nsap01.example. 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 nsap02.example. 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 nsec01.example. 3600 IN NSEC a.secure.example. A MX RRSIG NSEC TYPE1234 nsec02.example. 3600 IN NSEC . NSAP-PTR NSEC nsec03.example. 3600 IN NSEC . NSEC TYPE65535 nsec301.example. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM nsec302.example. 3600 IN NSEC3 1 1 12 - 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM nsec303.example. 3600 IN NSEC3 1 1 1 abcd alkmaao A openpgpkey.example. 3600 IN OPENPGPKEY mQENBEteQDsBCADYnatn9+5t43AdJlVk9dZC2RM0idPQcmrrKcjeAWDnISqoJzkvQ8ifX6mefquTBsDZC279uXShyTffYzQtvP2r9ewkK7zmSv52Ar563TSULAMwiLpe0gGQE0ex20mX5ggtYn6czdbEtcKpW0t+AfDqRk5YcpgqfZKXapKQ+A3CwWJKP9i3ldx2Jz//kuru4YqROLBYyB8D6V2jNUFOdaP6j5C5prh9dxfYFp2O/xFeAKLWlWuH9o96INUoIhgdEyj9PHPT3c821NMZu8tCvsZgUB+QPbHA/QYGa+aollcdGkJpVxXoHhbu6aMx/B+pXg55WM5pqOxmoVjyViHIUYfPABEBAAG0IUJvYiBIYWxsZXkgPGhhbGxleUBkbnNweXRob24ub3JnPokBPgQTAQIAKAUCS15AOwIbAwUJA8JnAAYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ6o6Gb8yUnXaflQgAhlhIqZGncRw3LV3d24JmPD+UEcEGiVh2b/Ic/1TMec46Ts7ZqRXAcOATNteQmpzqexx+BRKDWU8ZgYx12J4GZmC06jABr2JDWxgvbMX9qjkUUgDGZZgAS/B2x5AmKgy2ZnCUlaKfePcKmtKTB9yNJ8v/WERlFdGaUveEUiFU8g75xp1Hj9Wp9sXCg9yeG1K2RwQ3RQd5tLudhyE67EQdFGgqQFynR53md7cmVhAGopKLwMkpCtToKUlxxlfnDfpKZhhXThmhA0PsUQUkJptfGwYwH3O2N3KzfUw3wXRvLa3hona3TlHk3kfg7Qyd7oP4AZGbJKp97YHnfqo1kp8rObkBDQRLXkA7AQgA0ePG7g5GgZ/1SdtGZlJJiE2X15vTUc3KGfmx/kI5NaUDu4fXb+XK+yFy9I/X+UJ46JSkyhj6QvUxpoI+A7WWk9ThfjbynoZxRD820KbqidqxBSgtFF36SRWzmX8DZfKKAskT9ZGU1odeSKDXLCJF7qAbZVRTuFRiDFGwtoVIICeE6Xd65JO6ufhad+ELhgFt95vRwTiFvVrBRjwF7ZgN/nOXfYncxZ/2mpFqfwsnB2eu0A2XZBm8IngsSmr/Wrz1RQ7+SNMqt77E7CKwBX7UIAZgyoJxIRxWirJoOt1rIm5VUqRR25ubXLuzx9PaHYiC5GiQIU45pWAd0IWcTI/MJQARAQABiQElBBgBAgAPBQJLXkA7AhsMBQkDwmcAAAoJEOqOhm/MlJ12HRsIAKrB9E++9X9W6VTXBfdkShCFv0ykZVn2eVs6tkqzoub9s4f+Z5ylWw+a5nkMDMdGVe6bn4A3oIAbf0Tjykq1AetZLVPsHl/QosTbSQluis/PEvJkTQXHaKHB3bFhwA90c/3HNhrLGugt9AmcfLf9LAynXDgNLV5eYdPYqfKE+27qjEBARf6PYh/8WQ8CPKS8DILFbwCZbRxUogyrZf/7AiHAGdJi8dmpR1WPQYef2hF3kqGX6NngLBPzZ6CQRaHBhD4pHU1S/IRSlx9/3Ytww32PYD9AyO732NmCUcq3bmvqcOWy4Cc1NkEwU0Vg0qzwVBNGb84v/ex2MouwtAYScwc= ptr01.example. 3600 IN PTR example. px01.example. 3600 IN PX 65535 foo. bar. px02.example. 3600 IN PX 65535 . . resinfo.example. 3600 IN RESINFO "qnamemin" "exterr=15,16,17" "infourl=https://resolver.example.com/guide" rp01.example. 3600 IN RP mbox-dname.example. txt-dname.example. rp02.example. 3600 IN RP . . rrsig01.example. 3600 IN RRSIG NSEC 1 3 3600 20200101000000 20030101000000 2143 foo.example. MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= rrsig02.example. 3600 IN RRSIG NSEC 1 3 3600 20200101000000 20030101000000 2143 foo.example. MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= rt01.example. 3600 IN RT 0 intermediate-host.example. rt02.example. 3600 IN RT 65535 . s.example. 300 IN NS ns.s.example. ns.s.example. 300 IN A 73.80.65.49 smimea1.example. 3600 IN SMIMEA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065 smimea2.example. 3600 IN SMIMEA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955 smimea3.example. 3600 IN SMIMEA 1 0 2 81ee7f6c0ecc6b09b7785a9418f54432de630dd54dc6ee9e3c49de547708d236d4c413c3e97e44f969e635958aa410495844127c04883503e5b024cf7a8f6a94 spf.example. 3600 IN SPF "v=spf1 mx -all" srv01.example. 3600 IN SRV 0 0 0 . srv02.example. 3600 IN SRV 65535 65535 65535 old-slow-box.example.com. sshfp1.example. 3600 IN SSHFP 1 1 aa549bfe898489c02d1715d97d79c57ba2fa76ab svcb01.example. 3600 IN SVCB 100 foo.com. mandatory="alpn,port" alpn="h2,h3" no-default-alpn port="12345" ipv4hint="1.2.3.4,4.3.2.1" ech="abcd" ipv6hint="1::2,3::4" key12345="foo" svcb02.example. 3600 IN SVCB 16 foo.example.org. alpn="foo\\,bar,h2" svcb03.example. 3600 IN SVCB 16 foo.example.org. alpn="foo\\,bar,h2" svcb04.example. 3600 IN SVCB 16 foo.example.org. dohpath="/dns-query{?dns}" svcb05.example. 3600 IN SVCB 16 foo.example.org. ohttp t.example. 301 IN A 73.80.65.49 tlsa1.example. 3600 IN TLSA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065 tlsa2.example. 3600 IN TLSA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955 tlsa3.example. 3600 IN TLSA 1 0 2 81ee7f6c0ecc6b09b7785a9418f54432de630dd54dc6ee9e3c49de547708d236d4c413c3e97e44f969e635958aa410495844127c04883503e5b024cf7a8f6a94 txt01.example. 3600 IN TXT "foo" txt02.example. 3600 IN TXT "foo" "bar" txt03.example. 3600 IN TXT "foo" txt04.example. 3600 IN TXT "foo" "bar" txt05.example. 3600 IN TXT "foo bar" txt06.example. 3600 IN TXT "foo bar" txt07.example. 3600 IN TXT "foo bar" txt08.example. 3600 IN TXT "foo\010bar" txt09.example. 3600 IN TXT "foo\010bar" txt10.example. 3600 IN TXT "foo bar" txt11.example. 3600 IN TXT "\"foo\"" txt12.example. 3600 IN TXT "\"foo\"" txt13.example. 3600 IN TXT "foo;" txt14.example. 3600 IN TXT "foo;" txt15.example. 3600 IN TXT "bar\\;" u.example. 300 IN TXT "txt-not-in-nxt" a.u.example. 300 IN A 73.80.65.49 b.u.example. 300 IN A 73.80.65.49 unknown2.example. 3600 IN TYPE999 \# 8 0a0000010a000001 unknown3.example. 3600 IN A 127.0.0.2 uri01.example. 3600 IN URI 10 1 "ftp://ftp1.example.com/public" uri02.example. 3600 IN URI 10 1 "http://www.example.com/path" wallet.example. 3600 IN WALLET "EXAMPLE" "01234567890abcdef" wks01.example. 3600 IN WKS 10.0.0.1 6 0 1 2 21 23 wks02.example. 3600 IN WKS 10.0.0.1 17 0 1 2 53 wks03.example. 3600 IN WKS 10.0.0.2 6 65535 x2501.example. 3600 IN X25 "123456789" zonemd01.example. 3600 IN ZONEMD 2018031900 1 1 62e6cf51b02e54b9b5f967d547ce43136792901f9f88e637493daaf401c92c279dd10f0edb1c56f8080211f8480ee306 zonemd02.example. 3600 IN ZONEMD 2018031900 1 2 08cfa1115c7b948c4163a901270395ea226a930cd2cbcf2fa9a5e6eb85f37c8a4e114d884e66f176eab121cb02db7d652e0cc4827e7a3204f166b47e5613fd27 zonemd03.example. 3600 IN ZONEMD 2018031900 1 240 e2d523f654b9422a96c5a8f44607bbee zonemd04.example. 3600 IN ZONEMD 2018031900 241 1 e1846540e33a9e4189792d18d5d131f605fc283eaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa dnspython-2.7.0/tests/example3.good0000644000000000000000000002616513615410400014232 0ustar00@ 300 IN SOA ns1 hostmaster 1 2 3 4 5 @ 300 IN NS ns1 @ 300 IN NS ns2 @ 300 IN NSEC3PARAM 1 1 12 aabbccdd @ 300 IN NSEC3PARAM 1 1 12 - * 300 IN MX 10 mail a 300 IN TXT "foo foo foo" a 300 IN PTR foo.net. a01 3600 IN A 0.0.0.0 a02 3600 IN A 255.255.255.255 aaaa01 3600 IN AAAA ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff aaaa02 3600 IN AAAA ::1 afsdb01 3600 IN AFSDB 0 hostname afsdb02 3600 IN AFSDB 65535 . amtrelay01 3600 IN AMTRELAY 0 0 0 . amtrelay02 3600 IN AMTRELAY 0 1 0 . amtrelay03 3600 IN AMTRELAY 10 0 1 203.0.113.15 amtrelay04 3600 IN AMTRELAY 10 0 2 2001:db8::15 amtrelay05 3600 IN AMTRELAY 128 1 3 amtrelays.example.com. apl01 3600 IN APL 1:192.168.32.0/21 !1:192.168.38.0/28 apl02 3600 IN APL 1:224.0.0.0/4 2:ff00::/8 avc01 3600 IN AVC "app-name:WOLFGANG|app-class:OAM|business=yes" b 300 IN CNAME foo.net. c 300 IN A 73.80.65.49 caa01 3600 IN CAA 0 issue "ca.example.net" caa02 3600 IN CAA 0 iodef "mailto:security@example.com" caa03 3600 IN CAA 0 iodef "http://iodef.example.com/" caa04 3600 IN CAA 0 issue "ca.example.net; account=230123" caa05 3600 IN CAA 0 issue "ca.example.net; policy=ev" caa06 3600 IN CAA 128 tbs "Unknown" cdnskey01 3600 IN CDNSKEY 256 3 8 AwEAAbmiLgh411Pz3v3XCSBrvYf52A/G v55ItN1NbOLHCqt3Ec3p+VB/kQ87VjjM rycanZFnZT4l9uCFuYh21CcyxVpcxExb M0UuhX5rJoDyeFSXoQlkHrB01osPl5Vr i5YmKtcmqGxZ9An0VSunohkyiX1SrNRZ SdQnk9/pIHDe/c8D cds01 3600 IN CDS 12345 3 1 123456789abcdef67890123456789abcdef67890 cert01 3600 IN CERT 65534 65535 PRIVATEOID MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= cname01 3600 IN CNAME cname-target. cname02 3600 IN CNAME cname-target cname03 3600 IN CNAME . csync0 3600 IN CSYNC 12345 0 A MX RRSIG NSEC TYPE1234 d 300 IN A 73.80.65.49 dhcid01 3600 IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69 lOjxfNuVAA2kjEA= dhcid02 3600 IN DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQd WL3b/NaiUDlW2No= dhcid03 3600 IN DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6 VytcKD//7es/deY= dlv01 3600 IN DLV 12345 3 1 123456789abcdef67890123456789abcdef67890 dname01 3600 IN DNAME dname-target. dname02 3600 IN DNAME dname-target dname03 3600 IN DNAME . dnskey01 3600 IN DNSKEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= dnskey02 3600 IN DNSKEY 257 3 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= ds01 3600 IN DS 12345 3 1 123456789abcdef67890123456789abcdef67890 e 300 IN MX 10 mail e 300 IN TXT "one" e 300 IN TXT "three" e 300 IN TXT "two" e 300 IN A 73.80.65.49 e 300 IN A 73.80.65.50 e 300 IN A 73.80.65.52 e 300 IN A 73.80.65.51 eui48 3600 IN EUI48 00-00-5e-00-53-2a eui64 3600 IN EUI64 00-00-5e-ef-10-00-00-2a f 300 IN A 73.80.65.52 gpos01 3600 IN GPOS -22.6882 116.8652 250.0 hinfo01 3600 IN HINFO "Generic PC clone" "NetBSD-1.4" hinfo02 3600 IN HINFO "PC" "NetBSD" hip01 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D hip02 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com. hip03 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs1.example.com. rvs2.example.com. https01 3600 IN HTTPS 0 svc https02 3600 IN HTTPS 1 . port="8002" ech="abcd" ipseckey01 3600 IN IPSECKEY 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== ipseckey02 3600 IN IPSECKEY 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== ipseckey03 3600 IN IPSECKEY 10 3 2 mygateway.example.com. AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== ipseckey04 3600 IN IPSECKEY 10 2 2 2001:0DB8:0:8002::2000:1 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== ipseckey05 3600 IN IPSECKEY 10 3 2 mygateway2 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== isdn01 3600 IN ISDN "isdn-address" isdn02 3600 IN ISDN "isdn-address" "subaddress" isdn03 3600 IN ISDN "isdn-address" isdn04 3600 IN ISDN "isdn-address" "subaddress" kx01 3600 IN KX 10 kdc kx02 3600 IN KX 10 . l3201 3600 IN L32 10 10.1.2.0 l3202 3600 IN L32 20 10.1.4.0 l3203 3600 IN L32 10 10.1.8.0 l6401 3600 IN L64 10 2001:0DB8:1140:1000 l6402 3600 IN L64 20 2001:0DB8:2140:2000 l6403 3600 IN L64 10 2001:0DB8:4140:4000 loc01 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m loc02 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m loc03 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc04 3600 IN LOC 60 9 1.500 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc05 3600 IN LOC 60 9 1.510 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc06 3600 IN LOC 60 9 1.000 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc07 3600 IN LOC 0 9 1.000 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc08 3600 IN LOC 0 9 1.000 S 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m lp01 3600 IN LP 10 l64-subnet1.example.com. lp02 3600 IN LP 10 l64-subnet2.example.com. lp03 3600 IN LP 20 l32-subnet1.example.com. mx01 3600 IN MX 10 mail mx02 3600 IN MX 10 . naptr01 3600 IN NAPTR 0 0 "" "" "" . naptr02 3600 IN NAPTR 65535 65535 "blurgh" "blorf" "blegh" foo. nid01 3600 IN NID 10 0014:4fff:ff20:ee64 nid02 3600 IN NID 20 0015:5fff:ff21:ee65 nid03 3600 IN NID 10 0016:6fff:ff22:ee66 ns1 300 IN A 10.53.0.1 ns2 300 IN A 10.53.0.2 nsap-ptr01 3600 IN NSAP-PTR foo. nsap-ptr01 3600 IN NSAP-PTR . nsap01 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 nsap02 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 nsec01 3600 IN NSEC a.secure A MX RRSIG NSEC TYPE1234 nsec02 3600 IN NSEC . NSAP-PTR NSEC nsec03 3600 IN NSEC . NSEC TYPE65535 nsec301 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM nsec302 3600 IN NSEC3 1 1 12 - 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM nsec303 3600 IN NSEC3 1 1 1 abcd alkmaao A openpgpkey 3600 IN OPENPGPKEY mQENBEteQDsBCADYnatn9+5t43AdJlVk9dZC2RM0idPQcmrrKcjeAWDnISqoJzkvQ8ifX6mefquTBsDZC279uXShyTffYzQtvP2r9ewkK7zmSv52Ar563TSULAMwiLpe0gGQE0ex20mX5ggtYn6czdbEtcKpW0t+AfDqRk5YcpgqfZKXapKQ+A3CwWJKP9i3ldx2Jz//kuru4YqROLBYyB8D6V2jNUFOdaP6j5C5prh9dxfYFp2O/xFeAKLWlWuH9o96INUoIhgdEyj9PHPT3c821NMZu8tCvsZgUB+QPbHA/QYGa+aollcdGkJpVxXoHhbu6aMx/B+pXg55WM5pqOxmoVjyViHIUYfPABEBAAG0IUJvYiBIYWxsZXkgPGhhbGxleUBkbnNweXRob24ub3JnPokBPgQTAQIAKAUCS15AOwIbAwUJA8JnAAYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ6o6Gb8yUnXaflQgAhlhIqZGncRw3LV3d24JmPD+UEcEGiVh2b/Ic/1TMec46Ts7ZqRXAcOATNteQmpzqexx+BRKDWU8ZgYx12J4GZmC06jABr2JDWxgvbMX9qjkUUgDGZZgAS/B2x5AmKgy2ZnCUlaKfePcKmtKTB9yNJ8v/WERlFdGaUveEUiFU8g75xp1Hj9Wp9sXCg9yeG1K2RwQ3RQd5tLudhyE67EQdFGgqQFynR53md7cmVhAGopKLwMkpCtToKUlxxlfnDfpKZhhXThmhA0PsUQUkJptfGwYwH3O2N3KzfUw3wXRvLa3hona3TlHk3kfg7Qyd7oP4AZGbJKp97YHnfqo1kp8rObkBDQRLXkA7AQgA0ePG7g5GgZ/1SdtGZlJJiE2X15vTUc3KGfmx/kI5NaUDu4fXb+XK+yFy9I/X+UJ46JSkyhj6QvUxpoI+A7WWk9ThfjbynoZxRD820KbqidqxBSgtFF36SRWzmX8DZfKKAskT9ZGU1odeSKDXLCJF7qAbZVRTuFRiDFGwtoVIICeE6Xd65JO6ufhad+ELhgFt95vRwTiFvVrBRjwF7ZgN/nOXfYncxZ/2mpFqfwsnB2eu0A2XZBm8IngsSmr/Wrz1RQ7+SNMqt77E7CKwBX7UIAZgyoJxIRxWirJoOt1rIm5VUqRR25ubXLuzx9PaHYiC5GiQIU45pWAd0IWcTI/MJQARAQABiQElBBgBAgAPBQJLXkA7AhsMBQkDwmcAAAoJEOqOhm/MlJ12HRsIAKrB9E++9X9W6VTXBfdkShCFv0ykZVn2eVs6tkqzoub9s4f+Z5ylWw+a5nkMDMdGVe6bn4A3oIAbf0Tjykq1AetZLVPsHl/QosTbSQluis/PEvJkTQXHaKHB3bFhwA90c/3HNhrLGugt9AmcfLf9LAynXDgNLV5eYdPYqfKE+27qjEBARf6PYh/8WQ8CPKS8DILFbwCZbRxUogyrZf/7AiHAGdJi8dmpR1WPQYef2hF3kqGX6NngLBPzZ6CQRaHBhD4pHU1S/IRSlx9/3Ytww32PYD9AyO732NmCUcq3bmvqcOWy4Cc1NkEwU0Vg0qzwVBNGb84v/ex2MouwtAYScwc= ptr01 3600 IN PTR @ px01 3600 IN PX 65535 foo. bar. px02 3600 IN PX 65535 . . resinfo 3600 IN RESINFO "qnamemin" "exterr=15,16,17" "infourl=https://resolver.example.com/guide" rp01 3600 IN RP mbox-dname txt-dname rp02 3600 IN RP . . rrsig01 3600 IN RRSIG NSEC 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= rrsig02 3600 IN RRSIG NSEC 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= rt01 3600 IN RT 0 intermediate-host rt02 3600 IN RT 65535 . s 300 IN NS ns.s ns.s 300 IN A 73.80.65.49 smimea1 3600 IN SMIMEA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065 smimea2 3600 IN SMIMEA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955 smimea3 3600 IN SMIMEA 1 0 2 81ee7f6c0ecc6b09b7785a9418f54432de630dd54dc6ee9e3c49de547708d236d4c413c3e97e44f969e635958aa410495844127c04883503e5b024cf7a8f6a94 spf 3600 IN SPF "v=spf1 mx -all" srv01 3600 IN SRV 0 0 0 . srv02 3600 IN SRV 65535 65535 65535 old-slow-box.example.com. sshfp1 3600 IN SSHFP 1 1 aa549bfe898489c02d1715d97d79c57ba2fa76ab svcb01 3600 IN SVCB 100 foo.com. mandatory="alpn,port" alpn="h2,h3" no-default-alpn port="12345" ipv4hint="1.2.3.4,4.3.2.1" ech="abcd" ipv6hint="1::2,3::4" key12345="foo" svcb02 3600 IN SVCB 16 foo.example.org. alpn="foo\\,bar,h2" svcb03 3600 IN SVCB 16 foo.example.org. alpn="foo\\,bar,h2" svcb04 3600 IN SVCB 16 foo.example.org. dohpath="/dns-query{?dns}" svcb05 3600 IN SVCB 16 foo.example.org. ohttp t 301 IN A 73.80.65.49 tlsa1 3600 IN TLSA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065 tlsa2 3600 IN TLSA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955 tlsa3 3600 IN TLSA 1 0 2 81ee7f6c0ecc6b09b7785a9418f54432de630dd54dc6ee9e3c49de547708d236d4c413c3e97e44f969e635958aa410495844127c04883503e5b024cf7a8f6a94 txt01 3600 IN TXT "foo" txt02 3600 IN TXT "foo" "bar" txt03 3600 IN TXT "foo" txt04 3600 IN TXT "foo" "bar" txt05 3600 IN TXT "foo bar" txt06 3600 IN TXT "foo bar" txt07 3600 IN TXT "foo bar" txt08 3600 IN TXT "foo\010bar" txt09 3600 IN TXT "foo\010bar" txt10 3600 IN TXT "foo bar" txt11 3600 IN TXT "\"foo\"" txt12 3600 IN TXT "\"foo\"" txt13 3600 IN TXT "foo;" txt14 3600 IN TXT "foo;" txt15 3600 IN TXT "bar\\;" u 300 IN TXT "txt-not-in-nxt" a.u 300 IN A 73.80.65.49 b.u 300 IN A 73.80.65.49 unknown2 3600 IN TYPE999 \# 8 0a0000010a000001 unknown3 3600 IN A 127.0.0.2 uri01 3600 IN URI 10 1 "ftp://ftp1.example.com/public" uri02 3600 IN URI 10 1 "http://www.example.com/path" wallet 3600 IN WALLET "EXAMPLE" "01234567890abcdef" wks01 3600 IN WKS 10.0.0.1 6 0 1 2 21 23 wks02 3600 IN WKS 10.0.0.1 17 0 1 2 53 wks03 3600 IN WKS 10.0.0.2 6 65535 x2501 3600 IN X25 "123456789" zonemd01 3600 IN ZONEMD 2018031900 1 1 62e6cf51b02e54b9b5f967d547ce43136792901f9f88e637493daaf401c92c279dd10f0edb1c56f8080211f8480ee306 zonemd02 3600 IN ZONEMD 2018031900 1 2 08cfa1115c7b948c4163a901270395ea226a930cd2cbcf2fa9a5e6eb85f37c8a4e114d884e66f176eab121cb02db7d652e0cc4827e7a3204f166b47e5613fd27 zonemd03 3600 IN ZONEMD 2018031900 1 240 e2d523f654b9422a96c5a8f44607bbee zonemd04 3600 IN ZONEMD 2018031900 241 1 e1846540e33a9e4189792d18d5d131f605fc283eaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa dnspython-2.7.0/tests/example4.good0000644000000000000000000002620613615410400014227 0ustar00$ORIGIN example. @ 300 IN SOA ns1 hostmaster 1 2 3 4 5 @ 300 IN NS ns1 @ 300 IN NS ns2 @ 300 IN NSEC3PARAM 1 1 12 aabbccdd @ 300 IN NSEC3PARAM 1 1 12 - * 300 IN MX 10 mail a 300 IN TXT "foo foo foo" a 300 IN PTR foo.net. a01 3600 IN A 0.0.0.0 a02 3600 IN A 255.255.255.255 aaaa01 3600 IN AAAA ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff aaaa02 3600 IN AAAA ::1 afsdb01 3600 IN AFSDB 0 hostname afsdb02 3600 IN AFSDB 65535 . amtrelay01 3600 IN AMTRELAY 0 0 0 . amtrelay02 3600 IN AMTRELAY 0 1 0 . amtrelay03 3600 IN AMTRELAY 10 0 1 203.0.113.15 amtrelay04 3600 IN AMTRELAY 10 0 2 2001:db8::15 amtrelay05 3600 IN AMTRELAY 128 1 3 amtrelays.example.com. apl01 3600 IN APL 1:192.168.32.0/21 !1:192.168.38.0/28 apl02 3600 IN APL 1:224.0.0.0/4 2:ff00::/8 avc01 3600 IN AVC "app-name:WOLFGANG|app-class:OAM|business=yes" b 300 IN CNAME foo.net. c 300 IN A 73.80.65.49 caa01 3600 IN CAA 0 issue "ca.example.net" caa02 3600 IN CAA 0 iodef "mailto:security@example.com" caa03 3600 IN CAA 0 iodef "http://iodef.example.com/" caa04 3600 IN CAA 0 issue "ca.example.net; account=230123" caa05 3600 IN CAA 0 issue "ca.example.net; policy=ev" caa06 3600 IN CAA 128 tbs "Unknown" cdnskey01 3600 IN CDNSKEY 256 3 8 AwEAAbmiLgh411Pz3v3XCSBrvYf52A/G v55ItN1NbOLHCqt3Ec3p+VB/kQ87VjjM rycanZFnZT4l9uCFuYh21CcyxVpcxExb M0UuhX5rJoDyeFSXoQlkHrB01osPl5Vr i5YmKtcmqGxZ9An0VSunohkyiX1SrNRZ SdQnk9/pIHDe/c8D cds01 3600 IN CDS 12345 3 1 123456789abcdef67890123456789abcdef67890 cert01 3600 IN CERT 65534 65535 PRIVATEOID MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= cname01 3600 IN CNAME cname-target. cname02 3600 IN CNAME cname-target cname03 3600 IN CNAME . csync0 3600 IN CSYNC 12345 0 A MX RRSIG NSEC TYPE1234 d 300 IN A 73.80.65.49 dhcid01 3600 IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69 lOjxfNuVAA2kjEA= dhcid02 3600 IN DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQd WL3b/NaiUDlW2No= dhcid03 3600 IN DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6 VytcKD//7es/deY= dlv01 3600 IN DLV 12345 3 1 123456789abcdef67890123456789abcdef67890 dname01 3600 IN DNAME dname-target. dname02 3600 IN DNAME dname-target dname03 3600 IN DNAME . dnskey01 3600 IN DNSKEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= dnskey02 3600 IN DNSKEY 257 3 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= ds01 3600 IN DS 12345 3 1 123456789abcdef67890123456789abcdef67890 e 300 IN MX 10 mail e 300 IN TXT "one" e 300 IN TXT "three" e 300 IN TXT "two" e 300 IN A 73.80.65.49 e 300 IN A 73.80.65.50 e 300 IN A 73.80.65.52 e 300 IN A 73.80.65.51 eui48 3600 IN EUI48 00-00-5e-00-53-2a eui64 3600 IN EUI64 00-00-5e-ef-10-00-00-2a f 300 IN A 73.80.65.52 gpos01 3600 IN GPOS -22.6882 116.8652 250.0 hinfo01 3600 IN HINFO "Generic PC clone" "NetBSD-1.4" hinfo02 3600 IN HINFO "PC" "NetBSD" hip01 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D hip02 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com. hip03 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs1.example.com. rvs2.example.com. https01 3600 IN HTTPS 0 svc https02 3600 IN HTTPS 1 . port="8002" ech="abcd" ipseckey01 3600 IN IPSECKEY 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== ipseckey02 3600 IN IPSECKEY 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== ipseckey03 3600 IN IPSECKEY 10 3 2 mygateway.example.com. AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== ipseckey04 3600 IN IPSECKEY 10 2 2 2001:0DB8:0:8002::2000:1 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== ipseckey05 3600 IN IPSECKEY 10 3 2 mygateway2 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== isdn01 3600 IN ISDN "isdn-address" isdn02 3600 IN ISDN "isdn-address" "subaddress" isdn03 3600 IN ISDN "isdn-address" isdn04 3600 IN ISDN "isdn-address" "subaddress" kx01 3600 IN KX 10 kdc kx02 3600 IN KX 10 . l3201 3600 IN L32 10 10.1.2.0 l3202 3600 IN L32 20 10.1.4.0 l3203 3600 IN L32 10 10.1.8.0 l6401 3600 IN L64 10 2001:0DB8:1140:1000 l6402 3600 IN L64 20 2001:0DB8:2140:2000 l6403 3600 IN L64 10 2001:0DB8:4140:4000 loc01 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m loc02 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m loc03 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc04 3600 IN LOC 60 9 1.500 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc05 3600 IN LOC 60 9 1.510 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc06 3600 IN LOC 60 9 1.000 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc07 3600 IN LOC 0 9 1.000 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m loc08 3600 IN LOC 0 9 1.000 S 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m lp01 3600 IN LP 10 l64-subnet1.example.com. lp02 3600 IN LP 10 l64-subnet2.example.com. lp03 3600 IN LP 20 l32-subnet1.example.com. mx01 3600 IN MX 10 mail mx02 3600 IN MX 10 . naptr01 3600 IN NAPTR 0 0 "" "" "" . naptr02 3600 IN NAPTR 65535 65535 "blurgh" "blorf" "blegh" foo. nid01 3600 IN NID 10 0014:4fff:ff20:ee64 nid02 3600 IN NID 20 0015:5fff:ff21:ee65 nid03 3600 IN NID 10 0016:6fff:ff22:ee66 ns1 300 IN A 10.53.0.1 ns2 300 IN A 10.53.0.2 nsap-ptr01 3600 IN NSAP-PTR foo. nsap-ptr01 3600 IN NSAP-PTR . nsap01 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 nsap02 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 nsec01 3600 IN NSEC a.secure A MX RRSIG NSEC TYPE1234 nsec02 3600 IN NSEC . NSAP-PTR NSEC nsec03 3600 IN NSEC . NSEC TYPE65535 nsec301 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM nsec302 3600 IN NSEC3 1 1 12 - 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM nsec303 3600 IN NSEC3 1 1 1 abcd alkmaao A openpgpkey 3600 IN OPENPGPKEY mQENBEteQDsBCADYnatn9+5t43AdJlVk9dZC2RM0idPQcmrrKcjeAWDnISqoJzkvQ8ifX6mefquTBsDZC279uXShyTffYzQtvP2r9ewkK7zmSv52Ar563TSULAMwiLpe0gGQE0ex20mX5ggtYn6czdbEtcKpW0t+AfDqRk5YcpgqfZKXapKQ+A3CwWJKP9i3ldx2Jz//kuru4YqROLBYyB8D6V2jNUFOdaP6j5C5prh9dxfYFp2O/xFeAKLWlWuH9o96INUoIhgdEyj9PHPT3c821NMZu8tCvsZgUB+QPbHA/QYGa+aollcdGkJpVxXoHhbu6aMx/B+pXg55WM5pqOxmoVjyViHIUYfPABEBAAG0IUJvYiBIYWxsZXkgPGhhbGxleUBkbnNweXRob24ub3JnPokBPgQTAQIAKAUCS15AOwIbAwUJA8JnAAYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ6o6Gb8yUnXaflQgAhlhIqZGncRw3LV3d24JmPD+UEcEGiVh2b/Ic/1TMec46Ts7ZqRXAcOATNteQmpzqexx+BRKDWU8ZgYx12J4GZmC06jABr2JDWxgvbMX9qjkUUgDGZZgAS/B2x5AmKgy2ZnCUlaKfePcKmtKTB9yNJ8v/WERlFdGaUveEUiFU8g75xp1Hj9Wp9sXCg9yeG1K2RwQ3RQd5tLudhyE67EQdFGgqQFynR53md7cmVhAGopKLwMkpCtToKUlxxlfnDfpKZhhXThmhA0PsUQUkJptfGwYwH3O2N3KzfUw3wXRvLa3hona3TlHk3kfg7Qyd7oP4AZGbJKp97YHnfqo1kp8rObkBDQRLXkA7AQgA0ePG7g5GgZ/1SdtGZlJJiE2X15vTUc3KGfmx/kI5NaUDu4fXb+XK+yFy9I/X+UJ46JSkyhj6QvUxpoI+A7WWk9ThfjbynoZxRD820KbqidqxBSgtFF36SRWzmX8DZfKKAskT9ZGU1odeSKDXLCJF7qAbZVRTuFRiDFGwtoVIICeE6Xd65JO6ufhad+ELhgFt95vRwTiFvVrBRjwF7ZgN/nOXfYncxZ/2mpFqfwsnB2eu0A2XZBm8IngsSmr/Wrz1RQ7+SNMqt77E7CKwBX7UIAZgyoJxIRxWirJoOt1rIm5VUqRR25ubXLuzx9PaHYiC5GiQIU45pWAd0IWcTI/MJQARAQABiQElBBgBAgAPBQJLXkA7AhsMBQkDwmcAAAoJEOqOhm/MlJ12HRsIAKrB9E++9X9W6VTXBfdkShCFv0ykZVn2eVs6tkqzoub9s4f+Z5ylWw+a5nkMDMdGVe6bn4A3oIAbf0Tjykq1AetZLVPsHl/QosTbSQluis/PEvJkTQXHaKHB3bFhwA90c/3HNhrLGugt9AmcfLf9LAynXDgNLV5eYdPYqfKE+27qjEBARf6PYh/8WQ8CPKS8DILFbwCZbRxUogyrZf/7AiHAGdJi8dmpR1WPQYef2hF3kqGX6NngLBPzZ6CQRaHBhD4pHU1S/IRSlx9/3Ytww32PYD9AyO732NmCUcq3bmvqcOWy4Cc1NkEwU0Vg0qzwVBNGb84v/ex2MouwtAYScwc= ptr01 3600 IN PTR @ px01 3600 IN PX 65535 foo. bar. px02 3600 IN PX 65535 . . resinfo 3600 IN RESINFO "qnamemin" "exterr=15,16,17" "infourl=https://resolver.example.com/guide" rp01 3600 IN RP mbox-dname txt-dname rp02 3600 IN RP . . rrsig01 3600 IN RRSIG NSEC 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= rrsig02 3600 IN RRSIG NSEC 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= rt01 3600 IN RT 0 intermediate-host rt02 3600 IN RT 65535 . s 300 IN NS ns.s ns.s 300 IN A 73.80.65.49 smimea1 3600 IN SMIMEA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065 smimea2 3600 IN SMIMEA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955 smimea3 3600 IN SMIMEA 1 0 2 81ee7f6c0ecc6b09b7785a9418f54432de630dd54dc6ee9e3c49de547708d236d4c413c3e97e44f969e635958aa410495844127c04883503e5b024cf7a8f6a94 spf 3600 IN SPF "v=spf1 mx -all" srv01 3600 IN SRV 0 0 0 . srv02 3600 IN SRV 65535 65535 65535 old-slow-box.example.com. sshfp1 3600 IN SSHFP 1 1 aa549bfe898489c02d1715d97d79c57ba2fa76ab svcb01 3600 IN SVCB 100 foo.com. mandatory="alpn,port" alpn="h2,h3" no-default-alpn port="12345" ipv4hint="1.2.3.4,4.3.2.1" ech="abcd" ipv6hint="1::2,3::4" key12345="foo" svcb02 3600 IN SVCB 16 foo.example.org. alpn="foo\\,bar,h2" svcb03 3600 IN SVCB 16 foo.example.org. alpn="foo\\,bar,h2" svcb04 3600 IN SVCB 16 foo.example.org. dohpath="/dns-query{?dns}" svcb05 3600 IN SVCB 16 foo.example.org. ohttp t 301 IN A 73.80.65.49 tlsa1 3600 IN TLSA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065 tlsa2 3600 IN TLSA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955 tlsa3 3600 IN TLSA 1 0 2 81ee7f6c0ecc6b09b7785a9418f54432de630dd54dc6ee9e3c49de547708d236d4c413c3e97e44f969e635958aa410495844127c04883503e5b024cf7a8f6a94 txt01 3600 IN TXT "foo" txt02 3600 IN TXT "foo" "bar" txt03 3600 IN TXT "foo" txt04 3600 IN TXT "foo" "bar" txt05 3600 IN TXT "foo bar" txt06 3600 IN TXT "foo bar" txt07 3600 IN TXT "foo bar" txt08 3600 IN TXT "foo\010bar" txt09 3600 IN TXT "foo\010bar" txt10 3600 IN TXT "foo bar" txt11 3600 IN TXT "\"foo\"" txt12 3600 IN TXT "\"foo\"" txt13 3600 IN TXT "foo;" txt14 3600 IN TXT "foo;" txt15 3600 IN TXT "bar\\;" u 300 IN TXT "txt-not-in-nxt" a.u 300 IN A 73.80.65.49 b.u 300 IN A 73.80.65.49 unknown2 3600 IN TYPE999 \# 8 0a0000010a000001 unknown3 3600 IN A 127.0.0.2 uri01 3600 IN URI 10 1 "ftp://ftp1.example.com/public" uri02 3600 IN URI 10 1 "http://www.example.com/path" wallet 3600 IN WALLET "EXAMPLE" "01234567890abcdef" wks01 3600 IN WKS 10.0.0.1 6 0 1 2 21 23 wks02 3600 IN WKS 10.0.0.1 17 0 1 2 53 wks03 3600 IN WKS 10.0.0.2 6 65535 x2501 3600 IN X25 "123456789" zonemd01 3600 IN ZONEMD 2018031900 1 1 62e6cf51b02e54b9b5f967d547ce43136792901f9f88e637493daaf401c92c279dd10f0edb1c56f8080211f8480ee306 zonemd02 3600 IN ZONEMD 2018031900 1 2 08cfa1115c7b948c4163a901270395ea226a930cd2cbcf2fa9a5e6eb85f37c8a4e114d884e66f176eab121cb02db7d652e0cc4827e7a3204f166b47e5613fd27 zonemd03 3600 IN ZONEMD 2018031900 1 240 e2d523f654b9422a96c5a8f44607bbee zonemd04 3600 IN ZONEMD 2018031900 241 1 e1846540e33a9e4189792d18d5d131f605fc283eaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa dnspython-2.7.0/tests/keys.py0000644000000000000000000002077013615410400013163 0ustar00# DNSKEY test vectors # # private keys generate by OpenSSL # DNSKEY rdata generated by Knot DNS (after PEM import) from dataclasses import dataclass from dns.dnssectypes import Algorithm @dataclass(frozen=True) class TestKey: command: str private_pem: str dnskey: str algorithm: int test_dnskeys = [ TestKey( command="openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:2048", private_pem=""" -----BEGIN PRIVATE KEY----- MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDHve8aGCaof3lX Cc6QREh9gFvtc0pIm8iZAayiRu1KNS6EH2mN27+9jbfKRETywsxGN86XH/LZEEXH C0El2YMJGwRbg7OqjUp14zEI33X/34jZZsqlHWbzJ2WBLY49K9mBengDLdQu5Ve9 8YWl+QYDoyRrTxqfEDgL7JZ0gECQuFjV//cIiovIaoKcffCGmWDY0QknPtHzn8X4 LQVx/S21uGNPZM8JcSw6fgbJ/hv+cct4x3JtrSktf2XDBH8HZZ/fbxHqSSBuQ/Y+ Jvx6twptxbY0LFALDZhidd1HZxsIf8uPkf4kfswSGEYeZQDDtQamG1q4IbRb/PZM PHtCXydrAgMBAAECggEBAK9f/r3EkrzDIADh5XIZ4iP/Pbeg0Ior7dcZ9z+MUvAi /bKX+g/J7/I4qjR3+KnFi6HjggqCzLD1bq6zHQJkln66L/tCCdAnukcDsZv+yBZf aEKp1CdhR3EbGC5xlz/ybkkXBKSV6oU6bO2jUBtIKJWs+l8V12Pt06f0lK25pfbp uCDbBDA7uIMJIFaQ1jqejaFpCROTuFyJVS5QbyMJlWBhx+TvvQbpgFltqPHji+/R 0V1CY4TI89VB/phPQJdf0bwUbvd7pOp8WL/W0NB+TzOWhOsqlmy13D30D7/IrbOu OlDOPcfOs+g+dSiloO5hnSw1+mAd8vlkFvohEZz0vhECgYEA6QxXxHwCwSZ1n4i/ h5O0QfQbZSi8piDknzgyVvZp9cH9/WFhBOErvfbm4m2XLSaCsTrtlPEeEfefv73v nMyY8dE/yPr64NZrMjLv/NfM6+fH5oyGmXcARrQD/KG703IRlq1NbzoClFcsMhuc qbgY8I1CbvlQ8iaxiKvFGD3aFz8CgYEA22nd2MpxK33DAirmUDKJr24iQ2xQM33x 39gzbPPRQKU55OqpdXk9QcMB7q6mz7i9Phqia1JqqP3qc38be14mG3R0sT6glBPg i8FUO+eTAHL6XYzd8w0daTnYmHo1xuV8+h4srsdoYrqwcESLBt3mJ2wE8eAlNk9s Qnil9ZLyMNUCgYEA3Fp2Vmtnc1g5GXqElt37L+1vRcwp6+7oHQBW4NEnyV7/GGjO An4iDQF6uBglPGTQaGGuqQj/hL+dxgACo0D1UJio9hERzCwRuapeLrWhpmFHK2Au GMdjdHbb2jDW1wxhQxZkREoWjEqMmGhxTiyrMDBw41tLxVr+vJqlxtEc+KMCgYEA n3tv+WgMomQjHqw4BAr38T/IP+G22fatnNr1ZjhC3Q476px22CBr2iT4fpkMPug1 BbMuY3vgcz088P5u51kjsckQGNVAuuFH0c2QgIpuW2E3glAl88iQnC+jtBEAjbW5 BcRxDgl7Ymf4X2Iy+6bG59ioL3eRFMzeD+LKHpnU2JECgYA7kJn1MJHeB7LYkLpS lJ9PrYW3gfGRMoeEifhTs0f4FJDqbuiT8tsrEWUOJhsBebpXR9bfMD+F8aJ6Re3d sZio5F16RuyuhwHv7agNfIcrCCXIs2xERN+q8D0Gi6LzwrtGxeaRPQnQFXo7kEOQ HzK7xZItz01yelD1En+o4m2/Dg== -----END PRIVATE KEY----- """, dnskey="256 3 8 AwEAAce97xoYJqh/eVcJzpBESH2AW+1zSkibyJkBrKJG7Uo1LoQfaY3bv72Nt8pERPLCzEY3zpcf8tkQRccLQSXZgwkbBFuDs6qNSnXjMQjfdf/fiNlmyqUdZvMnZYEtjj0r2YF6eAMt1C7lV73xhaX5BgOjJGtPGp8QOAvslnSAQJC4WNX/9wiKi8hqgpx98IaZYNjRCSc+0fOfxfgtBXH9LbW4Y09kzwlxLDp+Bsn+G/5xy3jHcm2tKS1/ZcMEfwdln99vEepJIG5D9j4m/Hq3Cm3FtjQsUAsNmGJ13UdnGwh/y4+R/iR+zBIYRh5lAMO1BqYbWrghtFv89kw8e0JfJ2s=", algorithm=Algorithm.RSASHA256, ), TestKey( command="openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:4096", private_pem=""" -----BEGIN PRIVATE KEY----- MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDI/o4RjA9g/qWS DagWOYP+tY5f3EV5P8kKP3OMx+RRC/s4JnzQXKgy/yWM3eCnPcnYy1amtr4LCpQr wZd+8DV5Tup/WZrPHQu5YoRgLb+oKnvw2NGMMbGQ6jlehA8TffuF1bRQf1TPLBRa LKRJ79SemviyHcZunqtjiv8mbDmFkMmUAFVQFnCGrdv0vk8mbkxp98UEkzwBKk4E d2wiQZAl1FWMpWUhtAeZuJC4c1tHU1xNjN4c2XmYokRvK0j396l6B0ih/gi9wOYf 6jeTl5q0lStb+N0PaeQvljyOCjo75XqMkc3cVSaZ/9ekkprSFZyV5UfS1ajj5rEk h4OH/9IyITM8eForMlZ5Rqhnpn7xvLh12oZ1AZkki2x3Vq4h8O43uVIGtKXSGk2k rHusbjevVsa5zizbHTd8oBaUrvUhOY1L8OSm0MiPrSQGRaVyQ1AyBd3qEkwAqguZ vOUYWE30DK8ToiEmjjkb1dIWsJa4DeEkuh9Ioh2HHjLYan3PopZqkRrY4ZAdL3IL HC/qIh48Nv33Et/Q5JE5aPWSlqPZN0Z/NgjgAHxssWVv/S9cmArNHExnrGijEMxP 8U2mXL8VKZTNsNI1zxIOtRjuuVvGyi1FOvD8ntM4eQ59ihEv/syr+G9eJZZwLOnF QqqCkXoBzjWwlFrAD/kXIJs0MQvLkwIDAQABAoICAQCTaB1JQS8GM7u6IcnkgsoL Q5vnMeTBx8Xpfh+AYBlSVzcnNxLSvSGeRQGFDjR0cxxVossp+VvnPRrt/EzfC8wr 63SPcWfX/bVbgKUU5HhrHL1JJbqI1ukjHqR0bOWhpgORY+maH8hTKEDE4XibwQhu Sbma57tf5X5MwuPdigGls0ojARuQYOSl4VwvYmMqDDp+fPhBIrofIKeXHv5vISZW mCMlwycoUKBCXNnGbNPEu542Qdmjztse1eLapSQet8PTewQJygUfJRmgzmV0GPuc 9MmX6iw14bM4Mza19UpAI0x9S3Fu5gQpbTj5uYtSCAeO51iFh60Vd1rzL2+HjlbY oAu5qI3RuGKvfG/TDjQWz3NNFBxAuzYRxQ5BrMVGHnbq5lrzzW/2m+L9OjxHsslu Rbzb/z9G3wOh5fq+nTlfgPexUc+Ue89c9FBTgwkSPxOGdFvi6gIFY9do8yZMW6hh oUVpcE8vrkY0oswA3BV25U9sU+JayWOflJ1cptxP8wN6J1BPYCJIrriRTpnPDfbl 8pBLlWRUczteKIoTEcEMY136KeF3IMwBjwTN6KVE2GDu24ErgH4jcWZ91Fda3rh5 oM5Qh3hidc6wG0yeij/rfyNn56EP9Oa2QMCLJ9fr0gexK2LmkhfOYaHoqVWF1dpf Yi7XIHEIK1pmtP+znf2iAQKCAQEA64RD2aZNfVvpy+lKCQPHX746jE/WF/bUn3qH wVAfGRLwxsZqmCGHiNBSz819kGoCzu7iU1PSCr/4xC/ndmNu7InuL5sW7cFJFz1y qkYAL5kumjfoanodk3D7lZxBm2cE8EGTbbadbhMfBWvba9w54MYle3l6YaS1FS0F IWWlCxnCQljOS8yDDSsYZQk2cEohgfYSuw1GeeoI4kUVjymc52zz5zOGUaUKmerT kXOglEExMzQ2nj/UGIBCSHMMU/vbCiYHR6fLUl6R4T7Sw/2SYtl9qRrqXXbIZqA0 uFjrxp6aeRdZmZA6GGBpqH6xoxn8MuJjnf8gvfbqEhhnAym3xwKCAQEA2nmoPCYX SEzXPTi6FsvBsc1ssYejj1mix/tx017DP9ci/8726THG7QyyLNJOUUUldjqEU4Bf 1bwG4C4Q+IbOSHVK9MFY8dYOqW40Zgsim92A0mk0wYep9bnpFy6YAXqMi6/qRdcb CQXCTi4jMYU29dl0UaigAA3oO9R58+mD0gO+6ypmXUErQfji/zAWrbTOz6vdUyLD 5k7PLzXLn75ANWBf+Xduzi984JBF77jD3hbzMclpSp0ymB3IfRvMiYMDG0zD6Jtd SaX9zAd6mdmoTrRhlo+N4JnoMSiuhuFoeFTpV7HqBFz2Xu6LQ/BAgiUbcPsMdHCK YCQq7exB8UkF1QKCAQBaEx8EGhee701OwK2hHwHcu1uXGF2wkqWlTO6o36TVKSpP S8mu33v/tnVFprj0R6dFT5Xd+rvlgqB5ID0tSUA+VU50hKNTUU5MBiNZviYKDlMF hoZsWsH/BwIhqT5qWg9IeDwThPlXBRcjMqob6YF1VzM0szQ8LgtXyv0gVci2oyZp y58y3Efu/GF7GvfoIGIKW3u0cJJYxEqbh4KEW4z38fKipVEk3rNcRLSf95IdwYU4 qSqOgajzqfIv1ViMslGG4x57qFAZ87Nla2qerNeU2Mu3pmSmVGy222TucIvUTgqU b3rEQaYGdrFSUQpNb/3F1FH3NoFmRg4l15FmY0k3AoIBABu6oS2xL/dPOWpdztCh 392vUwJdUtcY614yfcn0Fxf9OEX7gL8sQDFKETs7HhGWkyCkYLMwcflwufauIh1J DtmHeZIDEETxhD7g6+mftC7QOE98ZuPBUkML65ezpDtb0IbSNwvSN243uuetV24r mEQv62GJ43TeTwF5AFmC4+Y973dtlDx1zwW6jyUQd3BoqG8XQyoQGYkbq5Q0YbnO rduYddX14KxuvozKAvZgHwwLIabKB4Ee3pMMBKxMYPN7G2PVpG/beEWmucWxlU/9 ni0PG+u+IKXHIv9KSIx6A4ZyUIN+41LWcbau1CI1VhqulwMJ+hS1S/rT3FcCS4RS XlkCggEBAKGDuMhE/Sf3cxZHPNU81iu+KO5FqNQYjbBPzZWmzrjsUCQTzd1TlixU mV4nlq8B9eNfhphw1EIcWujkLap0ttcWF5Gq/DBH+XjiAZpXIPdc0SSC4L8Ihtba RxMfIzTMMToyJJhI+pcuX+uIZyxgXqaPU/EP/iwrYTkc80fSTn32AojUrkYDl5dK bC4GpbaK19yYz2giYZ/++mSF7576mDhDI1E8CqSYhed/Pf7LsRAbpIV9lH448SvE hFKqR94vMlAyNj7FNl1VuN0VqUsceqXyhvrdNc6w/+YdOS4MDzzGL4gEFSJM3GQe bVQXjmugND3w6dydVZp/DrvEqfE1Ib0= -----END PRIVATE KEY----- """, dnskey="256 3 8 AwEAAcj+jhGMD2D+pZINqBY5g/61jl/cRXk/yQo/c4zH5FEL+zgmfNBcqDL/JYzd4Kc9ydjLVqa2vgsKlCvBl37wNXlO6n9Zms8dC7lihGAtv6gqe/DY0YwxsZDqOV6EDxN9+4XVtFB/VM8sFFospEnv1J6a+LIdxm6eq2OK/yZsOYWQyZQAVVAWcIat2/S+TyZuTGn3xQSTPAEqTgR3bCJBkCXUVYylZSG0B5m4kLhzW0dTXE2M3hzZeZiiRG8rSPf3qXoHSKH+CL3A5h/qN5OXmrSVK1v43Q9p5C+WPI4KOjvleoyRzdxVJpn/16SSmtIVnJXlR9LVqOPmsSSHg4f/0jIhMzx4WisyVnlGqGemfvG8uHXahnUBmSSLbHdWriHw7je5Uga0pdIaTaSse6xuN69WxrnOLNsdN3ygFpSu9SE5jUvw5KbQyI+tJAZFpXJDUDIF3eoSTACqC5m85RhYTfQMrxOiISaOORvV0hawlrgN4SS6H0iiHYceMthqfc+ilmqRGtjhkB0vcgscL+oiHjw2/fcS39DkkTlo9ZKWo9k3Rn82COAAfGyxZW/9L1yYCs0cTGesaKMQzE/xTaZcvxUplM2w0jXPEg61GO65W8bKLUU68Pye0zh5Dn2KES/+zKv4b14llnAs6cVCqoKRegHONbCUWsAP+RcgmzQxC8uT", algorithm=Algorithm.RSASHA256, ), TestKey( command="openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -pkeyopt ec_param_enc:named_curve", private_pem=""" -----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgJFyT16nmjmDgEF2v 1iTperYVGR52zVT8ej6A9eTmmSChRANCAASfsKTiVq2KNEKSUoYtPAXiZbDG6EEP 8TwdLumK8ge2F9AtE0Q343bnnZBCFpCxuvxtuWmS8QQwAWh8PizqKrDu -----END PRIVATE KEY----- """, dnskey="256 3 13 n7Ck4latijRCklKGLTwF4mWwxuhBD/E8HS7pivIHthfQLRNEN+N2552QQhaQsbr8bblpkvEEMAFofD4s6iqw7g==", algorithm=Algorithm.ECDSAP256SHA256, ), TestKey( command="openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -pkeyopt ec_param_enc:named_curve", private_pem=""" -----BEGIN PRIVATE KEY----- MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCNSZ3SrRmdh8wcUVPO h9ea2zw9Jyc3P1XuP2nOYZR/aQMHfScCtWA3AsMCcsseEmihZANiAATv2H3Q3jrI aH/Vmit9RefIpnh+iZzpyk29/m1EJKgkkwbA0OHClk8Nt7RL/4CO4CUpzaOcqamN 6B48G68LN4yZByMKt3z751qB86Z7rYc7SuOR0m7bPlXyUsO48+8o/hU= -----END PRIVATE KEY----- """, dnskey="256 3 14 79h90N46yGh/1ZorfUXnyKZ4fomc6cpNvf5tRCSoJJMGwNDhwpZPDbe0S/+AjuAlKc2jnKmpjegePBuvCzeMmQcjCrd8++dagfOme62HO0rjkdJu2z5V8lLDuPPvKP4V", algorithm=Algorithm.ECDSAP384SHA384, ), TestKey( command="openssl genpkey -algorithm ED25519", private_pem=""" -----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIKGelcdVWlxU5YlLE5/LAEfqhZq7P9s0NHlQqxOjBvcS -----END PRIVATE KEY----- """, dnskey="256 3 15 iHaBu3tWzJxuuMSzk1WMwCGF3LD60n0fkOdaCCqsL0A=", algorithm=Algorithm.ED25519, ), TestKey( command="openssl genpkey -algorithm ED448", private_pem=""" -----BEGIN PRIVATE KEY----- MEcCAQAwBQYDK2VxBDsEOfGENbZhfMbspoQV1c3/vljWPMFsIzef7M111gU0QTva dUd0khisgJ/gk+I1DWLtf/6M4wxXje5FLg== -----END PRIVATE KEY----- """, dnskey="256 3 16 ziFYQq6fEXyNKPGzq2GErJxCl9979MKNdW46r4Bqn/waS+iIAmAbaTG3klpwqJtl+Qvdj2xGqJwA", algorithm=Algorithm.ED448, ), ] dnspython-2.7.0/tests/md_module.py0000644000000000000000000000013313615410400014144 0ustar00import dns.rdtypes.nsbase class MD(dns.rdtypes.nsbase.NSBase): """Test MD record.""" dnspython-2.7.0/tests/mx-2-0.pickle0000644000000000000000000000033713615410400013744 0ustar00€•ÔŒdns.rdtypes.ANY.MX”ŒMX”“”)”}”(Œ preference”K Œexchange”Œdns.name”ŒName”“”)”}”Œlabels”Cmx”Cexample”C”‡”sbŒrdclass”Œdns.rdataclass”Œ RdataClass”“”K…”R”Œrdtype”Œ dns.rdatatype”Œ RdataType”“”K…”R”ub.dnspython-2.7.0/tests/nanonameserver.py0000644000000000000000000003721413615410400015234 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import contextlib import enum import errno import functools import logging import logging.config import socket import ssl import struct import threading import trio import dns.asyncquery import dns.inet import dns.message import dns.rcode from tests.util import here try: import tests.doq have_doq = True except Exception: have_doq = False try: import tests.doh have_doh = True except Exception as e: have_doh = False class ConnectionType(enum.IntEnum): UDP = 1 TCP = 2 DOT = 3 DOH = 4 DOQ = 5 DOH3 = 6 async def read_exactly(stream, count): """Read the specified number of bytes from stream. Keep trying until we either get the desired amount, or we hit EOF. """ s = b"" while count > 0: n = await stream.receive_some(count) if n == b"": raise EOFError count = count - len(n) s = s + n return s class Request: def __init__(self, message, wire, peer, local, connection_type): self.message = message self.wire = wire self.peer = peer self.local = local self.connection_type = connection_type @property def question(self): return self.message.question[0] @property def qname(self): return self.question.name @property def qclass(self): return self.question.rdclass @property def qtype(self): return self.question.rdtype class Server(threading.Thread): """The nanoserver is a nameserver skeleton suitable for faking a DNS server for various testing purposes. It executes with a trio run loop in a dedicated thread, and is a context manager. Exiting the context manager will ensure the server shuts down. If a port is not specified, random ports will be chosen. Applications should subclass the server and override the handle() method to determine how the server responds to queries. The default behavior is to refuse everything. If use_thread is set to False in the constructor, then the server's main() method can be used directly in a trio nursery, allowing the server's cancellation to be managed in the Trio way. In this case, no thread creation ever happens even though Server is a subclass of thread, because the start() method is never called. """ def __init__( self, *, address="127.0.0.1", port=0, dot_port=0, doh_port=0, protocols=tuple(p for p in ConnectionType), use_thread=True, origin=None, keyring=None, tls_chain=here("tls/public.crt"), tls_key=here("tls/private.pem"), ): super().__init__() self.address = address self.port = port self.dot_port = dot_port self.doh_port = doh_port self.protocols = set(protocols) if not have_doq: self.protocols.discard(ConnectionType.DOQ) if not have_doh: self.protocols.discard(ConnectionType.DOH) self.protocols.discard(ConnectionType.DOH3) self.use_thread = use_thread self.origin = origin self.keyring = keyring self.left = None self.right = None self.sockets = {} self.addresses = {} self.tls_chain = tls_chain self.tls_key = tls_key def get_address(self, connection_type): return self.addresses[connection_type] # For backwards compatibility @property def udp_address(self): return self.addresses[ConnectionType.UDP] @property def tcp_address(self): return self.addresses[ConnectionType.TCP] @property def doq_address(self): return self.addresses[ConnectionType.DOQ] def caught(self, who, e): print(who, "caught", type(e), e) def open_sockets(self, port, udp_type, tcp_type): want_udp = udp_type in self.protocols want_tcp = tcp_type in self.protocols udp = None tcp = None af = dns.inet.af_for_address(self.address) if port != 0 or not (want_udp and want_tcp): if want_udp: udp = socket.socket(af, socket.SOCK_DGRAM, 0) udp.bind((self.address, port)) self.sockets[udp_type] = udp if want_tcp: tcp = socket.create_server((self.address, port), family=af) self.sockets[tcp_type] = tcp return open_udp_sockets = [] try: while True: udp = socket.socket(af, socket.SOCK_DGRAM, 0) udp.bind((self.address, port)) try: udp_port = udp.getsockname()[1] tcp = socket.create_server((self.address, udp_port), family=af) self.sockets[udp_type] = udp self.sockets[tcp_type] = tcp return except OSError: # We failed to open the corresponding TCP port. Keep the UDP socket # open, try again, and hope we get a better port. if len(open_udp_sockets) < 100: open_udp_sockets.append(udp) continue # 100 tries to find a port is enough! Give up! raise finally: for udp_socket in open_udp_sockets: udp_socket.close() def __enter__(self): (self.left, self.right) = socket.socketpair() # We're making the sockets now so they can be sent to by the # caller immediately (i.e. no race with the listener starting # in the thread). self.open_sockets(self.port, ConnectionType.UDP, ConnectionType.TCP) self.open_sockets(self.dot_port, ConnectionType.DOQ, ConnectionType.DOT) self.open_sockets(self.doh_port, ConnectionType.DOH3, ConnectionType.DOH) for proto, sock in self.sockets.items(): self.addresses[proto] = sock.getsockname() if self.use_thread: self.start() return self def __exit__(self, ex_ty, ex_va, ex_tr): if self.left: self.left.close() if self.use_thread and self.is_alive(): self.join() if self.right: self.right.close() for sock in self.sockets.values(): sock.close() async def wait_for_input_or_eof(self): # # This trio task just waits for input on the right half of the # socketpair (the left half is owned by the context manager # returned by launch). As soon as something is read, or the # socket returns EOF, EOFError is raised, causing a the # nursery to cancel all other nursery tasks, in particular the # listeners. # try: with trio.socket.from_stdlib_socket(self.right) as sock: self.right = None # we own cleanup await sock.recv(1) finally: raise EOFError def handle(self, request): # # Handle request 'request'. Override this method to change # how the server behaves. # # The return value is either a dns.message.Message, a bytes, # None, or a list of one of those. We allow a bytes to be # returned for cases where handle wants to return an invalid # DNS message for testing purposes. We allow None to be # returned to indicate there is no response. If a list is # returned, then the output code will run for each returned # item. # r = dns.message.make_response(request.message) r.set_rcode(dns.rcode.REFUSED) return r def maybe_listify(self, thing): if isinstance(thing, list): return thing else: return [thing] def handle_wire(self, wire, peer, local, connection_type): # # This is the common code to parse wire format, call handle() on # the message, and then generate response wire format (if handle() # didn't do it). # # It also handles any exceptions from handle() # # Returns a (possibly empty) list of wire format message to send. items = [] r = None try: q = dns.message.from_wire(wire, keyring=self.keyring) except dns.message.ShortHeader: # There is no hope of answering this one! return except Exception: # Try to make a FORMERR using just the question section. try: q = dns.message.from_wire(wire, question_only=True) r = dns.message.make_response(q) r.set_rcode(dns.rcode.FORMERR) items.append(r) except Exception: # We could try to make a response from only the header # if dnspython had a header_only option to # from_wire(), or if we truncated wire ourselves, but # for now we just drop. return try: # items might have been appended to above, so skip # handle() if we already have a response. if not items: request = Request(q, wire, peer, local, connection_type) items = self.maybe_listify(self.handle(request)) except Exception as e: # Exceptions from handle get a SERVFAIL response, and a print because # they are usually bugs in the the test! self.caught("handle", e) r = dns.message.make_response(q) r.set_rcode(dns.rcode.SERVFAIL) items = [r] tsig_ctx = None multi = len(items) > 1 for thing in items: if isinstance(thing, dns.message.Message): out = thing.to_wire(self.origin, multi=multi, tsig_ctx=tsig_ctx) tsig_ctx = thing.tsig_ctx yield out elif thing is not None: yield thing async def serve_udp(self, connection_type): with trio.socket.from_stdlib_socket(self.sockets[connection_type]) as sock: self.sockets.pop(connection_type) # we own cleanup local = self.addresses[connection_type] while True: try: (wire, peer) = await sock.recvfrom(65535) for wire in self.handle_wire(wire, peer, local, connection_type): await sock.sendto(wire, peer) except Exception as e: self.caught("serve_udp", e) async def serve_tcp(self, connection_type, stream): try: if connection_type == ConnectionType.DOT: peer = stream.transport_stream.socket.getpeername() local = stream.transport_stream.socket.getsockname() else: assert connection_type == ConnectionType.TCP peer = stream.socket.getpeername() local = stream.socket.getsockname() while True: ldata = await read_exactly(stream, 2) (l,) = struct.unpack("!H", ldata) wire = await read_exactly(stream, l) for wire in self.handle_wire(wire, peer, local, connection_type): l = len(wire) stream_message = struct.pack("!H", l) + wire await stream.send_all(stream_message) except Exception as e: self.caught("serve_tcp", e) async def orchestrate_tcp(self, connection_type): with trio.socket.from_stdlib_socket(self.sockets[connection_type]) as sock: self.sockets.pop(connection_type) # we own cleanup listener = trio.SocketListener(sock) if connection_type == ConnectionType.DOT: ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2 ssl_context.load_cert_chain(self.tls_chain, self.tls_key) listener = trio.SSLListener(listener, ssl_context) serve = functools.partial(self.serve_tcp, connection_type) async with trio.open_nursery() as nursery: serve = functools.partial( trio.serve_listeners, serve, [listener], handler_nursery=nursery, ) nursery.start_soon(serve) async def serve_doq(self, connection_type) -> None: with trio.socket.from_stdlib_socket(self.sockets[connection_type]) as sock: self.sockets.pop(connection_type) # we own cleanup async with tests.doq.Listener( self, sock, connection_type, self.tls_chain, self.tls_key ) as listener: try: await listener.run() except Exception as e: self.caught("serve_doq", e) async def serve_doh(self, connection_type) -> None: server = tests.doh.make_server( self, self.sockets[connection_type], connection_type, self.tls_chain, self.tls_key, ) try: await server() except Exception as e: self.caught("serve_doh", e) async def main(self): handlers = { ConnectionType.UDP: self.serve_udp, ConnectionType.TCP: self.orchestrate_tcp, ConnectionType.DOT: self.orchestrate_tcp, ConnectionType.DOH: self.serve_doh, ConnectionType.DOH3: self.serve_doh, ConnectionType.DOQ: self.serve_doq, } try: async with trio.open_nursery() as nursery: if self.use_thread: nursery.start_soon(self.wait_for_input_or_eof) for connection_type in self.protocols: nursery.start_soon(handlers[connection_type], connection_type) except Exception as e: self.caught("nanoserver main", e) def run(self): if not self.use_thread: raise RuntimeError("start() called on a use_thread=False Server") trio.run(self.main) if __name__ == "__main__": import sys import time logger = logging.getLogger(__name__) format = "%(asctime)s %(levelname)s: %(message)s" logging.basicConfig(format=format, level=logging.INFO) logging.config.dictConfig( { "version": 1, "incremental": True, "loggers": { "quart.app": { "level": "INFO", }, "quart.serving": { "propagate": False, "level": "ERROR", }, "quic": { "level": "CRITICAL", }, }, } ) async def trio_main(): try: with Server( port=5354, dot_port=5355, doh_port=5356, use_thread=False ) as server: print("Trio mode") for proto, address in server.addresses.items(): print(f" listening on {proto.name}: {address}") async with trio.open_nursery() as nursery: nursery.start_soon(server.main) except Exception as e: print("trio_main caught", type(e), e) def threaded_main(): with Server(port=5354, dot_port=5355, doh_port=5356) as server: print("Thread mode") for proto, address in server.addresses.items(): print(f" listening on {proto.name}: {address}") time.sleep(300) if len(sys.argv) > 1 and sys.argv[1] == "trio": trio.run(trio_main) else: threaded_main() dnspython-2.7.0/tests/query0000644000000000000000000000021413615410400012715 0ustar00id 1234 opcode QUERY rcode NOERROR flags RD edns 0 eflags DO payload 4096 ;QUESTION wwww.dnspython.org. IN A ;ANSWER ;AUTHORITY ;ADDITIONAL dnspython-2.7.0/tests/stxt_module.py0000644000000000000000000000015713615410400014554 0ustar00import dns.rdtypes.txtbase class STXT(dns.rdtypes.txtbase.TXTBase): """Test singleton TXT-like record""" dnspython-2.7.0/tests/svcb_test_vectors.generic0000644000000000000000000001073513615410400016735 0ustar00; Alias form \# 19 ( 00 00 ; priority 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ; target ) ; Service form ; The first form is the simple "use the ownername". \# 3 ( 00 01 ; priority 00 ; target (root label) ) ; This vector only has a port. \# 25 ( 00 10 ; priority 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ; target 00 03 ; key 3 00 02 ; length 2 00 35 ; value ) ; This example has a key that is not registered, its value is unquoted. \# 28 ( 00 01 ; priority 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ; target 02 9b ; key 667 00 05 ; length 5 68 65 6c 6c 6f ; value ) ; This example has a key that is not registered, its value is quoted and ; contains a decimal-escaped character. \# 32 ( 00 01 ; priority 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ; target 02 9b ; key 667 00 09 ; length 9 68 65 6c 6c 6f d2 71 6f 6f ; value ) ; Here, two IPv6 hints are quoted in the presentation format. \# 55 ( 00 01 ; priority 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ; target 00 06 ; key 6 00 20 ; length 32 20 01 0d b8 00 00 00 00 00 00 00 00 00 00 00 01 ; first address 20 01 0d b8 00 00 00 00 00 00 00 00 00 53 00 01 ; second address ) ; This example shows a single IPv6 hint in IPv4 mapped IPv6 presentation format. \# 35 ( 00 01 ; priority 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ; target 00 06 ; key 6 00 10 ; length 16 20 01 0d b8 ff ff ff ff ff ff ff ff c6 33 64 64 ; address ) ; In the next vector, neither the SvcParamValues nor the mandatory keys are ; sorted in presentation format, but are correctly sorted in the wire-format. \# 48 ( 00 10 ; priority 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 ; target 00 00 ; key 0 00 04 ; param length 4 00 01 ; value: key 1 00 04 ; value: key 4 00 01 ; key 1 00 09 ; param length 9 02 ; alpn length 2 68 32 ; alpn value 05 ; alpn length 5 68 33 2d 31 39 ; alpn value 00 04 ; key 4 00 04 ; param length 4 c0 00 02 01 ; param value ) ; This last vector has an alpn value with an escaped comma and an escaped ; backslash in two presentation formats. \# 35 ( 00 10 ; priority 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 ; target 00 01 ; key 1 00 0c ; param length 12 08 ; alpn length 8 66 5c 6f 6f 2c 62 61 72 ; alpn value 02 ; alpn length 2 68 32 ; alpn value ) \# 35 ( 00 10 ; priority 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 ; target 00 01 ; key 1 00 0c ; param length 12 08 ; alpn length 8 66 5c 6f 6f 2c 62 61 72 ; alpn value 02 ; alpn length 2 68 32 ; alpn value ) dnspython-2.7.0/tests/svcb_test_vectors.text0000644000000000000000000000220513615410400016276 0ustar00; Alias form 0 foo.example.com. ; Service form ; The first form is the simple "use the ownername". 1 . ; This vector only has a port. 16 foo.example.com. port=53 ; This example has a key that is not registered, its value is unquoted. 1 foo.example.com. key667=hello ; This example has a key that is not registered, its value is quoted and ; contains a decimal-escaped character. 1 foo.example.com. key667="hello\210qoo" ; Here, two IPv6 hints are quoted in the presentation format. 1 foo.example.com. ipv6hint="2001:db8::1,2001:db8::53:1" ; This example shows a single IPv6 hint in IPv4 mapped IPv6 presentation format. 1 example.com. ipv6hint="2001:db8:ffff:ffff:ffff:ffff:198.51.100.100" ; In the next vector, neither the SvcParamValues nor the mandatory keys are ; sorted in presentation format, but are correctly sorted in the wire-format. 16 foo.example.org. (alpn=h2,h3-19 mandatory=ipv4hint,alpn ipv4hint=192.0.2.1) ; This last vector has an alpn value with an escaped comma and an escaped ; backslash in two presentation formats. 16 foo.example.org. alpn="f\\\\oo\\,bar,h2" 16 foo.example.org. alpn=f\\\092oo\092,bar,h2 dnspython-2.7.0/tests/test_address.py0000644000000000000000000005122413615410400014672 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import socket import sys import unittest import dns.exception import dns.ipv4 import dns.ipv6 class IPv4Tests(unittest.TestCase): def test_valid(self): valid = ( "1.2.3.4", "11.22.33.44", "254.7.237.98", "192.168.1.26", "192.168.1.1", "13.1.68.3", "129.144.52.38", "254.157.241.86", "12.34.56.78", "192.0.2.128", ) for s in valid: self.assertEqual(dns.ipv4.inet_aton(s), socket.inet_pton(socket.AF_INET, s)) def test_invalid(self): invalid = ( "", ".", "..", "400.2.3.4", "260.2.3.4", "256.2.3.4", "1.256.3.4", "1.2.256.4", "1.2.3.256", "300.2.3.4", "1.300.3.4", "1.2.300.4", "1.2.3.300", "900.2.3.4", "1.900.3.4", "1.2.900.4", "1.2.3.900", "300.300.300.300", "3000.30.30.30", "255Z255X255Y255", "192x168.1.26", "2.3.4", "257.1.2.3", "00.00.00.00", "000.000.000.000", "256.256.256.256", "255255.255.255", "255.255255.255", "255.255.255255", "1...", "1.2..", "1.2.3.", ".2..", ".2.3.", ".2.3.4", "..3.", "..3.4", "...4", ".1.2.3.4", "1.2.3.4.", " 1.2.3.4", "1.2.3.4 ", " 1.2.3.4 ", "::", ) for s in invalid: with self.assertRaises( dns.exception.SyntaxError, msg=f'invalid IPv4 address: "{s}"' ): dns.ipv4.inet_aton(s) class IPv6Tests(unittest.TestCase): def test_valid(self): valid = ( "::1", "::", "0:0:0:0:0:0:0:1", "0:0:0:0:0:0:0:0", "2001:DB8:0:0:8:800:200C:417A", "FF01:0:0:0:0:0:0:101", "2001:DB8::8:800:200C:417A", "FF01::101", "fe80::217:f2ff:fe07:ed62", "2001:0000:1234:0000:0000:C1C0:ABCD:0876", "3ffe:0b00:0000:0000:0001:0000:0000:000a", "FF02:0000:0000:0000:0000:0000:0000:0001", "0000:0000:0000:0000:0000:0000:0000:0001", "0000:0000:0000:0000:0000:0000:0000:0000", "2::10", "ff02::1", "fe80::", "2002::", "2001:db8::", "2001:0db8:1234::", "::ffff:0:0", "1:2:3:4:5:6:7:8", "1:2:3:4:5:6::8", "1:2:3:4:5::8", "1:2:3:4::8", "1:2:3::8", "1:2::8", "1::8", "1::2:3:4:5:6:7", "1::2:3:4:5:6", "1::2:3:4:5", "1::2:3:4", "1::2:3", "::2:3:4:5:6:7:8", "::2:3:4:5:6:7", "::2:3:4:5:6", "::2:3:4:5", "::2:3:4", "::2:3", "::8", "1:2:3:4:5:6::", "1:2:3:4:5::", "1:2:3:4::", "1:2:3::", "1:2::", "1::", "1:2:3:4:5::7:8", "1:2:3:4::7:8", "1:2:3::7:8", "1:2::7:8", "1::7:8", "1:2:3:4:5:6:1.2.3.4", "1:2:3:4:5::1.2.3.4", "1:2:3:4::1.2.3.4", "1:2:3::1.2.3.4", "1:2::1.2.3.4", "1::1.2.3.4", "1:2:3:4::5:1.2.3.4", "1:2:3::5:1.2.3.4", "1:2:3::5:1.2.3.4", "1:2::5:1.2.3.4", "1::5:1.2.3.4", "1::5:11.22.33.44", "fe80::217:f2ff:254.7.237.98", "::ffff:192.168.1.26", "::ffff:192.168.1.1", "0:0:0:0:0:0:13.1.68.3", "0:0:0:0:0:FFFF:129.144.52.38", "::13.1.68.3", "::FFFF:129.144.52.38", "fe80:0:0:0:204:61ff:254.157.241.86", "fe80::204:61ff:254.157.241.86", "::ffff:12.34.56.78", "::ffff:192.0.2.128", "fe80:0000:0000:0000:0204:61ff:fe9d:f156", "fe80:0:0:0:204:61ff:fe9d:f156", "fe80::204:61ff:fe9d:f156", "fe80::1", "::ffff:c000:280", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:db8:85a3:0:0:8a2e:370:7334", "2001:db8:85a3::8a2e:370:7334", "2001:0db8:0000:0000:0000:0000:1428:57ab", "2001:0db8:0000:0000:0000::1428:57ab", "2001:0db8:0:0:0:0:1428:57ab", "2001:0db8:0:0::1428:57ab", "2001:0db8::1428:57ab", "2001:db8::1428:57ab", "::ffff:0c22:384e", "2001:0db8:1234:0000:0000:0000:0000:0000", "2001:0db8:1234:ffff:ffff:ffff:ffff:ffff", "2001:db8:a::123", "1111:2222:3333:4444:5555:6666:7777:8888", "1111:2222:3333:4444:5555:6666:7777::", "1111:2222:3333:4444:5555:6666::", "1111:2222:3333:4444:5555::", "1111:2222:3333:4444::", "1111:2222:3333::", "1111:2222::", "1111::", "1111:2222:3333:4444:5555:6666::8888", "1111:2222:3333:4444:5555::8888", "1111:2222:3333:4444::8888", "1111:2222:3333::8888", "1111:2222::8888", "1111::8888", "::8888", "1111:2222:3333:4444:5555::7777:8888", "1111:2222:3333:4444::7777:8888", "1111:2222:3333::7777:8888", "1111:2222::7777:8888", "1111::7777:8888", "::7777:8888", "1111:2222:3333:4444::6666:7777:8888", "1111:2222:3333::6666:7777:8888", "1111:2222::6666:7777:8888", "1111::6666:7777:8888", "::6666:7777:8888", "1111:2222:3333::5555:6666:7777:8888", "1111:2222::5555:6666:7777:8888", "1111::5555:6666:7777:8888", "::5555:6666:7777:8888", "1111:2222::4444:5555:6666:7777:8888", "1111::4444:5555:6666:7777:8888", "::4444:5555:6666:7777:8888", "1111::3333:4444:5555:6666:7777:8888", "::3333:4444:5555:6666:7777:8888", "::2222:3333:4444:5555:6666:7777:8888", "1111:2222:3333:4444:5555:6666:123.123.123.123", "1111:2222:3333:4444:5555::123.123.123.123", "1111:2222:3333:4444::123.123.123.123", "1111:2222:3333::123.123.123.123", "1111:2222::123.123.123.123", "1111::123.123.123.123", "::123.123.123.123", "1111:2222:3333:4444::6666:123.123.123.123", "1111:2222:3333::6666:123.123.123.123", "1111:2222::6666:123.123.123.123", "1111::6666:123.123.123.123", "::6666:123.123.123.123", "1111:2222:3333::5555:6666:123.123.123.123", "1111:2222::5555:6666:123.123.123.123", "1111::5555:6666:123.123.123.123", "::5555:6666:123.123.123.123", "1111:2222::4444:5555:6666:123.123.123.123", "1111::4444:5555:6666:123.123.123.123", "::4444:5555:6666:123.123.123.123", "1111::3333:4444:5555:6666:123.123.123.123", "::2222:3333:4444:5555:6666:123.123.123.123", "::0:0:0:0:0:0:0", "::0:0:0:0:0:0", "::0:0:0:0:0", "::0:0:0:0", "::0:0:0", "::0:0", "::0", "0:0:0:0:0:0:0::", "0:0:0:0:0:0::", "0:0:0:0:0::", "0:0:0:0::", "0:0:0::", "0:0::", "0::", "0:a:b:c:d:e:f::", "::0:a:b:c:d:e:f", "a:b:c:d:e:f:0::", ) win32_invalid = { "::2:3:4:5:6:7:8", "::2222:3333:4444:5555:6666:7777:8888", "::2222:3333:4444:5555:6666:123.123.123.123", "::0:0:0:0:0:0:0", "::0:a:b:c:d:e:f", } for s in valid: if sys.platform == "win32" and s in win32_invalid: # socket.inet_pton() on win32 rejects some valid (as # far as we can tell) IPv6 addresses. Skip them. continue self.assertEqual( dns.ipv6.inet_aton(s), socket.inet_pton(socket.AF_INET6, s) ) def test_invalid(self): invalid = ( "", ":", ":::", "2001:DB8:0:0:8:800:200C:417A:221", "FF01::101::2", "02001:0000:1234:0000:0000:C1C0:ABCD:0876", "2001:0000:1234:0000:00001:C1C0:ABCD:0876", " 2001:0000:1234:0000:0000:C1C0:ABCD:0876", "2001:0000:1234:0000:0000:C1C0:ABCD:0876 ", " 2001:0000:1234:0000:0000:C1C0:ABCD:0876 ", "2001:0000:1234:0000:0000:C1C0:ABCD:0876 0", "2001:0000:1234: 0000:0000:C1C0:ABCD:0876", "3ffe:0b00:0000:0001:0000:0000:000a", "FF02:0000:0000:0000:0000:0000:0000:0000:0001", "3ffe:b00::1::a", "::1111:2222:3333:4444:5555:6666::", "1:2:3::4:5::7:8", "12345::6:7:8", "1::5:400.2.3.4", "1::5:260.2.3.4", "1::5:256.2.3.4", "1::5:1.256.3.4", "1::5:1.2.256.4", "1::5:1.2.3.256", "1::5:300.2.3.4", "1::5:1.300.3.4", "1::5:1.2.300.4", "1::5:1.2.3.300", "1::5:900.2.3.4", "1::5:1.900.3.4", "1::5:1.2.900.4", "1::5:1.2.3.900", "1::5:300.300.300.300", "1::5:3000.30.30.30", "1::400.2.3.4", "1::260.2.3.4", "1::256.2.3.4", "1::1.256.3.4", "1::1.2.256.4", "1::1.2.3.256", "1::300.2.3.4", "1::1.300.3.4", "1::1.2.300.4", "1::1.2.3.300", "1::900.2.3.4", "1::1.900.3.4", "1::1.2.900.4", "1::1.2.3.900", "1::300.300.300.300", "1::3000.30.30.30", "::400.2.3.4", "::260.2.3.4", "::256.2.3.4", "::1.256.3.4", "::1.2.256.4", "::1.2.3.256", "::300.2.3.4", "::1.300.3.4", "::1.2.300.4", "::1.2.3.300", "::900.2.3.4", "::1.900.3.4", "::1.2.900.4", "::1.2.3.900", "::300.300.300.300", "::3000.30.30.30", "::1.2.3.4.", "2001:1:1:1:1:1:255Z255X255Y255", "::ffff:192x168.1.26", "::ffff:2.3.4", "::ffff:257.1.2.3", "1.2.3.4", "1.2.3.4:1111:2222:3333:4444::5555", "1.2.3.4:1111:2222:3333::5555", "1.2.3.4:1111:2222::5555", "1.2.3.4:1111::5555", "1.2.3.4::5555", "1.2.3.4::", "fe80:0000:0000:0000:0204:61ff:254.157.241.086", "XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4", "1111:2222:3333:4444:5555:6666:00.00.00.00", "1111:2222:3333:4444:5555:6666:000.000.000.000", "1111:2222:3333:4444:5555:6666:256.256.256.256", "1111:2222:3333:4444::5555:", "1111:2222:3333::5555:", "1111:2222::5555:", "1111::5555:", "::5555:", "1111:", ":1111:2222:3333:4444::5555", ":1111:2222:3333::5555", ":1111:2222::5555", ":1111::5555", ":::5555", "123", "ldkfj", "2001::FFD3::57ab", "2001:db8:85a3::8a2e:37023:7334", "2001:db8:85a3::8a2e:370k:7334", "1:2:3:4:5:6:7:8:9", "1::2::3", "1:::3:4:5", "1:2:3::4:5:6:7:8:9", "XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX", "1111:2222:3333:4444:5555:6666:7777:8888:9999", "1111:2222:3333:4444:5555:6666:7777:8888::", "::2222:3333:4444:5555:6666:7777:8888:9999", "1111:2222:3333:4444:5555:6666:7777", "1111:2222:3333:4444:5555:6666", "1111:2222:3333:4444:5555", "1111:2222:3333:4444", "1111:2222:3333", "1111:2222", "1111", "11112222:3333:4444:5555:6666:7777:8888", "1111:22223333:4444:5555:6666:7777:8888", "1111:2222:33334444:5555:6666:7777:8888", "1111:2222:3333:44445555:6666:7777:8888", "1111:2222:3333:4444:55556666:7777:8888", "1111:2222:3333:4444:5555:66667777:8888", "1111:2222:3333:4444:5555:6666:77778888", "1111:2222:3333:4444:5555:6666:7777:8888:", "1111:2222:3333:4444:5555:6666:7777:", "1111:2222:3333:4444:5555:6666:", "1111:2222:3333:4444:5555:", "1111:2222:3333:4444:", "1111:2222:3333:", "1111:2222:", ":8888", ":7777:8888", ":6666:7777:8888", ":5555:6666:7777:8888", ":4444:5555:6666:7777:8888", ":3333:4444:5555:6666:7777:8888", ":2222:3333:4444:5555:6666:7777:8888", ":1111:2222:3333:4444:5555:6666:7777:8888", ":::2222:3333:4444:5555:6666:7777:8888", "1111:::3333:4444:5555:6666:7777:8888", "1111:2222:::4444:5555:6666:7777:8888", "1111:2222:3333:::5555:6666:7777:8888", "1111:2222:3333:4444:::6666:7777:8888", "1111:2222:3333:4444:5555:::7777:8888", "1111:2222:3333:4444:5555:6666:::8888", "::2222::4444:5555:6666:7777:8888", "::2222:3333::5555:6666:7777:8888", "::2222:3333:4444::6666:7777:8888", "::2222:3333:4444:5555::7777:8888", "::2222:3333:4444:5555:7777::8888", "::2222:3333:4444:5555:7777:8888::", "1111::3333::5555:6666:7777:8888", "1111::3333:4444::6666:7777:8888", "1111::3333:4444:5555::7777:8888", "1111::3333:4444:5555:6666::8888", "1111::3333:4444:5555:6666:7777::", "1111:2222::4444::6666:7777:8888", "1111:2222::4444:5555::7777:8888", "1111:2222::4444:5555:6666::8888", "1111:2222::4444:5555:6666:7777::", "1111:2222:3333::5555::7777:8888", "1111:2222:3333::5555:6666::8888", "1111:2222:3333::5555:6666:7777::", "1111:2222:3333:4444::6666::8888", "1111:2222:3333:4444::6666:7777::", "1111:2222:3333:4444:5555::7777::", "1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4", "1111:2222:3333:4444:5555:6666:7777:1.2.3.4", "1111:2222:3333:4444:5555:6666::1.2.3.4", "::2222:3333:4444:5555:6666:7777:1.2.3.4", "1111:2222:3333:4444:5555:6666:1.2.3.4.5", "1111:2222:3333:4444:5555:1.2.3.4", "1111:2222:3333:4444:1.2.3.4", "1111:2222:3333:1.2.3.4", "1111:2222:1.2.3.4", "1111:1.2.3.4", "11112222:3333:4444:5555:6666:1.2.3.4", "1111:22223333:4444:5555:6666:1.2.3.4", "1111:2222:33334444:5555:6666:1.2.3.4", "1111:2222:3333:44445555:6666:1.2.3.4", "1111:2222:3333:4444:55556666:1.2.3.4", "1111:2222:3333:4444:5555:66661.2.3.4", "1111:2222:3333:4444:5555:6666:255255.255.255", "1111:2222:3333:4444:5555:6666:255.255255.255", "1111:2222:3333:4444:5555:6666:255.255.255255", ":1.2.3.4", ":6666:1.2.3.4", ":5555:6666:1.2.3.4", ":4444:5555:6666:1.2.3.4", ":3333:4444:5555:6666:1.2.3.4", ":2222:3333:4444:5555:6666:1.2.3.4", ":1111:2222:3333:4444:5555:6666:1.2.3.4", ":::2222:3333:4444:5555:6666:1.2.3.4", "1111:::3333:4444:5555:6666:1.2.3.4", "1111:2222:::4444:5555:6666:1.2.3.4", "1111:2222:3333:::5555:6666:1.2.3.4", "1111:2222:3333:4444:::6666:1.2.3.4", "1111:2222:3333:4444:5555:::1.2.3.4", "::2222::4444:5555:6666:1.2.3.4", "::2222:3333::5555:6666:1.2.3.4", "::2222:3333:4444::6666:1.2.3.4", "::2222:3333:4444:5555::1.2.3.4", "1111::3333::5555:6666:1.2.3.4", "1111::3333:4444::6666:1.2.3.4", "1111::3333:4444:5555::1.2.3.4", "1111:2222::4444::6666:1.2.3.4", "1111:2222::4444:5555::1.2.3.4", "1111:2222:3333::5555::1.2.3.4", "::.", "::..", "::...", "::1...", "::1.2..", "::1.2.3.", "::.2..", "::.2.3.", "::.2.3.4", "::..3.", "::..3.4", "::...4", ":1111:2222:3333:4444:5555:6666:7777::", ":1111:2222:3333:4444:5555:6666::", ":1111:2222:3333:4444:5555::", ":1111:2222:3333:4444::", ":1111:2222:3333::", ":1111:2222::", ":1111::", ":1111:2222:3333:4444:5555:6666::8888", ":1111:2222:3333:4444:5555::8888", ":1111:2222:3333:4444::8888", ":1111:2222:3333::8888", ":1111:2222::8888", ":1111::8888", ":::8888", ":1111:2222:3333:4444:5555::7777:8888", ":1111:2222:3333:4444::7777:8888", ":1111:2222:3333::7777:8888", ":1111:2222::7777:8888", ":1111::7777:8888", ":::7777:8888", ":1111:2222:3333:4444::6666:7777:8888", ":1111:2222:3333::6666:7777:8888", ":1111:2222::6666:7777:8888", ":1111::6666:7777:8888", ":::6666:7777:8888", ":1111:2222:3333::5555:6666:7777:8888", ":1111:2222::5555:6666:7777:8888", ":1111::5555:6666:7777:8888", ":::5555:6666:7777:8888", ":1111:2222::4444:5555:6666:7777:8888", ":1111::4444:5555:6666:7777:8888", ":::4444:5555:6666:7777:8888", ":1111::3333:4444:5555:6666:7777:8888", ":::3333:4444:5555:6666:7777:8888", ":1111:2222:3333:4444:5555::1.2.3.4", ":1111:2222:3333:4444::1.2.3.4", ":1111:2222:3333::1.2.3.4", ":1111:2222::1.2.3.4", ":1111::1.2.3.4", ":::1.2.3.4", ":1111:2222:3333:4444::6666:1.2.3.4", ":1111:2222:3333::6666:1.2.3.4", ":1111:2222::6666:1.2.3.4", ":1111::6666:1.2.3.4", ":::6666:1.2.3.4", ":1111:2222:3333::5555:6666:1.2.3.4", ":1111:2222::5555:6666:1.2.3.4", ":1111::5555:6666:1.2.3.4", ":::5555:6666:1.2.3.4", ":1111:2222::4444:5555:6666:1.2.3.4", ":1111::4444:5555:6666:1.2.3.4", ":::4444:5555:6666:1.2.3.4", ":1111::3333:4444:5555:6666:1.2.3.4", "1111:2222:3333:4444:5555:6666:7777:::", "1111:2222:3333:4444:5555:6666:::", "1111:2222:3333:4444:5555:::", "1111:2222:3333:4444:::", "1111:2222:3333:::", "1111:2222:::", "1111:::", "1111:2222:3333:4444:5555:6666::8888:", "1111:2222:3333:4444:5555::8888:", "1111:2222:3333:4444::8888:", "1111:2222:3333::8888:", "1111:2222::8888:", "1111::8888:", "::8888:", "1111:2222:3333:4444:5555::7777:8888:", "1111:2222:3333:4444::7777:8888:", "1111:2222:3333::7777:8888:", "1111:2222::7777:8888:", "1111::7777:8888:", "::7777:8888:", "1111:2222:3333:4444::6666:7777:8888:", "1111:2222:3333::6666:7777:8888:", "1111:2222::6666:7777:8888:", "1111::6666:7777:8888:", "::6666:7777:8888:", "1111:2222:3333::5555:6666:7777:8888:", "1111:2222::5555:6666:7777:8888:", "1111::5555:6666:7777:8888:", "::5555:6666:7777:8888:", "1111:2222::4444:5555:6666:7777:8888:", "1111::4444:5555:6666:7777:8888:", "::4444:5555:6666:7777:8888:", "1111::3333:4444:5555:6666:7777:8888:", "::3333:4444:5555:6666:7777:8888:", "::2222:3333:4444:5555:6666:7777:8888:", "':10.0.0.1", ) for s in invalid: with self.assertRaises( dns.exception.SyntaxError, msg=f'invalid IPv6 address: "{s}"' ): dns.ipv6.inet_aton(s) dnspython-2.7.0/tests/test_async.py0000644000000000000000000007720513615410400014371 0ustar00# 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 # 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 asyncio import random import socket import time import unittest import dns.asyncbackend import dns.asyncquery import dns.asyncresolver import dns.message import dns.name import dns.query import dns.quic import dns.rcode import dns.rdataclass import dns.rdatatype import dns.resolver import tests.util # Some tests require TLS so skip those if it's not there. ssl = dns.query.ssl try: ssl.create_default_context() _ssl_available = True except Exception: _ssl_available = False # Look for systemd-resolved, as it does dangling CNAME responses incorrectly. # # Currently we simply check if the nameserver is 127.0.0.53. _systemd_resolved_present = False try: _resolver = dns.resolver.Resolver() if _resolver.nameservers == ["127.0.0.53"]: _systemd_resolved_present = True except Exception: pass # Docker too! It not only has problems with dangling CNAME, but also says NXDOMAIN when # it should say no error no data. _is_docker = tests.util.is_docker() query_addresses = [] family = socket.AF_UNSPEC if tests.util.have_ipv4(): query_addresses.append("8.8.8.8") family = socket.AF_INET if tests.util.have_ipv6(): have_v6 = True if family == socket.AF_INET: # we have both working, go back to UNSPEC family = socket.AF_UNSPEC else: # v6 only family = socket.AF_INET6 query_addresses.append("2001:4860:4860::8888") KNOWN_ANYCAST_DOH_RESOLVER_URLS = [ "https://cloudflare-dns.com/dns-query", "https://dns.google/dns-query", # 'https://dns11.quad9.net/dns-query', ] KNOWN_ANYCAST_DOH3_RESOLVER_URLS = [ "https://cloudflare-dns.com/dns-query", "https://dns.google/dns-query", ] class AsyncDetectionTests(unittest.TestCase): sniff_result = "asyncio" def async_run(self, afunc): return asyncio.run(afunc()) def test_sniff(self): dns.asyncbackend._default_backend = None async def run(): self.assertEqual(dns.asyncbackend.sniff(), self.sniff_result) self.async_run(run) def test_get_default_backend(self): dns.asyncbackend._default_backend = None async def run(): backend = dns.asyncbackend.get_default_backend() self.assertEqual(backend.name(), self.sniff_result) self.async_run(run) class NoSniffioAsyncDetectionTests(AsyncDetectionTests): expect_raise = False def setUp(self): dns.asyncbackend._no_sniffio = True def tearDown(self): dns.asyncbackend._no_sniffio = False def test_sniff(self): dns.asyncbackend._default_backend = None if self.expect_raise: async def abad(): dns.asyncbackend.sniff() def bad(): self.async_run(abad) self.assertRaises(dns.asyncbackend.AsyncLibraryNotFoundError, bad) else: super().test_sniff() def test_get_default_backend(self): dns.asyncbackend._default_backend = None if self.expect_raise: async def abad(): dns.asyncbackend.get_default_backend() def bad(): self.async_run(abad) self.assertRaises(dns.asyncbackend.AsyncLibraryNotFoundError, bad) else: super().test_get_default_backend() class MiscBackend(unittest.TestCase): def test_sniff_without_run_loop(self): dns.asyncbackend._default_backend = None def bad(): dns.asyncbackend.sniff() self.assertRaises(dns.asyncbackend.AsyncLibraryNotFoundError, bad) def test_bogus_backend(self): def bad(): dns.asyncbackend.get_backend("bogus") self.assertRaises(NotImplementedError, bad) class MiscQuery(unittest.TestCase): def test_source_tuple(self): t = dns.asyncquery._source_tuple(socket.AF_INET, None, 0) self.assertEqual(t, None) t = dns.asyncquery._source_tuple(socket.AF_INET6, None, 0) self.assertEqual(t, None) t = dns.asyncquery._source_tuple(socket.AF_INET, "1.2.3.4", 53) self.assertEqual(t, ("1.2.3.4", 53)) t = dns.asyncquery._source_tuple(socket.AF_INET6, "1::2", 53) self.assertEqual(t, ("1::2", 53)) t = dns.asyncquery._source_tuple(socket.AF_INET, None, 53) self.assertEqual(t, ("0.0.0.0", 53)) t = dns.asyncquery._source_tuple(socket.AF_INET6, None, 53) self.assertEqual(t, ("::", 53)) @unittest.skipIf(not tests.util.is_internet_reachable(), "Internet not reachable") class AsyncTests(unittest.TestCase): def setUp(self): self.backend = dns.asyncbackend.set_default_backend("asyncio") def async_run(self, afunc): return asyncio.run(afunc()) def testResolve(self): async def run(): answer = await dns.asyncresolver.resolve("dns.google.", "A") return set([rdata.address for rdata in answer]) seen = self.async_run(run) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) def testResolveAddress(self): async def run(): return await dns.asyncresolver.resolve_address("8.8.8.8") answer = self.async_run(run) dnsgoogle = dns.name.from_text("dns.google.") self.assertEqual(answer[0].target, dnsgoogle) def testResolveName(self): async def run1(): return await dns.asyncresolver.resolve_name("dns.google.") answers = self.async_run(run1) seen = set(answers.addresses()) self.assertEqual(len(seen), 4) self.assertIn("8.8.8.8", seen) self.assertIn("8.8.4.4", seen) self.assertIn("2001:4860:4860::8844", seen) self.assertIn("2001:4860:4860::8888", seen) async def run2(): return await dns.asyncresolver.resolve_name("dns.google.", socket.AF_INET) answers = self.async_run(run2) seen = set(answers.addresses()) self.assertEqual(len(seen), 2) self.assertIn("8.8.8.8", seen) self.assertIn("8.8.4.4", seen) async def run3(): return await dns.asyncresolver.resolve_name("dns.google.", socket.AF_INET6) answers = self.async_run(run3) seen = set(answers.addresses()) self.assertEqual(len(seen), 2) self.assertIn("2001:4860:4860::8844", seen) self.assertIn("2001:4860:4860::8888", seen) async def run4(): await dns.asyncresolver.resolve_name("nxdomain.dnspython.org") with self.assertRaises(dns.resolver.NXDOMAIN): self.async_run(run4) async def run5(): await dns.asyncresolver.resolve_name( dns.reversename.from_address("8.8.8.8") ) if not _is_docker: # docker returns NXDOMAIN! with self.assertRaises(dns.resolver.NoAnswer): self.async_run(run5) def testCanonicalNameNoCNAME(self): cname = dns.name.from_text("www.google.com") async def run(): return await dns.asyncresolver.canonical_name("www.google.com") self.assertEqual(self.async_run(run), cname) def testCanonicalNameCNAME(self): name = dns.name.from_text("www.dnspython.org") cname = dns.name.from_text("dmfrjf4ips8xa.cloudfront.net") async def run(): return await dns.asyncresolver.canonical_name(name) self.assertEqual(self.async_run(run), cname) @unittest.skipIf( _systemd_resolved_present or _is_docker, "systemd-resolved or docker in use" ) def testCanonicalNameDangling(self): name = dns.name.from_text("dangling-cname.dnspython.org") cname = dns.name.from_text("dangling-target.dnspython.org") async def run(): return await dns.asyncresolver.canonical_name(name) self.assertEqual(self.async_run(run), cname) def testZoneForName1(self): async def run(): name = dns.name.from_text("www.dnspython.org.") return await dns.asyncresolver.zone_for_name(name) ezname = dns.name.from_text("dnspython.org.") zname = self.async_run(run) self.assertEqual(zname, ezname) def testZoneForName2(self): async def run(): name = dns.name.from_text("a.b.www.dnspython.org.") return await dns.asyncresolver.zone_for_name(name) ezname = dns.name.from_text("dnspython.org.") zname = self.async_run(run) self.assertEqual(zname, ezname) def testZoneForName3(self): async def run(): name = dns.name.from_text("dnspython.org.") return await dns.asyncresolver.zone_for_name(name) ezname = dns.name.from_text("dnspython.org.") zname = self.async_run(run) self.assertEqual(zname, ezname) def testZoneForName4(self): def bad(): name = dns.name.from_text("dnspython.org", None) async def run(): return await dns.asyncresolver.zone_for_name(name) self.async_run(run) self.assertRaises(dns.resolver.NotAbsolute, bad) def testQueryUDP(self): for address in query_addresses: qname = dns.name.from_text("dns.google.") async def run(): q = dns.message.make_query(qname, dns.rdatatype.A) return await dns.asyncquery.udp(q, address, timeout=2) response = self.async_run(run) rrs = response.get_rrset( response.answer, qname, dns.rdataclass.IN, dns.rdatatype.A ) self.assertTrue(rrs is not None) seen = set([rdata.address for rdata in rrs]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) def testQueryUDPWithSocket(self): for address in query_addresses: qname = dns.name.from_text("dns.google.") async def run(): async with await self.backend.make_socket( dns.inet.af_for_address(address), socket.SOCK_DGRAM, 0, None, None, ) as s: q = dns.message.make_query(qname, dns.rdatatype.A) return await dns.asyncquery.udp(q, address, sock=s, timeout=2) response = self.async_run(run) rrs = response.get_rrset( response.answer, qname, dns.rdataclass.IN, dns.rdatatype.A ) self.assertTrue(rrs is not None) seen = set([rdata.address for rdata in rrs]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) def testQueryTCP(self): for address in query_addresses: qname = dns.name.from_text("dns.google.") async def run(): q = dns.message.make_query(qname, dns.rdatatype.A) return await dns.asyncquery.tcp(q, address, timeout=2) response = self.async_run(run) rrs = response.get_rrset( response.answer, qname, dns.rdataclass.IN, dns.rdatatype.A ) self.assertTrue(rrs is not None) seen = set([rdata.address for rdata in rrs]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) def testQueryTCPWithSocket(self): for address in query_addresses: qname = dns.name.from_text("dns.google.") async def run(): async with await self.backend.make_socket( dns.inet.af_for_address(address), socket.SOCK_STREAM, 0, None, (address, 53), 2, ) as s: # for basic coverage await s.getsockname() q = dns.message.make_query(qname, dns.rdatatype.A) return await dns.asyncquery.tcp(q, address, sock=s, timeout=2) response = self.async_run(run) rrs = response.get_rrset( response.answer, qname, dns.rdataclass.IN, dns.rdatatype.A ) self.assertTrue(rrs is not None) seen = set([rdata.address for rdata in rrs]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) @unittest.skipIf(not _ssl_available, "SSL not available") def testQueryTLS(self): for address in query_addresses: qname = dns.name.from_text("dns.google.") async def run(): q = dns.message.make_query(qname, dns.rdatatype.A) return await dns.asyncquery.tls(q, address, timeout=2) response = self.async_run(run) rrs = response.get_rrset( response.answer, qname, dns.rdataclass.IN, dns.rdatatype.A ) self.assertTrue(rrs is not None) seen = set([rdata.address for rdata in rrs]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) @unittest.skipIf(not _ssl_available, "SSL not available") def testQueryTLSWithContext(self): for address in query_addresses: qname = dns.name.from_text("dns.google.") async def run(): ssl_context = ssl.create_default_context() ssl_context.check_hostname = True q = dns.message.make_query(qname, dns.rdatatype.A) return await dns.asyncquery.tls( q, address, timeout=2, ssl_context=ssl_context ) response = self.async_run(run) rrs = response.get_rrset( response.answer, qname, dns.rdataclass.IN, dns.rdatatype.A ) self.assertTrue(rrs is not None) seen = set([rdata.address for rdata in rrs]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) @unittest.skipIf(not _ssl_available, "SSL not available") def testQueryTLSWithSocket(self): for address in query_addresses: qname = dns.name.from_text("dns.google.") async def run(): ssl_context = ssl.create_default_context() ssl_context.check_hostname = False async with await self.backend.make_socket( dns.inet.af_for_address(address), socket.SOCK_STREAM, 0, None, (address, 853), 2, ssl_context, None, ) as s: # for basic coverage await s.getsockname() q = dns.message.make_query(qname, dns.rdatatype.A) return await dns.asyncquery.tls(q, "8.8.8.8", sock=s, timeout=2) response = self.async_run(run) rrs = response.get_rrset( response.answer, qname, dns.rdataclass.IN, dns.rdatatype.A ) self.assertTrue(rrs is not None) seen = set([rdata.address for rdata in rrs]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) def testQueryUDPFallback(self): for address in query_addresses: qname = dns.name.from_text(".") async def run(): q = dns.message.make_query(qname, dns.rdatatype.DNSKEY) return await dns.asyncquery.udp_with_fallback(q, address, timeout=4) (_, tcp) = self.async_run(run) self.assertTrue(tcp) def testQueryUDPFallbackNoFallback(self): for address in query_addresses: qname = dns.name.from_text("dns.google.") async def run(): q = dns.message.make_query(qname, dns.rdatatype.A) return await dns.asyncquery.udp_with_fallback(q, address, timeout=2) (_, tcp) = self.async_run(run) self.assertFalse(tcp) def testUDPReceiveQuery(self): async def run(): async with await self.backend.make_socket( socket.AF_INET, socket.SOCK_DGRAM, source=("127.0.0.1", 0) ) as listener: listener_address = await listener.getsockname() async with await self.backend.make_socket( socket.AF_INET, socket.SOCK_DGRAM, source=("127.0.0.1", 0) ) as sender: sender_address = await sender.getsockname() q = dns.message.make_query("dns.google", dns.rdatatype.A) await dns.asyncquery.send_udp(sender, q, listener_address) expiration = time.time() + 2 (_, _, recv_address) = await dns.asyncquery.receive_udp( listener, expiration=expiration ) return (sender_address, recv_address) (sender_address, recv_address) = self.async_run(run) self.assertEqual(sender_address, recv_address) def testUDPReceiveTimeout(self): async def arun(): async with await self.backend.make_socket( socket.AF_INET, socket.SOCK_DGRAM, 0, ("127.0.0.1", 0) ) as s: try: # for basic coverage await s.getpeername() except Exception: # we expect failure as we haven't connected the socket pass await s.recvfrom(1000, 0.05) def run(): self.async_run(arun) self.assertRaises(dns.exception.Timeout, run) @unittest.skipIf(not dns.query._have_httpx, "httpx not available") def testDOHGetRequest(self): async def run(): nameserver_url = random.choice(KNOWN_ANYCAST_DOH_RESOLVER_URLS) q = dns.message.make_query("example.com.", dns.rdatatype.A) r = await dns.asyncquery.https( q, nameserver_url, post=False, timeout=4, family=family ) self.assertTrue(q.is_response(r)) self.async_run(run) @unittest.skipIf(not dns.query._have_httpx, "httpx not available") def testDOHPostRequest(self): async def run(): nameserver_url = random.choice(KNOWN_ANYCAST_DOH_RESOLVER_URLS) q = dns.message.make_query("example.com.", dns.rdatatype.A) r = await dns.asyncquery.https( q, nameserver_url, post=True, timeout=4, family=family ) self.assertTrue(q.is_response(r)) self.async_run(run) @unittest.skipIf(not dns.quic.have_quic, "aioquic not available") def testDoH3GetRequest(self): async def run(): nameserver_url = random.choice(KNOWN_ANYCAST_DOH3_RESOLVER_URLS) q = dns.message.make_query("dns.google.", dns.rdatatype.A) r = await dns.asyncquery.https( q, nameserver_url, post=False, timeout=4, family=family, http_version=dns.asyncquery.HTTPVersion.H3, ) self.assertTrue(q.is_response(r)) self.async_run(run) @unittest.skipIf(not dns.quic.have_quic, "aioquic not available") def TestDoH3PostRequest(self): async def run(): nameserver_url = random.choice(KNOWN_ANYCAST_DOH3_RESOLVER_URLS) q = dns.message.make_query("dns.google.", dns.rdatatype.A) r = await dns.asyncquery.https( q, nameserver_url, post=True, timeout=4, family=family, http_version=dns.asyncquery.HTTPVersion.H3, ) self.assertTrue(q.is_response(r)) self.async_run(run) @unittest.skipIf(not dns.quic.have_quic, "aioquic not available") def TestDoH3QueryIP(self): async def run(): nameserver_ip = "8.8.8.8" q = dns.message.make_query("example.com.", dns.rdatatype.A) r = dns.asyncquery.https( q, nameserver_ip, post=False, timeout=4, http_version=dns.asyncquery.HTTPVersion.H3, ) self.assertTrue(q.is_response(r)) self.async_run(run) @unittest.skipIf(not dns.query._have_httpx, "httpx not available") def testResolverDOH(self): async def run(): res = dns.asyncresolver.Resolver(configure=False) res.nameservers = ["https://dns.google/dns-query"] answer = await res.resolve("dns.google", "A", backend=self.backend) seen = set([rdata.address for rdata in answer]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) self.async_run(run) @unittest.skipIf(not tests.util.have_ipv4(), "IPv4 not reachable") def testResolveAtAddress(self): async def run(): answer = await dns.asyncresolver.resolve_at("8.8.8.8", "dns.google.", "A") seen = set([rdata.address for rdata in answer]) self.assertIn("8.8.8.8", seen) self.assertIn("8.8.4.4", seen) self.async_run(run) @unittest.skipIf(not tests.util.have_ipv4(), "IPv4 not reachable") def testResolveAtName(self): async def run(): answer = await dns.asyncresolver.resolve_at( "dns.google", "dns.google.", "A", family=socket.AF_INET ) seen = set([rdata.address for rdata in answer]) self.assertIn("8.8.8.8", seen) self.assertIn("8.8.4.4", seen) self.async_run(run) def testSleep(self): async def run(): before = time.time() await self.backend.sleep(0.1) after = time.time() self.assertTrue(after - before >= 0.1) self.async_run(run) @unittest.skipIf(not tests.util.is_internet_reachable(), "Internet not reachable") class AsyncioOnlyTests(unittest.TestCase): def setUp(self): self.backend = dns.asyncbackend.set_default_backend("asyncio") def async_run(self, afunc): return asyncio.run(afunc()) def testUseAfterTimeout(self): # Test #843 fix. async def run(): qname = dns.name.from_text("dns.google") query = dns.message.make_query(qname, "A") sock = await self.backend.make_socket(socket.AF_INET, socket.SOCK_DGRAM) async with sock: # First do something that will definitely timeout. try: response = await dns.asyncquery.udp( query, "8.8.8.8", timeout=0.0001, sock=sock ) except dns.exception.Timeout: pass except Exception: self.assertTrue(False) # Now try to reuse the socket with a reasonable timeout. try: response = await dns.asyncquery.udp( query, "8.8.8.8", timeout=5, sock=sock ) rrs = response.get_rrset( response.answer, qname, dns.rdataclass.IN, dns.rdatatype.A ) self.assertTrue(rrs is not None) seen = set([rdata.address for rdata in rrs]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) except Exception: self.assertTrue(False) self.async_run(run) try: import sniffio import trio class TrioAsyncDetectionTests(AsyncDetectionTests): sniff_result = "trio" def async_run(self, afunc): return trio.run(afunc) class TrioNoSniffioAsyncDetectionTests(NoSniffioAsyncDetectionTests): expect_raise = True def async_run(self, afunc): return trio.run(afunc) class TrioAsyncTests(AsyncTests): def setUp(self): self.backend = dns.asyncbackend.set_default_backend("trio") def async_run(self, afunc): return trio.run(afunc) except ImportError: pass class MockSock: def __init__(self, wire1, from1, wire2, from2): self.family = socket.AF_INET self.first_time = True self.wire1 = wire1 self.from1 = from1 self.wire2 = wire2 self.from2 = from2 async def sendto(self, data, where, timeout): return len(data) async def recvfrom(self, bufsize, expiration): if self.first_time: self.first_time = False return self.wire1, self.from1 else: return self.wire2, self.from2 class IgnoreErrors(unittest.TestCase): def setUp(self): self.q = dns.message.make_query("example.", "A") self.good_r = dns.message.make_response(self.q) self.good_r.set_rcode(dns.rcode.NXDOMAIN) self.good_r_wire = self.good_r.to_wire() dns.asyncbackend.set_default_backend("asyncio") def async_run(self, afunc): return asyncio.run(afunc()) async def mock_receive( self, wire1, from1, wire2, from2, ignore_unexpected=True, ignore_errors=True, raise_on_truncation=False, good_r=None, ): if good_r is None: good_r = self.good_r s = MockSock(wire1, from1, wire2, from2) (r, when, _) = await dns.asyncquery.receive_udp( s, ("127.0.0.1", 53), time.time() + 2, ignore_unexpected=ignore_unexpected, ignore_errors=ignore_errors, raise_on_truncation=raise_on_truncation, query=self.q, ) self.assertEqual(r, good_r) def test_good_mock(self): async def run(): await self.mock_receive(self.good_r_wire, ("127.0.0.1", 53), None, None) self.async_run(run) def test_bad_address(self): async def run(): await self.mock_receive( self.good_r_wire, ("127.0.0.2", 53), self.good_r_wire, ("127.0.0.1", 53) ) self.async_run(run) def test_bad_address_not_ignored(self): async def abad(): await self.mock_receive( self.good_r_wire, ("127.0.0.2", 53), self.good_r_wire, ("127.0.0.1", 53), ignore_unexpected=False, ) def bad(): self.async_run(abad) self.assertRaises(dns.query.UnexpectedSource, bad) def test_not_response_not_ignored_udp_level(self): async def abad(): bad_r = dns.message.make_response(self.q) bad_r.id += 1 bad_r_wire = bad_r.to_wire() s = MockSock( bad_r_wire, ("127.0.0.1", 53), self.good_r_wire, ("127.0.0.1", 53) ) await dns.asyncquery.udp(self.good_r, "127.0.0.1", sock=s) def bad(): self.async_run(abad) self.assertRaises(dns.query.BadResponse, bad) def test_bad_id(self): async def run(): bad_r = dns.message.make_response(self.q) bad_r.id += 1 bad_r_wire = bad_r.to_wire() await self.mock_receive( bad_r_wire, ("127.0.0.1", 53), self.good_r_wire, ("127.0.0.1", 53) ) self.async_run(run) def test_bad_id_not_ignored(self): bad_r = dns.message.make_response(self.q) bad_r.id += 1 bad_r_wire = bad_r.to_wire() async def abad(): (r, wire) = await self.mock_receive( bad_r_wire, ("127.0.0.1", 53), self.good_r_wire, ("127.0.0.1", 53), ignore_errors=False, ) def bad(): self.async_run(abad) self.assertRaises(AssertionError, bad) def test_bad_wire(self): async def run(): bad_r = dns.message.make_response(self.q) bad_r.id += 1 bad_r_wire = bad_r.to_wire() await self.mock_receive( bad_r_wire[:10], ("127.0.0.1", 53), self.good_r_wire, ("127.0.0.1", 53) ) self.async_run(run) def test_good_wire_with_truncation_flag_and_no_truncation_raise(self): async def run(): tc_r = dns.message.make_response(self.q) tc_r.flags |= dns.flags.TC tc_r_wire = tc_r.to_wire() await self.mock_receive( tc_r_wire, ("127.0.0.1", 53), None, None, good_r=tc_r ) self.async_run(run) def test_good_wire_with_truncation_flag_and_truncation_raise(self): async def agood(): tc_r = dns.message.make_response(self.q) tc_r.flags |= dns.flags.TC tc_r_wire = tc_r.to_wire() await self.mock_receive( tc_r_wire, ("127.0.0.1", 53), None, None, raise_on_truncation=True ) def good(): self.async_run(agood) self.assertRaises(dns.message.Truncated, good) def test_wrong_id_wire_with_truncation_flag_and_no_truncation_raise(self): async def run(): bad_r = dns.message.make_response(self.q) bad_r.id += 1 bad_r.flags |= dns.flags.TC bad_r_wire = bad_r.to_wire() await self.mock_receive( bad_r_wire, ("127.0.0.1", 53), self.good_r_wire, ("127.0.0.1", 53) ) self.async_run(run) def test_wrong_id_wire_with_truncation_flag_and_truncation_raise(self): async def run(): bad_r = dns.message.make_response(self.q) bad_r.id += 1 bad_r.flags |= dns.flags.TC bad_r_wire = bad_r.to_wire() await self.mock_receive( bad_r_wire, ("127.0.0.1", 53), self.good_r_wire, ("127.0.0.1", 53), raise_on_truncation=True, ) self.async_run(run) def test_bad_wire_not_ignored(self): bad_r = dns.message.make_response(self.q) bad_r.id += 1 bad_r_wire = bad_r.to_wire() async def abad(): await self.mock_receive( bad_r_wire[:10], ("127.0.0.1", 53), self.good_r_wire, ("127.0.0.1", 53), ignore_errors=False, ) def bad(): self.async_run(abad) self.assertRaises(dns.message.ShortHeader, bad) def test_trailing_wire(self): async def run(): wire = self.good_r_wire + b"abcd" await self.mock_receive( wire, ("127.0.0.1", 53), self.good_r_wire, ("127.0.0.1", 53) ) self.async_run(run) def test_trailing_wire_not_ignored(self): wire = self.good_r_wire + b"abcd" async def abad(): await self.mock_receive( wire, ("127.0.0.1", 53), self.good_r_wire, ("127.0.0.1", 53), ignore_errors=False, ) def bad(): self.async_run(abad) self.assertRaises(dns.message.TrailingJunk, bad) dnspython-2.7.0/tests/test_bugs.py0000644000000000000000000000725013615410400014205 0ustar00# 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 # 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. from io import BytesIO import unittest import binascii import dns.rdata import dns.rdataclass import dns.rdatatype import dns.rdtypes.ANY.TXT import dns.ttl class BugsTestCase(unittest.TestCase): def test_float_LOC(self): rdata = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.LOC, "30 30 0.000 N 100 30 0.000 W 10.00m 20m 2000m 20m", ) self.assertEqual(rdata.float_latitude, 30.5) self.assertEqual(rdata.float_longitude, -100.5) def test_SOA_BIND8_TTL(self): rdata1 = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.SOA, "a b 100 1s 1m 1h 1d" ) rdata2 = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.SOA, "a b 100 1 60 3600 86400" ) self.assertEqual(rdata1, rdata2) def test_empty_NSEC3_window(self): rdata = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.NSEC3, "1 0 100 ABCD SCBCQHKU35969L2A68P3AD59LHF30715", ) self.assertEqual(rdata.windows, ()) def test_zero_size_APL(self): rdata = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.APL, "") rdata2 = dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.APL, "", 0, 0) self.assertEqual(rdata, rdata2) def test_CAA_from_wire(self): rdata = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.CAA, '0 issue "ca.example.net"' ) f = BytesIO() rdata.to_wire(f) wire = f.getvalue() rdlen = len(wire) wire += b"trailing garbage" rdata2 = dns.rdata.from_wire( dns.rdataclass.IN, dns.rdatatype.CAA, wire, 0, rdlen ) self.assertEqual(rdata, rdata2) def test_trailing_zero_APL(self): in4 = "!1:127.0.0.0/1" rd4 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.APL, in4) out4 = rd4.to_digestable(dns.name.from_text("test")) text4 = binascii.hexlify(out4).decode("ascii") self.assertEqual(text4, "000101817f") in6 = "!2:::1000/1" rd6 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.APL, in6) out6 = rd6.to_digestable(dns.name.from_text("test")) text6 = binascii.hexlify(out6).decode("ascii") self.assertEqual(text6, "0002018f000000000000000000000000000010") def test_TXT_conversions(self): t1 = dns.rdtypes.ANY.TXT.TXT(dns.rdataclass.IN, dns.rdatatype.TXT, [b"foo"]) t2 = dns.rdtypes.ANY.TXT.TXT(dns.rdataclass.IN, dns.rdatatype.TXT, b"foo") t3 = dns.rdtypes.ANY.TXT.TXT(dns.rdataclass.IN, dns.rdatatype.TXT, "foo") t4 = dns.rdtypes.ANY.TXT.TXT(dns.rdataclass.IN, dns.rdatatype.TXT, ["foo"]) self.assertEqual(t1, t2) self.assertEqual(t1, t2) self.assertEqual(t1, t4) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_constants.py0000644000000000000000000000250713615410400015261 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import unittest import dns.dnssec import dns.edns import dns.flags import dns.message import dns.opcode import dns.rcode import dns.rdtypes.dnskeybase import dns.update import tests.util class ConstantsTestCase(unittest.TestCase): def test_dnssec_constants(self): tests.util.check_enum_exports( dns.dnssec, self.assertEqual, only={dns.dnssec.Algorithm} ) tests.util.check_enum_exports(dns.rdtypes.dnskeybase, self.assertEqual) def test_flags_constants(self): tests.util.check_enum_exports(dns.flags, self.assertEqual) tests.util.check_enum_exports(dns.rcode, self.assertEqual) tests.util.check_enum_exports(dns.opcode, self.assertEqual) def test_message_constants(self): tests.util.check_enum_exports( dns.message, self.assertEqual, only={dns.message.MessageSection} ) tests.util.check_enum_exports(dns.update, self.assertEqual) def test_rdata_constants(self): tests.util.check_enum_exports(dns.rdataclass, self.assertEqual) tests.util.check_enum_exports(dns.rdatatype, self.assertEqual) def test_edns_constants(self): tests.util.check_enum_exports( dns.edns, self.assertEqual, only={dns.edns.OptionType} ) dnspython-2.7.0/tests/test_ddr.py0000644000000000000000000000235213615410400014014 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import asyncio import time import pytest import dns.asyncbackend import dns.asyncresolver import dns.resolver import dns.nameserver import tests.util @pytest.mark.skipif( not tests.util.is_internet_reachable(), reason="Internet not reachable" ) def test_basic_ddr_sync(): for nameserver in ["1.1.1.1", "8.8.8.8"]: res = dns.resolver.Resolver(configure=False) res.nameservers = [nameserver] res.try_ddr() for nameserver in res.nameservers: assert isinstance(nameserver, dns.nameserver.Nameserver) assert nameserver.kind() != "Do53" @pytest.mark.skipif( not tests.util.is_internet_reachable(), reason="Internet not reachable" ) def test_basic_ddr_async(): async def run(): dns.asyncbackend._default_backend = None for nameserver in ["1.1.1.1", "8.8.8.8"]: res = dns.asyncresolver.Resolver(configure=False) res.nameservers = [nameserver] await res.try_ddr() for nameserver in res.nameservers: assert isinstance(nameserver, dns.nameserver.Nameserver) assert nameserver.kind() != "Do53" asyncio.run(run()) dnspython-2.7.0/tests/test_dnssec.py0000644000000000000000000015137713615410400014536 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 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 functools import time import unittest from datetime import datetime, timedelta, timezone from typing import Any import dns.dnssec import dns.name import dns.rdata import dns.rdataclass import dns.rdataset import dns.rdatatype import dns.rdtypes.ANY.CDNSKEY import dns.rdtypes.ANY.CDS import dns.rdtypes.ANY.DNSKEY import dns.rdtypes.ANY.DS import dns.rrset import dns.zone from dns.rdtypes.dnskeybase import Flag from .keys import test_dnskeys try: from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import dsa, ec, ed448, ed25519, rsa from cryptography.hazmat.primitives.serialization import load_pem_private_key except ImportError: pass # Cryptography ImportError already handled in dns.dnssec # pylint: disable=line-too-long abs_dnspython_org = dns.name.from_text("dnspython.org") abs_keys = { abs_dnspython_org: dns.rrset.from_text( "dnspython.org.", 3600, "IN", "DNSKEY", "257 3 5 AwEAAenVTr9L1OMlL1/N2ta0Qj9LLLnnmFWIr1dJoAsWM9BQfsbV7kFZ" " XbAkER/FY9Ji2o7cELxBwAsVBuWn6IUUAJXLH74YbC1anY0lifjgt29z" " SwDzuB7zmC7yVYZzUunBulVW4zT0tg1aePbpVL2EtTL8VzREqbJbE25R" " KuQYHZtFwG8S4iBxJUmT2Bbd0921LLxSQgVoFXlQx/gFV2+UERXcJ5ce" " iX6A6wc02M/pdg/YbJd2rBa0MYL3/Fz/Xltre0tqsImZGxzi6YtYDs45" " NC8gH+44egz82e2DATCVM1ICPmRDjXYTLldQiWA2ZXIWnK0iitl5ue24 7EsWJefrIhE=", "256 3 5 AwEAAdSSghOGjU33IQZgwZM2Hh771VGXX05olJK49FxpSyuEAjDBXY58" " LGU9R2Zgeecnk/b9EAhFu/vCV9oECtiTCvwuVAkt9YEweqYDluQInmgP" " NGMJCKdSLlnX93DkjDw8rMYv5dqXCuSGPlKChfTJOLQxIAxGloS7lL+c 0CTZydAF", ) } abs_keys_duplicate_keytag = { abs_dnspython_org: dns.rrset.from_text( "dnspython.org.", 3600, "IN", "DNSKEY", "257 3 5 AwEAAenVTr9L1OMlL1/N2ta0Qj9LLLnnmFWIr1dJoAsWM9BQfsbV7kFZ" " XbAkER/FY9Ji2o7cELxBwAsVBuWn6IUUAJXLH74YbC1anY0lifjgt29z" " SwDzuB7zmC7yVYZzUunBulVW4zT0tg1aePbpVL2EtTL8VzREqbJbE25R" " KuQYHZtFwG8S4iBxJUmT2Bbd0921LLxSQgVoFXlQx/gFV2+UERXcJ5ce" " iX6A6wc02M/pdg/YbJd2rBa0MYL3/Fz/Xltre0tqsImZGxzi6YtYDs45" " NC8gH+44egz82e2DATCVM1ICPmRDjXYTLldQiWA2ZXIWnK0iitl5ue24 7EsWJefrIhE=", "256 3 5 AwEAAdSSg++++THIS/IS/NOT/THE/CORRECT/KEY++++++++++++++++" " ++++++++++++++++++++++++++++++++++++++++++++++++++++++++" " ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ AaOSydAF", "256 3 5 AwEAAdSSghOGjU33IQZgwZM2Hh771VGXX05olJK49FxpSyuEAjDBXY58" " LGU9R2Zgeecnk/b9EAhFu/vCV9oECtiTCvwuVAkt9YEweqYDluQInmgP" " NGMJCKdSLlnX93DkjDw8rMYv5dqXCuSGPlKChfTJOLQxIAxGloS7lL+c 0CTZydAF", ) } rel_keys = { dns.name.empty: dns.rrset.from_text( "@", 3600, "IN", "DNSKEY", "257 3 5 AwEAAenVTr9L1OMlL1/N2ta0Qj9LLLnnmFWIr1dJoAsWM9BQfsbV7kFZ" " XbAkER/FY9Ji2o7cELxBwAsVBuWn6IUUAJXLH74YbC1anY0lifjgt29z" " SwDzuB7zmC7yVYZzUunBulVW4zT0tg1aePbpVL2EtTL8VzREqbJbE25R" " KuQYHZtFwG8S4iBxJUmT2Bbd0921LLxSQgVoFXlQx/gFV2+UERXcJ5ce" " iX6A6wc02M/pdg/YbJd2rBa0MYL3/Fz/Xltre0tqsImZGxzi6YtYDs45" " NC8gH+44egz82e2DATCVM1ICPmRDjXYTLldQiWA2ZXIWnK0iitl5ue24 7EsWJefrIhE=", "256 3 5 AwEAAdSSghOGjU33IQZgwZM2Hh771VGXX05olJK49FxpSyuEAjDBXY58" " LGU9R2Zgeecnk/b9EAhFu/vCV9oECtiTCvwuVAkt9YEweqYDluQInmgP" " NGMJCKdSLlnX93DkjDw8rMYv5dqXCuSGPlKChfTJOLQxIAxGloS7lL+c 0CTZydAF", ) } when = 1290250287 abs_soa = dns.rrset.from_text( "dnspython.org.", 3600, "IN", "SOA", "howl.dnspython.org. hostmaster.dnspython.org. 2010020047 3600 1800 604800 3600", ) abs_other_soa = dns.rrset.from_text( "dnspython.org.", 3600, "IN", "SOA", "foo.dnspython.org. hostmaster.dnspython.org. 2010020047 3600 1800 604800 3600", ) abs_soa_rrsig = dns.rrset.from_text( "dnspython.org.", 3600, "IN", "RRSIG", "SOA 5 2 3600 20101127004331 20101119213831 61695 dnspython.org." " sDUlltRlFTQw5ITFxOXW3TgmrHeMeNpdqcZ4EXxM9FHhIlte6V9YCnDw" " t6dvM9jAXdIEi03l9H/RAd9xNNW6gvGMHsBGzpvvqFQxIBR2PoiZA1mX" " /SWHZFdbt4xjYTtXqpyYvrMK0Dt7bUYPadyhPFCJ1B+I8Zi7B5WJEOd0 8vs=", ) rel_soa = dns.rrset.from_text( "@", 3600, "IN", "SOA", "howl hostmaster 2010020047 3600 1800 604800 3600" ) rel_other_soa = dns.rrset.from_text( "@", 3600, "IN", "SOA", "foo hostmaster 2010020047 3600 1800 604800 3600" ) rel_soa_rrsig = dns.rrset.from_text( "@", 3600, "IN", "RRSIG", "SOA 5 2 3600 20101127004331 20101119213831 61695 @" " sDUlltRlFTQw5ITFxOXW3TgmrHeMeNpdqcZ4EXxM9FHhIlte6V9YCnDw" " t6dvM9jAXdIEi03l9H/RAd9xNNW6gvGMHsBGzpvvqFQxIBR2PoiZA1mX" " /SWHZFdbt4xjYTtXqpyYvrMK0Dt7bUYPadyhPFCJ1B+I8Zi7B5WJEOd0 8vs=", ) sep_key = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.DNSKEY, "257 3 5 AwEAAenVTr9L1OMlL1/N2ta0Qj9LLLnnmFWIr1dJoAsWM9BQfsbV7kFZ" " XbAkER/FY9Ji2o7cELxBwAsVBuWn6IUUAJXLH74YbC1anY0lifjgt29z" " SwDzuB7zmC7yVYZzUunBulVW4zT0tg1aePbpVL2EtTL8VzREqbJbE25R" " KuQYHZtFwG8S4iBxJUmT2Bbd0921LLxSQgVoFXlQx/gFV2+UERXcJ5ce" " iX6A6wc02M/pdg/YbJd2rBa0MYL3/Fz/Xltre0tqsImZGxzi6YtYDs45" " NC8gH+44egz82e2DATCVM1ICPmRDjXYTLldQiWA2ZXIWnK0iitl5ue24 7EsWJefrIhE=", ) good_ds = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.DS, "57349 5 2 53A79A3E7488AB44FFC56B2D1109F0699D1796DD977E72108B841F96 E47D7013", ) good_cds = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.CDS, "57349 5 2 53A79A3E7488AB44FFC56B2D1109F0699D1796DD977E72108B841F96 E47D7013", ) when2 = 1290425644 abs_example = dns.name.from_text("example") abs_dsa_keys = { abs_example: dns.rrset.from_text( "example.", 86400, "IN", "DNSKEY", "257 3 3 CI3nCqyJsiCJHTjrNsJOT4RaszetzcJPYuoH3F9ZTVt3KJXncCVR3bwn" " 1w0iavKljb9hDlAYSfHbFCp4ic/rvg4p1L8vh5s8ToMjqDNl40A0hUGQ" " Ybx5hsECyK+qHoajilUX1phYSAD8d9WAGO3fDWzUPBuzR7o85NiZCDxz" " yXuNVfni0uhj9n1KYhEO5yAbbruDGN89wIZcxMKuQsdUY2GYD93ssnBv" " a55W6XRABYWayKZ90WkRVODLVYLSn53Pj/wwxGH+XdhIAZJXimrZL4yl" " My7rtBsLMqq8Ihs4Tows7LqYwY7cp6y/50tw6pj8tFqMYcPUjKZV36l1" " M/2t5BVg3i7IK61Aidt6aoC3TDJtzAxg3ZxfjZWJfhHjMJqzQIfbW5b9" " q1mjFsW5EUv39RaNnX+3JWPRLyDqD4pIwDyqfutMsdk/Py3paHn82FGp" " CaOg+nicqZ9TiMZURN/XXy5JoXUNQ3RNvbHCUiPUe18KUkY6mTfnyHld" " 1l9YCWmzXQVClkx/hOYxjJ4j8Ife58+Obu5X", "256 3 3 CJE1yb9YRQiw5d2xZrMUMR+cGCTt1bp1KDCefmYKmS+Z1+q9f42ETVhx" " JRiQwXclYwmxborzIkSZegTNYIV6mrYwbNB27Q44c3UGcspb3PiOw5TC" " jNPRYEcdwGvDZ2wWy+vkSV/S9tHXY8O6ODiE6abZJDDg/RnITyi+eoDL" " R3KZ5n/V1f1T1b90rrV6EewhBGQJpQGDogaXb2oHww9Tm6NfXyo7SoMM" " pbwbzOckXv+GxRPJIQNSF4D4A9E8XCksuzVVdE/0lr37+uoiAiPia38U" " 5W2QWe/FJAEPLjIp2eTzf0TrADc1pKP1wrA2ASpdzpm/aX3IB5RPp8Ew" " S9U72eBFZJAUwg635HxJVxH1maG6atzorR566E+e0OZSaxXS9o1o6QqN" " 3oPlYLGPORDiExilKfez3C/x/yioOupW9K5eKF0gmtaqrHX0oq9s67f/" " RIM2xVaKHgG9Vf2cgJIZkhv7sntujr+E4htnRmy9P9BxyFxsItYxPI6Z" " bzygHAZpGhlI/7ltEGlIwKxyTK3ZKBm67q7B", ) } abs_dsa_soa = dns.rrset.from_text( "example.", 86400, "IN", "SOA", "ns1.example. hostmaster.example. 2 10800 3600 604800 86400", ) abs_other_dsa_soa = dns.rrset.from_text( "example.", 86400, "IN", "SOA", "ns1.example. hostmaster.example. 2 10800 3600 604800 86401", ) abs_dsa_soa_rrsig = dns.rrset.from_text( "example.", 86400, "IN", "RRSIG", "SOA 3 1 86400 20101129143231 20101122112731 42088 example." " CGul9SuBofsktunV8cJs4eRs6u+3NCS3yaPKvBbD+pB2C76OUXDZq9U=", ) example_sep_key = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.DNSKEY, "257 3 3 CI3nCqyJsiCJHTjrNsJOT4RaszetzcJPYuoH3F9ZTVt3KJXncCVR3bwn" " 1w0iavKljb9hDlAYSfHbFCp4ic/rvg4p1L8vh5s8ToMjqDNl40A0hUGQ" " Ybx5hsECyK+qHoajilUX1phYSAD8d9WAGO3fDWzUPBuzR7o85NiZCDxz" " yXuNVfni0uhj9n1KYhEO5yAbbruDGN89wIZcxMKuQsdUY2GYD93ssnBv" " a55W6XRABYWayKZ90WkRVODLVYLSn53Pj/wwxGH+XdhIAZJXimrZL4yl" " My7rtBsLMqq8Ihs4Tows7LqYwY7cp6y/50tw6pj8tFqMYcPUjKZV36l1" " M/2t5BVg3i7IK61Aidt6aoC3TDJtzAxg3ZxfjZWJfhHjMJqzQIfbW5b9" " q1mjFsW5EUv39RaNnX+3JWPRLyDqD4pIwDyqfutMsdk/Py3paHn82FGp" " CaOg+nicqZ9TiMZURN/XXy5JoXUNQ3RNvbHCUiPUe18KUkY6mTfnyHld" " 1l9YCWmzXQVClkx/hOYxjJ4j8Ife58+Obu5X", ) example_ds_sha1 = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.DS, "18673 3 1 71b71d4f3e11bbd71b4eff12cde69f7f9215bbe7", ) example_ds_sha256 = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.DS, "18673 3 2 eb8344cbbf07c9d3d3d6c81d10c76653e28d8611a65e639ef8f716e4e4e5d913", ) example_ds_sha384 = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.DS, "18673 3 4 61ab241025c5f88d2537be04dcfba96f952adaefe0b382ec" "bc4108c97b75768c9e99fd16caed2a09634c51e8089fb84f", ) when3 = 1379801800 abs_ecdsa256_keys = { abs_example: dns.rrset.from_text( "example.", 86400, "IN", "DNSKEY", "256 3 13 +3ss1sCpdARVA61DJigEsL/8quo2a8MszKtn2gkkfxgzFs8S2UHtpb4N" " fY+XFmNW+JK6MsCkI3jHYN8eEQUgMw==", "257 3 13 eJCEVH7AS3wnoaQpaNlAXH0W8wxymtT9P6P3qjN2ZCV641ED8pF7wZ5V" " yWfOpgTs6oaZevbJgehl/GaRPUgVyQ==", ) } abs_ecdsa256_soa = dns.rrset.from_text( "example.", 86400, "IN", "SOA", "ns1.example. hostmaster.example. 4 10800 3600 604800 86400", ) abs_other_ecdsa256_soa = dns.rrset.from_text( "example.", 86400, "IN", "SOA", "ns1.example. hostmaster.example. 2 10800 3600 604800 86401", ) abs_ecdsa256_soa_rrsig = dns.rrset.from_text( "example.", 86400, "IN", "RRSIG", "SOA 13 1 86400 20130921221753 20130921221638 7460 example." " Sm09SOGz1ULB5D/duwdE2Zpn8bWbVBM77H6N1wPkc42LevvVO+kZEjpq" " 2nq4GOMJcih52667GIAbMrwmU5P2MQ==", ) when4 = 1379804850 abs_ecdsa384_keys = { abs_example: dns.rrset.from_text( "example.", 86400, "IN", "DNSKEY", "256 3 14 1bG8qWviKNXQX3BIuG6/T5jrP1FISiLW/8qGF6BsM9DQtWYhhZUA3Owr" " OAEiyHAhQwjkN2kTvWiAYoPN80Ii+5ff9/atzY4F9W50P4l75Dj9PYrL HN/hLUgWMNVc9pvA", "257 3 14 mSub2n0KRt6u2FaD5XJ3oQu0R4XvB/9vUJcyW6+oo0y+KzfQeTdkf1ro" " ZMVKoyWXW9zUKBYGJpMUIdbAxzrYi7f5HyZ3yDpBFz1hw9+o3CX+gtgb +RyhHfJDwwFXBid9", ) } abs_ecdsa384_soa = dns.rrset.from_text( "example.", 86400, "IN", "SOA", "ns1.example. hostmaster.example. 2 10800 3600 604800 86400", ) abs_other_ecdsa384_soa = dns.rrset.from_text( "example.", 86400, "IN", "SOA", "ns1.example. hostmaster.example. 2 10800 3600 604800 86401", ) abs_ecdsa384_soa_rrsig = dns.rrset.from_text( "example.", 86400, "IN", "RRSIG", "SOA 14 1 86400 20130929021229 20130921230729 63571 example." " CrnCu34EeeRz0fEhL9PLlwjpBKGYW8QjBjFQTwd+ViVLRAS8tNkcDwQE" " NhSV89NEjj7ze1a/JcCfcJ+/mZgnvH4NHLNg3Tf6KuLZsgs2I4kKQXEk 37oIHravPEOlGYNI", ) abs_example_com = dns.name.from_text("example.com") abs_ed25519_mx = dns.rrset.from_text( "example.com.", 3600, "IN", "MX", "10 mail.example.com." ) abs_other_ed25519_mx = dns.rrset.from_text( "example.com.", 3600, "IN", "MX", "11 mail.example.com." ) abs_ed25519_keys_1 = { abs_example_com: dns.rrset.from_text( "example.com", 3600, "IN", "DNSKEY", "257 3 15 l02Woi0iS8Aa25FQkUd9RMzZHJpBoRQwAQEX1SxZJA4=", ) } abs_ed25519_mx_rrsig_1 = dns.rrset.from_text( "example.com.", 3600, "IN", "RRSIG", "MX 15 2 3600 1440021600 1438207200 3613 example.com." " oL9krJun7xfBOIWcGHi7mag5/hdZrKWw15jPGrHpjQeR" "AvTdszaPD+QLs3fx8A4M3e23mRZ9VrbpMngwcrqNAg==", ) abs_ed25519_keys_2 = { abs_example_com: dns.rrset.from_text( "example.com", 3600, "IN", "DNSKEY", "257 3 15 zPnZ/QwEe7S8C5SPz2OfS5RR40ATk2/rYnE9xHIEijs=", ) } abs_ed25519_mx_rrsig_2 = dns.rrset.from_text( "example.com.", 3600, "IN", "RRSIG", "MX 15 2 3600 1440021600 1438207200 35217 example.com." " zXQ0bkYgQTEFyfLyi9QoiY6D8ZdYo4wyUhVioYZXFdT4" "10QPRITQSqJSnzQoSm5poJ7gD7AQR0O7KuI5k2pcBg==", ) abs_ed448_mx = abs_ed25519_mx abs_other_ed448_mx = abs_other_ed25519_mx abs_ed448_keys_1 = { abs_example_com: dns.rrset.from_text( "example.com", 3600, "IN", "DNSKEY", "257 3 16 3kgROaDjrh0H2iuixWBrc8g2EpBBLCdGzHmn+" "G2MpTPhpj/OiBVHHSfPodx1FYYUcJKm1MDpJtIA", ) } abs_ed448_mx_rrsig_1 = dns.rrset.from_text( "example.com.", 3600, "IN", "RRSIG", "MX 16 2 3600 1440021600 1438207200 9713 example.com." " 3cPAHkmlnxcDHMyg7vFC34l0blBhuG1qpwLmjInI8w1CMB29FkEAIJUA0amxWndkmnBZ6SKiwZSA" "xGILn/NBtOXft0+Gj7FSvOKxE/07+4RQvE581N3Aj/JtIyaiYVdnYtyMWbSNyGEY2213WKsJlwEA", ) abs_ed448_keys_2 = { abs_example_com: dns.rrset.from_text( "example.com", 3600, "IN", "DNSKEY", "257 3 16 kkreGWoccSDmUBGAe7+zsbG6ZAFQp+syPmYUur" "BRQc3tDjeMCJcVMRDmgcNLp5HlHAMy12VoISsA", ) } abs_ed448_mx_rrsig_2 = dns.rrset.from_text( "example.com.", 3600, "IN", "RRSIG", "MX 16 2 3600 1440021600 1438207200 38353 example.com." " E1/oLjSGIbmLny/4fcgM1z4oL6aqo+izT3urCyHyvEp4Sp8Syg1eI+lJ57CSnZqjJP41O/9l4m0A" "sQ4f7qI1gVnML8vWWiyW2KXhT9kuAICUSxv5OWbf81Rq7Yu60npabODB0QFPb/rkW3kUZmQ0YQUA", ) when5 = 1440021600 when5_start = 1438207200 wildcard_keys = { abs_example_com: dns.rrset.from_text( "example.com", 3600, "IN", "DNSKEY", "256 3 5 AwEAAecNZbwD2thg3kaRLVqCC7ASP/3F79ZIu7pCu8HvZZ6ZdinffnxT" " npNoVvavjouHKFYTtJyUZAfw3ZMJSsGvEerc7uh6Ex9TgvOJtWPGUtxB" " Nnni2u9Nk+5k6nJzMiS3sL3RLvrfZW5d2Bwbl9L5f9Ud+r2Dbm7EG3tY pMY5OE8f", ) } wildcard_example_com = dns.name.from_text("*", abs_example_com) wildcard_txt = dns.rrset.from_text("*.example.com.", 3600, "IN", "TXT", "foo") wildcard_txt_rrsig = dns.rrset.from_text( "*.example.com.", 3600, "IN", "RRSIG", "TXT 5 2 3600 20200707211255 20200630180755 42486 example.com." " qevJYhdAHq1VmehXQ5i+Epa32xs4zcd4qmb39pHa3GUKr1V504nxzdzQ" " gsT5mvDkRoY95+HAiysDON6DCDtZc69iBUIHWWuFo/OrcD2q/mWANG4x" " vyU28Pf0U1gN6Gd5iapKC0Ya12flKh//NQiNN2skOQ2MoF2MW2/MaAK2 HBc=", ) wildcard_when = 1593541048 rsamd5_keys = { abs_example: dns.rrset.from_text( "example", 3600, "in", "dnskey", "257 3 1 AwEAAewnoEWe+AVEnQzcZTwpl8K/QKuScYIX" " 9xHOhejAL1enMjE0j97Gq3XXJJPWF7eQQGHs" " 1De4Srv2UT0zRCLkH9r36lOR/ggANvthO/Ub" " Es0hlD3A58LumEPudgIDwEkxGvQAXMFTMw0x" " 1d/a82UtzmNoPVzFOl2r+OCXx9Jbdh/L;" " KSK; alg = RSAMD5; key id = 30239", "256 3 1 AwEAAb8OJM5YcqaYG0fenUdRlrhBQ6LuwCvr" " 5BRlrVbVzadSDBpq+yIiklfdGNBg3WZztDy1" " du62NWC/olMfc6uRe/SjqTa7IJ3MdEuZQXQw" " MedGdNSF73zbokx8wg7zBBr74xHczJcEpQhr" " ZLzwCDmIPu0yoVi3Yqdl4dm4vNBj9hAD;" " ZSK; alg = RSAMD5; key id = 62992", ) } rsamd5_ns = dns.rrset.from_text( "example.", 3600, "in", "ns", "ns1.example.", "ns2.example." ) rsamd5_ns_rrsig = dns.rrset.from_text( "example.", 3600, "in", "rrsig", "NS 1 1 3600 20200825153103 20200726153103 62992 example." " YPv0WVqzQBDH45mFcYGo9psCVoMoeeHeAugh" " 9RZuO2NmdwfQ3mmiQm7WJ3AYnzYIozFGf7CL" " nwn3vN8/fjsfcQgEv5xfhFTSd4IoAzJJiZAa" " vrI4L5590C/+aXQ8tjRmbMTPiqoudaXvsevE jP2lTFg5DCruJyFq5dnAY5b90RY=", ) rsamd5_when = 1595781671 rsasha512_keys = { abs_example: dns.rrset.from_text( "example", 3600, "in", "dnskey", "256 3 10 AwEAAb2JvKjZ6l5qg2ab3qqUQhLGGjsiMIuQ" " 2zhaXJHdTntS+8LgUXo5yLFn7YF9YL1VX9V4" " 5ASGxUpz0u0chjWqBNtUO3Ymzas/vck9o21M" " 2Ce/LrpfYsqvJaLvGf/dozW9uSeMQq1mPKYG" " xo4uxyhZBhZewX8znXZySrAIozBPH3yp ;" " ZSK; alg = RSASHA512 ; key id = 5957", "257 3 10 AwEAAc7Lnoe+mHijJ8OOHgyJHKYantQGKx5t" " rIs267gOePyAL7cUt9HO1Sm3vABSGNsoHL6w" " 8/542SxGbT21osVISamtq7kUPTgDU9iKqCBq" " VdXEdzXYbhBKVoQkGPl4PflfbOgg/45xAiTi" " 7qOUERuRCPdKEkd4FW0tg6VfZmm7QjP1 ;" " KSK; alg = RSASHA512 ; key id = 53212", ) } rsasha512_ns = dns.rrset.from_text( "example.", 3600, "in", "ns", "ns1.example.", "ns2.example." ) rsasha512_ns_rrsig = dns.rrset.from_text( "example.", 3600, "in", "rrsig", "NS 10 1 3600 20200825161255 20200726161255 5957 example." " P9A+1zYke7yIiKEnxFMm+UIW2CIwy2WDvbx6" " g8hHiI8qISe6oeKveFW23OSk9+VwFgBiOpeM" " ygzzFbckY7RkGbOr4TR8ogDRANt6LhV402Hu" " SXTV9hCLVFWU4PS+/fxxfOHCetsY5tWWSxZi" " zSHfgpGfsHWzQoAamag4XYDyykc=", ) rsasha512_when = 1595783997 unknown_alg_keys = { abs_example: dns.rrset.from_text( "example", 3600, "in", "dnskey", "256 3 100 Ym9ndXM=", "257 3 100 Ym9ndXM=" ) } unknown_alg_ns_rrsig = dns.rrset.from_text( "example.", 3600, "in", "rrsig", "NS 100 1 3600 20200825161255 20200726161255 16713 example." " P9A+1zYke7yIiKEnxFMm+UIW2CIwy2WDvbx6" " g8hHiI8qISe6oeKveFW23OSk9+VwFgBiOpeM" " ygzzFbckY7RkGbOr4TR8ogDRANt6LhV402Hu" " SXTV9hCLVFWU4PS+/fxxfOHCetsY5tWWSxZi" " zSHfgpGfsHWzQoAamag4XYDyykc=", ) fake_gost_keys = { abs_example: dns.rrset.from_text( "example", 3600, "in", "dnskey", "256 3 12 Ym9ndXM=", "257 3 12 Ym9ndXM=" ) } fake_gost_ns_rrsig = dns.rrset.from_text( "example.", 3600, "in", "rrsig", "NS 12 1 3600 20200825161255 20200726161255 16625 example." " P9A+1zYke7yIiKEnxFMm+UIW2CIwy2WDvbx6" " g8hHiI8qISe6oeKveFW23OSk9+VwFgBiOpeM" " ygzzFbckY7RkGbOr4TR8ogDRANt6LhV402Hu" " SXTV9hCLVFWU4PS+/fxxfOHCetsY5tWWSxZi zSHfgpGfsHWzQoAamag4XYDyykc=", ) test_zone_sans_nsec = """ example. 3600 IN SOA foo.example. bar.example. 1 2 3 4 5 example. 3600 IN NS ns1.example. example. 3600 IN NS ns2.example. bar.foo.example. 3600 IN MX 0 blaz.foo.example. ns1.example. 3600 IN A 10.0.0.1 ns2.example. 3600 IN A 10.0.0.2 sub.example. 3600 IN NS ns1.example. sub.example. 3600 IN NS ns2.example. sub.example. 3600 IN NS ns3.sub.example. sub.example. 3600 IN DS 12345 13 2 0100D208742A23024DF3C8827DFF3EB3E25126E9B72850E99D6055E18913CB2F sub.sub.example. 3600 IN NS ns3.sub.example. ns3.sub.example. 3600 IN A 10.0.0.3 """ test_zone_rrsigs = set( [ ("example.", dns.rdatatype.DNSKEY), ("example.", dns.rdatatype.NS), ("example.", dns.rdatatype.NSEC), ("example.", dns.rdatatype.SOA), ("bar.foo.example.", dns.rdatatype.MX), ("bar.foo.example.", dns.rdatatype.NSEC), ("ns1.example.", dns.rdatatype.A), ("ns1.example.", dns.rdatatype.NSEC), ("ns2.example.", dns.rdatatype.A), ("ns2.example.", dns.rdatatype.NSEC), ("sub.example.", dns.rdatatype.DS), ("sub.example.", dns.rdatatype.NSEC), ] ) test_zone_with_nsec = """ example. 3600 IN SOA foo.example. bar.example. 1 2 3 4 5 example. 3600 IN NS ns1.example. example. 3600 IN NS ns2.example. example. 5 IN NSEC bar.foo.example. NS NSEC SOA RRSIG bar.foo.example. 3600 IN MX 0 blaz.foo.example. bar.foo.example. 5 IN NSEC ns1.example. MX NSEC RRSIG ns1.example. 3600 IN A 10.0.0.1 ns1.example. 5 IN NSEC ns2.example. A NSEC RRSIG ns2.example. 3600 IN A 10.0.0.2 ns2.example. 5 IN NSEC sub.example. A NSEC RRSIG sub.example. 3600 IN NS ns1.example. sub.example. 3600 IN NS ns2.example. sub.example. 3600 IN NS ns3.sub.example. sub.example. 3600 IN DS 12345 13 2 0100D208742A23024DF3C8827DFF3EB3E25126E9B72850E99D6055E18913CB2F sub.example. 5 IN NSEC example. DS NS NSEC RRSIG sub.sub.example. 3600 IN NS ns3.sub.example. ns3.sub.example. 3600 IN A 10.0.0.3 """ @unittest.skipUnless(dns.dnssec._have_pyca, "Python Cryptography cannot be imported") class DNSSECValidatorTestCase(unittest.TestCase): def testAbsoluteRSAMD5Good(self): # type: () -> None dns.dnssec.validate( rsamd5_ns, rsamd5_ns_rrsig, rsamd5_keys, None, rsamd5_when, policy=dns.dnssec.allow_all_policy, ) def testAbsoluteRSAMD5GoodDeniedByPolicy(self): # type: () -> None with self.assertRaises(dns.dnssec.ValidationFailure): dns.dnssec.validate( rsamd5_ns, rsamd5_ns_rrsig, rsamd5_keys, None, rsamd5_when ) def testRSAMD5Keyid(self): self.assertEqual(dns.dnssec.key_id(rsamd5_keys[abs_example][0]), 30239) self.assertEqual(dns.dnssec.key_id(rsamd5_keys[abs_example][1]), 62992) def testAbsoluteRSAGood(self): # type: () -> None dns.dnssec.validate(abs_soa, abs_soa_rrsig, abs_keys, None, when) def testDuplicateKeytag(self): # type: () -> None dns.dnssec.validate( abs_soa, abs_soa_rrsig, abs_keys_duplicate_keytag, None, when ) def testAbsoluteRSABad(self): # type: () -> None def bad(): # type: () -> None dns.dnssec.validate(abs_other_soa, abs_soa_rrsig, abs_keys, None, when) self.assertRaises(dns.dnssec.ValidationFailure, bad) def testRelativeRSAGood(self): # type: () -> None dns.dnssec.validate(rel_soa, rel_soa_rrsig, rel_keys, abs_dnspython_org, when) # test the text conversion for origin too dns.dnssec.validate(rel_soa, rel_soa_rrsig, rel_keys, "dnspython.org", when) def testRelativeRSABad(self): # type: () -> None def bad(): # type: () -> None dns.dnssec.validate( rel_other_soa, rel_soa_rrsig, rel_keys, abs_dnspython_org, when ) self.assertRaises(dns.dnssec.ValidationFailure, bad) def testAbsoluteDSAGood(self): # type: () -> None dns.dnssec.validate( abs_dsa_soa, abs_dsa_soa_rrsig, abs_dsa_keys, None, when2, policy=dns.dnssec.allow_all_policy, ) def testAbsoluteDSAGoodDeniedByPolicy(self): # type: () -> None with self.assertRaises(dns.dnssec.ValidationFailure): dns.dnssec.validate( abs_dsa_soa, abs_dsa_soa_rrsig, abs_dsa_keys, None, when2 ) def testAbsoluteDSABad(self): # type: () -> None def bad(): # type: () -> None dns.dnssec.validate( abs_other_dsa_soa, abs_dsa_soa_rrsig, abs_dsa_keys, None, when2, policy=dns.dnssec.allow_all_policy, ) self.assertRaises(dns.dnssec.ValidationFailure, bad) def testAbsoluteECDSA256Good(self): # type: () -> None dns.dnssec.validate( abs_ecdsa256_soa, abs_ecdsa256_soa_rrsig, abs_ecdsa256_keys, None, when3 ) def testAbsoluteECDSA256Bad(self): # type: () -> None def bad(): # type: () -> None dns.dnssec.validate( abs_other_ecdsa256_soa, abs_ecdsa256_soa_rrsig, abs_ecdsa256_keys, None, when3, ) self.assertRaises(dns.dnssec.ValidationFailure, bad) def testAbsoluteECDSA384Good(self): # type: () -> None dns.dnssec.validate( abs_ecdsa384_soa, abs_ecdsa384_soa_rrsig, abs_ecdsa384_keys, None, when4 ) def testAbsoluteECDSA384Bad(self): # type: () -> None def bad(): # type: () -> None dns.dnssec.validate( abs_other_ecdsa384_soa, abs_ecdsa384_soa_rrsig, abs_ecdsa384_keys, None, when4, ) self.assertRaises(dns.dnssec.ValidationFailure, bad) def testAbsoluteED25519Good(self): # type: () -> None dns.dnssec.validate( abs_ed25519_mx, abs_ed25519_mx_rrsig_1, abs_ed25519_keys_1, None, when5 ) dns.dnssec.validate( abs_ed25519_mx, abs_ed25519_mx_rrsig_2, abs_ed25519_keys_2, None, when5 ) def testAbsoluteED25519Bad(self): # type: () -> None with self.assertRaises(dns.dnssec.ValidationFailure): dns.dnssec.validate( abs_other_ed25519_mx, abs_ed25519_mx_rrsig_1, abs_ed25519_keys_1, None, when5, ) with self.assertRaises(dns.dnssec.ValidationFailure): dns.dnssec.validate( abs_other_ed25519_mx, abs_ed25519_mx_rrsig_2, abs_ed25519_keys_2, None, when5, ) def testAbsoluteED448Good(self): # type: () -> None dns.dnssec.validate( abs_ed448_mx, abs_ed448_mx_rrsig_1, abs_ed448_keys_1, None, when5 ) dns.dnssec.validate( abs_ed448_mx, abs_ed448_mx_rrsig_2, abs_ed448_keys_2, None, when5 ) def testAbsoluteED448Bad(self): # type: () -> None with self.assertRaises(dns.dnssec.ValidationFailure): dns.dnssec.validate( abs_other_ed448_mx, abs_ed448_mx_rrsig_1, abs_ed448_keys_1, None, when5 ) with self.assertRaises(dns.dnssec.ValidationFailure): dns.dnssec.validate( abs_other_ed448_mx, abs_ed448_mx_rrsig_2, abs_ed448_keys_2, None, when5 ) def testAbsoluteRSASHA512Good(self): dns.dnssec.validate( rsasha512_ns, rsasha512_ns_rrsig, rsasha512_keys, None, rsasha512_when ) def testWildcardGoodAndBad(self): dns.dnssec.validate( wildcard_txt, wildcard_txt_rrsig, wildcard_keys, None, wildcard_when ) def clone_rrset(rrset, name): return dns.rrset.from_rdata(name, rrset.ttl, rrset[0]) a_name = dns.name.from_text("a.example.com") a_txt = clone_rrset(wildcard_txt, a_name) a_txt_rrsig = clone_rrset(wildcard_txt_rrsig, a_name) dns.dnssec.validate(a_txt, a_txt_rrsig, wildcard_keys, None, wildcard_when) abc_name = dns.name.from_text("a.b.c.example.com") abc_txt = clone_rrset(wildcard_txt, abc_name) abc_txt_rrsig = clone_rrset(wildcard_txt_rrsig, abc_name) dns.dnssec.validate(abc_txt, abc_txt_rrsig, wildcard_keys, None, wildcard_when) com_name = dns.name.from_text("com.") com_txt = clone_rrset(wildcard_txt, com_name) com_txt_rrsig = clone_rrset(wildcard_txt_rrsig, abc_name) with self.assertRaises(dns.dnssec.ValidationFailure): dns.dnssec.validate_rrsig( com_txt, com_txt_rrsig[0], wildcard_keys, None, wildcard_when ) # check some bogus label lengths a_name = dns.name.from_text("a.example.com") a_txt = clone_rrset(wildcard_txt, a_name) a_txt_rrsig = clone_rrset(wildcard_txt_rrsig, a_name) bad_rrsig = a_txt_rrsig[0].replace(labels=99) with self.assertRaises(dns.dnssec.ValidationFailure): dns.dnssec.validate_rrsig( a_txt, bad_rrsig, wildcard_keys, None, wildcard_when ) bad_rrsig = a_txt_rrsig[0].replace(labels=3) with self.assertRaises(dns.dnssec.ValidationFailure): dns.dnssec.validate_rrsig( a_txt, bad_rrsig, wildcard_keys, None, wildcard_when ) bad_rrsig = a_txt_rrsig[0].replace(labels=1) with self.assertRaises(dns.dnssec.ValidationFailure): dns.dnssec.validate_rrsig( a_txt, bad_rrsig, wildcard_keys, None, wildcard_when ) def testAlternateParameterFormats(self): # type: () -> None # Pass rrset and rrsigset as (name, rdataset) tuples, not rrsets rrset = (abs_soa.name, abs_soa.to_rdataset()) rrsigset = (abs_soa_rrsig.name, abs_soa_rrsig.to_rdataset()) dns.dnssec.validate(rrset, rrsigset, abs_keys, None, when) # Pass keys as a name->node dict, not a name->rrset dict keys = {} for name, key_rrset in abs_keys.items(): keys[name] = dns.node.Node() keys[name].rdatasets.append(key_rrset.to_rdataset()) dns.dnssec.validate(abs_soa, abs_soa_rrsig, keys, None, when) # test key not found. keys = {} for name, key_rrset in abs_keys.items(): keys[name] = dns.node.Node() with self.assertRaises(dns.dnssec.ValidationFailure): dns.dnssec.validate(abs_soa, abs_soa_rrsig, keys, None, when) # Pass origin as a string, not a name. dns.dnssec.validate(rel_soa, rel_soa_rrsig, rel_keys, "dnspython.org", when) dns.dnssec.validate_rrsig( rel_soa, rel_soa_rrsig[0], rel_keys, "dnspython.org", when ) def testAbsoluteKeyNotFound(self): with self.assertRaises(dns.dnssec.ValidationFailure): dns.dnssec.validate(abs_ed448_mx, abs_ed448_mx_rrsig_1, {}, None, when5) def testTimeBounds(self): # not yet valid with self.assertRaises(dns.dnssec.ValidationFailure): dns.dnssec.validate( abs_ed448_mx, abs_ed448_mx_rrsig_1, abs_ed448_keys_1, None, when5_start - 1, ) # expired with self.assertRaises(dns.dnssec.ValidationFailure): dns.dnssec.validate( abs_ed448_mx, abs_ed448_mx_rrsig_1, abs_ed448_keys_1, None, when5 + 1 ) # expired using the current time (to test the "get the time" code # path) with self.assertRaises(dns.dnssec.ValidationFailure): dns.dnssec.validate( abs_ed448_mx, abs_ed448_mx_rrsig_1, abs_ed448_keys_1, None ) def testOwnerNameMismatch(self): bogus = dns.name.from_text("example.bogus") with self.assertRaises(dns.dnssec.ValidationFailure): dns.dnssec.validate( (bogus, abs_ed448_mx), abs_ed448_mx_rrsig_1, abs_ed448_keys_1, None, when5 + 1, ) def testGOSTNotSupported(self): with self.assertRaises(dns.dnssec.ValidationFailure): dns.dnssec.validate( rsasha512_ns, fake_gost_ns_rrsig, fake_gost_keys, None, rsasha512_when ) def testUnknownAlgorithm(self): with self.assertRaises(dns.dnssec.ValidationFailure): dns.dnssec.validate( rsasha512_ns, unknown_alg_ns_rrsig, unknown_alg_keys, None, rsasha512_when, ) def check_candidates(self, flags, protocol, expected_number_of_candidates): algorithm = dns.dnssec.Algorithm.ED25519 zsk_private_key = ed25519.Ed25519PrivateKey.generate() zsk_dnskey = dns.dnssec.make_dnskey( public_key=zsk_private_key.public_key(), algorithm=algorithm, flags=flags, protocol=protocol, ) zsk_dnskey_rdataset = dns.rdataset.from_rdata(300, zsk_dnskey) signer = dns.name.from_text("example") a_rrset = dns.rrset.from_text(signer, 300, "IN", "A", "10.0.0.1") inception = time.time() expiration = inception + 86400 a_rrsig = dns.dnssec.sign( a_rrset, zsk_private_key, signer, zsk_dnskey, inception, expiration ) candidates = dns.dnssec._find_candidate_keys( {signer: zsk_dnskey_rdataset}, a_rrsig ) self.assertTrue(len(candidates) == expected_number_of_candidates) def testCandidateKeyMustBeProtocol3(self): self.check_candidates(Flag.ZONE, 1, 0) def testCandidateKeyMustHaveZoneFlag(self): self.check_candidates(0, 3, 0) def testGoodCandidateKeyIsFound(self): self.check_candidates(Flag.ZONE, 3, 1) @unittest.skipUnless(dns.dnssec._have_pyca, "Python Cryptography cannot be imported") class DNSSECMiscTestCase(unittest.TestCase): def testDigestToBig(self): with self.assertRaises(ValueError): dns.dnssec.DSDigest.make(256) def testNSEC3HashTooBig(self): with self.assertRaises(ValueError): dns.dnssec.NSEC3Hash.make(256) def testToTimestamp(self): REFERENCE_TIMESTAMP = 441812220 ts = dns.dnssec.to_timestamp( datetime(year=1984, month=1, day=1, hour=13, minute=37, tzinfo=timezone.utc) ) self.assertEqual(ts, REFERENCE_TIMESTAMP) ts = dns.dnssec.to_timestamp("19840101133700") self.assertEqual(ts, REFERENCE_TIMESTAMP) ts = dns.dnssec.to_timestamp(441812220.0) self.assertEqual(ts, REFERENCE_TIMESTAMP) ts = dns.dnssec.to_timestamp(441812220) self.assertEqual(ts, REFERENCE_TIMESTAMP) def testInceptionExpiration(self): zsk_private_key = ed25519.Ed25519PrivateKey.generate() zsk_dnskey = dns.dnssec.make_dnskey( public_key=zsk_private_key.public_key(), algorithm=dns.dnssec.Algorithm.ED25519, ) signer = dns.name.from_text("example") a_rrset = dns.rrset.from_text(signer, 300, "IN", "A", "10.0.0.1") inception = 10 expiration = inception + 86400 a_rrsig = dns.dnssec.sign( a_rrset, zsk_private_key, signer, zsk_dnskey, inception, expiration ) self.assertEqual(a_rrsig.inception, inception) self.assertEqual(a_rrsig.expiration, expiration) a_rrsig = dns.dnssec.sign( a_rrset, zsk_private_key, signer, zsk_dnskey, inception, lifetime=86400 ) self.assertEqual(a_rrsig.inception, inception) self.assertEqual(a_rrsig.expiration, expiration) a_rrsig = dns.dnssec.sign( a_rrset, zsk_private_key, signer, zsk_dnskey, lifetime=86400 ) self.assertEqual(a_rrsig.expiration - a_rrsig.inception, 86400) # Allow a little slop in case the clock ticks. self.assertTrue(time.time() - a_rrsig.inception <= 2) def do_test_sign_zone(self, relativize): zone = dns.zone.from_text( test_zone_sans_nsec, "example.", relativize=relativize ) algorithm = dns.dnssec.Algorithm.ED25519 lifetime = 3600 ksk_private_key = ed25519.Ed25519PrivateKey.generate() ksk_dnskey = dns.dnssec.make_dnskey( public_key=ksk_private_key.public_key(), algorithm=algorithm, flags=Flag.ZONE | Flag.SEP, ) zsk_private_key = ed25519.Ed25519PrivateKey.generate() zsk_dnskey = dns.dnssec.make_dnskey( public_key=zsk_private_key.public_key(), algorithm=algorithm, flags=Flag.ZONE, ) keys = [(ksk_private_key, ksk_dnskey), (zsk_private_key, zsk_dnskey)] with zone.writer() as txn: dns.dnssec.sign_zone( zone=zone, txn=txn, keys=keys, lifetime=lifetime, ) print(zone.to_text()) rrsigs = set( [ (str(name.derelativize(zone.origin)), rdataset.covers) for (name, rdataset) in zone.iterate_rdatasets() if rdataset.rdtype == dns.rdatatype.RRSIG ] ) self.assertEqual(rrsigs, test_zone_rrsigs) signers = set( [ ( str(name.derelativize(zone.origin)), rdataset.covers, rdataset[0].key_tag, ) for (name, rdataset) in zone.iterate_rdatasets() if rdataset.rdtype == dns.rdatatype.RRSIG ] ) for name, covers, key_tag in signers: if covers in [ dns.rdatatype.DNSKEY, dns.rdatatype.CDNSKEY, dns.rdatatype.CDS, ]: self.assertEqual(key_tag, dns.dnssec.key_id(ksk_dnskey)) else: self.assertEqual(key_tag, dns.dnssec.key_id(zsk_dnskey)) def test_sign_zone_absolute(self): self.do_test_sign_zone(False) def test_sign_zone_relative(self): self.do_test_sign_zone(True) def test_sign_zone_nsec_null_signer(self): def rrset_signer( txn: dns.transaction.Transaction, rrset: dns.rrset.RRset, ) -> None: pass zone1 = dns.zone.from_text(test_zone_sans_nsec, "example.", relativize=False) dns.dnssec.sign_zone(zone1, rrset_signer=rrset_signer) zone2 = dns.zone.from_text(test_zone_with_nsec, "example.", relativize=False) self.assertEqual(zone1.to_text(), zone2.to_text()) @unittest.skipUnless(dns.dnssec._have_pyca, "Python Cryptography cannot be imported") class DNSSECMakeDSTestCase(unittest.TestCase): def testMnemonicParser(self): good_ds_mnemonic = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.DS, "57349 RSASHA1 2 53A79A3E7488AB44FFC56B2D1109F0699D1796DD977E72108B841F96" " E47D7013", ) self.assertEqual(good_ds, good_ds_mnemonic) def testMakeExampleSHA1DS(self): # type: () -> None algorithm: Any for algorithm in ("SHA1", "sha1", dns.dnssec.DSDigest.SHA1): ds = dns.dnssec.make_ds( abs_example, example_sep_key, algorithm, policy=dns.dnssec.allow_all_policy, ) self.assertEqual(ds, example_ds_sha1) ds = dns.dnssec.make_ds( "example.", example_sep_key, algorithm, policy=dns.dnssec.allow_all_policy, ) self.assertEqual(ds, example_ds_sha1) def testMakeExampleSHA1DSValidationOkByPolicy(self): # type: () -> None algorithm: Any for algorithm in ("SHA1", "sha1", dns.dnssec.DSDigest.SHA1): ds = dns.dnssec.make_ds( abs_example, example_sep_key, algorithm, policy=dns.dnssec.allow_all_policy, ) self.assertEqual(ds, example_ds_sha1) ds = dns.dnssec.make_ds( "example.", example_sep_key, algorithm, validating=True ) self.assertEqual(ds, example_ds_sha1) def testMakeExampleSHA1DSDeniedByPolicy(self): # type: () -> None with self.assertRaises(dns.dnssec.DeniedByPolicy): ds = dns.dnssec.make_ds(abs_example, example_sep_key, "SHA1") self.assertEqual(ds, example_ds_sha1) def testMakeExampleSHA256DS(self): # type: () -> None algorithm: Any for algorithm in ("SHA256", "sha256", dns.dnssec.DSDigest.SHA256): ds = dns.dnssec.make_ds(abs_example, example_sep_key, algorithm) self.assertEqual(ds, example_ds_sha256) def testMakeExampleSHA384DS(self): # type: () -> None algorithm: Any for algorithm in ("SHA384", "sha384", dns.dnssec.DSDigest.SHA384): ds = dns.dnssec.make_ds(abs_example, example_sep_key, algorithm) self.assertEqual(ds, example_ds_sha384) def testMakeSHA256DS(self): # type: () -> None ds = dns.dnssec.make_ds(abs_dnspython_org, sep_key, "SHA256") self.assertEqual(ds, good_ds) def testMakeSHA256CDS(self): # type: () -> None cds = dns.dnssec.make_cds(abs_dnspython_org, sep_key, "SHA256") self.assertEqual(cds, good_cds) def testInvalidAlgorithm(self): # type: () -> None algorithm: Any for algorithm in (10, "shax"): with self.assertRaises(dns.dnssec.UnsupportedAlgorithm): ds = dns.dnssec.make_ds(abs_example, example_sep_key, algorithm) def testReservedDigestType(self): # type: () -> None with self.assertRaises(dns.exception.SyntaxError) as cm: dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.DS, f"18673 3 0 71b71d4f3e11bbd71b4eff12cde69f7f9215bbe7", ) self.assertEqual("digest type 0 is reserved", str(cm.exception)) def testUnknownDigestType(self): # type: () -> None digest_types = [dns.rdatatype.DS, dns.rdatatype.CDS] for rdtype in digest_types: rd = dns.rdata.from_text( dns.rdataclass.IN, rdtype, f"18673 3 5 71b71d4f3e11bbd71b4eff12cde69f7f9215bbe7", ) assert isinstance(rd, dns.rdtypes.ANY.DS.DS) or isinstance( rd, dns.rdtypes.ANY.CDS.CDS ) self.assertEqual(rd.digest_type, 5) self.assertEqual( rd.digest, bytes.fromhex("71b71d4f3e11bbd71b4eff12cde69f7f9215bbe7") ) def testInvalidDigestLength(self): # type: () -> None test_records = [] for rdata in [example_ds_sha1, example_ds_sha256, example_ds_sha384]: flags, digest = rdata.to_text().rsplit(" ", 1) # Make sure the construction is working dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.DS, f"{flags} {digest}" ) test_records.append( f"{flags} {digest[:len(digest)//2]}" ) # too short digest test_records.append(f"{flags} {digest*2}") # too long digest for record in test_records: with self.assertRaises(dns.exception.SyntaxError) as cm: dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DS, record) self.assertEqual( "digest length inconsistent with digest type", str(cm.exception) ) def testInvalidDigestLengthCDS0(self): # type: () -> None # Make sure the construction is working dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CDS, f"0 0 0 00") test_records = { "expecting another identifier": ["0 0 0", "0 0 0 "], "digest length inconsistent with digest type": ["0 0 0 0000"], "Odd-length string": ["0 0 0 0", "0 0 0 000"], } for msg, records in test_records.items(): for record in records: with self.assertRaises(dns.exception.SyntaxError) as cm: dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CDS, record) self.assertEqual(msg, str(cm.exception)) def testMakeCDS(self): # type: () -> None name = dns.name.from_text("example.com") key = ed448.Ed448PrivateKey.generate() for dnskey in [ dns.dnssec.make_dnskey( key.public_key(), algorithm=dns.dnssec.Algorithm.ED448 ), dns.dnssec.make_cdnskey( key.public_key(), algorithm=dns.dnssec.Algorithm.ED448 ), ]: dnskey_rdataset = dns.rdataset.from_rdata_list(3600, [dnskey]) cds_rdataset = dns.dnssec.dnskey_rdataset_to_cds_rdataset( name, dnskey_rdataset, "SHA256" ) self.assertEqual(len(dnskey_rdataset), len(cds_rdataset)) for d, c in zip(dnskey_rdataset, cds_rdataset): self.assertTrue( isinstance( d, ( dns.rdtypes.ANY.DNSKEY.DNSKEY, dns.rdtypes.ANY.CDNSKEY.CDNSKEY, ), ) ) self.assertTrue(isinstance(c, dns.rdtypes.ANY.CDS.CDS)) self.assertEqual(dns.dnssec.key_id(d), c.key_tag) self.assertEqual(d.algorithm, c.algorithm) def testMakeManyDSfromCDS(self): # type: () -> None name = dns.name.from_text("example.com") nkeys = 3 algorithms = ["SHA256", "SHA384"] keys = [ed448.Ed448PrivateKey.generate() for _ in range(0, nkeys)] dnskeys = [ dns.dnssec.make_dnskey( key.public_key(), algorithm=dns.dnssec.Algorithm.ED448 ) for key in keys ] dnskey_rdataset = dns.rdataset.from_rdata_list(3600, dnskeys) cds_rdataset = dns.dnssec.dnskey_rdataset_to_cds_rdataset( name, dnskey_rdataset, "SHA256" ) cds_rrset = dns.rrset.from_rdata_list(name, 3600, cds_rdataset) ds_rdataset = dns.dnssec.make_ds_rdataset(cds_rrset, algorithms) self.assertEqual(len(cds_rdataset), nkeys) def testMakeManyDSfromDNSKEY(self): # type: () -> None name = dns.name.from_text("example.com") nkeys = 3 algorithms = ["SHA256", "SHA384"] keys = [ed448.Ed448PrivateKey.generate() for _ in range(0, nkeys)] dnskeys = [ dns.dnssec.make_dnskey( key.public_key(), algorithm=dns.dnssec.Algorithm.ED448 ) for key in keys ] dnskey_rrset = dns.rrset.from_rdata_list(name, 3600, dnskeys) ds_rdataset = dns.dnssec.make_ds_rdataset(dnskey_rrset, algorithms) self.assertEqual(len(ds_rdataset), nkeys * len(algorithms)) @unittest.skipUnless(dns.dnssec._have_pyca, "Python Cryptography cannot be imported") class DNSSECMakeDNSKEYTestCase(unittest.TestCase): def testKnownDNSKEYs(self): # type: () -> None for tk in test_dnskeys: print(tk.command) key = load_pem_private_key(tk.private_pem.encode(), password=None) rdata1 = str(dns.dnssec.make_dnskey(key.public_key(), tk.algorithm)) rdata2 = str( dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DNSKEY, tk.dnskey) ) self.assertEqual(rdata1, rdata2) def testInvalidMakeDNSKEY(self): # type: () -> None key = rsa.generate_private_key( public_exponent=65537, key_size=1024, backend=default_backend(), ) with self.assertRaises(dns.exception.AlgorithmKeyMismatch): dns.dnssec.make_dnskey(key.public_key(), dns.dnssec.Algorithm.ED448) with self.assertRaises(dns.exception.AlgorithmKeyMismatch): dns.dnssec.make_dnskey("xyzzy", dns.dnssec.Algorithm.ED448) key = dsa.generate_private_key(2048) with self.assertRaises(ValueError): dns.dnssec.make_dnskey(key.public_key(), dns.dnssec.Algorithm.DSA) def testMakeCDNSKEY(self): # type: () -> None key = ed448.Ed448PrivateKey.generate() dnskey = dns.dnssec.make_dnskey( key.public_key(), algorithm=dns.dnssec.Algorithm.ED448 ) cdnskey = dns.dnssec.make_cdnskey( key.public_key(), algorithm=dns.dnssec.Algorithm.ED448 ) self.assertEqual(dnskey.flags, cdnskey.flags) self.assertEqual(dnskey.protocol, cdnskey.protocol) self.assertEqual(dnskey.algorithm, cdnskey.algorithm) self.assertEqual(dnskey.key, cdnskey.key) dnskey_rdataset = dns.rdataset.from_rdata_list(3600, [dnskey]) cdnskey_rdataset = dns.dnssec.dnskey_rdataset_to_cdnskey_rdataset( dnskey_rdataset ) self.assertEqual(len(dnskey_rdataset), len(cdnskey_rdataset)) for d, c in zip(dnskey_rdataset, cdnskey_rdataset): self.assertTrue(isinstance(d, dns.rdtypes.ANY.DNSKEY.DNSKEY)) self.assertTrue(isinstance(c, dns.rdtypes.ANY.CDNSKEY.CDNSKEY)) self.assertEqual(d, c) # XXXRTH This test is fine but is noticably slow, so I have commented it out for # now # def testRSALargeExponent(self): # type: () -> None # for key_size, public_exponent, dnskey_key_length in [ # (1024, 3, 130), # (1024, 65537, 132), # (2048, 3, 258), # (2048, 65537, 260), # (4096, 3, 514), # (4096, 65537, 516), # ]: # key = rsa.generate_private_key( # public_exponent=public_exponent, # key_size=key_size, # backend=default_backend(), # ) # dnskey = dns.dnssec.make_dnskey( # key.public_key(), algorithm=dns.dnssec.Algorithm.RSASHA256 # ) # self.assertEqual(len(dnskey.key), dnskey_key_length) @unittest.skipUnless(dns.dnssec._have_pyca, "Python Cryptography cannot be imported") class DNSSECSignatureTestCase(unittest.TestCase): def testSignatureData(self): # type: () -> None rrsig_template = abs_soa_rrsig[0] data = dns.dnssec._make_rrsig_signature_data(abs_soa, rrsig_template) def testSignatureRSASHA1(self): # type: () -> None key = rsa.generate_private_key( public_exponent=65537, key_size=2048, backend=default_backend() ) self._test_signature(key, dns.dnssec.Algorithm.RSASHA1, abs_soa) def testSignatureRSASHA256(self): # type: () -> None key = rsa.generate_private_key( public_exponent=65537, key_size=2048, backend=default_backend() ) self._test_signature(key, dns.dnssec.Algorithm.RSASHA256, abs_soa) def testSignatureECDSAP256SHA256(self): # type: () -> None key = ec.generate_private_key(curve=ec.SECP256R1(), backend=default_backend()) self._test_signature(key, dns.dnssec.Algorithm.ECDSAP256SHA256, abs_soa) def testDeterministicSignatureECDSAP256SHA256(self): # type: () -> None key = ec.generate_private_key(curve=ec.SECP256R1(), backend=default_backend()) inception = time.time() rrsigset1 = self._test_signature( key, dns.dnssec.Algorithm.ECDSAP256SHA256, abs_soa, inception=inception, deterministic=True, ) rrsigset2 = self._test_signature( key, dns.dnssec.Algorithm.ECDSAP256SHA256, abs_soa, inception=inception, deterministic=True, ) assert rrsigset1 == rrsigset2 def testNonDeterministicSignatureECDSAP256SHA256(self): # type: () -> None key = ec.generate_private_key(curve=ec.SECP256R1(), backend=default_backend()) inception = time.time() rrsigset1 = self._test_signature( key, dns.dnssec.Algorithm.ECDSAP256SHA256, abs_soa, inception=inception, deterministic=False, ) rrsigset2 = self._test_signature( key, dns.dnssec.Algorithm.ECDSAP256SHA256, abs_soa, inception=inception, deterministic=False, ) assert rrsigset1 != rrsigset2 def testSignatureECDSAP384SHA384(self): # type: () -> None key = ec.generate_private_key(curve=ec.SECP384R1(), backend=default_backend()) self._test_signature(key, dns.dnssec.Algorithm.ECDSAP384SHA384, abs_soa) def testSignatureED25519(self): # type: () -> None key = ed25519.Ed25519PrivateKey.generate() self._test_signature(key, dns.dnssec.Algorithm.ED25519, abs_soa) def testSignatureED448(self): # type: () -> None key = ed448.Ed448PrivateKey.generate() self._test_signature(key, dns.dnssec.Algorithm.ED448, abs_soa) def testSignRdataset(self): # type: () -> None key = ed448.Ed448PrivateKey.generate() name = dns.name.from_text("example.com") rdataset = dns.rdataset.from_text_list("in", "a", 30, ["10.0.0.1", "10.0.0.2"]) rrset = (name, rdataset) self._test_signature(key, dns.dnssec.Algorithm.ED448, rrset) def testSignWildRdataset(self): # type: () -> None key = ed448.Ed448PrivateKey.generate() name = dns.name.from_text("*.example.com") rdataset = dns.rdataset.from_text_list("in", "a", 30, ["10.0.0.1", "10.0.0.2"]) rrset = (name, rdataset) rrsigset = self._test_signature(key, dns.dnssec.Algorithm.ED448, rrset) self.assertEqual(rrsigset[0].labels, 2) def _test_signature( self, key, algorithm, rrset, signer=None, policy=None, inception=None, deterministic=True, ): ttl = 60 lifetime = 3600 if isinstance(rrset, tuple): rrname = rrset[0] else: rrname = rrset.name signer = signer or rrname dnskey = dns.dnssec.make_dnskey( public_key=key.public_key(), algorithm=algorithm ) dnskey_rrset = dns.rrset.from_rdata(signer, ttl, dnskey) rrsig = dns.dnssec.sign( rrset=rrset, private_key=key, dnskey=dnskey, inception=inception, lifetime=lifetime, signer=signer, verify=True, deterministic=deterministic, policy=policy, ) keys = {signer: dnskey_rrset} rrsigset = dns.rrset.from_rdata(rrname, ttl, rrsig) dns.dnssec.validate(rrset=rrset, rrsigset=rrsigset, keys=keys, policy=policy) return rrsigset if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_dnssecalgs.py0000644000000000000000000002403013615410400015366 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 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 os import unittest import dns.dnssec import dns.exception from dns.dnssectypes import Algorithm from dns.rdtypes.ANY.DNSKEY import DNSKEY try: from dns.dnssecalgs import ( get_algorithm_cls, get_algorithm_cls_from_dnskey, register_algorithm_cls, ) from dns.dnssecalgs.dsa import PrivateDSA, PrivateDSANSEC3SHA1 from dns.dnssecalgs.ecdsa import PrivateECDSAP256SHA256, PrivateECDSAP384SHA384 from dns.dnssecalgs.eddsa import PrivateED448, PrivateED25519, PublicED25519 from dns.dnssecalgs.rsa import ( PrivateRSAMD5, PrivateRSASHA1, PrivateRSASHA1NSEC3SHA1, PrivateRSASHA256, PrivateRSASHA512, ) except ImportError: pass # Cryptography ImportError already handled in dns.dnssec @unittest.skipUnless(dns.dnssec._have_pyca, "Python Cryptography cannot be imported") class DNSSECAlgorithm(unittest.TestCase): def _test_dnssec_alg(self, private_cls, key_size=None): public_cls = private_cls.public_cls private_key = ( private_cls.generate(key_size) if key_size else private_cls.generate() ) # sign random data data = os.urandom(1024) signature = private_key.sign(data, verify=True) # validate signature using public key public_key = private_key.public_key() public_key.verify(signature, data) # create DNSKEY dnskey = public_key.to_dnskey() dnskey2 = public_cls.from_dnskey(dnskey).to_dnskey() self.assertEqual(dnskey, dnskey2) # test cryptography keys _ = private_cls(key=private_key.key) _ = public_cls(key=public_key.key) # to/from PEM password = b"mekmitasdigoat" private_pem = private_key.to_pem() private_pem_encrypted = private_key.to_pem(password=password) public_pem = public_key.to_pem() _ = private_cls.from_pem(private_pem) _ = private_cls.from_pem(private_pem_encrypted, password) _ = public_cls.from_pem(public_pem) def test_rsa(self): self._test_dnssec_alg(PrivateRSAMD5, 2048) self._test_dnssec_alg(PrivateRSASHA1, 2048) self._test_dnssec_alg(PrivateRSASHA1NSEC3SHA1, 2048) self._test_dnssec_alg(PrivateRSASHA256, 2048) self._test_dnssec_alg(PrivateRSASHA512, 2048) def test_dsa(self): self._test_dnssec_alg(PrivateDSA, 1024) self._test_dnssec_alg(PrivateDSANSEC3SHA1, 1024) with self.assertRaises(ValueError): k = PrivateDSA.generate(2048) k.sign(b"hello") def test_ecdsa(self): self._test_dnssec_alg(PrivateECDSAP256SHA256) self._test_dnssec_alg(PrivateECDSAP384SHA384) def test_eddsa(self): self._test_dnssec_alg(PrivateED25519) self._test_dnssec_alg(PrivateED448) def test_algorithm_mismatch(self): private_key_ed448 = PrivateED448.generate() dnskey_ed448 = private_key_ed448.public_key().to_dnskey() with self.assertRaises(dns.exception.AlgorithmKeyMismatch): PublicED25519.from_dnskey(dnskey_ed448) @unittest.skipUnless(dns.dnssec._have_pyca, "Python Cryptography cannot be imported") class DNSSECAlgorithmPrivateAlgorithm(unittest.TestCase): def test_private(self): class PublicExampleAlgorithm(PublicED25519): algorithm = Algorithm.PRIVATEDNS name = dns.name.from_text("algorithm.example.com") def encode_key_bytes(self) -> bytes: return self.name.to_wire() + super().encode_key_bytes() @classmethod def from_dnskey(cls, key: DNSKEY) -> "PublicEDDSA": return cls( key=cls.key_cls.from_public_bytes( key.key[len(cls.name.to_wire()) :] ), ) class PrivateExampleAlgorithm(PrivateED25519): public_cls = PublicExampleAlgorithm register_algorithm_cls( algorithm=Algorithm.PRIVATEDNS, algorithm_cls=PrivateExampleAlgorithm, name=PublicExampleAlgorithm.name, ) private_key = PrivateExampleAlgorithm.generate() public_key = private_key.public_key() name = dns.name.from_text("example.com") rdataset = dns.rdataset.from_text_list("in", "a", 30, ["10.0.0.1", "10.0.0.2"]) rrset = (name, rdataset) ttl = 60 lifetime = 3600 rrname = rrset[0] signer = rrname dnskey = dns.dnssec.make_dnskey( public_key=public_key, algorithm=Algorithm.PRIVATEDNS ) dnskey_rrset = dns.rrset.from_rdata(signer, ttl, dnskey) rrsig = dns.dnssec.sign( rrset=rrset, private_key=private_key, dnskey=dnskey, lifetime=lifetime, signer=signer, verify=True, policy=None, ) keys = {signer: dnskey_rrset} rrsigset = dns.rrset.from_rdata(rrname, ttl, rrsig) dns.dnssec.validate(rrset=rrset, rrsigset=rrsigset, keys=keys, policy=None) def test_register(self): register_algorithm_cls( algorithm=Algorithm.PRIVATEDNS, algorithm_cls=PrivateED25519, name="ed25519.example.com", ) register_algorithm_cls( algorithm=Algorithm.PRIVATEOID, algorithm_cls=PrivateED448, oid=bytes([1, 2, 3, 4]), ) register_algorithm_cls( algorithm=251, algorithm_cls=PrivateED25519, ) with self.assertRaises(TypeError): register_algorithm_cls(algorithm=251, algorithm_cls=str, name="example.com") with self.assertRaises(ValueError): register_algorithm_cls( algorithm=251, algorithm_cls=PrivateED25519, name="example.com" ) with self.assertRaises(ValueError): register_algorithm_cls( algorithm=251, algorithm_cls=PrivateED25519, oid=bytes([1, 2, 3, 4]) ) with self.assertRaises(ValueError): register_algorithm_cls( algorithm=Algorithm.PRIVATEDNS, algorithm_cls=PrivateED25519, oid=bytes([1, 2, 3, 4]), ) with self.assertRaises(ValueError): register_algorithm_cls( algorithm=Algorithm.PRIVATEOID, algorithm_cls=PrivateED25519, name="example.com", ) dnskey_251 = DNSKEY( "IN", "DNSKEY", 256, 3, 251, b"hello", ) dnskey_dns = DNSKEY( "IN", "DNSKEY", 256, 3, Algorithm.PRIVATEDNS, dns.name.from_text("ed25519.example.com").to_wire() + b"hello", ) dnskey_dns_unknown = DNSKEY( "IN", "DNSKEY", 256, 3, Algorithm.PRIVATEDNS, dns.name.from_text("unknown.example.com").to_wire() + b"hello", ) dnskey_oid = DNSKEY( "IN", "DNSKEY", 256, 3, Algorithm.PRIVATEOID, bytes([4, 1, 2, 3, 4]) + b"hello", ) dnskey_oid_unknown = DNSKEY( "IN", "DNSKEY", 256, 3, Algorithm.PRIVATEOID, bytes([4, 42, 42, 42, 42]) + b"hello", ) with self.assertRaises(dns.exception.UnsupportedAlgorithm): _ = get_algorithm_cls(250) algorithm_cls = get_algorithm_cls(251) self.assertEqual(algorithm_cls, PrivateED25519) algorithm_cls = get_algorithm_cls_from_dnskey(dnskey_251) self.assertEqual(algorithm_cls, PrivateED25519) algorithm_cls = get_algorithm_cls_from_dnskey(dnskey_dns) self.assertEqual(algorithm_cls, PrivateED25519) with self.assertRaises(dns.exception.UnsupportedAlgorithm): _ = get_algorithm_cls_from_dnskey(dnskey_dns_unknown) algorithm_cls = get_algorithm_cls_from_dnskey(dnskey_oid) self.assertEqual(algorithm_cls, PrivateED448) with self.assertRaises(dns.exception.UnsupportedAlgorithm): _ = get_algorithm_cls_from_dnskey(dnskey_oid_unknown) def test_register_canonical_lookup(self): register_algorithm_cls( algorithm=Algorithm.PRIVATEDNS, algorithm_cls=PrivateED25519, name="testing1234.example.com", ) dnskey_dns = DNSKEY( "IN", "DNSKEY", 256, 3, Algorithm.PRIVATEDNS, dns.name.from_text("TESTING1234.EXAMPLE.COM").to_wire() + b"hello", ) algorithm_cls = get_algorithm_cls_from_dnskey(dnskey_dns) self.assertEqual(algorithm_cls, PrivateED25519) def test_register_private_without_prefix(self): with self.assertRaises(ValueError): register_algorithm_cls( algorithm=Algorithm.PRIVATEDNS, algorithm_cls=PrivateED25519, ) with self.assertRaises(ValueError): register_algorithm_cls( algorithm=Algorithm.PRIVATEOID, algorithm_cls=PrivateED25519, ) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_doh.py0000644000000000000000000002127213615410400014017 0ustar00# 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 random import socket import unittest try: import ssl _have_ssl = True except Exception: _have_ssl = False import dns.edns import dns.message import dns.query import dns.quic import dns.rdatatype import dns.resolver if dns.query._have_httpx: import httpx import tests.util resolver_v4_addresses = [] resolver_v6_addresses = [] family = socket.AF_UNSPEC if tests.util.have_ipv4(): resolver_v4_addresses = [ "1.1.1.1", "8.8.8.8", # '9.9.9.9', ] family = socket.AF_INET if tests.util.have_ipv6(): resolver_v6_addresses = [ "2606:4700:4700::1111", "2001:4860:4860::8888", # '2620:fe::fe', ] if family == socket.AF_INET: # we have both working, go back to UNSPEC family = socket.AF_UNSPEC else: # v6 only family = socket.AF_INET6 KNOWN_ANYCAST_DOH_RESOLVER_URLS = [ "https://cloudflare-dns.com/dns-query", "https://dns.google/dns-query", # 'https://dns11.quad9.net/dns-query', ] KNOWN_ANYCAST_DOH3_RESOLVER_URLS = [ "https://cloudflare-dns.com/dns-query", "https://dns.google/dns-query", ] KNOWN_PAD_AWARE_DOH_RESOLVER_URLS = [ "https://cloudflare-dns.com/dns-query", "https://dns.google/dns-query", ] @unittest.skipUnless( dns.query._have_httpx and tests.util.is_internet_reachable() and _have_ssl, "Python httpx cannot be imported; no DNS over HTTPS (DOH)", ) class DNSOverHTTPSTestCaseHttpx(unittest.TestCase): def setUp(self): self.session = httpx.Client(http1=True, http2=True, verify=True) def tearDown(self): self.session.close() def test_get_request(self): nameserver_url = random.choice(KNOWN_ANYCAST_DOH_RESOLVER_URLS) q = dns.message.make_query("example.com.", dns.rdatatype.A) r = dns.query.https( q, nameserver_url, session=self.session, post=False, timeout=4, family=family, ) self.assertTrue(q.is_response(r)) def test_post_request(self): nameserver_url = random.choice(KNOWN_ANYCAST_DOH_RESOLVER_URLS) q = dns.message.make_query("example.com.", dns.rdatatype.A) r = dns.query.https( q, nameserver_url, session=self.session, post=True, timeout=4, family=family, ) self.assertTrue(q.is_response(r)) def test_build_url_from_ip(self): self.assertTrue(resolver_v4_addresses or resolver_v6_addresses) if resolver_v4_addresses: nameserver_ip = random.choice(resolver_v4_addresses) q = dns.message.make_query("example.com.", dns.rdatatype.A) # For some reason Google's DNS over HTTPS fails when you POST to # https://8.8.8.8/dns-query # So we're just going to do GET requests here r = dns.query.https( q, nameserver_ip, session=self.session, post=False, timeout=4 ) self.assertTrue(q.is_response(r)) if resolver_v6_addresses: nameserver_ip = random.choice(resolver_v6_addresses) q = dns.message.make_query("example.com.", dns.rdatatype.A) r = dns.query.https( q, nameserver_ip, session=self.session, post=False, timeout=4 ) self.assertTrue(q.is_response(r)) # This test is temporarily disabled as there's an expired certificate issue on one # of the servers, so it fails on the part that is supposed to succeed (2023-07-15). # def test_bootstrap_address_fails(self): # # We test this to see if v4 is available # if resolver_v4_addresses: # ip = "185.228.168.168" # invalid_tls_url = "https://{}/doh/family-filter/".format(ip) # valid_tls_url = "https://doh.cleanbrowsing.org/doh/family-filter/" # q = dns.message.make_query("example.com.", dns.rdatatype.A) # # make sure CleanBrowsing's IP address will fail TLS certificate # # check. # with self.assertRaises(httpx.ConnectError): # dns.query.https(q, invalid_tls_url, session=self.session, timeout=4) # # And if we don't mangle the URL, it should work. # r = dns.query.https( # q, # valid_tls_url, # session=self.session, # bootstrap_address=ip, # timeout=4, # ) # self.assertTrue(q.is_response(r)) def test_new_session(self): nameserver_url = random.choice(KNOWN_ANYCAST_DOH_RESOLVER_URLS) q = dns.message.make_query("example.com.", dns.rdatatype.A) r = dns.query.https(q, nameserver_url, timeout=4) self.assertTrue(q.is_response(r)) def test_resolver(self): res = dns.resolver.Resolver(configure=False) res.nameservers = ["https://dns.google/dns-query"] answer = res.resolve("dns.google", "A") seen = set([rdata.address for rdata in answer]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) def test_padded_get(self): nameserver_url = random.choice(KNOWN_PAD_AWARE_DOH_RESOLVER_URLS) q = dns.message.make_query("example.com.", dns.rdatatype.A, use_edns=0, pad=128) r = dns.query.https( q, nameserver_url, session=self.session, post=False, timeout=4 ) self.assertTrue(q.is_response(r)) # the response should have a padding option self.assertIsNotNone(r.opt) has_pad = False for o in r.opt[0].options: if o.otype == dns.edns.OptionType.PADDING: has_pad = True self.assertTrue(has_pad) @unittest.skipUnless( dns.quic.have_quic and tests.util.is_internet_reachable() and _have_ssl, "Aioquic cannot be imported; no DNS over HTTP3 (DOH3)", ) class DNSOverHTTP3TestCase(unittest.TestCase): def testDoH3GetRequest(self): nameserver_url = random.choice(KNOWN_ANYCAST_DOH3_RESOLVER_URLS) q = dns.message.make_query("dns.google.", dns.rdatatype.A) r = dns.query.https( q, nameserver_url, post=False, timeout=4, family=family, http_version=dns.query.HTTPVersion.H3, ) self.assertTrue(q.is_response(r)) def testDoH3PostRequest(self): nameserver_url = random.choice(KNOWN_ANYCAST_DOH3_RESOLVER_URLS) q = dns.message.make_query("dns.google.", dns.rdatatype.A) r = dns.query.https( q, nameserver_url, post=True, timeout=4, family=family, http_version=dns.query.HTTPVersion.H3, ) self.assertTrue(q.is_response(r)) def test_build_url_from_ip(self): self.assertTrue(resolver_v4_addresses or resolver_v6_addresses) if resolver_v4_addresses: nameserver_ip = random.choice(resolver_v4_addresses) q = dns.message.make_query("example.com.", dns.rdatatype.A) # For some reason Google's DNS over HTTPS fails when you POST to # https://8.8.8.8/dns-query # So we're just going to do GET requests here r = dns.query.https( q, nameserver_ip, post=False, timeout=4, http_version=dns.query.HTTPVersion.H3, ) self.assertTrue(q.is_response(r)) if resolver_v6_addresses: nameserver_ip = random.choice(resolver_v6_addresses) q = dns.message.make_query("example.com.", dns.rdatatype.A) r = dns.query.https( q, nameserver_ip, post=False, timeout=4, http_version=dns.query.HTTPVersion.H3, ) self.assertTrue(q.is_response(r)) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_doq.py0000644000000000000000000000357713615410400014040 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import asyncio import sys import pytest import dns._features import dns.asyncbackend import dns.asyncquery import dns.message import dns.query import dns.rcode from .util import have_ipv4, have_ipv6, here have_quic = dns._features.have("doq") try: from .nanonameserver import Server except ImportError: pass if not have_quic: class Server(object): pass addresses = [] if have_ipv4(): addresses.append("127.0.0.1") if have_ipv6(): addresses.append("::1") if len(addresses) == 0: # no networking have_quic = False @pytest.mark.skipif(not have_quic, reason="requires aioquic") def test_basic_sync(): q = dns.message.make_query("www.example.", "A") for address in addresses: with Server(address=address) as server: port = server.doq_address[1] r = dns.query.quic(q, address, port=port, verify=here("tls/ca.crt")) assert r.rcode() == dns.rcode.REFUSED async def amain(address, port): q = dns.message.make_query("www.example.", "A") r = await dns.asyncquery.quic(q, address, port=port, verify=here("tls/ca.crt")) assert r.rcode() == dns.rcode.REFUSED @pytest.mark.skipif(not have_quic, reason="requires aioquic") def test_basic_asyncio(): dns.asyncbackend.set_default_backend("asyncio") for address in addresses: with Server(address=address) as server: port = server.doq_address[1] asyncio.run(amain(address, port)) try: import trio @pytest.mark.skipif(not have_quic, reason="requires aioquic") def test_basic_trio(): dns.asyncbackend.set_default_backend("trio") for address in addresses: with Server(address=address) as server: port = server.doq_address[1] trio.run(amain, address, port) except ImportError: pass dnspython-2.7.0/tests/test_edns.py0000644000000000000000000002662013615410400014200 0ustar00# -*- coding: utf-8 # 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 operator import struct import unittest from io import BytesIO import dns.edns import dns.wire class OptionTestCase(unittest.TestCase): def testGenericOption(self): opt = dns.edns.GenericOption(3, b"data") io = BytesIO() opt.to_wire(io) data = io.getvalue() self.assertEqual(data, b"data") self.assertEqual(dns.edns.option_from_wire(3, data, 0, len(data)), opt) self.assertEqual(str(opt), "Generic 3") def testECSOption_prefix_length(self): opt = dns.edns.ECSOption("1.2.255.33", 20) io = BytesIO() opt.to_wire(io) data = io.getvalue() self.assertEqual(data, b"\x00\x01\x14\x00\x01\x02\xf0") def testECSOption(self): opt = dns.edns.ECSOption("1.2.3.4", 24) io = BytesIO() opt.to_wire(io) data = io.getvalue() self.assertEqual(data, b"\x00\x01\x18\x00\x01\x02\x03") # default srclen opt = dns.edns.ECSOption("1.2.3.4") io = BytesIO() opt.to_wire(io) data = io.getvalue() self.assertEqual(data, b"\x00\x01\x18\x00\x01\x02\x03") self.assertEqual(opt.to_text(), "ECS 1.2.3.4/24 scope/0") def testECSOption25(self): opt = dns.edns.ECSOption("1.2.3.255", 25) io = BytesIO() opt.to_wire(io) data = io.getvalue() self.assertEqual(data, b"\x00\x01\x19\x00\x01\x02\x03\x80") opt2 = dns.edns.option_from_wire(dns.edns.ECS, data, 0, len(data)) self.assertEqual(opt2.otype, dns.edns.ECS) self.assertEqual(opt2.address, "1.2.3.128") self.assertEqual(opt2.srclen, 25) self.assertEqual(opt2.scopelen, 0) def testECSOption_v6(self): opt = dns.edns.ECSOption("2001:4b98::1") io = BytesIO() opt.to_wire(io) data = io.getvalue() self.assertEqual(data, b"\x00\x02\x38\x00\x20\x01\x4b\x98\x00\x00\x00") opt2 = dns.edns.option_from_wire(dns.edns.ECS, data, 0, len(data)) self.assertEqual(opt2.otype, dns.edns.ECS) self.assertEqual(opt2.address, "2001:4b98::") self.assertEqual(opt2.srclen, 56) self.assertEqual(opt2.scopelen, 0) def testECSOption_from_text_valid(self): ecs1 = dns.edns.ECSOption.from_text("1.2.3.4/24/0") self.assertEqual(ecs1, dns.edns.ECSOption("1.2.3.4", 24, 0)) ecs2 = dns.edns.ECSOption.from_text("1.2.3.4/24") self.assertEqual(ecs2, dns.edns.ECSOption("1.2.3.4", 24, 0)) ecs3 = dns.edns.ECSOption.from_text("ECS 1.2.3.4/24") self.assertEqual(ecs3, dns.edns.ECSOption("1.2.3.4", 24, 0)) ecs4 = dns.edns.ECSOption.from_text("ECS 1.2.3.4/24/32") self.assertEqual(ecs4, dns.edns.ECSOption("1.2.3.4", 24, 32)) ecs5 = dns.edns.ECSOption.from_text("2001:4b98::1/64/56") self.assertEqual(ecs5, dns.edns.ECSOption("2001:4b98::1", 64, 56)) ecs6 = dns.edns.ECSOption.from_text("2001:4b98::1/64") self.assertEqual(ecs6, dns.edns.ECSOption("2001:4b98::1", 64, 0)) ecs7 = dns.edns.ECSOption.from_text("ECS 2001:4b98::1/0") self.assertEqual(ecs7, dns.edns.ECSOption("2001:4b98::1", 0, 0)) ecs8 = dns.edns.ECSOption.from_text("ECS 2001:4b98::1/64/128") self.assertEqual(ecs8, dns.edns.ECSOption("2001:4b98::1", 64, 128)) def testECSOption_from_text_invalid(self): with self.assertRaises(ValueError): dns.edns.ECSOption.from_text("some random text 1.2.3.4/24/0 24") with self.assertRaises(ValueError): dns.edns.ECSOption.from_text("1.2.3.4/twentyfour") with self.assertRaises(ValueError): dns.edns.ECSOption.from_text("BOGUS 1.2.3.4/5/6/7") with self.assertRaises(ValueError): dns.edns.ECSOption.from_text("1.2.3.4/5/6/7") with self.assertRaises(ValueError): dns.edns.ECSOption.from_text("1.2.3.4/24/O") # <-- that's not a zero with self.assertRaises(ValueError): dns.edns.ECSOption.from_text("") with self.assertRaises(ValueError): dns.edns.ECSOption.from_text("1.2.3.4/2001:4b98::1/24") def testECSOption_from_wire_invalid(self): with self.assertRaises(ValueError): opt = dns.edns.option_from_wire( dns.edns.ECS, b"\x00\xff\x18\x00\x01\x02\x03", 0, 7 ) def testEDEOption(self): opt = dns.edns.EDEOption(3) io = BytesIO() opt.to_wire(io) data = io.getvalue() self.assertEqual(data, b"\x00\x03") self.assertEqual(str(opt), "EDE 3 (Stale Answer)") # with text opt = dns.edns.EDEOption(16, "test") io = BytesIO() opt.to_wire(io) data = io.getvalue() self.assertEqual(data, b"\x00\x10test") def testEDEOption_invalid(self): with self.assertRaises(ValueError): opt = dns.edns.EDEOption(-1) with self.assertRaises(ValueError): opt = dns.edns.EDEOption(65536) with self.assertRaises(ValueError): opt = dns.edns.EDEOption(0, 0) def testEDEOption_from_wire(self): data = b"\x00\01" self.assertEqual( dns.edns.option_from_wire(dns.edns.EDE, data, 0, 2), dns.edns.EDEOption(1) ) data = b"\x00\01test" self.assertEqual( dns.edns.option_from_wire(dns.edns.EDE, data, 0, 6), dns.edns.EDEOption(1, "test"), ) # utf-8 text MAY be null-terminated data = b"\x00\01test\x00" self.assertEqual( dns.edns.option_from_wire(dns.edns.EDE, data, 0, 7), dns.edns.EDEOption(1, "test"), ) def test_basic_relations(self): o1 = dns.edns.ECSOption.from_text("1.2.3.0/24/0") o2 = dns.edns.ECSOption.from_text("1.2.4.0/24/0") self.assertTrue(o1 == o1) self.assertTrue(o1 != o2) self.assertTrue(o1 < o2) self.assertTrue(o1 <= o2) self.assertTrue(o2 > o1) self.assertTrue(o2 >= o1) o1 = dns.edns.ECSOption.from_text("1.2.4.0/23/0") o2 = dns.edns.ECSOption.from_text("1.2.4.0/24/0") self.assertTrue(o1 < o2) o1 = dns.edns.ECSOption.from_text("1.2.4.0/24/0") o2 = dns.edns.ECSOption.from_text("1.2.4.0/24/1") self.assertTrue(o1 < o2) def test_incompatible_relations(self): o1 = dns.edns.GenericOption(3, b"data") o2 = dns.edns.ECSOption.from_text("1.2.3.5/24/0") for oper in [operator.lt, operator.le, operator.ge, operator.gt]: self.assertRaises(TypeError, lambda: oper(o1, o2)) self.assertFalse(o1 == o2) self.assertTrue(o1 != o2) self.assertFalse(o1 == 123) self.assertTrue(o1 != 123) def testNSIDOption(self): opt = dns.edns.NSIDOption(b"testing") io = BytesIO() opt.to_wire(io) data = io.getvalue() self.assertEqual(data, b"testing") self.assertEqual(str(opt), "NSID testing") opt = dns.edns.NSIDOption(b"\xfe\xff") io = BytesIO() opt.to_wire(io) data = io.getvalue() self.assertEqual(data, b"\xfe\xff") self.assertEqual(str(opt), "NSID feff") o = dns.edns.option_from_wire(dns.edns.OptionType.NSID, data, 0, len(data)) self.assertEqual(o.nsid, b"\xfe\xff") def testCookieOption(self): opt = dns.edns.CookieOption(b"12345678", b"") io = BytesIO() opt.to_wire(io) data = io.getvalue() self.assertEqual(data, b"12345678") self.assertEqual(str(opt), "COOKIE 3132333435363738") opt = dns.edns.CookieOption(b"12345678", b"abcdefgh") data = opt.to_wire() self.assertEqual(data, b"12345678abcdefgh") self.assertEqual(str(opt), "COOKIE 31323334353637386162636465666768") # maximal server opt = dns.edns.CookieOption(b"12345678", b"abcdefghabcdefghabcdefghabcdefgh") io = BytesIO() opt.to_wire(io) data = io.getvalue() self.assertEqual(data, b"12345678abcdefghabcdefghabcdefghabcdefgh") # from wire opt2 = dns.edns.option_from_wire(dns.edns.OptionType.COOKIE, data, 0, len(data)) self.assertEqual(opt.client, opt2.client) self.assertEqual(opt.server, opt2.server) # client too short with self.assertRaises(ValueError): opt = dns.edns.CookieOption(b"1234567", b"") # client too long with self.assertRaises(ValueError): opt = dns.edns.CookieOption(b"123456789", b"") # server too short with self.assertRaises(ValueError): opt = dns.edns.CookieOption(b"12345678", b"a") # server too long with self.assertRaises(ValueError): opt = dns.edns.CookieOption( b"12345678", b"abcdefghabcdefghabcdefghabcdefghi" ) def testReportChannelOption(self): agent_domain = dns.name.from_text("agent.example.") expected_wire = b"\x05agent\x07example\x00" opt = dns.edns.ReportChannelOption(agent_domain) io = BytesIO() opt.to_wire(io) data = io.getvalue() self.assertEqual(data, expected_wire) self.assertEqual(str(opt), "REPORTCHANNEL agent.example.") opt2 = dns.edns.option_from_wire( dns.edns.OptionType.REPORTCHANNEL, expected_wire, 0, len(expected_wire) ) self.assertEqual(opt2.agent_domain, agent_domain) def test_option_registration(self): U32OptionType = 9999 class U32Option(dns.edns.Option): def __init__(self, value=None): super().__init__(U32OptionType) self.value = value def to_wire(self, file=None): data = struct.pack("!I", self.value) if file: file.write(data) else: return data @classmethod def from_wire_parser(cls, otype, parser): (value,) = parser.get_struct("!I") return cls(value) try: dns.edns.register_type(U32Option, U32OptionType) generic = dns.edns.GenericOption(U32OptionType, b"\x00\x00\x00\x01") wire1 = generic.to_wire() u32 = dns.edns.option_from_wire_parser( U32OptionType, dns.wire.Parser(wire1) ) self.assertEqual(u32.value, 1) wire2 = u32.to_wire() self.assertEqual(wire1, wire2) self.assertEqual(u32, generic) finally: dns.edns._type_to_class.pop(U32OptionType, None) opt = dns.edns.option_from_wire_parser(9999, dns.wire.Parser(wire1)) self.assertEqual(opt, generic) dnspython-2.7.0/tests/test_entropy.py0000644000000000000000000000367113615410400014750 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import unittest import dns.entropy # these tests are mostly for minimal coverage testing class EntropyTestCase(unittest.TestCase): def test_pool(self): pool = dns.entropy.EntropyPool(b"seed-value") self.assertEqual(pool.random_8(), 94) self.assertEqual(pool.random_16(), 61532) self.assertEqual(pool.random_32(), 4226376065) self.assertEqual(pool.random_between(10, 50), 29) # stir in some not-really-entropy to exercise the stir API pool.stir(b"not-really-entropy") def test_pool_random(self): pool = dns.entropy.EntropyPool() values = {pool.random_32() for n in range(12)} # Make sure that the results are at least somewhat random. self.assertGreater(len(values), 8) def test_pool_random_between(self): pool = dns.entropy.EntropyPool() def bad(): pool.random_between(0, 4294967296) self.assertRaises(ValueError, bad) v = pool.random_between(50, 50 + 100000) self.assertTrue(v >= 50 and v <= 50 + 100000) v = pool.random_between(50, 50 + 10000) self.assertTrue(v >= 50 and v <= 50 + 10000) v = pool.random_between(50, 50 + 100) self.assertTrue(v >= 50 and v <= 50 + 100) def test_functions(self): v = dns.entropy.random_16() self.assertTrue(0 <= v <= 65535) v = dns.entropy.between(10, 50) self.assertTrue(10 <= v <= 50) class EntropyForcePoolTestCase(unittest.TestCase): def setUp(self): self.saved_system_random = dns.entropy.system_random dns.entropy.system_random = None def tearDown(self): dns.entropy.system_random = self.saved_system_random def test_functions(self): v = dns.entropy.random_16() self.assertTrue(0 <= v <= 65535) v = dns.entropy.between(10, 50) self.assertTrue(10 <= v <= 50) dnspython-2.7.0/tests/test_exceptions.py0000644000000000000000000000422013615410400015420 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2006, 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 unittest from dns.exception import DNSException class FormatedError(DNSException): fmt = "Custom format: {parameter}" supp_kwargs = {"parameter"} class ExceptionTestCase(unittest.TestCase): def test_custom_message(self): msg = "this is a custom message" try: raise DNSException(msg) except DNSException as ex: self.assertEqual(str(ex), msg) def test_implicit_message(self): try: raise DNSException() except DNSException as ex: self.assertEqual(ex.__class__.__doc__, str(ex)) def test_formatted_error(self): """Exceptions with explicit format has to respect it.""" params = {"parameter": "value"} try: raise FormatedError(**params) except FormatedError as ex: msg = FormatedError.fmt.format(**params) self.assertEqual(msg, str(ex)) def test_kwargs_only(self): """Kwargs cannot be combined with args.""" with self.assertRaises(AssertionError): raise FormatedError(1, a=2) def test_kwargs_unsupported(self): """Only supported kwargs are accepted.""" with self.assertRaises(AssertionError): raise FormatedError(unsupported=2) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_features.py0000644000000000000000000000416513615410400015065 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import pytest from dns._features import ( _cache, _requirements, _tuple_from_text, _version_check, force, have, ) try: import cryptography v = _tuple_from_text(cryptography.__version__) have_cryptography = v >= (42, 0, 0) except ImportError: have_cryptography = False def test_tuple_from_text(): assert _tuple_from_text("") == () assert _tuple_from_text("1") == (1,) assert _tuple_from_text("1.2") == (1, 2) assert _tuple_from_text("1.2rc1") == (1, 2) assert _tuple_from_text("1.2.junk3") == (1, 2) @pytest.mark.skipif( not have_cryptography, reason="cryptography not available or too old" ) def test_version_check(): assert _version_check("cryptography>=42") assert not _version_check("cryptography>=10000") assert not _version_check("totallyboguspackagename>=10000") @pytest.mark.skipif( not have_cryptography, reason="cryptography not available or too old" ) def test_have(): # ensure cache is empty; we can't just assign as our local is shadowing the # variable in dns._features while len(_cache) > 0: _cache.popitem() assert have("dnssec") assert _cache["dnssec"] == True assert not have("bogusfeature") assert _cache["bogusfeature"] == False _requirements["unavailable"] = ["bogusmodule>=10000"] try: assert not have("unavailable") finally: del _requirements["unavailable"] def test_force(): while len(_cache) > 0: _cache.popitem() assert not have("bogusfeature") assert _cache["bogusfeature"] == False force("bogusfeature", True) assert have("bogusfeature") assert _cache["bogusfeature"] == True force("bogusfeature", False) assert not have("bogusfeature") assert _cache["bogusfeature"] == False _requirements["unavailable"] = ["bogusmodule>=10000"] try: assert not have("unavailable") assert _cache["unavailable"] == False force("unavailable", True) assert _cache["unavailable"] == True finally: del _requirements["unavailable"] dnspython-2.7.0/tests/test_flags.py0000644000000000000000000000554213615410400014343 0ustar00# 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 unittest import dns.flags import dns.rcode import dns.opcode class FlagsTestCase(unittest.TestCase): def test_rcode1(self): self.assertEqual(dns.rcode.from_text("FORMERR"), dns.rcode.FORMERR) def test_rcode2(self): self.assertEqual(dns.rcode.to_text(dns.rcode.FORMERR), "FORMERR") def test_rcode3(self): self.assertEqual(dns.rcode.to_flags(dns.rcode.FORMERR), (1, 0)) def test_rcode4(self): self.assertEqual(dns.rcode.to_flags(dns.rcode.BADVERS), (0, 0x01000000)) def test_rcode6(self): self.assertEqual(dns.rcode.from_flags(0, 0x01000000), dns.rcode.BADVERS) def test_rcode7(self): self.assertEqual(dns.rcode.from_flags(5, 0), dns.rcode.REFUSED) def test_rcode8(self): def bad(): dns.rcode.to_flags(4096) self.assertRaises(ValueError, bad) def test_flags1(self): self.assertEqual( dns.flags.from_text("RA RD AA QR"), dns.flags.QR | dns.flags.AA | dns.flags.RD | dns.flags.RA, ) def test_flags2(self): flags = dns.flags.QR | dns.flags.AA | dns.flags.RD | dns.flags.RA self.assertEqual(dns.flags.to_text(flags), "QR AA RD RA") def test_rcode_badvers(self): rcode = dns.rcode.BADVERS self.assertEqual(rcode.value, 16) self.assertEqual(rcode.name, "BADVERS") self.assertEqual(dns.rcode.to_text(rcode), "BADVERS") def test_rcode_badsig(self): rcode = dns.rcode.BADSIG self.assertEqual(rcode.value, 16) # Yes, we mean BADVERS on the next line. BADSIG and BADVERS have # the same code. self.assertEqual(rcode.name, "BADVERS") self.assertEqual(dns.rcode.to_text(rcode), "BADVERS") # In TSIG text mode, it should be BADSIG self.assertEqual(dns.rcode.to_text(rcode, True), "BADSIG") def test_unknown_rcode(self): with self.assertRaises(dns.rcode.UnknownRcode): dns.rcode.Rcode.make("BOGUS") if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_generate.py0000644000000000000000000005746413615410400015053 0ustar00# 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 sys sys.path.insert(0, "../") # Force the local project to be *the* dns import unittest import dns.exception import dns.rdata import dns.rdataclass import dns.rdatatype import dns.rrset import dns.zone example_text = """$TTL 1h $ORIGIN 0.0.192.IN-ADDR.ARPA. $GENERATE 1-2 0 CNAME SERVER$.EXAMPLE. """ example_text1 = """$TTL 1h $ORIGIN 0.0.192.IN-ADDR.ARPA. $GENERATE 1-10 fooo$ CNAME $.0 """ example_text2 = """$TTL 1h @ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 bar.foo 300 IN MX 0 blaz.foo ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.2 $GENERATE 3-5 foo$ A 10.0.0.$ """ example_text3 = """$TTL 1h @ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 bar.foo 300 IN MX 0 blaz.foo ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.2 $GENERATE 4-8/2 foo$ A 10.0.0.$ """ example_text4 = """$TTL 1h @ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 bar.foo 300 IN MX 0 blaz.foo ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.2 $GENERATE 11-13 wp-db${-10,2,d}.services.mozilla.com 0 CNAME SERVER.FOOBAR. """ example_text5 = """$TTL 1h @ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 bar.foo 300 IN MX 0 blaz.foo ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.2 $GENERATE 11-13 wp-db${10,2,d}.services.mozilla.com 0 CNAME SERVER.FOOBAR. """ example_text6 = """$TTL 1h @ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 bar.foo 300 IN MX 0 blaz.foo ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.2 $GENERATE 11-13 wp-db${+10,2,d}.services.mozilla.com 0 CNAME SERVER.FOOBAR. """ example_text7 = """$TTL 1h @ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 bar.foo 300 IN MX 0 blaz.foo ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.2 $GENERATE 11-13 sync${-10}.db IN A 10.10.16.0 """ example_text8 = """$TTL 1h @ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 bar.foo 300 IN MX 0 blaz.foo ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.2 $GENERATE 11-12 wp-db${-10,2,d} IN A 10.10.16.0 """ example_text9 = """$TTL 1h @ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 bar.foo 300 IN MX 0 blaz.foo ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.2 $GENERATE 11-12 wp-db${-10,2,d} IN A 10.10.16.0 $GENERATE 11-13 sync${-10}.db IN A 10.10.16.0 """ example_text10 = """$TTL 1h @ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 bar.foo 300 IN MX 0 blaz.foo ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.2 $GENERATE 27-28 $.2 PTR zlb${-26}.oob """ example_text11 = """$TTL 1h @ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 bar.foo 300 IN MX 0 blaz.foo ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.2 $GENERATE 27-28 prefix-${0,3} A 10.0.0.$ """ last_ttl_input = """foo 300 mx 10 target. $GENERATE 1-10 foo$ CNAME $.0 @ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 """ def _rdata_sort(a): return (a[0], a[2].rdclass, a[2].to_text()) class GenerateTestCase(unittest.TestCase): def testFromText(self): # type: () -> None def bad(): # type: () -> None dns.zone.from_text(example_text, "example.", relativize=True) self.assertRaises(dns.zone.NoSOA, bad) def testFromText1(self): # type: () -> None def bad(): # type: () -> None dns.zone.from_text(example_text1, "example.", relativize=True) self.assertRaises(dns.zone.NoSOA, bad) def testIterateAllRdatas2(self): # type: () -> None z = dns.zone.from_text(example_text2, "example.", relativize=True) l = list(z.iterate_rdatas()) l.sort(key=_rdata_sort) exl = [ ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns1"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns2"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.SOA, "foo bar 1 2 3 4 5" ), ), ( dns.name.from_text("bar.foo", None), 300, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, "0 blaz.foo"), ), ( dns.name.from_text("ns1", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1"), ), ( dns.name.from_text("ns2", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.2"), ), ( dns.name.from_text("foo3", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.3"), ), ( dns.name.from_text("foo4", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.4"), ), ( dns.name.from_text("foo5", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.5"), ), ] exl.sort(key=_rdata_sort) self.assertEqual(l, exl) def testIterateAllRdatas3(self): # type: () -> None z = dns.zone.from_text(example_text3, "example.", relativize=True) l = list(z.iterate_rdatas()) l.sort(key=_rdata_sort) exl = [ ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns1"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns2"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.SOA, "foo bar 1 2 3 4 5" ), ), ( dns.name.from_text("bar.foo", None), 300, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, "0 blaz.foo"), ), ( dns.name.from_text("ns1", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1"), ), ( dns.name.from_text("ns2", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.2"), ), ( dns.name.from_text("foo4", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.4"), ), ( dns.name.from_text("foo6", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.6"), ), ( dns.name.from_text("foo8", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.8"), ), ] exl.sort(key=_rdata_sort) self.assertEqual(l, exl) def testGenerate1(self): # type: () -> None z = dns.zone.from_text(example_text4, "example.", relativize=True) l = list(z.iterate_rdatas()) l.sort(key=_rdata_sort) exl = [ ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns1"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns2"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.SOA, "foo bar 1 2 3 4 5" ), ), ( dns.name.from_text("bar.foo", None), 300, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, "0 blaz.foo"), ), ( dns.name.from_text("ns1", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1"), ), ( dns.name.from_text("ns2", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.2"), ), ( dns.name.from_text("wp-db01.services.mozilla.com", None), 0, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.CNAME, "SERVER.FOOBAR." ), ), ( dns.name.from_text("wp-db02.services.mozilla.com", None), 0, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.CNAME, "SERVER.FOOBAR." ), ), ( dns.name.from_text("wp-db03.services.mozilla.com", None), 0, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.CNAME, "SERVER.FOOBAR." ), ), ] exl.sort(key=_rdata_sort) self.assertEqual(l, exl) def testGenerate2(self): # type: () -> None z = dns.zone.from_text(example_text5, "example.", relativize=True) l = list(z.iterate_rdatas()) l.sort(key=_rdata_sort) exl = [ ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns1"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns2"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.SOA, "foo bar 1 2 3 4 5" ), ), ( dns.name.from_text("bar.foo", None), 300, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, "0 blaz.foo"), ), ( dns.name.from_text("ns1", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1"), ), ( dns.name.from_text("ns2", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.2"), ), ( dns.name.from_text("wp-db21.services.mozilla.com", None), 0, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.CNAME, "SERVER.FOOBAR." ), ), ( dns.name.from_text("wp-db22.services.mozilla.com", None), 0, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.CNAME, "SERVER.FOOBAR." ), ), ( dns.name.from_text("wp-db23.services.mozilla.com", None), 0, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.CNAME, "SERVER.FOOBAR." ), ), ] exl.sort(key=_rdata_sort) self.assertEqual(l, exl) def testGenerate3(self): # type: () -> None z = dns.zone.from_text(example_text6, "example.", relativize=True) l = list(z.iterate_rdatas()) l.sort(key=_rdata_sort) exl = [ ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns1"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns2"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.SOA, "foo bar 1 2 3 4 5" ), ), ( dns.name.from_text("bar.foo", None), 300, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, "0 blaz.foo"), ), ( dns.name.from_text("ns1", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1"), ), ( dns.name.from_text("ns2", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.2"), ), ( dns.name.from_text("wp-db21.services.mozilla.com", None), 0, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.CNAME, "SERVER.FOOBAR." ), ), ( dns.name.from_text("wp-db22.services.mozilla.com", None), 0, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.CNAME, "SERVER.FOOBAR." ), ), ( dns.name.from_text("wp-db23.services.mozilla.com", None), 0, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.CNAME, "SERVER.FOOBAR." ), ), ] exl.sort(key=_rdata_sort) self.assertEqual(l, exl) def testGenerate4(self): # type: () -> None z = dns.zone.from_text(example_text7, "example.", relativize=True) l = list(z.iterate_rdatas()) l.sort(key=_rdata_sort) exl = [ ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns1"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns2"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.SOA, "foo bar 1 2 3 4 5" ), ), ( dns.name.from_text("bar.foo", None), 300, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, "0 blaz.foo"), ), ( dns.name.from_text("ns1", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1"), ), ( dns.name.from_text("ns2", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.2"), ), ( dns.name.from_text("sync1.db", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.10.16.0"), ), ( dns.name.from_text("sync2.db", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.10.16.0"), ), ( dns.name.from_text("sync3.db", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.10.16.0"), ), ] exl.sort(key=_rdata_sort) self.assertEqual(l, exl) def testGenerate6(self): # type: () -> None z = dns.zone.from_text(example_text9, "example.", relativize=True) l = list(z.iterate_rdatas()) l.sort(key=_rdata_sort) exl = [ ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns1"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns2"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.SOA, "foo bar 1 2 3 4 5" ), ), ( dns.name.from_text("bar.foo", None), 300, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, "0 blaz.foo"), ), ( dns.name.from_text("ns1", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1"), ), ( dns.name.from_text("ns2", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.2"), ), ( dns.name.from_text("wp-db01", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.10.16.0"), ), ( dns.name.from_text("wp-db02", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.10.16.0"), ), ( dns.name.from_text("sync1.db", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.10.16.0"), ), ( dns.name.from_text("sync2.db", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.10.16.0"), ), ( dns.name.from_text("sync3.db", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.10.16.0"), ), ] exl.sort(key=_rdata_sort) self.assertEqual(l, exl) def testGenerate7(self): # type: () -> None z = dns.zone.from_text(example_text10, "example.", relativize=True) l = list(z.iterate_rdatas()) l.sort(key=_rdata_sort) exl = [ ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns1"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns2"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.SOA, "foo bar 1 2 3 4 5" ), ), ( dns.name.from_text("bar.foo", None), 300, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, "0 blaz.foo"), ), ( dns.name.from_text("ns1", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1"), ), ( dns.name.from_text("ns2", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.2"), ), ( dns.name.from_text("27.2", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.PTR, "zlb1.oob"), ), ( dns.name.from_text("28.2", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.PTR, "zlb2.oob"), ), ] exl.sort(key=_rdata_sort) self.assertEqual(l, exl) def testGenerate8(self): # type: () -> None z = dns.zone.from_text(example_text11, "example.", relativize=True) l = list(z.iterate_rdatas()) l.sort(key=_rdata_sort) exl = [ ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns1"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns2"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.SOA, "foo bar 1 2 3 4 5" ), ), ( dns.name.from_text("bar.foo", None), 300, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, "0 blaz.foo"), ), ( dns.name.from_text("prefix-027", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.27"), ), ( dns.name.from_text("prefix-028", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.28"), ), ( dns.name.from_text("ns1", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1"), ), ( dns.name.from_text("ns2", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.2"), ), ] exl.sort(key=_rdata_sort) self.assertEqual(l, exl) def testNoOrigin(self): def bad(): dns.zone.from_text("$GENERATE 1-10 fooo$ CNAME $.0") self.assertRaises(dns.zone.UnknownOrigin, bad) def testBadRdata(self): def bad(): dns.zone.from_text("$GENERATE 1-10 fooo$ CNAME 10 $.0", "example") self.assertRaises(dns.exception.SyntaxError, bad) def testUsesLastTTL(self): z = dns.zone.from_text(last_ttl_input, "example") rrs = z.find_rrset("foo9", "CNAME") self.assertEqual(rrs.ttl, 300) def testClassMismatch(self): def bad(): dns.zone.from_text("$GENERATE 1-10 fooo$ CH CNAME $.0", "example") self.assertRaises(dns.exception.SyntaxError, bad) def testUnknownRdatatype(self): def bad(): dns.zone.from_text("$GENERATE 1-10 fooo$ BOGUSTYPE $.0", "example") self.assertRaises(dns.exception.SyntaxError, bad) def testBadAndDangling(self): def bad1(): dns.zone.from_text("$GENERATE bogus fooo$ CNAME $.0", "example.") self.assertRaises(dns.exception.SyntaxError, bad1) def bad2(): dns.zone.from_text("$GENERATE 1-10", "example.") self.assertRaises(dns.exception.SyntaxError, bad2) def bad3(): dns.zone.from_text("$GENERATE 1-10 foo$", "example.") self.assertRaises(dns.exception.SyntaxError, bad3) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_grange.py0000644000000000000000000000575713615410400014522 0ustar00# 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 sys sys.path.insert(0, "../") import unittest import dns import dns.exception import dns.grange class GRangeTestCase(unittest.TestCase): def testFromText1(self): start, stop, step = dns.grange.from_text("1-1") self.assertEqual(start, 1) self.assertEqual(stop, 1) self.assertEqual(step, 1) def testFromText2(self): start, stop, step = dns.grange.from_text("1-4") self.assertEqual(start, 1) self.assertEqual(stop, 4) self.assertEqual(step, 1) def testFromText3(self): start, stop, step = dns.grange.from_text("4-255") self.assertEqual(start, 4) self.assertEqual(stop, 255) self.assertEqual(step, 1) def testFromText4(self): start, stop, step = dns.grange.from_text("1-1/1") self.assertEqual(start, 1) self.assertEqual(stop, 1) self.assertEqual(step, 1) def testFromText5(self): start, stop, step = dns.grange.from_text("1-4/2") self.assertEqual(start, 1) self.assertEqual(stop, 4) self.assertEqual(step, 2) def testFromText6(self): start, stop, step = dns.grange.from_text("4-255/77") self.assertEqual(start, 4) self.assertEqual(stop, 255) self.assertEqual(step, 77) def testFailFromText1(self): with self.assertRaises(dns.exception.SyntaxError): start = 2 stop = 1 step = 1 dns.grange.from_text("%d-%d/%d" % (start, stop, step)) self.assertTrue(False) def testFailFromText2(self): with self.assertRaises(dns.exception.SyntaxError): start = "-1" stop = 3 step = 1 dns.grange.from_text("%s-%d/%d" % (start, stop, step)) def testFailFromText3(self): with self.assertRaises(dns.exception.SyntaxError): start = 1 stop = 4 step = "-2" dns.grange.from_text("%d-%d/%s" % (start, stop, step)) def testFailFromText4(self): with self.assertRaises(dns.exception.SyntaxError): dns.grange.from_text("1") if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_immutable.py0000644000000000000000000001044013615410400015217 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import unittest import dns.immutable import dns._immutable_ctx class ImmutableTestCase(unittest.TestCase): def test_immutable_dict_hash(self): d1 = dns.immutable.Dict({"a": 1, "b": 2}) d2 = dns.immutable.Dict({"b": 2, "a": 1}) d3 = {"b": 2, "a": 1} self.assertEqual(d1, d2) self.assertEqual(d2, d3) self.assertEqual(hash(d1), hash(d2)) def test_immutable_dict_hash_cache(self): d = dns.immutable.Dict({"a": 1, "b": 2}) self.assertEqual(d._hash, None) h1 = hash(d) self.assertEqual(d._hash, h1) h2 = hash(d) self.assertEqual(h1, h2) def test_constify(self): items = ( (bytearray([1, 2, 3]), b"\x01\x02\x03"), ((1, 2, 3), (1, 2, 3)), ((1, [2], 3), (1, (2,), 3)), ([1, 2, 3], (1, 2, 3)), ([1, {"a": [1, 2]}], (1, dns.immutable.Dict({"a": (1, 2)}))), ("hi", "hi"), (b"hi", b"hi"), ) for input, expected in items: self.assertEqual(dns.immutable.constify(input), expected) self.assertIsInstance(dns.immutable.constify({"a": 1}), dns.immutable.Dict) class DecoratorTestCase(unittest.TestCase): immutable_module = dns._immutable_ctx def make_classes(self): class A: def __init__(self, a, akw=10): self.a = a self.akw = akw class B(A): def __init__(self, a, b): super().__init__(a, akw=20) self.b = b B = self.immutable_module.immutable(B) # note C is immutable by inheritance class C(B): def __init__(self, a, b, c): super().__init__(a, b) self.c = c C = self.immutable_module.immutable(C) class SA: __slots__ = ("a", "akw") def __init__(self, a, akw=10): self.a = a self.akw = akw class SB(A): __slots__ = "b" def __init__(self, a, b): super().__init__(a, akw=20) self.b = b SB = self.immutable_module.immutable(SB) # note SC is immutable by inheritance and has no slots of its own class SC(SB): def __init__(self, a, b, c): super().__init__(a, b) self.c = c SC = self.immutable_module.immutable(SC) return ((A, B, C), (SA, SB, SC)) def test_basic(self): for A, B, C in self.make_classes(): a = A(1) self.assertEqual(a.a, 1) self.assertEqual(a.akw, 10) b = B(11, 21) self.assertEqual(b.a, 11) self.assertEqual(b.akw, 20) self.assertEqual(b.b, 21) c = C(111, 211, 311) self.assertEqual(c.a, 111) self.assertEqual(c.akw, 20) self.assertEqual(c.b, 211) self.assertEqual(c.c, 311) # changing A is ok! a.a = 11 self.assertEqual(a.a, 11) # changing B is not! with self.assertRaises(TypeError): b.a = 11 with self.assertRaises(TypeError): del b.a def test_constructor_deletes_attribute(self): class A: def __init__(self, a): self.a = a self.b = a del self.b A = self.immutable_module.immutable(A) a = A(10) self.assertEqual(a.a, 10) self.assertFalse(hasattr(a, "b")) def test_no_collateral_damage(self): # A and B are immutable but not related. The magic that lets # us write to immutable things while initializing B should not let # B mess with A. class A: def __init__(self, a): self.a = a A = self.immutable_module.immutable(A) class B: def __init__(self, a, b): self.b = a.a + b # rudely attempt to mutate innocent immutable bystander 'a' a.a = 1000 B = self.immutable_module.immutable(B) a = A(10) self.assertEqual(a.a, 10) with self.assertRaises(TypeError): B(a, 20) self.assertEqual(a.a, 10) dnspython-2.7.0/tests/test_message.py0000644000000000000000000010517713615410400014700 0ustar00# -*- coding: utf-8 # 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 binascii import unittest import dns.edns import dns.exception import dns.flags import dns.message import dns.name import dns.rdataclass import dns.rdatatype import dns.rdtypes.ANY.OPT import dns.rdtypes.ANY.TSIG import dns.rrset import dns.tsig import dns.tsigkeyring import dns.update from tests.util import here query_text = """id 1234 opcode QUERY rcode NOERROR flags RD edns 0 eflags DO payload 4096 ;QUESTION wwww.dnspython.org. IN A ;ANSWER ;AUTHORITY ;ADDITIONAL""" goodhex = ( b"04d201000001000000000001047777777709646e73707974686f6e" b"036f726700000100010000291000000080000000" ) goodwire = binascii.unhexlify(goodhex) answer_text = """id 1234 opcode QUERY rcode NOERROR flags QR AA RD ;QUESTION dnspython.org. IN SOA ;ANSWER dnspython.org. 3600 IN SOA woof.dnspython.org. hostmaster.dnspython.org. 2003052700 3600 1800 604800 3600 ;AUTHORITY dnspython.org. 3600 IN NS ns1.staff.nominum.org. dnspython.org. 3600 IN NS ns2.staff.nominum.org. dnspython.org. 3600 IN NS woof.play-bow.org. ;ADDITIONAL woof.play-bow.org. 3600 IN A 204.152.186.150 """ goodhex2 = ( "04d2 8500 0001 0001 0003 0001" "09646e73707974686f6e036f726700 0006 0001" "c00c 0006 0001 00000e10 0028 " "04776f6f66c00c 0a686f73746d6173746572c00c" "7764289c 00000e10 00000708 00093a80 00000e10" "c00c 0002 0001 00000e10 0014" "036e7331057374616666076e6f6d696e756dc016" "c00c 0002 0001 00000e10 0006 036e7332c063" "c00c 0002 0001 00000e10 0010 04776f6f6608706c61792d626f77c016" "c091 0001 0001 00000e10 0004 cc98ba96" ) goodwire2 = binascii.unhexlify(goodhex2.replace(" ", "").encode()) query_text_2 = """id 1234 opcode QUERY rcode 4095 flags RD edns 0 eflags DO payload 4096 ;QUESTION wwww.dnspython.org. IN A ;ANSWER ;AUTHORITY ;ADDITIONAL""" goodhex3 = ( b"04d2010f0001000000000001047777777709646e73707974686f6e" b"036f726700000100010000291000ff0080000000" ) goodwire3 = binascii.unhexlify(goodhex3) idna_text = """id 1234 opcode QUERY rcode NOERROR flags QR AA RD ;QUESTION Königsgäßchen. IN NS ;ANSWER Königsgäßchen. 3600 IN NS Königsgäßchen. """ class MessageTestCase(unittest.TestCase): def test_class(self): m = dns.message.from_text(query_text) self.assertTrue(isinstance(m, dns.message.QueryMessage)) def test_comparison_eq1(self): q1 = dns.message.from_text(query_text) q2 = dns.message.from_text(query_text) self.assertEqual(q1, q2) def test_comparison_ne1(self): q1 = dns.message.from_text(query_text) q2 = dns.message.from_text(query_text) q2.id = 10 self.assertNotEqual(q1, q2) def test_comparison_ne2(self): q1 = dns.message.from_text(query_text) q2 = dns.message.from_text(query_text) q2.question = [] self.assertNotEqual(q1, q2) def test_comparison_ne3(self): q1 = dns.message.from_text(query_text) self.assertNotEqual(q1, 1) def test_EDNS_to_wire1(self): q = dns.message.from_text(query_text) w = q.to_wire() self.assertEqual(w, goodwire) def test_EDNS_from_wire1(self): m = dns.message.from_wire(goodwire) self.assertEqual(str(m), query_text) def test_EDNS_to_wire2(self): q = dns.message.from_text(query_text_2) w = q.to_wire() self.assertEqual(w, goodwire3) def test_EDNS_from_wire2(self): m = dns.message.from_wire(goodwire3) self.assertEqual(str(m), query_text_2) def test_EDNS_options_wire(self): m = dns.message.make_query("foo", "A") opt = dns.edns.GenericOption(3, b"data") m.use_edns(options=[opt]) m2 = dns.message.from_wire(m.to_wire()) self.assertEqual(m2.edns, 0) self.assertEqual(len(m2.options), 1) self.assertEqual(m2.options[0], opt) def test_keeping_wire(self): m = dns.message.from_wire(goodwire) self.assertEqual(m.wire, goodwire) m = dns.message.make_query("example", "A") self.assertEqual(m.wire, None) def test_recording_wire(self): m = dns.message.from_wire(goodwire) self.assertEqual(m.wire, goodwire) m.wire = None wire = m.to_wire() self.assertEqual(wire, goodwire) self.assertEqual(m.wire, goodwire) def test_TooBig(self): def bad(): q = dns.message.from_text(query_text) for i in range(0, 25): rrset = dns.rrset.from_text( "foo%d." % i, 3600, dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.%d" % i, ) q.additional.append(rrset) q.to_wire(max_size=512) self.assertRaises(dns.exception.TooBig, bad) def test_answer1(self): a = dns.message.from_text(answer_text) wire = a.to_wire(want_shuffle=False) self.assertEqual(wire, goodwire2) def test_TrailingJunk(self): def bad(): badwire = goodwire + b"\x00" dns.message.from_wire(badwire) self.assertRaises(dns.message.TrailingJunk, bad) def test_ShortHeader(self): def bad(): badwire = b"\x00" * 11 dns.message.from_wire(badwire) self.assertRaises(dns.message.ShortHeader, bad) def test_RespondingToResponse(self): def bad(): q = dns.message.make_query("foo", "A") r1 = dns.message.make_response(q) dns.message.make_response(r1) self.assertRaises(dns.exception.FormError, bad) def test_RespondingToEDNSRequestAndSettingRA(self): q = dns.message.make_query("foo", "A", use_edns=0) r = dns.message.make_response(q, True) self.assertTrue(r.flags & dns.flags.RA != 0) self.assertEqual(r.edns, 0) def _make_copy_query(self): q = dns.message.make_query("foo", "A") # These are nonsensical, but all we care about is they get copied. q.answer.append(dns.rrset.from_text("foo", 300, "IN", "A", "10.0.0.1")) q.authority.append(dns.rrset.from_text("foo2", 300, "IN", "A", "10.0.0.2")) q.additional.append(dns.rrset.from_text("foo3", 300, "IN", "A", "10.0.0.3")) return q def test_MakeResponseCopyNothing(self): q = self._make_copy_query() r = dns.message.make_response(q, copy_mode=dns.message.CopyMode.NOTHING) self.assertEqual(len(r.question), 0) self.assertEqual(len(r.answer), 0) self.assertEqual(len(r.authority), 0) self.assertEqual(len(r.additional), 0) def test_MakeResponseCopyDefault(self): q = self._make_copy_query() r = dns.message.make_response(q) self.assertTrue(len(r.question) == 1 and q.question[0] == r.question[0]) self.assertEqual(len(r.answer), 0) self.assertEqual(len(r.authority), 0) self.assertEqual(len(r.additional), 0) def test_MakeResponseCopyQuestion(self): q = self._make_copy_query() r = dns.message.make_response(q, copy_mode=dns.message.CopyMode.QUESTION) self.assertTrue(len(r.question) == 1 and q.question[0] == r.question[0]) self.assertEqual(len(r.answer), 0) self.assertEqual(len(r.authority), 0) self.assertEqual(len(r.additional), 0) def test_MakeResponseCopyEverything(self): q = self._make_copy_query() r = dns.message.make_response(q, copy_mode=dns.message.CopyMode.EVERYTHING) self.assertTrue(len(r.question) == 1 and q.question == r.question) self.assertTrue(len(r.answer) == 1 and q.answer == r.answer) self.assertTrue(len(r.authority) == 1 and q.authority == r.authority) self.assertTrue(len(r.additional) == 1 and q.additional == r.additional) def test_ExtendedRcodeSetting(self): m = dns.message.make_query("foo", "A") m.set_rcode(4095) self.assertEqual(m.rcode(), 4095) self.assertEqual(m.edns, 0) m.set_rcode(2) self.assertEqual(m.rcode(), 2) def test_EDNSVersionCoherence(self): m = dns.message.make_query("foo", "A") m.use_edns(1) self.assertEqual((m.ednsflags >> 16) & 0xFF, 1) def test_EDNSVersionCoherenceWithDNSSEC(self): m = dns.message.make_query("foo", "A") m.use_edns(1) m.want_dnssec(True) self.assertEqual((m.ednsflags >> 16) & 0xFF, 1) def test_EDNSVersionCoherenceWithoutDNSSEC(self): m = dns.message.make_query("foo", "A") m.use_edns(1) m.want_dnssec(False) self.assertEqual((m.ednsflags >> 16) & 0xFF, 1) def test_SettingNoEDNSOptionsImpliesNoEDNS(self): m = dns.message.make_query("foo", "A") self.assertEqual(m.edns, -1) def test_SettingEDNSFlagsImpliesEDNS(self): m = dns.message.make_query("foo", "A", ednsflags=dns.flags.DO) self.assertEqual(m.edns, 0) def test_SettingEDNSPayloadImpliesEDNS(self): m = dns.message.make_query("foo", "A", payload=4096) self.assertEqual(m.edns, 0) def test_SettingEDNSRequestPayloadImpliesEDNS(self): m = dns.message.make_query("foo", "A", request_payload=4096) self.assertEqual(m.edns, 0) def test_SettingOptionsImpliesEDNS(self): m = dns.message.make_query("foo", "A", options=[]) self.assertEqual(m.edns, 0) def test_FindRRset(self): a = dns.message.from_text(answer_text) n = dns.name.from_text("dnspython.org.") rrs1 = a.find_rrset(a.answer, n, dns.rdataclass.IN, dns.rdatatype.SOA) rrs2 = a.find_rrset(dns.message.ANSWER, n, dns.rdataclass.IN, dns.rdatatype.SOA) self.assertEqual(rrs1, rrs2) def test_FindRRsetUnindexed(self): a = dns.message.from_text(answer_text) a.index = None n = dns.name.from_text("dnspython.org.") rrs1 = a.find_rrset(a.answer, n, dns.rdataclass.IN, dns.rdatatype.SOA) rrs2 = a.find_rrset(dns.message.ANSWER, n, dns.rdataclass.IN, dns.rdatatype.SOA) self.assertEqual(rrs1, rrs2) def test_GetRRset(self): a = dns.message.from_text(answer_text) a.index = None n = dns.name.from_text("dnspython.org.") rrs1 = a.get_rrset(a.answer, n, dns.rdataclass.IN, dns.rdatatype.SOA) rrs2 = a.get_rrset(dns.message.ANSWER, n, dns.rdataclass.IN, dns.rdatatype.SOA) self.assertEqual(rrs1, rrs2) def test_GetNonexistentRRset(self): a = dns.message.from_text(answer_text) a.index = None n = dns.name.from_text("dnspython.org.") rrs1 = a.get_rrset(a.answer, n, dns.rdataclass.IN, dns.rdatatype.TXT) rrs2 = a.get_rrset(dns.message.ANSWER, n, dns.rdataclass.IN, dns.rdatatype.TXT) self.assertTrue(rrs1 is None) self.assertEqual(rrs1, rrs2) def test_GetRRsetStrings(self): a = dns.message.from_text(answer_text) a.index = None n = dns.name.from_text("dnspython.org.") rrs1 = a.get_rrset(a.answer, n, dns.rdataclass.IN, dns.rdatatype.SOA) rrs2 = a.get_rrset("ANSWER", "dnspython.org.", "IN", "SOA") self.assertEqual(rrs1, rrs2) def test_CleanTruncated(self): def bad(): a = dns.message.from_text(answer_text) a.flags |= dns.flags.TC wire = a.to_wire(want_shuffle=False) dns.message.from_wire(wire, raise_on_truncation=True) self.assertRaises(dns.message.Truncated, bad) def test_MessyTruncated(self): def bad(): a = dns.message.from_text(answer_text) a.flags |= dns.flags.TC wire = a.to_wire(want_shuffle=False) dns.message.from_wire(wire[:-3], raise_on_truncation=True) self.assertRaises(dns.message.Truncated, bad) def test_IDNA_2003(self): a = dns.message.from_text(idna_text, idna_codec=dns.name.IDNA_2003) rrs = dns.rrset.from_text_list( "xn--knigsgsschen-lcb0w.", 30, "in", "ns", ["xn--knigsgsschen-lcb0w."], idna_codec=dns.name.IDNA_2003, ) self.assertEqual(a.answer[0], rrs) @unittest.skipUnless( dns.name.have_idna_2008, "Python idna cannot be imported; no IDNA2008" ) def test_IDNA_2008(self): a = dns.message.from_text(idna_text, idna_codec=dns.name.IDNA_2008) rrs = dns.rrset.from_text_list( "xn--knigsgchen-b4a3dun.", 30, "in", "ns", ["xn--knigsgchen-b4a3dun."], idna_codec=dns.name.IDNA_2008, ) self.assertEqual(a.answer[0], rrs) def test_bad_section_number(self): m = dns.message.make_query("foo", "A") self.assertRaises(ValueError, lambda: m.section_number(123)) def test_section_from_number(self): m = dns.message.make_query("foo", "A") self.assertEqual(m.section_from_number(dns.message.QUESTION), m.question) self.assertEqual(m.section_from_number(dns.message.ANSWER), m.answer) self.assertEqual(m.section_from_number(dns.message.AUTHORITY), m.authority) self.assertEqual(m.section_from_number(dns.message.ADDITIONAL), m.additional) self.assertRaises(ValueError, lambda: m.section_from_number(999)) def test_wanting_EDNS_true_is_EDNS0(self): m = dns.message.make_query("foo", "A") self.assertEqual(m.edns, -1) m.use_edns(True) self.assertEqual(m.edns, 0) def test_wanting_DNSSEC_turns_on_EDNS(self): m = dns.message.make_query("foo", "A") self.assertEqual(m.edns, -1) m.want_dnssec() self.assertEqual(m.edns, 0) self.assertTrue(m.ednsflags & dns.flags.DO) def test_EDNS_default_payload_is_1232(self): m = dns.message.make_query("foo", "A") m.use_edns() self.assertEqual(m.payload, dns.message.DEFAULT_EDNS_PAYLOAD) def test_from_file(self): m = dns.message.from_file(here("query")) expected = dns.message.from_text(query_text) self.assertEqual(m, expected) def test_explicit_header_comment(self): m = dns.message.from_text(";HEADER\n" + query_text) expected = dns.message.from_text(query_text) self.assertEqual(m, expected) def test_repr(self): q = dns.message.from_text(query_text) self.assertEqual(repr(q), "") def test_non_question_setters(self): rrset = dns.rrset.from_text("foo", 300, "in", "a", "10.0.0.1") q = dns.message.QueryMessage(id=1) q.answer = [rrset] self.assertEqual(q.sections[1], [rrset]) self.assertEqual(q.sections[2], []) self.assertEqual(q.sections[3], []) q.authority = [rrset] self.assertEqual(q.sections[2], [rrset]) self.assertEqual(q.sections[3], []) q.additional = [rrset] self.assertEqual(q.sections[3], [rrset]) def test_is_a_response_empty_question(self): q = dns.message.make_query("www.dnspython.org.", "a") r = dns.message.make_response(q) r.question = [] r.set_rcode(dns.rcode.FORMERR) self.assertTrue(q.is_response(r)) def test_not_a_response(self): q = dns.message.QueryMessage(id=1) self.assertFalse(q.is_response(q)) r = dns.message.QueryMessage(id=2) r.flags = dns.flags.QR self.assertFalse(q.is_response(r)) r = dns.update.UpdateMessage(id=1) self.assertFalse(q.is_response(r)) q1 = dns.message.make_query("www.dnspython.org.", "a") q2 = dns.message.make_query("www.google.com.", "a") # Give them the same id, as we want to test if responses for # differing questions are rejected. q1.id = 1 q2.id = 1 r = dns.message.make_response(q2) self.assertFalse(q1.is_response(r)) # Now set rcode to FORMERR and check again. It should still # not be a response as we check the question section for FORMERR # if it is present. r.set_rcode(dns.rcode.FORMERR) self.assertFalse(q1.is_response(r)) # Test the other case of differing questions, where there is # something in the response's question section that is not in # the question's. We have to do multiple questions to test # this :) r = dns.message.make_query("www.dnspython.org.", "a") r.flags |= dns.flags.QR r.id = 1 r.find_rrset( r.question, dns.name.from_text("example"), dns.rdataclass.IN, dns.rdatatype.A, create=True, force_unique=True, ) self.assertFalse(q1.is_response(r)) def test_more_not_equal_cases(self): q1 = dns.message.make_query("www.dnspython.org.", "a") q2 = dns.message.make_query("www.dnspython.org.", "a") # ensure ids are same q1.id = 1 q2.id = 1 # and flags are different q2.flags |= dns.flags.QR self.assertFalse(q1 == q2) q2.flags = q1.flags q2.find_rrset( q2.question, dns.name.from_text("example"), dns.rdataclass.IN, dns.rdatatype.A, create=True, force_unique=True, ) self.assertFalse(q1 == q2) def test_edns_properties(self): q = dns.message.make_query("www.dnspython.org.", "a") self.assertEqual(q.edns, -1) self.assertEqual(q.payload, 0) self.assertEqual(q.options, ()) q = dns.message.make_query("www.dnspython.org.", "a", use_edns=0, payload=4096) self.assertEqual(q.edns, 0) self.assertEqual(q.payload, 4096) self.assertEqual(q.options, ()) def test_setting_id(self): q = dns.message.make_query("www.dnspython.org.", "a", id=12345) self.assertEqual(q.id, 12345) def test_setting_flags(self): q = dns.message.make_query( "www.dnspython.org.", "a", flags=dns.flags.RD | dns.flags.CD ) self.assertEqual(q.flags, dns.flags.RD | dns.flags.CD) self.assertEqual(q.flags, 0x0110) def test_generic_message_class(self): q1 = dns.message.Message(id=1) q1.set_opcode(dns.opcode.NOTIFY) q1.flags |= dns.flags.AA q1.find_rrset( q1.question, dns.name.from_text("example"), dns.rdataclass.IN, dns.rdatatype.SOA, create=True, force_unique=True, ) w = q1.to_wire() q2 = dns.message.from_wire(w) self.assertTrue(isinstance(q2, dns.message.Message)) self.assertFalse(isinstance(q2, dns.message.QueryMessage)) self.assertFalse(isinstance(q2, dns.update.UpdateMessage)) self.assertEqual(q1, q2) def test_truncated_exception_message(self): q = dns.message.Message(id=1) q.flags |= dns.flags.TC te = dns.message.Truncated(message=q) self.assertEqual(te.message(), q) def test_bad_opt(self): # Not in additional q = dns.message.Message(id=1) opt = dns.rdtypes.ANY.OPT.OPT(1200, dns.rdatatype.OPT, ()) rrs = dns.rrset.from_rdata(dns.name.root, 0, opt) q.answer.append(rrs) wire = q.to_wire() with self.assertRaises(dns.message.BadEDNS): dns.message.from_wire(wire) # Owner name not root name q = dns.message.Message(id=1) rrs = dns.rrset.from_rdata("foo.", 0, opt) q.additional.append(rrs) wire = q.to_wire() with self.assertRaises(dns.message.BadEDNS): dns.message.from_wire(wire) # Multiple opts q = dns.message.Message(id=1) rrs = dns.rrset.from_rdata(dns.name.root, 0, opt) q.additional.append(rrs) q.additional.append(rrs) wire = q.to_wire() with self.assertRaises(dns.message.BadEDNS): dns.message.from_wire(wire) def test_bad_tsig(self): keyname = dns.name.from_text("key.") # Not in additional q = dns.message.Message(id=1) tsig = dns.rdtypes.ANY.TSIG.TSIG( dns.rdataclass.ANY, dns.rdatatype.TSIG, dns.tsig.HMAC_SHA256, 0, 300, b"1234", 0, 0, b"", ) rrs = dns.rrset.from_rdata(keyname, 0, tsig) q.answer.append(rrs) wire = q.to_wire() with self.assertRaises(dns.message.BadTSIG): dns.message.from_wire(wire) # Multiple tsigs q = dns.message.Message(id=1) q.additional.append(rrs) q.additional.append(rrs) wire = q.to_wire() with self.assertRaises(dns.message.BadTSIG): dns.message.from_wire(wire) # Class not ANY tsig = dns.rdtypes.ANY.TSIG.TSIG( dns.rdataclass.IN, dns.rdatatype.TSIG, dns.tsig.HMAC_SHA256, 0, 300, b"1234", 0, 0, b"", ) rrs = dns.rrset.from_rdata(keyname, 0, tsig) wire = q.to_wire() with self.assertRaises(dns.message.BadTSIG): dns.message.from_wire(wire) def test_read_no_content_message(self): m = dns.message.from_text(";comment") self.assertIsInstance(m, dns.message.QueryMessage) def test_eflags_turns_on_edns(self): m = dns.message.from_text("eflags DO") self.assertIsInstance(m, dns.message.QueryMessage) self.assertEqual(m.edns, 0) def test_payload_turns_on_edns(self): m = dns.message.from_text("payload 1200") self.assertIsInstance(m, dns.message.QueryMessage) self.assertEqual(m.payload, 1200) def test_bogus_header(self): with self.assertRaises(dns.message.UnknownHeaderField): dns.message.from_text("bogus foo") def test_question_only(self): m = dns.message.from_text(answer_text) w = m.to_wire() r = dns.message.from_wire(w, question_only=True) self.assertEqual(r.id, m.id) self.assertEqual(r.question[0], m.question[0]) self.assertEqual(len(r.answer), 0) self.assertEqual(len(r.authority), 0) self.assertEqual(len(r.additional), 0) def test_bad_resolve_chaining(self): r = dns.message.make_query("www.dnspython.org.", "a") with self.assertRaises(dns.message.NotQueryResponse): r.resolve_chaining() r.flags |= dns.flags.QR r.id = 1 r.find_rrset( r.question, dns.name.from_text("example"), dns.rdataclass.IN, dns.rdatatype.A, create=True, force_unique=True, ) with self.assertRaises(dns.exception.FormError): r.resolve_chaining() def test_resolve_chaining_no_infinite_loop(self): r = dns.message.from_text( """id 1 flags QR ;QUESTION www.example. IN CNAME ;AUTHORITY example. 300 IN SOA . . 1 2 3 4 5 """ ) # passing is not going into an infinite loop in this call result = r.resolve_chaining() self.assertEqual(result.canonical_name, dns.name.from_text("www.example.")) self.assertEqual(result.minimum_ttl, 5) self.assertIsNone(result.answer) def test_bad_text_questions(self): with self.assertRaises(dns.exception.SyntaxError): dns.message.from_text( """id 1 ;QUESTION example. """ ) with self.assertRaises(dns.exception.SyntaxError): dns.message.from_text( """id 1 ;QUESTION example. IN """ ) with self.assertRaises(dns.rdatatype.UnknownRdatatype): dns.message.from_text( """id 1 ;QUESTION example. INA """ ) with self.assertRaises(dns.rdatatype.UnknownRdatatype): dns.message.from_text( """id 1 ;QUESTION example. IN BOGUS """ ) def test_bad_text_rrs(self): with self.assertRaises(dns.exception.SyntaxError): dns.message.from_text( """id 1 flags QR ;QUESTION example. IN A ;ANSWER example. """ ) with self.assertRaises(dns.exception.SyntaxError): dns.message.from_text( """id 1 flags QR ;QUESTION example. IN A ;ANSWER example. IN """ ) with self.assertRaises(dns.exception.SyntaxError): dns.message.from_text( """id 1 flags QR ;QUESTION example. IN A ;ANSWER example. 300 """ ) with self.assertRaises(dns.rdatatype.UnknownRdatatype): dns.message.from_text( """id 1 flags QR ;QUESTION example. IN A ;ANSWER example. 30a IN A """ ) with self.assertRaises(dns.rdatatype.UnknownRdatatype): dns.message.from_text( """id 1 flags QR ;QUESTION example. IN A ;ANSWER example. 300 INA A """ ) with self.assertRaises(dns.exception.UnexpectedEnd): dns.message.from_text( """id 1 flags QR ;QUESTION example. IN A ;ANSWER example. 300 IN A """ ) with self.assertRaises(dns.exception.SyntaxError): dns.message.from_text( """id 1 flags QR opcode UPDATE ;ZONE example. IN SOA ;UPDATE example. 300 IN A """ ) with self.assertRaises(dns.exception.SyntaxError): dns.message.from_text( """id 1 flags QR opcode UPDATE ;ZONE example. IN SOA ;UPDATE example. 300 NONE A """ ) with self.assertRaises(dns.exception.SyntaxError): dns.message.from_text( """id 1 flags QR opcode UPDATE ;ZONE example. IN SOA ;PREREQ example. 300 NONE A 10.0.0.1 """ ) with self.assertRaises(dns.exception.SyntaxError): dns.message.from_text( """id 1 flags QR ;ANSWER 300 IN A 10.0.0.1 """ ) with self.assertRaises(dns.exception.SyntaxError): dns.message.from_text( """id 1 flags QR ;QUESTION IN SOA """ ) def test_from_wire_makes_Flag(self): m = dns.message.from_wire(goodwire) self.assertIsInstance(m.flags, dns.flags.Flag) self.assertEqual(m.flags, dns.flags.Flag.RD) def test_continue_on_error(self): good_message = dns.message.from_text( """id 1234 opcode QUERY rcode NOERROR flags QR AA RD ;QUESTION www.dnspython.org. IN SOA ;ANSWER www.dnspython.org. 300 IN SOA . . 1 2 3 4 4294967295 www.dnspython.org. 300 IN A 1.2.3.4 www.dnspython.org. 300 IN AAAA ::1 """ ) wire = good_message.to_wire() # change ANCOUNT to 255 bad_wire = wire[:6] + b"\x00\xff" + wire[8:] # change AAAA into rdata with rdlen 0 bad_wire = bad_wire[:-18] + b"\x00" * 2 m = dns.message.from_wire(bad_wire, continue_on_error=True) self.assertEqual(len(m.errors), 2) print(m.errors) self.assertEqual(str(m.errors[0].exception), "IPv6 addresses are 16 bytes long") self.assertEqual(str(m.errors[1].exception), "DNS message is malformed.") expected_message = dns.message.from_text( """id 1234 opcode QUERY rcode NOERROR flags QR AA RD ;QUESTION www.dnspython.org. IN SOA ;ANSWER www.dnspython.org. 300 IN SOA . . 1 2 3 4 4294967295 www.dnspython.org. 300 IN A 1.2.3.4 """ ) self.assertEqual(m, expected_message) def test_padding_basic(self): q = dns.message.make_query("www.example", "a", use_edns=0, pad=0) w = q.to_wire() self.assertEqual(len(w), 40) q = dns.message.make_query("www.example", "a", use_edns=0, pad=128) w = q.to_wire() self.assertEqual(len(w), 128) q2 = dns.message.from_wire(w) self.assertEqual(q, q2) def test_padding_various(self): q = dns.message.make_query("www.example", "a", use_edns=0, pad=1) w = q.to_wire() self.assertEqual(len(w), 44) q = dns.message.make_query("www.example", "a", use_edns=0, pad=2) w = q.to_wire() self.assertEqual(len(w), 44) q = dns.message.make_query("www.example", "a", use_edns=0, pad=3) w = q.to_wire() self.assertEqual(len(w), 45) q = dns.message.make_query("www.example", "a", use_edns=0, pad=44) w = q.to_wire() self.assertEqual(len(w), 44) q = dns.message.make_query("www.example", "a", use_edns=0, pad=67) w = q.to_wire() self.assertEqual(len(w), 67) def test_padding_with_option(self): options = [dns.edns.ECSOption("1.2.3.0", 24)] q = dns.message.make_query( "www.example", "a", use_edns=0, pad=128, options=options ) w = q.to_wire() self.assertEqual(len(w), 128) q2 = dns.message.from_wire(w) self.assertEqual(q, q2) def test_padding_with_tsig_and_option(self): keyring = dns.tsigkeyring.from_text({"keyname.": "NjHwPsMKjdN++dOfE5iAiQ=="}) options = [dns.edns.ECSOption("1.2.3.0", 24)] q = dns.message.make_query( "www.example", "a", use_edns=0, options=options, pad=128 ) q.use_tsig(keyring) w = q.to_wire() self.assertEqual(len(w), 256) q2 = dns.message.from_wire(w, keyring=keyring) self.assertIsNotNone(q2.tsig) self.assertEqual(q, q2) def test_response_padding(self): q = dns.message.make_query("www.example", "a", use_edns=0, pad=128) w = q.to_wire() self.assertEqual(len(w), 128) # We need to go read the wire as the padding isn't instantiated in q. pq = dns.message.from_wire(w) r = dns.message.make_response(pq) assert r.pad == 468 r = dns.message.make_response(pq, pad=0) assert r.pad == 0 r = dns.message.make_response(pq, pad=40) assert r.pad == 40 q = dns.message.make_query("www.example", "a", use_edns=0, pad=0) w = q.to_wire() pq = dns.message.from_wire(w) r = dns.message.make_response(pq) assert r.pad == 0 def test_prefer_truncation_answer(self): q = dns.message.make_query("www.example", "a") rrs = [ dns.rrset.from_text("www.example.", 3600, "in", "a", f"1.2.3.{n}") for n in range(32) ] r = dns.message.make_response(q) r.answer.extend(rrs) # Normally, we get an exception with self.assertRaises(dns.exception.TooBig): w1 = r.to_wire(max_size=512) # With prefer_truncation, we get a truncated response where 1 record # doesn't fit, and TC is set. w2 = r.to_wire(max_size=512, prefer_truncation=True) r2 = dns.message.from_wire(w2, one_rr_per_rrset=True) self.assertNotEqual(r2.flags & dns.flags.TC, 0) self.assertEqual(len(r2.answer), 30) def test_prefer_truncation_edns(self): q = dns.message.make_query("www.example", "a", payload=512) rrs = [ dns.rrset.from_text("www.example.", 3600, "in", "a", f"1.2.3.{n}") for n in range(32) ] r = dns.message.make_response(q) r.answer.extend(rrs) # Normally, we get an exception with self.assertRaises(dns.exception.TooBig): w1 = r.to_wire(max_size=512) # With prefer_truncation, we get a truncated response where 2 records # don't fit, and TC is set. w2 = r.to_wire(max_size=512, prefer_truncation=True) r2 = dns.message.from_wire(w2, one_rr_per_rrset=True) self.assertNotEqual(r2.flags & dns.flags.TC, 0) self.assertEqual(len(r2.answer), 29) def test_prefer_truncation_additional(self): q = dns.message.make_query("www.example", "a") rrs = [ dns.rrset.from_text("www.example.", 3600, "in", "a", f"1.2.3.{n}") for n in range(32) ] r = dns.message.make_response(q) r.additional.extend(rrs) # Normally, we get an exception with self.assertRaises(dns.exception.TooBig): w1 = r.to_wire(max_size=512) # With prefer_truncation, we get a truncated response where 1 record # doesn't fit, and TC is not set. w2 = r.to_wire(max_size=512, prefer_truncation=True) r2 = dns.message.from_wire(w2, one_rr_per_rrset=True) self.assertEqual(r2.flags & dns.flags.TC, 0) self.assertEqual(len(r2.additional), 30) def test_section_count(self): a = dns.message.from_text(answer_text) self.assertEqual(a.section_count(a.question), 1) self.assertEqual(a.section_count(a.answer), 1) self.assertEqual(a.section_count("authority"), 3) self.assertEqual(a.section_count(dns.message.MessageSection.ADDITIONAL), 1) a.use_edns() a.use_tsig(dns.tsig.Key("foo.", b"abcd")) self.assertEqual(a.section_count(dns.message.MessageSection.ADDITIONAL), 3) def test_section_count_update(self): update = dns.update.Update("example") update.id = 1 # These each add 1 record to the prereq section update.present("foo") update.present("foo", "a") update.present("bar", "a", "10.0.0.5") update.absent("blaz2") update.absent("blaz2", "a") # This adds 3 records to the update section update.replace("foo", 300, "a", "10.0.0.1", "10.0.0.2") # These each add 1 record to the update section update.add("bar", dns.rdataset.from_text(1, 1, 300, "10.0.0.3")) update.delete("bar", "a", "10.0.0.4") update.delete("blaz", "a") update.delete("blaz2") self.assertEqual(update.section_count(dns.update.UpdateSection.ZONE), 1) self.assertEqual(update.section_count(dns.update.UpdateSection.PREREQ), 5) self.assertEqual(update.section_count(dns.update.UpdateSection.UPDATE), 7) def test_extended_errors(self): options = [ dns.edns.EDEOption(dns.edns.EDECode.NETWORK_ERROR, "tubes not tubing"), dns.edns.EDEOption(dns.edns.EDECode.OTHER, "catch all code"), ] r = dns.message.make_query("example", "A", use_edns=0, options=options) r.flags |= dns.flags.QR self.assertEqual(r.extended_errors(), options) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_name.py0000644000000000000000000012511013615410400014161 0ustar00# -*- coding: utf-8 # 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 # 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 copy import operator import pickle import unittest from io import BytesIO from typing import Dict # pylint: disable=unused-import import dns.e164 import dns.name import dns.reversename # pylint: disable=line-too-long,unsupported-assignment-operation def expand(text): # This is a helper routine to expand name patterns for RFC 4471 tests. # # Basically it turns {} into instances of the character. # For example: # # r"fo{2}.example." => r"foo.example.". # # Two characters get special treatment: # "-" is mapped to r"\000" and "+" is mapped to r"\255". For example # # r"+{3}-.example." -> r"\255\255\255\000.example." # # We do this just to make parsing simpler, so we don't have to process escapes # ourselves. i = 0 l = len(text) previous = "" reading_count = False count = 0 expanded = [] for c in text: if c == "-": c = r"\000" elif c == "+": c = r"\255" if reading_count: assert len(c) == 1 if c >= "0" and c <= "9": count *= 10 count += ord(c) - ord("0") elif c == "}": expanded.append(previous * count) previous = "" reading_count = False count = 0 elif c == "{": reading_count = True else: expanded.append(previous) previous = c # don't forget the last char (if there is one) expanded.append(previous) x = "".join(expanded) return x class NameTestCase(unittest.TestCase): def setUp(self): self.origin = dns.name.from_text("example.") def testFromTextRel1(self): n = dns.name.from_text("foo.bar") self.assertEqual(n.labels, (b"foo", b"bar", b"")) def testFromTextRel2(self): n = dns.name.from_text("foo.bar", origin=self.origin) self.assertEqual(n.labels, (b"foo", b"bar", b"example", b"")) def testFromTextRel3(self): n = dns.name.from_text("foo.bar", origin=None) self.assertEqual(n.labels, (b"foo", b"bar")) def testFromTextRel4(self): n = dns.name.from_text("@", origin=None) self.assertEqual(n, dns.name.empty) def testFromTextRel5(self): n = dns.name.from_text("@", origin=self.origin) self.assertEqual(n, self.origin) def testFromTextAbs1(self): n = dns.name.from_text("foo.bar.") self.assertEqual(n.labels, (b"foo", b"bar", b"")) def testTortureFromText(self): good = [ rb".", rb"a", rb"a.", rb"a" * 63, (rb"a" * 63 + rb".") * 3 + rb"a" * 61, rb"\000.\008.\010.\032.\046.\092.\099.\255", rb"\\", rb"\..\.", rb"\\.\\", rb'!"#%&/()=+-', (rb"\255" * 63 + rb".") * 3 + rb"\255" * 61, ] bad = [ rb"..", rb".a", rb"\\..", b"\\", # yes, we don't want the 'r' prefix! rb"\0", rb"\00", rb"\00Z", rb"a" * 64, (rb"a" * 63 + rb".") * 3 + rb"a" * 62, (rb"\255" * 63 + rb".") * 3 + rb"\255" * 62, ] for t in good: try: dns.name.from_text(t) except Exception: self.fail("good test '%r' raised an exception" % t) for t in bad: caught = False try: dns.name.from_text(t) except Exception: caught = True if not caught: self.fail("bad test '%r' did not raise an exception" % t) def testImmutable1(self): def bad(): self.origin.labels = () self.assertRaises(TypeError, bad) def testImmutable2(self): def bad(): self.origin.labels[0] = "foo" # type: ignore self.assertRaises(TypeError, bad) def testAbs1(self): self.assertTrue(dns.name.root.is_absolute()) def testAbs2(self): self.assertFalse(dns.name.empty.is_absolute()) def testAbs3(self): self.assertTrue(self.origin.is_absolute()) def testAbs4(self): n = dns.name.from_text("foo", origin=None) self.assertFalse(n.is_absolute()) def testWild1(self): n = dns.name.from_text("*.foo", origin=None) self.assertTrue(n.is_wild()) def testWild2(self): n = dns.name.from_text("*a.foo", origin=None) self.assertFalse(n.is_wild()) def testWild3(self): n = dns.name.from_text("a.*.foo", origin=None) self.assertFalse(n.is_wild()) def testWild4(self): self.assertFalse(dns.name.root.is_wild()) def testWild5(self): self.assertFalse(dns.name.empty.is_wild()) def testHash1(self): n1 = dns.name.from_text("fOo.COM") n2 = dns.name.from_text("foo.com") self.assertEqual(hash(n1), hash(n2)) def testCompare1(self): n1 = dns.name.from_text("a") n2 = dns.name.from_text("b") self.assertLess(n1, n2) self.assertLessEqual(n1, n2) self.assertGreater(n2, n1) self.assertGreaterEqual(n2, n1) def testCompare2(self): n1 = dns.name.from_text("") n2 = dns.name.from_text("b") self.assertLess(n1, n2) self.assertLessEqual(n1, n2) self.assertGreater(n2, n1) self.assertGreaterEqual(n2, n1) def testCompare3(self): self.assertLess(dns.name.empty, dns.name.root) self.assertGreater(dns.name.root, dns.name.empty) def testCompare4(self): self.assertNotEqual(dns.name.root, 1) def testSubdomain1(self): self.assertFalse(dns.name.empty.is_subdomain(dns.name.root)) def testSubdomain2(self): self.assertFalse(dns.name.root.is_subdomain(dns.name.empty)) def testSubdomain3(self): n = dns.name.from_text("foo", origin=self.origin) self.assertTrue(n.is_subdomain(self.origin)) def testSubdomain4(self): n = dns.name.from_text("foo", origin=self.origin) self.assertTrue(n.is_subdomain(dns.name.root)) def testSubdomain5(self): n = dns.name.from_text("foo", origin=self.origin) self.assertTrue(n.is_subdomain(n)) def testSuperdomain1(self): self.assertFalse(dns.name.empty.is_superdomain(dns.name.root)) def testSuperdomain2(self): self.assertFalse(dns.name.root.is_superdomain(dns.name.empty)) def testSuperdomain3(self): n = dns.name.from_text("foo", origin=self.origin) self.assertTrue(self.origin.is_superdomain(n)) def testSuperdomain4(self): n = dns.name.from_text("foo", origin=self.origin) self.assertTrue(dns.name.root.is_superdomain(n)) def testSuperdomain5(self): n = dns.name.from_text("foo", origin=self.origin) self.assertTrue(n.is_superdomain(n)) def testCanonicalize1(self): n = dns.name.from_text("FOO.bar", origin=self.origin) c = n.canonicalize() self.assertEqual(c.labels, (b"foo", b"bar", b"example", b"")) def testToText1(self): n = dns.name.from_text("FOO.bar", origin=self.origin) t = n.to_text() self.assertEqual(t, "FOO.bar.example.") def testToText2(self): n = dns.name.from_text("FOO.bar", origin=self.origin) t = n.to_text(True) self.assertEqual(t, "FOO.bar.example") def testToText3(self): n = dns.name.from_text("FOO.bar", origin=None) t = n.to_text() self.assertEqual(t, "FOO.bar") def testToText4(self): t = dns.name.empty.to_text() self.assertEqual(t, "@") def testToText5(self): t = dns.name.root.to_text() self.assertEqual(t, ".") def testToText6(self): n = dns.name.from_text("FOO bar", origin=None) t = n.to_text() self.assertEqual(t, r"FOO\032bar") def testToText7(self): n = dns.name.from_text(r"FOO\.bar", origin=None) t = n.to_text() self.assertEqual(t, r"FOO\.bar") def testToText8(self): n = dns.name.from_text(r"\070OO\.bar", origin=None) t = n.to_text() self.assertEqual(t, r"FOO\.bar") def testToText9(self): n = dns.name.from_text("FOO bar", origin=None) t = n.to_unicode() self.assertEqual(t, "FOO\\032bar") def testToText10(self): t = dns.name.empty.to_unicode() self.assertEqual(t, "@") def testToText11(self): t = dns.name.root.to_unicode() self.assertEqual(t, ".") def testToText12(self): n = dns.name.from_text(r"a\.b.c") t = n.to_unicode() self.assertEqual(t, r"a\.b.c.") def testToText13(self): n = dns.name.from_text(r"\150\151\152\153\154\155\156\157\158\159.") t = n.to_text() self.assertEqual(t, r"\150\151\152\153\154\155\156\157\158\159.") def testToText14(self): # Something that didn't start as unicode should go to escapes and not # raise due to interpreting arbitrary binary DNS labels as UTF-8. n = dns.name.from_text(r"\150\151\152\153\154\155\156\157\158\159.") t = n.to_unicode() self.assertEqual(t, r"\150\151\152\153\154\155\156\157\158\159.") def testSlice1(self): n = dns.name.from_text(r"a.b.c.", origin=None) s = n[:] self.assertEqual(s, (b"a", b"b", b"c", b"")) def testSlice2(self): n = dns.name.from_text(r"a.b.c.", origin=None) s = n[:2] self.assertEqual(s, (b"a", b"b")) def testSlice3(self): n = dns.name.from_text(r"a.b.c.", origin=None) s = n[2:] self.assertEqual(s, (b"c", b"")) def testEmptyLabel1(self): def bad(): dns.name.Name(["a", "", "b"]) self.assertRaises(dns.name.EmptyLabel, bad) def testEmptyLabel2(self): def bad(): dns.name.Name(["", "b"]) self.assertRaises(dns.name.EmptyLabel, bad) def testEmptyLabel3(self): n = dns.name.Name(["b", ""]) self.assertTrue(n) def testLongLabel(self): n = dns.name.Name(["a" * 63]) self.assertTrue(n) def testLabelTooLong(self): def bad(): dns.name.Name(["a" * 64, "b"]) self.assertRaises(dns.name.LabelTooLong, bad) def testLongName(self): n = dns.name.Name(["a" * 63, "a" * 63, "a" * 63, "a" * 62]) self.assertTrue(n) def testNameTooLong(self): def bad(): dns.name.Name(["a" * 63, "a" * 63, "a" * 63, "a" * 63]) self.assertRaises(dns.name.NameTooLong, bad) def testConcat1(self): n1 = dns.name.Name(["a", "b"]) n2 = dns.name.Name(["c", "d"]) e = dns.name.Name(["a", "b", "c", "d"]) r = n1 + n2 self.assertEqual(r, e) def testConcat2(self): n1 = dns.name.Name(["a", "b"]) n2 = dns.name.Name([]) e = dns.name.Name(["a", "b"]) r = n1 + n2 self.assertEqual(r, e) def testConcat3(self): n1 = dns.name.Name([]) n2 = dns.name.Name(["a", "b"]) e = dns.name.Name(["a", "b"]) r = n1 + n2 self.assertEqual(r, e) def testConcat4(self): n1 = dns.name.Name(["a", "b", ""]) n2 = dns.name.Name([]) e = dns.name.Name(["a", "b", ""]) r = n1 + n2 self.assertEqual(r, e) def testConcat5(self): n1 = dns.name.Name(["a", "b"]) n2 = dns.name.Name(["c", ""]) e = dns.name.Name(["a", "b", "c", ""]) r = n1 + n2 self.assertEqual(r, e) def testConcat6(self): def bad(): n1 = dns.name.Name(["a", "b", ""]) n2 = dns.name.Name(["c"]) return n1 + n2 self.assertRaises(dns.name.AbsoluteConcatenation, bad) def testBadEscape(self): def bad(): n = dns.name.from_text(r"a.b\0q1.c.") self.assertRaises(dns.name.BadEscape, bad) def testDigestable1(self): n = dns.name.from_text("FOO.bar") d = n.to_digestable() self.assertEqual(d, b"\x03foo\x03bar\x00") def testDigestable2(self): n1 = dns.name.from_text("FOO.bar") n2 = dns.name.from_text("foo.BAR.") d1 = n1.to_digestable() d2 = n2.to_digestable() self.assertEqual(d1, d2) def testDigestable3(self): d = dns.name.root.to_digestable() self.assertEqual(d, b"\x00") def testDigestable4(self): n = dns.name.from_text("FOO.bar", None) d = n.to_digestable(dns.name.root) self.assertEqual(d, b"\x03foo\x03bar\x00") def testBadDigestable(self): def bad(): n = dns.name.from_text("FOO.bar", None) n.to_digestable() self.assertRaises(dns.name.NeedAbsoluteNameOrOrigin, bad) def testToWire1(self): n = dns.name.from_text("FOO.bar") f = BytesIO() compress = {} # type: Dict[dns.name.Name,int] n.to_wire(f, compress) self.assertEqual(f.getvalue(), b"\x03FOO\x03bar\x00") def testToWire2(self): n = dns.name.from_text("FOO.bar") f = BytesIO() compress = {} # type: Dict[dns.name.Name,int] n.to_wire(f, compress) n.to_wire(f, compress) self.assertEqual(f.getvalue(), b"\x03FOO\x03bar\x00\xc0\x00") def testToWire3(self): n1 = dns.name.from_text("FOO.bar") n2 = dns.name.from_text("foo.bar") f = BytesIO() compress = {} # type: Dict[dns.name.Name,int] n1.to_wire(f, compress) n2.to_wire(f, compress) self.assertEqual(f.getvalue(), b"\x03FOO\x03bar\x00\xc0\x00") def testToWire4(self): n1 = dns.name.from_text("FOO.bar") n2 = dns.name.from_text("a.foo.bar") f = BytesIO() compress = {} # type: Dict[dns.name.Name,int] n1.to_wire(f, compress) n2.to_wire(f, compress) self.assertEqual(f.getvalue(), b"\x03FOO\x03bar\x00\x01\x61\xc0\x00") def testToWire5(self): n1 = dns.name.from_text("FOO.bar") n2 = dns.name.from_text("a.foo.bar") f = BytesIO() compress = {} # type: Dict[dns.name.Name,int] n1.to_wire(f, compress) n2.to_wire(f, None) self.assertEqual(f.getvalue(), b"\x03FOO\x03bar\x00\x01\x61\x03foo\x03bar\x00") def testToWire6(self): n = dns.name.from_text("FOO.bar") v = n.to_wire() self.assertEqual(v, b"\x03FOO\x03bar\x00") def testToWireRelativeNameWithOrigin(self): n = dns.name.from_text("FOO", None) o = dns.name.from_text("bar") v = n.to_wire(origin=o) self.assertEqual(v, b"\x03FOO\x03bar\x00") def testToWireRelativeNameWithoutOrigin(self): n = dns.name.from_text("FOO", None) def bad(): v = n.to_wire() self.assertRaises(dns.name.NeedAbsoluteNameOrOrigin, bad) def testBadToWire(self): def bad(): n = dns.name.from_text("FOO.bar", None) f = BytesIO() compress = {} # type: Dict[dns.name.Name,int] n.to_wire(f, compress) self.assertRaises(dns.name.NeedAbsoluteNameOrOrigin, bad) def testGiantCompressionTable(self): # Only the first 16KiB of a message can have compression pointers. f = BytesIO() compress = {} # type: Dict[dns.name.Name,int] # exactly 16 bytes encoded n = dns.name.from_text("0000000000.com.") n.to_wire(f, compress) # There are now two entries in the compression table (for the full # name, and for the com. suffix. self.assertEqual(len(compress), 2) for i in range(1023): # exactly 16 bytes encoded with compression n = dns.name.from_text(f"{i:013d}.com") n.to_wire(f, compress) # There are now 1025 entries in the compression table with # the last entry at offset 16368. self.assertEqual(len(compress), 1025) self.assertEqual(compress[n], 16368) # Adding another name should not increase the size of the compression # table, as the pointer would be at offset 16384, which is too big. n = dns.name.from_text("toobig.com.") n.to_wire(f, compress) self.assertEqual(len(compress), 1025) def testSplit1(self): n = dns.name.from_text("foo.bar.") (prefix, suffix) = n.split(2) ep = dns.name.from_text("foo", None) es = dns.name.from_text("bar.", None) self.assertEqual(prefix, ep) self.assertEqual(suffix, es) def testSplit2(self): n = dns.name.from_text("foo.bar.") (prefix, suffix) = n.split(1) ep = dns.name.from_text("foo.bar", None) es = dns.name.from_text(".", None) self.assertEqual(prefix, ep) self.assertEqual(suffix, es) def testSplit3(self): n = dns.name.from_text("foo.bar.") (prefix, suffix) = n.split(0) ep = dns.name.from_text("foo.bar.", None) es = dns.name.from_text("", None) self.assertEqual(prefix, ep) self.assertEqual(suffix, es) def testSplit4(self): n = dns.name.from_text("foo.bar.") (prefix, suffix) = n.split(3) ep = dns.name.from_text("", None) es = dns.name.from_text("foo.bar.", None) self.assertEqual(prefix, ep) self.assertEqual(suffix, es) def testBadSplit1(self): def bad(): n = dns.name.from_text("foo.bar.") n.split(-1) self.assertRaises(ValueError, bad) def testBadSplit2(self): def bad(): n = dns.name.from_text("foo.bar.") n.split(4) self.assertRaises(ValueError, bad) def testRelativize1(self): n = dns.name.from_text("a.foo.bar.", None) o = dns.name.from_text("bar.", None) e = dns.name.from_text("a.foo", None) self.assertEqual(n.relativize(o), e) def testRelativize2(self): n = dns.name.from_text("a.foo.bar.", None) o = n e = dns.name.empty self.assertEqual(n.relativize(o), e) def testRelativize3(self): n = dns.name.from_text("a.foo.bar.", None) o = dns.name.from_text("blaz.", None) e = n self.assertEqual(n.relativize(o), e) def testRelativize4(self): n = dns.name.from_text("a.foo", None) o = dns.name.root e = n self.assertEqual(n.relativize(o), e) def testDerelativize1(self): n = dns.name.from_text("a.foo", None) o = dns.name.from_text("bar.", None) e = dns.name.from_text("a.foo.bar.", None) self.assertEqual(n.derelativize(o), e) def testDerelativize2(self): n = dns.name.empty o = dns.name.from_text("a.foo.bar.", None) e = o self.assertEqual(n.derelativize(o), e) def testDerelativize3(self): n = dns.name.from_text("a.foo.bar.", None) o = dns.name.from_text("blaz.", None) e = n self.assertEqual(n.derelativize(o), e) def testChooseRelativity1(self): n = dns.name.from_text("a.foo.bar.", None) o = dns.name.from_text("bar.", None) e = dns.name.from_text("a.foo", None) self.assertEqual(n.choose_relativity(o, True), e) def testChooseRelativity2(self): n = dns.name.from_text("a.foo.bar.", None) o = dns.name.from_text("bar.", None) e = n self.assertEqual(n.choose_relativity(o, False), e) def testChooseRelativity3(self): n = dns.name.from_text("a.foo", None) o = dns.name.from_text("bar.", None) e = dns.name.from_text("a.foo.bar.", None) self.assertEqual(n.choose_relativity(o, False), e) def testChooseRelativity4(self): n = dns.name.from_text("a.foo", None) o = None e = n self.assertEqual(n.choose_relativity(o, True), e) def testChooseRelativity5(self): n = dns.name.from_text("a.foo", None) o = None e = n self.assertEqual(n.choose_relativity(o, False), e) def testChooseRelativity6(self): n = dns.name.from_text("a.foo.", None) o = None e = n self.assertEqual(n.choose_relativity(o, True), e) def testChooseRelativity7(self): n = dns.name.from_text("a.foo.", None) o = None e = n self.assertEqual(n.choose_relativity(o, False), e) def testFromWire1(self): w = b"\x03foo\x00\xc0\x00" (n1, cused1) = dns.name.from_wire(w, 0) (n2, cused2) = dns.name.from_wire(w, cused1) en1 = dns.name.from_text("foo.") en2 = en1 ecused1 = 5 ecused2 = 2 self.assertEqual(n1, en1) self.assertEqual(cused1, ecused1) self.assertEqual(n2, en2) self.assertEqual(cused2, ecused2) def testFromWire2(self): w = b"\x03foo\x00\x01a\xc0\x00\x01b\xc0\x05" current = 0 (n1, cused1) = dns.name.from_wire(w, current) current += cused1 (n2, cused2) = dns.name.from_wire(w, current) current += cused2 (n3, cused3) = dns.name.from_wire(w, current) en1 = dns.name.from_text("foo.") en2 = dns.name.from_text("a.foo.") en3 = dns.name.from_text("b.a.foo.") ecused1 = 5 ecused2 = 4 ecused3 = 4 self.assertEqual(n1, en1) self.assertEqual(cused1, ecused1) self.assertEqual(n2, en2) self.assertEqual(cused2, ecused2) self.assertEqual(n3, en3) self.assertEqual(cused3, ecused3) def testBadFromWire1(self): def bad(): w = b"\x03foo\xc0\x04" dns.name.from_wire(w, 0) self.assertRaises(dns.name.BadPointer, bad) def testBadFromWire2(self): def bad(): w = b"\x03foo\xc0\x05" dns.name.from_wire(w, 0) self.assertRaises(dns.name.BadPointer, bad) def testBadFromWire3(self): def bad(): w = b"\xbffoo" dns.name.from_wire(w, 0) self.assertRaises(dns.name.BadLabelType, bad) def testBadFromWire4(self): def bad(): w = b"\x41foo" dns.name.from_wire(w, 0) self.assertRaises(dns.name.BadLabelType, bad) def testParent1(self): n = dns.name.from_text("foo.bar.") self.assertEqual(n.parent(), dns.name.from_text("bar.")) self.assertEqual(n.parent().parent(), dns.name.root) def testParent2(self): n = dns.name.from_text("foo.bar", None) self.assertEqual(n.parent(), dns.name.from_text("bar", None)) self.assertEqual(n.parent().parent(), dns.name.empty) def testParent3(self): def bad(): n = dns.name.root n.parent() self.assertRaises(dns.name.NoParent, bad) def testParent4(self): def bad(): n = dns.name.empty n.parent() self.assertRaises(dns.name.NoParent, bad) def testFromUnicode1(self): n = dns.name.from_text("foo.bar") self.assertEqual(n.labels, (b"foo", b"bar", b"")) def testFromUnicode2(self): n = dns.name.from_text("foo\u1234bar.bar") self.assertEqual(n.labels, (b"xn--foobar-r5z", b"bar", b"")) def testFromUnicodeAlternateDot1(self): n = dns.name.from_text("foo\u3002bar") self.assertEqual(n.labels, (b"foo", b"bar", b"")) def testFromUnicodeAlternateDot2(self): n = dns.name.from_text("foo\uff0ebar") self.assertEqual(n.labels, (b"foo", b"bar", b"")) def testFromUnicodeAlternateDot3(self): n = dns.name.from_text("foo\uff61bar") self.assertEqual(n.labels, (b"foo", b"bar", b"")) def testFromUnicodeRoot(self): n = dns.name.from_text(".") self.assertEqual(n.labels, (b"",)) def testFromUnicodeAlternateRoot1(self): n = dns.name.from_text("\u3002") self.assertEqual(n.labels, (b"",)) def testFromUnicodeAlternateRoot2(self): n = dns.name.from_text("\uff0e") self.assertEqual(n.labels, (b"",)) def testFromUnicodeAlternateRoot3(self): n = dns.name.from_text("\uff61") self.assertEqual(n.labels, (b"",)) def testFromUnicodeIDNA2003Explicit(self): t = "Königsgäßchen" e = dns.name.from_unicode(t, idna_codec=dns.name.IDNA_2003) self.assertEqual(str(e), "xn--knigsgsschen-lcb0w.") def testFromUnicodeIDNA2003Default(self): t = "Königsgäßchen" e = dns.name.from_unicode(t) self.assertEqual(str(e), "xn--knigsgsschen-lcb0w.") @unittest.skipUnless( dns.name.have_idna_2008, "Python idna cannot be imported; no IDNA2008" ) def testFromUnicodeIDNA2008(self): t = "Königsgäßchen" def bad(): codec = dns.name.IDNA_2008_Strict return dns.name.from_unicode(t, idna_codec=codec) self.assertRaises(dns.name.IDNAException, bad) e1 = dns.name.from_unicode(t, idna_codec=dns.name.IDNA_2008) self.assertEqual(str(e1), "xn--knigsgchen-b4a3dun.") c2 = dns.name.IDNA_2008_Transitional e2 = dns.name.from_unicode(t, idna_codec=c2) self.assertEqual(str(e2), "xn--knigsgsschen-lcb0w.") @unittest.skipUnless( dns.name.have_idna_2008, "Python idna cannot be imported; no IDNA2008" ) def testFromUnicodeIDNA2008Mixed(self): # the IDN rules for names are very restrictive, disallowing # practical names like '_sip._tcp.Königsgäßchen'. Dnspython # has a "practical" mode which permits labels which are purely # ASCII to go straight through, and thus not invalid useful # things in the real world. t = "_sip._tcp.Königsgäßchen" def bad1(): codec = dns.name.IDNA_2008_Strict return dns.name.from_unicode(t, idna_codec=codec) def bad2(): codec = dns.name.IDNA_2008_UTS_46 return dns.name.from_unicode(t, idna_codec=codec) def bad3(): codec = dns.name.IDNA_2008_Transitional return dns.name.from_unicode(t, idna_codec=codec) self.assertRaises(dns.name.IDNAException, bad1) self.assertRaises(dns.name.IDNAException, bad2) self.assertRaises(dns.name.IDNAException, bad3) e = dns.name.from_unicode(t, idna_codec=dns.name.IDNA_2008_Practical) self.assertEqual(str(e), "_sip._tcp.xn--knigsgchen-b4a3dun.") def testFromUnicodeEscapes(self): n = dns.name.from_unicode(r"\097.\098.\099.") t = n.to_unicode() self.assertEqual(t, "a.b.c.") def testToUnicode1(self): n = dns.name.from_text("foo.bar") s = n.to_unicode() self.assertEqual(s, "foo.bar.") def testToUnicode2(self): n = dns.name.from_text("foo\u1234bar.bar") s = n.to_unicode() self.assertEqual(s, "foo\u1234bar.bar.") def testToUnicode3(self): n = dns.name.from_text("foo.bar") s = n.to_unicode() self.assertEqual(s, "foo.bar.") @unittest.skipUnless( dns.name.have_idna_2008, "Python idna cannot be imported; no IDNA2008" ) def testToUnicode4(self): n = dns.name.from_text("ドメイン.テスト", idna_codec=dns.name.IDNA_2008) s = n.to_unicode() self.assertEqual(str(n), "xn--eckwd4c7c.xn--zckzah.") self.assertEqual(s, "ドメイン.テスト.") @unittest.skipUnless( dns.name.have_idna_2008, "Python idna cannot be imported; no IDNA2008" ) def testToUnicode5(self): # Exercise UTS 46 remapping in decode. This doesn't normally happen # as you can see from us having to instantiate the codec as # transitional with strict decoding, not one of our usual choices. codec = dns.name.IDNA2008Codec(True, True, False, True) n = dns.name.from_text("xn--gro-7ka.com") self.assertEqual(n.to_unicode(idna_codec=codec), "gross.com.") @unittest.skipUnless( dns.name.have_idna_2008, "Python idna cannot be imported; no IDNA2008" ) def testToUnicode6(self): # Test strict 2008 decoding without UTS 46 n = dns.name.from_text("xn--gro-7ka.com") self.assertEqual( n.to_unicode(idna_codec=dns.name.IDNA_2008_Strict), "groß.com." ) def testDefaultDecodeIsJustPunycode(self): # groß.com. in IDNA2008 form, pre-encoded. n = dns.name.from_text("xn--gro-7ka.com") # output using default codec which just decodes the punycode and # doesn't test for IDNA2003 or IDNA2008. self.assertEqual(n.to_unicode(), "groß.com.") def testStrictINDA2003Decode(self): # groß.com. in IDNA2008 form, pre-encoded. n = dns.name.from_text("xn--gro-7ka.com") def bad(): # This throws in IDNA2003 because it doesn't "round trip". n.to_unicode(idna_codec=dns.name.IDNA_2003_Strict) self.assertRaises(dns.name.IDNAException, bad) def testINDA2008Decode(self): # groß.com. in IDNA2008 form, pre-encoded. n = dns.name.from_text("xn--gro-7ka.com") self.assertEqual(n.to_unicode(idna_codec=dns.name.IDNA_2008), "groß.com.") def testToUnicodeOmitFinalDot(self): # groß.com. in IDNA2008 form, pre-encoded. n = dns.name.from_text("xn--gro-7ka.com") self.assertEqual(n.to_unicode(True, dns.name.IDNA_2008), "groß.com") def testIDNA2003Misc(self): self.assertEqual(dns.name.IDNA_2003.encode(""), b"") self.assertRaises( dns.name.LabelTooLong, lambda: dns.name.IDNA_2003.encode("x" * 64) ) @unittest.skipUnless( dns.name.have_idna_2008, "Python idna cannot be imported; no IDNA2008" ) def testIDNA2008Misc(self): self.assertEqual(dns.name.IDNA_2008.encode(""), b"") self.assertRaises( dns.name.LabelTooLong, lambda: dns.name.IDNA_2008.encode("x" * 64) ) self.assertRaises( dns.name.LabelTooLong, lambda: dns.name.IDNA_2008.encode("groß" + "x" * 60) ) def testReverseIPv4(self): e = dns.name.from_text("1.0.0.127.in-addr.arpa.") n = dns.reversename.from_address("127.0.0.1") self.assertEqual(e, n) def testReverseIPv6(self): e = dns.name.from_text( "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa." ) n = dns.reversename.from_address("::1") self.assertEqual(e, n) def testReverseIPv6MappedIpv4(self): e = dns.name.from_text("1.0.0.127.in-addr.arpa.") n = dns.reversename.from_address("::ffff:127.0.0.1") self.assertEqual(e, n) def testBadReverseIPv4(self): def bad(): dns.reversename.from_address("127.0.foo.1") self.assertRaises(dns.exception.SyntaxError, bad) def testBadReverseIPv6(self): def bad(): dns.reversename.from_address("::1::1") self.assertRaises(dns.exception.SyntaxError, bad) def testReverseIPv4AlternateOrigin(self): e = dns.name.from_text("1.0.0.127.foo.bar.") origin = dns.name.from_text("foo.bar") n = dns.reversename.from_address("127.0.0.1", v4_origin=origin) self.assertEqual(e, n) def testReverseIPv6AlternateOrigin(self): e = dns.name.from_text( "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.foo.bar." ) origin = dns.name.from_text("foo.bar") n = dns.reversename.from_address("::1", v6_origin=origin) self.assertEqual(e, n) def testForwardIPv4(self): n = dns.name.from_text("1.0.0.127.in-addr.arpa.") e = "127.0.0.1" text = dns.reversename.to_address(n) self.assertEqual(text, e) def testForwardIPv6(self): n = dns.name.from_text( "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa." ) e = "::1" text = dns.reversename.to_address(n) self.assertEqual(text, e) def testForwardIPv4AlternateOrigin(self): n = dns.name.from_text("1.0.0.127.foo.bar.") e = "127.0.0.1" origin = dns.name.from_text("foo.bar") text = dns.reversename.to_address(n, v4_origin=origin) self.assertEqual(text, e) def testForwardIPv6AlternateOrigin(self): n = dns.name.from_text( "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.foo.bar." ) e = "::1" origin = dns.name.from_text("foo.bar") text = dns.reversename.to_address(n, v6_origin=origin) self.assertEqual(text, e) def testUnknownReverseOrigin(self): n = dns.name.from_text("1.2.3.4.unknown.") with self.assertRaises(dns.exception.SyntaxError): dns.reversename.to_address(n) def testE164ToEnum(self): text = "+1 650 555 1212" e = dns.name.from_text("2.1.2.1.5.5.5.0.5.6.1.e164.arpa.") n = dns.e164.from_e164(text) self.assertEqual(n, e) def testEnumToE164(self): n = dns.name.from_text("2.1.2.1.5.5.5.0.5.6.1.e164.arpa.") e = "+16505551212" text = dns.e164.to_e164(n) self.assertEqual(text, e) def testBadEnumToE164(self): n = dns.name.from_text("2.1.2.q.5.5.5.0.5.6.1.e164.arpa.") self.assertRaises(dns.exception.SyntaxError, lambda: dns.e164.to_e164(n)) def test_incompatible_relations(self): n1 = dns.name.from_text("example") n2 = "abc" for oper in [operator.lt, operator.le, operator.ge, operator.gt]: self.assertRaises(TypeError, lambda: oper(n1, n2)) self.assertFalse(n1 == n2) self.assertTrue(n1 != n2) def testFromUnicodeSimpleEscape(self): n = dns.name.from_unicode(r"a.\b") e = dns.name.from_unicode(r"a.b") self.assertEqual(n, e) def testFromUnicodeBadEscape(self): def bad1(): n = dns.name.from_unicode(r"a.b\0q1.c.") self.assertRaises(dns.name.BadEscape, bad1) def bad2(): n = dns.name.from_unicode(r"a.b\0") self.assertRaises(dns.name.BadEscape, bad2) def testFromUnicodeNotString(self): def bad(): dns.name.from_unicode(b"123") # type: ignore self.assertRaises(ValueError, bad) def testFromUnicodeBadOrigin(self): def bad(): dns.name.from_unicode("example", 123) # type: ignore self.assertRaises(ValueError, bad) def testFromUnicodeEmptyLabel(self): def bad(): dns.name.from_unicode("a..b.example") self.assertRaises(dns.name.EmptyLabel, bad) def testFromUnicodeEmptyName(self): self.assertEqual(dns.name.from_unicode("@", None), dns.name.empty) def testFromTextNotString(self): def bad(): dns.name.from_text(123) # type: ignore self.assertRaises(ValueError, bad) def testFromTextBadOrigin(self): def bad(): dns.name.from_text("example", 123) # type: ignore self.assertRaises(ValueError, bad) def testFromWireNotBytes(self): def bad(): dns.name.from_wire(123, 0) # type: ignore self.assertRaises(ValueError, bad) def testBadPunycode(self): c = dns.name.IDNACodec() with self.assertRaises(dns.name.IDNAException): c.decode(b"xn--0000h") def testRootLabel2003StrictDecode(self): c = dns.name.IDNA_2003_Strict self.assertEqual(c.decode(b""), "") @unittest.skipUnless( dns.name.have_idna_2008, "Python idna cannot be imported; no IDNA2008" ) def testRootLabel2008StrictDecode(self): c = dns.name.IDNA_2008_Strict self.assertEqual(c.decode(b""), "") @unittest.skipUnless( dns.name.have_idna_2008, "Python idna cannot be imported; no IDNA2008" ) def testCodecNotFoundRaises(self): dns.name.have_idna_2008 = False with self.assertRaises(dns.name.NoIDNA2008): c = dns.name.IDNA2008Codec() c.encode("Königsgäßchen") with self.assertRaises(dns.name.NoIDNA2008): c = dns.name.IDNA2008Codec(strict_decode=True) c.decode(b"xn--eckwd4c7c.xn--zckzah.") dns.name.have_idna_2008 = True @unittest.skipUnless( dns.name.have_idna_2008, "Python idna cannot be imported; no IDNA2008" ) def testBadPunycodeStrict2008(self): c = dns.name.IDNA2008Codec(strict_decode=True) with self.assertRaises(dns.name.IDNAException): c.decode(b"xn--0000h") def testRelativizeSubtractionSyntax(self): n = dns.name.from_text("foo.example.") o = dns.name.from_text("example.") e = dns.name.from_text("foo", None) self.assertEqual(n - o, e) def testCopy(self): n1 = dns.name.from_text("foo.example.") n2 = copy.copy(n1) self.assertTrue(n1 is not n2) # the Name constructor always copies labels, so there is no # difference between copy and deepcopy self.assertTrue(n1.labels is not n2.labels) self.assertEqual(len(n1.labels), len(n2.labels)) for i, l in enumerate(n1.labels): self.assertTrue(l is n2[i]) def testDeepCopy(self): n1 = dns.name.from_text("foo.example.") n2 = copy.deepcopy(n1) self.assertTrue(n1 is not n2) self.assertTrue(n1.labels is not n2.labels) self.assertEqual(len(n1.labels), len(n2.labels)) for i, l in enumerate(n1.labels): self.assertTrue(l is n2[i]) def testNoAttributeDeletion(self): n = dns.name.from_text("foo.example.") with self.assertRaises(TypeError): del n.labels def testUnicodeEscapify(self): n = dns.name.from_unicode("Königsgäßchen;\ttext") self.assertEqual(n.to_unicode(), "königsgässchen\\;\\009text.") def test_pickle(self): n1 = dns.name.from_text("foo.example") p = pickle.dumps(n1) n2 = pickle.loads(p) self.assertEqual(n1, n2) def test_pad_to_max_name(self): # Test edge cases in our padding helper. tests = [ ("o{61}.o{63}.o{63}.o{63}.", "o{61}.o{63}.o{63}.o{63}."), ("o{60}.o{63}.o{63}.o{63}.", "o{60}.o{63}.o{63}.o{63}."), ("o{59}.o{63}.o{63}.o{63}.", "+.o{59}.o{63}.o{63}.o{63}."), ("o{63}.o{63}.o{63}.", "+{61}.o{63}.o{63}.o{63}."), ("o{63}.o{63}.", "+{61}.+{63}.o{63}.o{63}."), ] for name_text, expected_text in tests: name = dns.name.from_text(expand(name_text)) expected = dns.name.from_text(expand(expected_text)) self.assertEqual(dns.name._pad_to_max_name(name), expected) def test_predecessors_and_successors(self): # Test RFC 4471 predecessor and successor methods. # Here we're actually testing the test suite, but expand() is complicated enough # to deserve a little testing. self.assertEqual(expand("f+{3}-o.example."), r"f\255\255\255\000o.example.") # Ok, now test successors! origin = dns.name.from_text("example.com.") tests = [ # Examples from the RFC. ("foo", True, "\\000.foo"), ("foo", False, "foo\\000"), # The syntax here is almost the RFC's "alternate syntax" except that to # make expand simpler, i.e. not have to understand that \000 was one octet, # I made the convention that "-" means 0 and "+" means 255. We use raw # string constants where needed to avoid escaping backslash. ( "fo{47}.o{63}.o{63}.o{63}", True, "fo{47}-.o{63}.o{63}.o{63}", ), ( "fo{48}.o{63}.o{63}.o{63}", True, "fo{47}p.o{63}.o{63}.o{63}", ), ("+{49}.o{63}.o{63}.o{63}", True, "o{62}p.o{63}.o{63}"), ( "fo{40}+{8}.o{63}.o{63}.o{63}", True, "fo{39}p.o{63}.o{63}.o{63}", ), ( r"fo{47}\@.o{63}.o{63}.o{63}", True, r"fo{47}\[.o{63}.o{63}.o{63}", ), ("+{49}.+{63}.+{63}.+{63}", True, ""), # Some more tests not in the RFC ("+{49}.+{63}.o{63}.o{63}", True, "o{62}p.o{63}"), ( "+{48}.o{63}.o{63}.o{63}", True, "+{48}-.o{63}.o{63}.o{63}", ), ] for test_origin in [origin, None]: for name_text, prefix_ok, expected_successor_text in tests: name = dns.name.from_text(expand(name_text), test_origin) expected_successor = dns.name.from_text( expand(expected_successor_text), test_origin ) successor = name.successor(origin, prefix_ok) self.assertEqual(successor, expected_successor) self.assertTrue( successor > name or successor == origin or successor == dns.name.empty ) # Now test the predecessor predecessor = successor.predecessor(origin, prefix_ok) self.assertEqual(predecessor, name) # Finally, test that a maximal length origin is its own predecessor and # successor. origin = dns.name.from_text(expand("+{49}.+{63}.o{63}.o{63}.example.com.")) assert origin.successor(origin, True) == origin assert origin.predecessor(origin, True) == origin def test_predecessor_and_successor_errors(self): name = dns.name.from_text("name", None) origin = dns.name.from_text("origin", None) # note Relative! with self.assertRaises(dns.name.NeedAbsoluteNameOrOrigin): name.successor(origin, True) with self.assertRaises(dns.name.NeedAbsoluteNameOrOrigin): name.predecessor(origin, True) name = dns.name.from_text("name.") # Note absolute origin = dns.name.from_text("origin.") # 'name' is not a subdomain of 'origin' with self.assertRaises(dns.name.NeedSubdomainOfOrigin): name.successor(origin, True) with self.assertRaises(dns.name.NeedSubdomainOfOrigin): name.predecessor(origin, True) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_namedict.py0000644000000000000000000001277113615410400015035 0ustar00# 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 unittest import dns.name import dns.namedict class NameTestCase(unittest.TestCase): def setUp(self): self.ndict = dns.namedict.NameDict() n1 = dns.name.from_text("foo.bar.") n2 = dns.name.from_text("bar.") self.ndict[n1] = 1 self.ndict[n2] = 2 self.rndict = dns.namedict.NameDict() n1 = dns.name.from_text("foo.bar", None) n2 = dns.name.from_text("bar", None) self.rndict[n1] = 1 self.rndict[n2] = 2 def testDepth(self): self.assertEqual(self.ndict.max_depth, 3) def testLookup1(self): k = dns.name.from_text("foo.bar.") self.assertEqual(self.ndict[k], 1) def testLookup2(self): k = dns.name.from_text("foo.bar.") self.assertEqual(self.ndict.get_deepest_match(k)[1], 1) def testLookup3(self): k = dns.name.from_text("a.b.c.foo.bar.") self.assertEqual(self.ndict.get_deepest_match(k)[1], 1) def testLookup4(self): k = dns.name.from_text("a.b.c.bar.") self.assertEqual(self.ndict.get_deepest_match(k)[1], 2) def testLookup5(self): def bad(): n = dns.name.from_text("a.b.c.") self.ndict.get_deepest_match(n) self.assertRaises(KeyError, bad) def testLookup6(self): def bad(): self.ndict.get_deepest_match(dns.name.empty) self.assertRaises(KeyError, bad) def testLookup7(self): self.ndict[dns.name.empty] = 100 n = dns.name.from_text("a.b.c.") v = self.ndict.get_deepest_match(n)[1] self.assertEqual(v, 100) def testLookup8(self): def bad(): self.ndict["foo"] = 100 self.assertRaises(ValueError, bad) def testRelDepth(self): self.assertEqual(self.rndict.max_depth, 2) def testRelLookup1(self): k = dns.name.from_text("foo.bar", None) self.assertEqual(self.rndict[k], 1) def testRelLookup2(self): k = dns.name.from_text("foo.bar", None) self.assertEqual(self.rndict.get_deepest_match(k)[1], 1) def testRelLookup3(self): k = dns.name.from_text("a.b.c.foo.bar", None) self.assertEqual(self.rndict.get_deepest_match(k)[1], 1) def testRelLookup4(self): k = dns.name.from_text("a.b.c.bar", None) self.assertEqual(self.rndict.get_deepest_match(k)[1], 2) def testRelLookup7(self): self.rndict[dns.name.empty] = 100 n = dns.name.from_text("a.b.c", None) v = self.rndict.get_deepest_match(n)[1] self.assertEqual(v, 100) def test_max_depth_increases(self): n = dns.name.from_text("a.foo.bar.") self.assertEqual(self.ndict.max_depth, 3) self.ndict[n] = 1 self.assertEqual(self.ndict.max_depth, 4) def test_delete_no_max_depth_change(self): self.assertEqual(self.ndict.max_depth, 3) n = dns.name.from_text("bar.") del self.ndict[n] self.assertEqual(self.ndict.max_depth, 3) self.assertEqual(self.ndict.get(n), None) def test_delete_max_depth_changes(self): self.assertEqual(self.ndict.max_depth, 3) n = dns.name.from_text("foo.bar.") del self.ndict[n] self.assertEqual(self.ndict.max_depth, 2) self.assertEqual(self.ndict.get(n), None) def test_delete_multiple_max_depth_changes(self): self.assertEqual(self.ndict.max_depth, 3) nr = dns.name.from_text("roo.") self.ndict[nr] = 1 nf = dns.name.from_text("foo.bar.") nb = dns.name.from_text("bar.bar.") self.ndict[nb] = 1 self.assertEqual(self.ndict.max_depth, 3) self.assertEqual(self.ndict.max_depth_items, 2) del self.ndict[nb] self.assertEqual(self.ndict.max_depth, 3) self.assertEqual(self.ndict.max_depth_items, 1) del self.ndict[nf] self.assertEqual(self.ndict.max_depth, 2) self.assertEqual(self.ndict.max_depth_items, 2) self.assertEqual(self.ndict.get(nf), None) self.assertEqual(self.ndict.get(nb), None) def test_iter(self): nf = dns.name.from_text("foo.bar.") nb = dns.name.from_text("bar.") keys = set([x for x in self.ndict]) self.assertEqual(len(keys), 2) self.assertTrue(nf in keys) self.assertTrue(nb in keys) def test_len(self): self.assertEqual(len(self.ndict), 2) def test_haskey(self): nf = dns.name.from_text("foo.bar.") nb = dns.name.from_text("bar.") nx = dns.name.from_text("x.") self.assertTrue(self.ndict.has_key(nf)) self.assertTrue(self.ndict.has_key(nb)) self.assertFalse(self.ndict.has_key(nx)) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_nsec3.py0000644000000000000000000000356313615410400014263 0ustar00# 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 # documentation for any purpose with or without fee is hereby granted, # provided that the above copyright notice and this permission notice # appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import unittest import dns.exception import dns.rdata import dns.rdataclass import dns.rdatatype import dns.rdtypes.ANY.TXT import dns.ttl class NSEC3TestCase(unittest.TestCase): def test_NSEC3_bitmap(self): rdata = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.NSEC3, "1 0 100 ABCD SCBCQHKU35969L2A68P3AD59LHF30715 A CAA TYPE65534", ) bitmap = bytearray(b"\0" * 32) bitmap[31] = bitmap[31] | 2 self.assertEqual( rdata.windows, ((0, b"@"), (1, b"@"), (255, bitmap)) # CAA = 257 ) def test_NSEC3_bad_bitmaps(self): rdata = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.NSEC3, "1 0 100 ABCD SCBCQHKU35969L2A68P3AD59LHF30715 A CAA", ) with self.assertRaises(dns.exception.FormError): copy = bytearray(rdata.to_wire()) copy[-3] = 0 dns.rdata.from_wire("IN", "NSEC3", copy, 0, len(copy)) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_nsec3_hash.py0000644000000000000000000000714513615410400015266 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import unittest from dns import dnssec, name class NSEC3Hash(unittest.TestCase): DATA = [ # Source: https://tools.ietf.org/html/rfc5155#appendix-A ("example", "aabbccdd", 12, "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom", 1), ("a.example", "aabbccdd", 12, "35mthgpgcu1qg68fab165klnsnk3dpvl", 1), ("ai.example", "aabbccdd", 12, "gjeqe526plbf1g8mklp59enfd789njgi", 1), ("ns1.example", "aabbccdd", 12, "2t7b4g4vsa5smi47k61mv5bv1a22bojr", 1), ("ns2.example", "aabbccdd", 12, "q04jkcevqvmu85r014c7dkba38o0ji5r", 1), ("w.example", "aabbccdd", 12, "k8udemvp1j2f7eg6jebps17vp3n8i58h", 1), ("*.w.example", "aabbccdd", 12, "r53bq7cc2uvmubfu5ocmm6pers9tk9en", 1), ("x.w.example", "aabbccdd", 12, "b4um86eghhds6nea196smvmlo4ors995", 1), ("y.w.example", "aabbccdd", 12, "ji6neoaepv8b5o6k4ev33abha8ht9fgc", 1), ("x.y.w.example", "aabbccdd", 12, "2vptu5timamqttgl4luu9kg21e0aor3s", 1), ("xx.example", "aabbccdd", 12, "t644ebqk9bibcna874givr6joj62mlhv", 1), ( "2t7b4g4vsa5smi47k61mv5bv1a22bojr.example", "aabbccdd", 12, "kohar7mbb8dc2ce8a9qvl8hon4k53uhi", 1, ), # Source: generated with knsec3hash (Linux knot package) ("example.com", "9F1AB450CF71D6", 0, "qfo2sv6jaej4cm11a3npoorfrckdao2c", 1), ("example.com", "9F1AB450CF71D6", 1, "1nr64to0bb861lku97deb4ubbk6cl5qh", 1), ("example.com.", "AF6AB45CCF79D6", 6, "sale3fn6penahh1lq5oqtr5rcl1d113a", 1), ("test.domain.dev.", "", 6, "8q98lv9jgkhoq272e42c8blesivia7bu", 1), ("www.test.domain.dev.", "B4", 2, "nv7ti6brgh94ke2f3pgiigjevfgpo5j0", 1), ("*.test-domain.dev", "", 0, "o6uadafckb6hea9qpcgir2gl71vt23gu", 1), ("*.test-domain.dev", "", 45, "505k9g118d9sofnjhh54rr8fadgpa0ct", 1), # Alternate forms of parameters ( name.from_text("example"), "aabbccdd", 12, "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom", 1, ), ( "example", b"\xaa\xbb\xcc\xdd", 12, "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom", 1, ), ("*.test-domain.dev", None, 45, "505k9g118d9sofnjhh54rr8fadgpa0ct", 1), ( "example", "aabbccdd", 12, "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom", dnssec.NSEC3Hash.SHA1, ), ("example", "aabbccdd", 12, "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom", "SHA1"), ("example", "aabbccdd", 12, "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom", "sha1"), ] def test_hash_function(self): for d in self.DATA: hash = dnssec.nsec3_hash(d[0], d[1], d[2], d[4]) self.assertEqual(hash, d[3].upper(), "Error {}".format(d)) def test_hash_invalid_salt_length(self): data = ( "example.com", "9F1AB450CF71D", 0, "qfo2sv6jaej4cm11a3npoorfrckdao2c", 1, ) with self.assertRaises(ValueError): hash = dnssec.nsec3_hash(data[0], data[1], data[2], data[4]) def test_hash_invalid_algorithm(self): data = ( "example.com", "9F1AB450CF71D", 0, "qfo2sv6jaej4cm11a3npoorfrckdao2c", 1, ) with self.assertRaises(ValueError): dnssec.nsec3_hash(data[0], data[1], data[2], 10) with self.assertRaises(ValueError): dnssec.nsec3_hash(data[0], data[1], data[2], "foo") if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_ntoaaton.py0000644000000000000000000003024113615410400015064 0ustar00# 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 binascii import itertools import socket import unittest import dns.exception import dns.inet import dns.ipv4 import dns.ipv6 # for convenience aton4 = dns.ipv4.inet_aton ntoa4 = dns.ipv4.inet_ntoa aton6 = dns.ipv6.inet_aton ntoa6 = dns.ipv6.inet_ntoa v4_bad_addrs = [ "256.1.1.1", "1.1.1", "1.1.1.1.1", "+1.1.1.1", "1.1.1.1+", "1..2.3.4", ".1.2.3.4", "1.2.3.4.", ] v4_canonicalize_addrs = [ # (input, expected) ("127.0.0.1", "127.0.0.1"), (b"127.0.0.1", "127.0.0.1"), ] v6_canonicalize_addrs = [ # (input, expected) ("2001:503:83eb:0:0:0:0:30", "2001:503:83eb::30"), (b"2001:503:83eb:0:0:0:0:30", "2001:503:83eb::30"), ("2001:db8::1:1:1:1:1", "2001:db8:0:1:1:1:1:1"), ("2001:DB8::1:1:1:1:1", "2001:db8:0:1:1:1:1:1"), ] bad_canonicalize_addrs = [ "127.00.0.1", "hi there", "2001::db8::1:1:1:1:1", "fe80::1%lo0", ] class NtoAAtoNTestCase(unittest.TestCase): def test_aton1(self): a = aton6("::") self.assertEqual(a, b"\x00" * 16) def test_aton2(self): a = aton6("::1") self.assertEqual(a, b"\x00" * 15 + b"\x01") def test_aton3(self): a = aton6("::10.0.0.1") self.assertEqual(a, b"\x00" * 12 + b"\x0a\x00\x00\x01") def test_aton4(self): a = aton6("abcd::dcba") self.assertEqual(a, b"\xab\xcd" + b"\x00" * 12 + b"\xdc\xba") def test_aton5(self): a = aton6("1:2:3:4:5:6:7:8") self.assertEqual(a, binascii.unhexlify(b"00010002000300040005000600070008")) def test_bad_aton1(self): def bad(): aton6("abcd:dcba") self.assertRaises(dns.exception.SyntaxError, bad) def test_bad_aton2(self): def bad(): aton6("abcd::dcba::1") self.assertRaises(dns.exception.SyntaxError, bad) def test_bad_aton3(self): def bad(): aton6("1:2:3:4:5:6:7:8:9") self.assertRaises(dns.exception.SyntaxError, bad) def test_bad_aton4(self): def bad(): aton4("001.002.003.004") self.assertRaises(dns.exception.SyntaxError, bad) def test_aton6(self): a = aton6("::") self.assertEqual(a, b"\x00" * 16) def test_aton7(self): a = aton6("::1") self.assertEqual(a, b"\x00" * 15 + b"\x01") def test_aton8(self): a = aton6("::10.0.0.1") self.assertEqual(a, b"\x00" * 12 + b"\x0a\x00\x00\x01") def test_aton9(self): a = aton6("abcd::dcba") self.assertEqual(a, b"\xab\xcd" + b"\x00" * 12 + b"\xdc\xba") def test_ntoa1(self): b = binascii.unhexlify(b"00010002000300040005000600070008") t = ntoa6(b) self.assertEqual(t, "1:2:3:4:5:6:7:8") def test_ntoa2(self): b = b"\x00" * 16 t = ntoa6(b) self.assertEqual(t, "::") def test_ntoa3(self): b = b"\x00" * 15 + b"\x01" t = ntoa6(b) self.assertEqual(t, "::1") def test_ntoa4(self): b = b"\x80" + b"\x00" * 15 t = ntoa6(b) self.assertEqual(t, "8000::") def test_ntoa5(self): b = b"\x01\xcd" + b"\x00" * 12 + b"\x03\xef" t = ntoa6(b) self.assertEqual(t, "1cd::3ef") def test_ntoa6(self): b = binascii.unhexlify(b"ffff00000000ffff000000000000ffff") t = ntoa6(b) self.assertEqual(t, "ffff:0:0:ffff::ffff") def test_ntoa7(self): b = binascii.unhexlify(b"00000000ffff000000000000ffffffff") t = ntoa6(b) self.assertEqual(t, "0:0:ffff::ffff:ffff") def test_ntoa8(self): b = binascii.unhexlify(b"ffff0000ffff00000000ffff00000000") t = ntoa6(b) self.assertEqual(t, "ffff:0:ffff::ffff:0:0") def test_ntoa9(self): b = binascii.unhexlify(b"0000000000000000000000000a000001") t = ntoa6(b) self.assertEqual(t, "::10.0.0.1") def test_ntoa10(self): b = binascii.unhexlify(b"0000000000000000000000010a000001") t = ntoa6(b) self.assertEqual(t, "::1:a00:1") def test_ntoa11(self): b = binascii.unhexlify(b"00000000000000000000ffff0a000001") t = ntoa6(b) self.assertEqual(t, "::ffff:10.0.0.1") def test_ntoa12(self): b = binascii.unhexlify(b"000000000000000000000000ffffffff") t = ntoa6(b) self.assertEqual(t, "::255.255.255.255") def test_ntoa13(self): b = binascii.unhexlify(b"00000000000000000000ffffffffffff") t = ntoa6(b) self.assertEqual(t, "::ffff:255.255.255.255") def test_ntoa14(self): b = binascii.unhexlify(b"0000000000000000000000000001ffff") t = ntoa6(b) self.assertEqual(t, "::0.1.255.255") def test_ntoa15(self): # This exercises the current_len > best_len branch in the <= case. b = binascii.unhexlify(b"0000ffff00000000ffff00000000ffff") t = ntoa6(b) self.assertEqual(t, "0:ffff::ffff:0:0:ffff") def test_bad_ntoa1(self): def bad(): ntoa6(b"") self.assertRaises(ValueError, bad) def test_bad_ntoa2(self): def bad(): ntoa6(b"\x00" * 17) self.assertRaises(ValueError, bad) def test_bad_ntoa3(self): def bad(): ntoa4(b"\x00" * 5) # Ideally we'd have been consistent and raised ValueError as # we do for IPv6, but oh well! self.assertRaises(dns.exception.SyntaxError, bad) def test_good_v4_aton(self): pairs = [ ("1.2.3.4", b"\x01\x02\x03\x04"), ("255.255.255.255", b"\xff\xff\xff\xff"), ("0.0.0.0", b"\x00\x00\x00\x00"), ] for t, b in pairs: b1 = aton4(t) t1 = ntoa4(b1) self.assertEqual(b1, b) self.assertEqual(t1, t) def test_bad_v4_aton(self): def make_bad(a): def bad(): return aton4(a) return bad for addr in v4_bad_addrs: self.assertRaises(dns.exception.SyntaxError, make_bad(addr)) def test_bad_v6_aton(self): addrs = ["+::0", "0::0::", "::0::", "1:2:3:4:5:6:7:8:9", ":::::::"] embedded = ["::" + x for x in v4_bad_addrs] addrs.extend(embedded) def make_bad(a): def bad(): x = aton6(a) return bad for addr in addrs: self.assertRaises(dns.exception.SyntaxError, make_bad(addr)) def test_rfc5952_section_4_2_2(self): addr = "2001:db8:0:1:1:1:1:1" b1 = aton6(addr) t1 = ntoa6(b1) self.assertEqual(t1, addr) def test_is_mapped(self): t1 = "2001:db8:0:1:1:1:1:1" t2 = "::ffff:127.0.0.1" t3 = "1::ffff:127.0.0.1" self.assertFalse(dns.ipv6.is_mapped(aton6(t1))) self.assertTrue(dns.ipv6.is_mapped(aton6(t2))) self.assertFalse(dns.ipv6.is_mapped(aton6(t3))) def test_is_multicast(self): t1 = "223.0.0.1" t2 = "240.0.0.1" t3 = "224.0.0.1" t4 = "239.0.0.1" t5 = "fe00::1" t6 = "ff00::1" self.assertFalse(dns.inet.is_multicast(t1)) self.assertFalse(dns.inet.is_multicast(t2)) self.assertTrue(dns.inet.is_multicast(t3)) self.assertTrue(dns.inet.is_multicast(t4)) self.assertFalse(dns.inet.is_multicast(t5)) self.assertTrue(dns.inet.is_multicast(t6)) def test_is_multicast_bad_input(self): def bad(): dns.inet.is_multicast("hello world") self.assertRaises(ValueError, bad) def test_ignore_scope(self): t1 = "fe80::1%lo0" t2 = "fe80::1" self.assertEqual(aton6(t1, True), aton6(t2)) def test_do_not_ignore_scope(self): def bad(): t1 = "fe80::1%lo0" aton6(t1) self.assertRaises(dns.exception.SyntaxError, bad) def test_multiple_scopes_bad(self): def bad(): t1 = "fe80::1%lo0%lo1" aton6(t1, True) self.assertRaises(dns.exception.SyntaxError, bad) def test_ptontop(self): for af, a in [ (socket.AF_INET, "1.2.3.4"), (socket.AF_INET6, "2001:db8:0:1:1:1:1:1"), ]: self.assertEqual(dns.inet.inet_ntop(af, dns.inet.inet_pton(af, a)), a) def test_isaddress(self): for t, e in [ ("1.2.3.4", True), ("2001:db8:0:1:1:1:1:1", True), ("hello world", False), ("http://www.dnspython.org", False), ("1.2.3.4a", False), ("2001:db8:0:1:1:1:1:q1", False), ]: self.assertEqual(dns.inet.is_address(t), e) def test_low_level_address_tuple(self): t = dns.inet.low_level_address_tuple(("1.2.3.4", 53)) self.assertEqual(t, ("1.2.3.4", 53)) t = dns.inet.low_level_address_tuple(("2600::1", 53)) self.assertEqual(t, ("2600::1", 53, 0, 0)) t = dns.inet.low_level_address_tuple(("1.2.3.4", 53), socket.AF_INET) self.assertEqual(t, ("1.2.3.4", 53)) t = dns.inet.low_level_address_tuple(("2600::1", 53), socket.AF_INET6) self.assertEqual(t, ("2600::1", 53, 0, 0)) t = dns.inet.low_level_address_tuple(("fd80::1%2", 53), socket.AF_INET6) self.assertEqual(t, ("fd80::1", 53, 0, 2)) try: # This can fail on windows for python < 3.8, so we tolerate # the failure and only test if we have something we can work # with. info = socket.if_nameindex() except Exception: info = [] if info: # find first thing on list that is not zero (should be first thing! pair = None for p in info: if p[0] != 0: pair = p break if pair: address = "fd80::1%" + pair[1] t = dns.inet.low_level_address_tuple((address, 53), socket.AF_INET6) self.assertEqual(t, ("fd80::1", 53, 0, pair[0])) def bad(): bogus = socket.AF_INET + socket.AF_INET6 + 1 t = dns.inet.low_level_address_tuple(("2600::1", 53), bogus) self.assertRaises(NotImplementedError, bad) def test_bogus_family(self): self.assertRaises( NotImplementedError, lambda: dns.inet.inet_pton(12345, "bogus") ) self.assertRaises( NotImplementedError, lambda: dns.inet.inet_ntop(12345, b"bogus") ) def test_ipv4_canonicalize(self): for address, expected in v4_canonicalize_addrs: self.assertEqual(dns.ipv4.canonicalize(address), expected) for bad_address in bad_canonicalize_addrs: self.assertRaises( dns.exception.SyntaxError, lambda: dns.ipv4.canonicalize(bad_address) ) def test_ipv6_canonicalize(self): for address, expected in v6_canonicalize_addrs: self.assertEqual(dns.ipv6.canonicalize(address), expected) for bad_address in bad_canonicalize_addrs: self.assertRaises( dns.exception.SyntaxError, lambda: dns.ipv6.canonicalize(bad_address) ) def test_inet_canonicalize(self): for address, expected in itertools.chain( v4_canonicalize_addrs, v6_canonicalize_addrs ): self.assertEqual(dns.inet.canonicalize(address), expected) for bad_address in bad_canonicalize_addrs: self.assertRaises(ValueError, lambda: dns.inet.canonicalize(bad_address)) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_processing_order.py0000644000000000000000000000770513615410400016621 0ustar00import dns.rdata import dns.rdataset import dns.rdtypes.IN.SRV def test_processing_order_shuffle(): rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.1", "10.0.0.2", "10.0.0.3") seen = set() for i in range(100): po = rds.processing_order() assert len(po) == 3 for j in range(3): assert rds[j] in po seen.add(tuple(po)) assert len(seen) == 6 def test_processing_order_priority_mx(): rds = dns.rdataset.from_text("in", "mx", 300, "10 a", "20 b", "20 c") seen = set() for i in range(100): po = rds.processing_order() assert len(po) == 3 for j in range(3): assert rds[j] in po assert rds[0] == po[0] seen.add(tuple(po)) assert len(seen) == 2 def test_processing_order_priority_weighted(): rds = dns.rdataset.from_text( "in", "srv", 300, "1 10 1234 a", "2 90 1234 b", "2 10 1234 c" ) seen = set() weight_90_count = 0 weight_10_count = 0 for i in range(100): po = rds.processing_order() assert len(po) == 3 for j in range(3): assert rds[j] in po assert rds[0] == po[0] assert isinstance(po[1], dns.rdtypes.IN.SRV.SRV) if po[1].weight == 90: weight_90_count += 1 else: assert po[1].weight == 10 weight_10_count += 1 seen.add(tuple(po)) assert len(seen) == 2 # We can't assert anything with certainty given these are random # draws, but it's super likely that weight_90_count > weight_10_count, # so we just assert that. assert weight_90_count > weight_10_count def test_processing_order_priority_naptr(): rds = dns.rdataset.from_text( "in", "naptr", 300, "1 10 a b c foo.", "1 20 a b c foo.", "2 10 a b c foo.", "2 10 d e f bar.", ) seen = set() for i in range(100): po = rds.processing_order() assert len(po) == 4 for j in range(4): assert rds[j] in po assert rds[0] == po[0] assert rds[1] == po[1] seen.add(tuple(po)) assert len(seen) == 2 def test_processing_order_empty(): rds = dns.rdataset.from_text("in", "naptr", 300) po = rds.processing_order() assert po == [] def test_processing_singleton_priority(): rds = dns.rdataset.from_text("in", "mx", 300, "10 a") po = rds.processing_order() assert po == [rds[0]] def test_processing_singleton_weighted(): rds = dns.rdataset.from_text("in", "srv", 300, "1 10 1234 a") po = rds.processing_order() assert po == [rds[0]] def test_processing_all_zero_weight_srv(): rds = dns.rdataset.from_text( "in", "srv", 300, "1 0 1234 a", "1 0 1234 b", "1 0 1234 c" ) seen = set() for i in range(100): po = rds.processing_order() assert len(po) == 3 for j in range(3): assert rds[j] in po seen.add(tuple(po)) assert len(seen) == 6 def test_processing_order_uri(): # We're testing here just to provide coverage for URI methods; the # testing of the weighting algorithm is done above in tests with # SRV. rds = dns.rdataset.from_text( "in", "uri", 300, '1 1 "ftp://ftp1.example.com/public"', '2 2 "ftp://ftp2.example.com/public"', '3 3 "ftp://ftp3.example.com/public"', ) po = rds.processing_order() assert len(po) == 3 for i in range(3): assert po[i] == rds[i] def test_processing_order_svcb(): # We're testing here just to provide coverage for SVCB methods; the # testing of the priority algorithm is done above in tests with # MX and NAPTR. rds = dns.rdataset.from_text( "in", "svcb", 300, "1 . mandatory=alpn alpn=h2", "2 . mandatory=alpn alpn=h2", "3 . mandatory=alpn alpn=h2", ) po = rds.processing_order() assert len(po) == 3 for i in range(3): assert po[i] == rds[i] dnspython-2.7.0/tests/test_query.py0000644000000000000000000007346613615410400014426 0ustar00# 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 # 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 contextlib import socket import sys import time import unittest try: import ssl have_ssl = True except Exception: have_ssl = False import dns.exception import dns.flags import dns.inet import dns.message import dns.name import dns.query import dns.rcode import dns.rdataclass import dns.rdatatype import dns.tsigkeyring import dns.zone import tests.util # Some tests use a "nano nameserver" for testing. It requires trio # and threading, so try to import it and if it doesn't work, skip # those tests. try: from .nanonameserver import Server _nanonameserver_available = True except ImportError: _nanonameserver_available = False class Server(object): pass query_addresses = [] if tests.util.have_ipv4(): query_addresses.append("8.8.8.8") if tests.util.have_ipv6(): query_addresses.append("2001:4860:4860::8888") keyring = dns.tsigkeyring.from_text({"name": "tDz6cfXXGtNivRpQ98hr6A=="}) @unittest.skipIf(not tests.util.is_internet_reachable(), "Internet not reachable") class QueryTests(unittest.TestCase): def testQueryUDP(self): for address in query_addresses: qname = dns.name.from_text("dns.google.") q = dns.message.make_query(qname, dns.rdatatype.A) response = dns.query.udp(q, address, timeout=2) rrs = response.get_rrset( response.answer, qname, dns.rdataclass.IN, dns.rdatatype.A ) self.assertTrue(rrs is not None) seen = set([rdata.address for rdata in rrs]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) def testQueryUDPWithSocket(self): for address in query_addresses: with socket.socket( dns.inet.af_for_address(address), socket.SOCK_DGRAM ) as s: s.setblocking(0) qname = dns.name.from_text("dns.google.") q = dns.message.make_query(qname, dns.rdatatype.A) response = dns.query.udp(q, address, sock=s, timeout=2) rrs = response.get_rrset( response.answer, qname, dns.rdataclass.IN, dns.rdatatype.A ) self.assertTrue(rrs is not None) seen = set([rdata.address for rdata in rrs]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) def testQueryTCP(self): for address in query_addresses: qname = dns.name.from_text("dns.google.") q = dns.message.make_query(qname, dns.rdatatype.A) response = dns.query.tcp(q, address, timeout=2) rrs = response.get_rrset( response.answer, qname, dns.rdataclass.IN, dns.rdatatype.A ) self.assertTrue(rrs is not None) seen = set([rdata.address for rdata in rrs]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) def testQueryTCPWithSocket(self): for address in query_addresses: with socket.socket( dns.inet.af_for_address(address), socket.SOCK_STREAM ) as s: ll = dns.inet.low_level_address_tuple((address, 53)) s.settimeout(2) s.connect(ll) s.setblocking(0) qname = dns.name.from_text("dns.google.") q = dns.message.make_query(qname, dns.rdatatype.A) response = dns.query.tcp(q, None, sock=s, timeout=2) rrs = response.get_rrset( response.answer, qname, dns.rdataclass.IN, dns.rdatatype.A ) self.assertTrue(rrs is not None) seen = set([rdata.address for rdata in rrs]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) @unittest.skipUnless(have_ssl, "No SSL support") def testQueryTLS(self): for address in query_addresses: qname = dns.name.from_text("dns.google.") q = dns.message.make_query(qname, dns.rdatatype.A) response = dns.query.tls(q, address, timeout=2) rrs = response.get_rrset( response.answer, qname, dns.rdataclass.IN, dns.rdatatype.A ) self.assertTrue(rrs is not None) seen = set([rdata.address for rdata in rrs]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) @unittest.skipUnless(have_ssl, "No SSL support") def testQueryTLSWithContext(self): for address in query_addresses: qname = dns.name.from_text("dns.google.") q = dns.message.make_query(qname, dns.rdatatype.A) ssl_context = ssl.create_default_context() ssl_context.check_hostname = False response = dns.query.tls(q, address, timeout=2, ssl_context=ssl_context) rrs = response.get_rrset( response.answer, qname, dns.rdataclass.IN, dns.rdatatype.A ) self.assertTrue(rrs is not None) seen = set([rdata.address for rdata in rrs]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) @unittest.skipUnless(have_ssl, "No SSL support") def testQueryTLSWithSocket(self): for address in query_addresses: with socket.socket( dns.inet.af_for_address(address), socket.SOCK_STREAM ) as base_s: ll = dns.inet.low_level_address_tuple((address, 853)) base_s.settimeout(2) base_s.connect(ll) ctx = ssl.create_default_context() ctx.minimum_version = ssl.TLSVersion.TLSv1_2 with ctx.wrap_socket( base_s, server_hostname="dns.google" ) as s: # lgtm[py/insecure-protocol] s.setblocking(0) qname = dns.name.from_text("dns.google.") q = dns.message.make_query(qname, dns.rdatatype.A) response = dns.query.tls(q, None, sock=s, timeout=2) rrs = response.get_rrset( response.answer, qname, dns.rdataclass.IN, dns.rdatatype.A ) self.assertTrue(rrs is not None) seen = set([rdata.address for rdata in rrs]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) @unittest.skipUnless(have_ssl, "No SSL support") def testQueryTLSwithPadding(self): for address in query_addresses: qname = dns.name.from_text("dns.google.") q = dns.message.make_query(qname, dns.rdatatype.A, use_edns=0, pad=128) response = dns.query.tls(q, address, timeout=2) rrs = response.get_rrset( response.answer, qname, dns.rdataclass.IN, dns.rdatatype.A ) self.assertTrue(rrs is not None) seen = set([rdata.address for rdata in rrs]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) # the response should have a padding option self.assertIsNotNone(response.opt) has_pad = False for o in response.opt[0].options: if o.otype == dns.edns.OptionType.PADDING: has_pad = True self.assertTrue(has_pad) def testQueryUDPFallback(self): for address in query_addresses: qname = dns.name.from_text(".") q = dns.message.make_query(qname, dns.rdatatype.DNSKEY) (_, tcp) = dns.query.udp_with_fallback(q, address, timeout=4) self.assertTrue(tcp) def testQueryUDPFallbackWithSocket(self): for address in query_addresses: af = dns.inet.af_for_address(address) with socket.socket(af, socket.SOCK_DGRAM) as udp_s: udp_s.setblocking(0) with socket.socket(af, socket.SOCK_STREAM) as tcp_s: ll = dns.inet.low_level_address_tuple((address, 53)) tcp_s.settimeout(2) tcp_s.connect(ll) tcp_s.setblocking(0) qname = dns.name.from_text(".") q = dns.message.make_query(qname, dns.rdatatype.DNSKEY) (_, tcp) = dns.query.udp_with_fallback( q, address, udp_sock=udp_s, tcp_sock=tcp_s, timeout=4 ) self.assertTrue(tcp) def testQueryUDPFallbackNoFallback(self): for address in query_addresses: qname = dns.name.from_text("dns.google.") q = dns.message.make_query(qname, dns.rdatatype.A) (_, tcp) = dns.query.udp_with_fallback(q, address, timeout=2) self.assertFalse(tcp) def testUDPReceiveQuery(self): with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as listener: listener.bind(("127.0.0.1", 0)) with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sender: sender.bind(("127.0.0.1", 0)) q = dns.message.make_query("dns.google", dns.rdatatype.A) dns.query.send_udp(sender, q, listener.getsockname()) expiration = time.time() + 2 (q, _, addr) = dns.query.receive_udp(listener, expiration=expiration) self.assertEqual(addr, sender.getsockname()) # for brevity _d_and_s = dns.query._destination_and_source class DestinationAndSourceTests(unittest.TestCase): def test_af_inferred_from_where(self): (af, d, s) = _d_and_s("1.2.3.4", 53, None, 0) self.assertEqual(af, socket.AF_INET) def test_af_inferred_from_where(self): (af, d, s) = _d_and_s("1::2", 53, None, 0) self.assertEqual(af, socket.AF_INET6) def test_af_inferred_from_source(self): (af, d, s) = _d_and_s("https://example/dns-query", 443, "1.2.3.4", 0, False) self.assertEqual(af, socket.AF_INET) def test_af_mismatch(self): def bad(): (af, d, s) = _d_and_s("1::2", 53, "1.2.3.4", 0) self.assertRaises(ValueError, bad) def test_source_port_but_no_af_inferred(self): def bad(): (af, d, s) = _d_and_s("https://example/dns-query", 443, None, 12345, False) self.assertRaises(ValueError, bad) def test_where_must_be_an_address(self): def bad(): (af, d, s) = _d_and_s("not a valid address", 53, "1.2.3.4", 0) self.assertRaises(ValueError, bad) def test_destination_is_none_of_where_url(self): (af, d, s) = _d_and_s("https://example/dns-query", 443, None, 0, False) self.assertEqual(d, None) def test_v4_wildcard_source_set(self): (af, d, s) = _d_and_s("1.2.3.4", 53, None, 12345) self.assertEqual(s, ("0.0.0.0", 12345)) def test_v6_wildcard_source_set(self): (af, d, s) = _d_and_s("1::2", 53, None, 12345) self.assertEqual(s, ("::", 12345, 0, 0)) class AddressesEqualTestCase(unittest.TestCase): def test_v4(self): self.assertTrue( dns.query._addresses_equal( socket.AF_INET, ("10.0.0.1", 53), ("10.0.0.1", 53) ) ) self.assertFalse( dns.query._addresses_equal( socket.AF_INET, ("10.0.0.1", 53), ("10.0.0.2", 53) ) ) def test_v6(self): self.assertTrue( dns.query._addresses_equal( socket.AF_INET6, ("1::1", 53), ("0001:0000::1", 53) ) ) self.assertFalse( dns.query._addresses_equal(socket.AF_INET6, ("::1", 53), ("::2", 53)) ) def test_mixed(self): self.assertFalse( dns.query._addresses_equal(socket.AF_INET, ("10.0.0.1", 53), ("::2", 53)) ) axfr_zone = """ $TTL 300 @ SOA ns1 root 1 7200 900 1209600 86400 @ NS ns1 @ NS ns2 ns1 A 10.0.0.1 ns2 A 10.0.0.1 """ class AXFRNanoNameserver(Server): def handle(self, request): self.zone = dns.zone.from_text(axfr_zone, origin=self.origin) self.origin = self.zone.origin items = [] soa = self.zone.find_rrset(dns.name.empty, dns.rdatatype.SOA) response = dns.message.make_response(request.message) response.flags |= dns.flags.AA response.answer.append(soa) items.append(response) response = dns.message.make_response(request.message) response.question = [] response.flags |= dns.flags.AA for name, rdataset in self.zone.iterate_rdatasets(): if rdataset.rdtype == dns.rdatatype.SOA and name == dns.name.empty: continue rrset = dns.rrset.RRset( name, rdataset.rdclass, rdataset.rdtype, rdataset.covers ) rrset.update(rdataset) response.answer.append(rrset) items.append(response) response = dns.message.make_response(request.message) response.question = [] response.flags |= dns.flags.AA response.answer.append(soa) items.append(response) return items ixfr_message = """id 12345 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN IXFR ;ANSWER example. 300 IN SOA ns1.example. root.example. 4 7200 900 1209600 86400 example. 300 IN SOA ns1.example. root.example. 2 7200 900 1209600 86400 deleted.example. 300 IN A 10.0.0.1 changed.example. 300 IN A 10.0.0.2 example. 300 IN SOA ns1.example. root.example. 3 7200 900 1209600 86400 changed.example. 300 IN A 10.0.0.4 added.example. 300 IN A 10.0.0.3 example. 300 SOA ns1.example. root.example. 3 7200 900 1209600 86400 example. 300 IN SOA ns1.example. root.example. 4 7200 900 1209600 86400 added2.example. 300 IN A 10.0.0.5 example. 300 IN SOA ns1.example. root.example. 4 7200 900 1209600 86400 """ ixfr_trailing_junk = ixfr_message + "junk.example. 300 IN A 10.0.0.6" ixfr_up_to_date_message = """id 12345 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN IXFR ;ANSWER example. 300 IN SOA ns1.example. root.example. 2 7200 900 1209600 86400 """ axfr_trailing_junk = """id 12345 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN AXFR ;ANSWER example. 300 IN SOA ns1.example. root.example. 3 7200 900 1209600 86400 added.example. 300 IN A 10.0.0.3 added2.example. 300 IN A 10.0.0.5 changed.example. 300 IN A 10.0.0.4 example. 300 IN SOA ns1.example. root.example. 3 7200 900 1209600 86400 junk.example. 300 IN A 10.0.0.6 """ class IXFRNanoNameserver(Server): def __init__(self, response_text): super().__init__() self.response_text = response_text def handle(self, request): try: r = dns.message.from_text(self.response_text, one_rr_per_rrset=True) r.id = request.message.id return r except Exception: pass @unittest.skipIf(not _nanonameserver_available, "nanonameserver required") class XfrTests(unittest.TestCase): def test_axfr(self): expected = dns.zone.from_text(axfr_zone, origin="example") with AXFRNanoNameserver(origin="example") as ns: xfr = dns.query.xfr(ns.tcp_address[0], "example", port=ns.tcp_address[1]) zone = dns.zone.from_xfr(xfr) self.assertEqual(zone, expected) def test_axfr_tsig(self): expected = dns.zone.from_text(axfr_zone, origin="example") with AXFRNanoNameserver(origin="example", keyring=keyring) as ns: xfr = dns.query.xfr( ns.tcp_address[0], "example", port=ns.tcp_address[1], keyring=keyring, keyname="name", ) zone = dns.zone.from_xfr(xfr) self.assertEqual(zone, expected) def test_axfr_root_tsig(self): expected = dns.zone.from_text(axfr_zone, origin=".") with AXFRNanoNameserver(origin=".", keyring=keyring) as ns: xfr = dns.query.xfr( ns.tcp_address[0], ".", port=ns.tcp_address[1], keyring=keyring, keyname="name", ) zone = dns.zone.from_xfr(xfr) self.assertEqual(zone, expected) def test_axfr_udp(self): def bad(): with AXFRNanoNameserver(origin="example") as ns: xfr = dns.query.xfr( ns.udp_address[0], "example", port=ns.udp_address[1], use_udp=True ) l = list(xfr) self.assertRaises(ValueError, bad) def test_axfr_bad_rcode(self): def bad(): # We just use Server here as by default it will refuse. with Server() as ns: xfr = dns.query.xfr( ns.tcp_address[0], "example", port=ns.tcp_address[1] ) l = list(xfr) self.assertRaises(dns.query.TransferError, bad) def test_axfr_trailing_junk(self): # we use the IXFR server here as it returns messages def bad(): with IXFRNanoNameserver(axfr_trailing_junk) as ns: xfr = dns.query.xfr( ns.tcp_address[0], "example", dns.rdatatype.AXFR, port=ns.tcp_address[1], ) l = list(xfr) self.assertRaises(dns.exception.FormError, bad) def test_ixfr_tcp(self): with IXFRNanoNameserver(ixfr_message) as ns: xfr = dns.query.xfr( ns.tcp_address[0], "example", dns.rdatatype.IXFR, port=ns.tcp_address[1], serial=2, relativize=False, ) l = list(xfr) self.assertEqual(len(l), 1) expected = dns.message.from_text(ixfr_message, one_rr_per_rrset=True) expected.id = l[0].id self.assertEqual(l[0], expected) def test_ixfr_udp(self): with IXFRNanoNameserver(ixfr_message) as ns: xfr = dns.query.xfr( ns.udp_address[0], "example", dns.rdatatype.IXFR, port=ns.udp_address[1], serial=2, relativize=False, use_udp=True, ) l = list(xfr) self.assertEqual(len(l), 1) expected = dns.message.from_text(ixfr_message, one_rr_per_rrset=True) expected.id = l[0].id self.assertEqual(l[0], expected) def test_ixfr_up_to_date(self): with IXFRNanoNameserver(ixfr_up_to_date_message) as ns: xfr = dns.query.xfr( ns.tcp_address[0], "example", dns.rdatatype.IXFR, port=ns.tcp_address[1], serial=2, relativize=False, ) l = list(xfr) self.assertEqual(len(l), 1) expected = dns.message.from_text( ixfr_up_to_date_message, one_rr_per_rrset=True ) expected.id = l[0].id self.assertEqual(l[0], expected) def test_ixfr_trailing_junk(self): def bad(): with IXFRNanoNameserver(ixfr_trailing_junk) as ns: xfr = dns.query.xfr( ns.tcp_address[0], "example", dns.rdatatype.IXFR, port=ns.tcp_address[1], serial=2, relativize=False, ) l = list(xfr) self.assertRaises(dns.exception.FormError, bad) def test_ixfr_base_serial_mismatch(self): def bad(): with IXFRNanoNameserver(ixfr_message) as ns: xfr = dns.query.xfr( ns.tcp_address[0], "example", dns.rdatatype.IXFR, port=ns.tcp_address[1], serial=1, relativize=False, ) l = list(xfr) self.assertRaises(dns.exception.FormError, bad) class TSIGNanoNameserver(Server): def handle(self, request): response = dns.message.make_response(request.message) response.set_rcode(dns.rcode.REFUSED) response.flags |= dns.flags.RA try: if request.qtype == dns.rdatatype.A and request.qclass == dns.rdataclass.IN: rrs = dns.rrset.from_text(request.qname, 300, "IN", "A", "1.2.3.4") response.answer.append(rrs) response.set_rcode(dns.rcode.NOERROR) response.flags |= dns.flags.AA except Exception: pass return response @unittest.skipIf(not _nanonameserver_available, "nanonameserver required") class TsigTests(unittest.TestCase): def test_tsig(self): with TSIGNanoNameserver(keyring=keyring) as ns: qname = dns.name.from_text("example.com") q = dns.message.make_query(qname, "A") q.use_tsig(keyring=keyring, keyname="name") response = dns.query.udp(q, ns.udp_address[0], port=ns.udp_address[1]) self.assertTrue(response.had_tsig) rrs = response.get_rrset( response.answer, qname, dns.rdataclass.IN, dns.rdatatype.A ) self.assertTrue(rrs is not None) seen = set([rdata.address for rdata in rrs]) self.assertTrue("1.2.3.4" in seen) @unittest.skipIf(sys.platform == "win32", "low level tests do not work on win32") class LowLevelWaitTests(unittest.TestCase): def test_wait_for(self): try: (l, r) = socket.socketpair() # already expired with self.assertRaises(dns.exception.Timeout): dns.query._wait_for(l, True, True, True, 0) # simple timeout with self.assertRaises(dns.exception.Timeout): dns.query._wait_for(l, False, False, False, time.time() + 0.05) # writable no timeout (not hanging is passing) dns.query._wait_for(l, False, True, False, None) finally: l.close() r.close() class MiscTests(unittest.TestCase): def test_matches_destination(self): self.assertTrue( dns.query._matches_destination( socket.AF_INET, ("10.0.0.1", 1234), ("10.0.0.1", 1234), True ) ) self.assertTrue( dns.query._matches_destination( socket.AF_INET6, ("1::2", 1234), ("0001::2", 1234), True ) ) self.assertTrue( dns.query._matches_destination( socket.AF_INET, ("10.0.0.1", 1234), None, True ) ) self.assertFalse( dns.query._matches_destination( socket.AF_INET, ("10.0.0.1", 1234), ("10.0.0.2", 1234), True ) ) self.assertFalse( dns.query._matches_destination( socket.AF_INET, ("10.0.0.1", 1234), ("10.0.0.1", 1235), True ) ) with self.assertRaises(dns.query.UnexpectedSource): dns.query._matches_destination( socket.AF_INET, ("10.0.0.1", 1234), ("10.0.0.1", 1235), False ) @contextlib.contextmanager def mock_udp_recv(wire1, from1, wire2, from2): saved = dns.query._udp_recv first_time = True def mock(sock, max_size, expiration): nonlocal first_time if first_time: first_time = False return wire1, from1 else: return wire2, from2 try: dns.query._udp_recv = mock yield None finally: dns.query._udp_recv = saved class MockSock: def __init__(self): self.family = socket.AF_INET def sendto(self, data, where): return len(data) class IgnoreErrors(unittest.TestCase): def setUp(self): self.q = dns.message.make_query("example.", "A") self.good_r = dns.message.make_response(self.q) self.good_r.set_rcode(dns.rcode.NXDOMAIN) self.good_r_wire = self.good_r.to_wire() def mock_receive( self, wire1, from1, wire2, from2, ignore_unexpected=True, ignore_errors=True, raise_on_truncation=False, good_r=None, ): if good_r is None: good_r = self.good_r s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: with mock_udp_recv(wire1, from1, wire2, from2): (r, when) = dns.query.receive_udp( s, ("127.0.0.1", 53), time.time() + 2, ignore_unexpected=ignore_unexpected, ignore_errors=ignore_errors, raise_on_truncation=raise_on_truncation, query=self.q, ) self.assertEqual(r, good_r) finally: s.close() def test_good_mock(self): self.mock_receive(self.good_r_wire, ("127.0.0.1", 53), None, None) def test_bad_address(self): self.mock_receive( self.good_r_wire, ("127.0.0.2", 53), self.good_r_wire, ("127.0.0.1", 53) ) def test_bad_address_not_ignored(self): def bad(): self.mock_receive( self.good_r_wire, ("127.0.0.2", 53), self.good_r_wire, ("127.0.0.1", 53), ignore_unexpected=False, ) self.assertRaises(dns.query.UnexpectedSource, bad) def test_bad_id(self): bad_r = dns.message.make_response(self.q) bad_r.id += 1 bad_r_wire = bad_r.to_wire() self.mock_receive( bad_r_wire, ("127.0.0.1", 53), self.good_r_wire, ("127.0.0.1", 53) ) def test_bad_id_not_ignored(self): bad_r = dns.message.make_response(self.q) bad_r.id += 1 bad_r_wire = bad_r.to_wire() def bad(): (r, wire) = self.mock_receive( bad_r_wire, ("127.0.0.1", 53), self.good_r_wire, ("127.0.0.1", 53), ignore_errors=False, ) self.assertRaises(AssertionError, bad) def test_not_response_not_ignored_udp_level(self): def bad(): bad_r = dns.message.make_response(self.q) bad_r.id += 1 bad_r_wire = bad_r.to_wire() with mock_udp_recv( bad_r_wire, ("127.0.0.1", 53), self.good_r_wire, ("127.0.0.1", 53) ): s = MockSock() dns.query.udp(self.good_r, "127.0.0.1", sock=s) self.assertRaises(dns.query.BadResponse, bad) def test_bad_wire(self): bad_r = dns.message.make_response(self.q) bad_r.id += 1 bad_r_wire = bad_r.to_wire() self.mock_receive( bad_r_wire[:10], ("127.0.0.1", 53), self.good_r_wire, ("127.0.0.1", 53) ) def test_good_wire_with_truncation_flag_and_no_truncation_raise(self): tc_r = dns.message.make_response(self.q) tc_r.flags |= dns.flags.TC tc_r_wire = tc_r.to_wire() self.mock_receive(tc_r_wire, ("127.0.0.1", 53), None, None, good_r=tc_r) def test_good_wire_with_truncation_flag_and_truncation_raise(self): def good(): tc_r = dns.message.make_response(self.q) tc_r.flags |= dns.flags.TC tc_r_wire = tc_r.to_wire() self.mock_receive( tc_r_wire, ("127.0.0.1", 53), None, None, raise_on_truncation=True ) self.assertRaises(dns.message.Truncated, good) def test_wrong_id_wire_with_truncation_flag_and_no_truncation_raise(self): bad_r = dns.message.make_response(self.q) bad_r.id += 1 bad_r.flags |= dns.flags.TC bad_r_wire = bad_r.to_wire() self.mock_receive( bad_r_wire, ("127.0.0.1", 53), self.good_r_wire, ("127.0.0.1", 53) ) def test_wrong_id_wire_with_truncation_flag_and_truncation_raise(self): bad_r = dns.message.make_response(self.q) bad_r.id += 1 bad_r.flags |= dns.flags.TC bad_r_wire = bad_r.to_wire() self.mock_receive( bad_r_wire, ("127.0.0.1", 53), self.good_r_wire, ("127.0.0.1", 53), raise_on_truncation=True, ) def test_bad_wire_not_ignored(self): bad_r = dns.message.make_response(self.q) bad_r.id += 1 bad_r_wire = bad_r.to_wire() def bad(): self.mock_receive( bad_r_wire[:10], ("127.0.0.1", 53), self.good_r_wire, ("127.0.0.1", 53), ignore_errors=False, ) self.assertRaises(dns.message.ShortHeader, bad) def test_trailing_wire(self): wire = self.good_r_wire + b"abcd" self.mock_receive(wire, ("127.0.0.1", 53), self.good_r_wire, ("127.0.0.1", 53)) def test_trailing_wire_not_ignored(self): wire = self.good_r_wire + b"abcd" def bad(): self.mock_receive( wire, ("127.0.0.1", 53), self.good_r_wire, ("127.0.0.1", 53), ignore_errors=False, ) self.assertRaises(dns.message.TrailingJunk, bad) dnspython-2.7.0/tests/test_rdata.py0000644000000000000000000013040313615410400014335 0ustar00# -*- coding: utf-8 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license # Copyright (C) 2006, 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 io import operator import pickle import struct import unittest import dns.exception import dns.name import dns.rdata import dns.rdataclass import dns.rdataset import dns.rdatatype import dns.rdtypes.ANY.RRSIG import dns.rdtypes.IN.APL import dns.rdtypes.util import dns.tokenizer import dns.ttl import dns.wire import tests.md_module import tests.stxt_module import tests.ttxt_module from dns.rdtypes.ANY.GPOS import GPOS from dns.rdtypes.ANY.LOC import LOC from dns.rdtypes.ANY.OPT import OPT from tests.util import here class RdataTestCase(unittest.TestCase): def test_str(self): rdata = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "1.2.3.4") self.assertEqual(rdata.address, "1.2.3.4") def test_unicode(self): rdata = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "1.2.3.4") self.assertEqual(rdata.address, "1.2.3.4") def test_module_registration(self): TTXT = 64001 dns.rdata.register_type(tests.ttxt_module, TTXT, "TTXT") rdata = dns.rdata.from_text(dns.rdataclass.IN, TTXT, "hello world") self.assertEqual(rdata.strings, (b"hello", b"world")) self.assertEqual(dns.rdatatype.to_text(TTXT), "TTXT") self.assertEqual(dns.rdatatype.from_text("TTXT"), TTXT) self.assertEqual(dns.rdatatype.RdataType.make("TTXT"), TTXT) self.assertEqual(dns.rdatatype.from_text("ttxt"), TTXT) self.assertEqual(dns.rdatatype.RdataType.make("ttxt"), TTXT) def test_module_reregistration(self): def bad(): TTXTTWO = dns.rdatatype.TXT dns.rdata.register_type(tests.ttxt_module, TTXTTWO, "TTXTTWO") self.assertRaises(dns.rdata.RdatatypeExists, bad) def test_module_registration_singleton(self): STXT = 64002 dns.rdata.register_type(tests.stxt_module, STXT, "STXT", is_singleton=True) rdata1 = dns.rdata.from_text(dns.rdataclass.IN, STXT, "hello") rdata2 = dns.rdata.from_text(dns.rdataclass.IN, STXT, "world") rdataset = dns.rdataset.from_rdata(3600, rdata1, rdata2) self.assertEqual(len(rdataset), 1) self.assertEqual(rdataset[0].strings, (b"world",)) def test_replace(self): a1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "1.2.3.4") a2 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "2.3.4.5") self.assertEqual(a1.replace(address="2.3.4.5"), a2) mx = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, "10 foo.example") name = dns.name.from_text("bar.example") self.assertEqual(mx.replace(preference=20).preference, 20) self.assertEqual(mx.replace(preference=20).exchange, mx.exchange) self.assertEqual(mx.replace(exchange=name).exchange, name) self.assertEqual(mx.replace(exchange=name).preference, mx.preference) for invalid_parameter in ("rdclass", "rdtype", "foo", "__class__"): with self.assertRaises(AttributeError): mx.replace(invalid_parameter=1) def test_invalid_replace(self): a1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "1.2.3.4") with self.assertRaises(dns.exception.SyntaxError): a1.replace(address="bogus") def test_replace_comment(self): a1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "1.2.3.4 ;foo") self.assertEqual(a1.rdcomment, "foo") a2 = a1.replace(rdcomment="bar") self.assertEqual(a1, a2) self.assertEqual(a1.rdcomment, "foo") self.assertEqual(a2.rdcomment, "bar") def test_no_replace_class_or_type(self): a1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "1.2.3.4") with self.assertRaises(AttributeError): a1.replace(rdclass=255) with self.assertRaises(AttributeError): a1.replace(rdtype=2) def test_to_generic(self): a = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "1.2.3.4") self.assertEqual(str(a.to_generic()), r"\# 4 01020304") mx = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, "10 foo.") self.assertEqual(str(mx.to_generic()), r"\# 7 000a03666f6f00") origin = dns.name.from_text("example") ns = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.NS, "foo.example.", relativize_to=origin ) self.assertEqual( str(ns.to_generic(origin=origin)), r"\# 13 03666f6f076578616d706c6500" ) def test_txt_unicode(self): # TXT records are not defined for Unicode, but if we get # Unicode we should convert it to UTF-8 to preserve meaning as # best we can. Note that it when the TXT record is sent # to_text(), it does NOT convert embedded UTF-8 back to # Unicode; it's just treated as binary TXT data. Probably # there should be a TXT-like record with an encoding field. rdata = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.TXT, '"foo\u200bbar"' ) self.assertEqual(str(rdata), '"foo\\226\\128\\139bar"') # We used to encode UTF-8 in UTF-8 because we processed # escapes in quoted strings immediately. This meant that the # \\226 below would be inserted as Unicode code point 226, and # then when we did to_text, we would UTF-8 encode that code # point, emitting \\195\\162 instead of \\226, and thus # from_text followed by to_text was not the equal to the # original input like it ought to be. rdata = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.TXT, '"foo\\226\\128\\139bar"' ) self.assertEqual(str(rdata), '"foo\\226\\128\\139bar"') # Our fix for TXT-like records uses a new tokenizer method, # unescape_to_bytes(), which converts Unicode to UTF-8 only # once. rdata = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.TXT, '"foo\u200b\\123bar"' ) self.assertEqual(str(rdata), '"foo\\226\\128\\139{bar"') def test_unicode_idna2003_in_rdata(self): rdata = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.NS, "Königsgäßchen" ) self.assertEqual(str(rdata.target), "xn--knigsgsschen-lcb0w") @unittest.skipUnless( dns.name.have_idna_2008, "Python idna cannot be imported; no IDNA2008" ) def test_unicode_idna2008_in_rdata(self): rdata = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.NS, "Königsgäßchen", idna_codec=dns.name.IDNA_2008, ) self.assertEqual(str(rdata.target), "xn--knigsgchen-b4a3dun") def test_digestable_downcasing(self): # Make sure all the types listed in RFC 4034 section 6.2 are # downcased properly, except for: # # types we don't implement: MD, MF, MB, MG, MR, MINFO, SIG, # NXT, A6 # # types that don't have names: HINFO # # NSEC3, whose downcasing was removed by RFC 6840 section 5.1 # cases = [ ("SOA", "NAME NAME 1 2 3 4 5"), ("AFSDB", "0 NAME"), ("CNAME", "NAME"), ("DNAME", "NAME"), ("KX", "10 NAME"), ("MX", "10 NAME"), ("NS", "NAME"), ("NAPTR", "0 0 a B c NAME"), ("PTR", "NAME"), ("PX", "65535 NAME NAME"), ("RP", "NAME NAME"), ("RT", "0 NAME"), ("SRV", "0 0 0 NAME"), ("RRSIG", "A 1 3 3600 20200701000000 20200601000000 1 NAME Ym9ndXM="), ] for rdtype, text in cases: upper_origin = dns.name.from_text("EXAMPLE") lower_origin = dns.name.from_text("example") canonical_text = text.replace("NAME", "name") rdata = dns.rdata.from_text( dns.rdataclass.IN, rdtype, text, origin=upper_origin, relativize=False ) canonical_rdata = dns.rdata.from_text( dns.rdataclass.IN, rdtype, canonical_text, origin=lower_origin, relativize=False, ) digestable_wire = rdata.to_digestable() f = io.BytesIO() canonical_rdata.to_wire(f) expected_wire = f.getvalue() self.assertEqual(digestable_wire, expected_wire) def test_digestable_no_downcasing(self): # Make sure that currently known types with domain names that # are NOT supposed to be downcased when canonicalized are # handled properly. # cases = [ ("HIP", "2 200100107B1A74DF365639CC39F1D578 Ym9ndXM= NAME name"), ("IPSECKEY", "10 3 2 NAME Ym9ndXM="), ("NSEC", "NAME A"), ] for rdtype, text in cases: origin = dns.name.from_text("example") rdata = dns.rdata.from_text( dns.rdataclass.IN, rdtype, text, origin=origin, relativize=False ) digestable_wire = rdata.to_digestable(origin) expected_wire = rdata.to_wire(origin=origin) self.assertEqual(digestable_wire, expected_wire) def test_basic_relations(self): r1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1") r2 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.2") self.assertTrue(r1 == r1) self.assertTrue(r1 != r2) self.assertTrue(r1 < r2) self.assertTrue(r1 <= r2) self.assertTrue(r2 > r1) self.assertTrue(r2 >= r1) def test_incompatible_relations(self): r1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1") r2 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.AAAA, "::1") for oper in [operator.lt, operator.le, operator.ge, operator.gt]: self.assertRaises(TypeError, lambda: oper(r1, r2)) self.assertFalse(r1 == r2) self.assertTrue(r1 != r2) def test_immutability(self): def bad1(): r = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1") r.address = "10.0.0.2" self.assertRaises(TypeError, bad1) def bad2(): r = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1") del r.address self.assertRaises(TypeError, bad2) def test_pickle(self): r1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1") p = pickle.dumps(r1) r2 = pickle.loads(p) self.assertEqual(r1, r2) # Pickle something with a longer inheritance chain r3 = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.MX, "10 mail.example." ) p = pickle.dumps(r3) r4 = pickle.loads(p) self.assertEqual(r3, r4) def test_AFSDB_properties(self): rd = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.AFSDB, "0 afsdb.example." ) self.assertEqual(rd.preference, rd.subtype) self.assertEqual(rd.exchange, rd.hostname) def equal_loc(self, a, b): rda = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.LOC, a) rdb = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.LOC, b) self.assertEqual(rda, rdb) def test_misc_good_LOC_text(self): # test just degrees self.equal_loc( "60 N 24 39 0.000 E 10.00m 20m 2000m 20m", "60 0 0 N 24 39 0.000 E 10.00m 20m 2000m 20m", ) self.equal_loc( "60 0 0 N 24 E 10.00m 20m 2000m 20m", "60 0 0 N 24 0 0 E 10.00m 20m 2000m 20m", ) # test variable length latitude self.equal_loc( "60 9 0.510 N 24 39 0.000 E 10.00m 20m 2000m 20m", "60 9 0.51 N 24 39 0.000 E 10.00m 20m 2000m 20m", ) self.equal_loc( "60 9 0.500 N 24 39 0.000 E 10.00m 20m 2000m 20m", "60 9 0.5 N 24 39 0.000 E 10.00m 20m 2000m 20m", ) self.equal_loc( "60 9 1.000 N 24 39 0.000 E 10.00m 20m 2000m 20m", "60 9 1 N 24 39 0.000 E 10.00m 20m 2000m 20m", ) # test variable length longtitude self.equal_loc( "60 9 0.000 N 24 39 0.510 E 10.00m 20m 2000m 20m", "60 9 0.000 N 24 39 0.51 E 10.00m 20m 2000m 20m", ) self.equal_loc( "60 9 0.000 N 24 39 0.500 E 10.00m 20m 2000m 20m", "60 9 0.000 N 24 39 0.5 E 10.00m 20m 2000m 20m", ) self.equal_loc( "60 9 0.000 N 24 39 1.000 E 10.00m 20m 2000m 20m", "60 9 0.000 N 24 39 1 E 10.00m 20m 2000m 20m", ) # test siz, hp, vp defaults self.equal_loc( "60 9 0.510 N 24 39 0.000 E 10.00m", "60 9 0.51 N 24 39 0.000 E 10.00m 1m 10000m 10m", ) self.equal_loc( "60 9 0.510 N 24 39 0.000 E 10.00m 2m", "60 9 0.51 N 24 39 0.000 E 10.00m 2m 10000m 10m", ) self.equal_loc( "60 9 0.510 N 24 39 0.000 E 10.00m 2m 2000m", "60 9 0.51 N 24 39 0.000 E 10.00m 2m 2000m 10m", ) # test siz, hp, vp optional units self.equal_loc( "60 9 0.510 N 24 39 0.000 E 1m 20m 2000m 20m", "60 9 0.51 N 24 39 0.000 E 1 20 2000 20", ) def test_LOC_to_text_SW_hemispheres(self): # As an extra, we test int->float conversion in the constructor loc = LOC(dns.rdataclass.IN, dns.rdatatype.LOC, -60, -24, 1) text = "60 0 0.000 S 24 0 0.000 W 0.01m" self.assertEqual(loc.to_text(), text) def test_zero_size(self): # This is to exercise the 0 path in _exponent_of. loc = dns.rdata.from_text("in", "loc", "60 S 24 W 1 0") self.assertEqual(loc.size, 0.0) def test_bad_LOC_text(self): bad_locs = [ "60 9 a.000 N 24 39 0.000 E 10.00m 20m 2000m 20m", "60 9 60.000 N 24 39 0.000 E 10.00m 20m 2000m 20m", "60 9 0.00a N 24 39 0.000 E 10.00m 20m 2000m 20m", "60 9 0.0001 N 24 39 0.000 E 10.00m 20m 2000m 20m", "60 9 0.000 Z 24 39 0.000 E 10.00m 20m 2000m 20m", "91 9 0.000 N 24 39 0.000 E 10.00m 20m 2000m 20m", "60 60 0.000 N 24 39 0.000 E 10.00m 20m 2000m 20m", "60 9 0.000 N 24 39 a.000 E 10.00m 20m 2000m 20m", "60 9 0.000 N 24 39 60.000 E 10.00m 20m 2000m 20m", "60 9 0.000 N 24 39 0.00a E 10.00m 20m 2000m 20m", "60 9 0.000 N 24 39 0.0001 E 10.00m 20m 2000m 20m", "60 9 0.000 N 24 39 0.000 Z 10.00m 20m 2000m 20m", "60 9 0.000 N 181 39 0.000 E 10.00m 20m 2000m 20m", "60 9 0.000 N 24 60 0.000 E 10.00m 20m 2000m 20m", "60 9 0.000 N 24 39 0.000 E 10.00m 100000000m 2000m 20m", "60 9 0.000 N 24 39 0.000 E 10.00m 20m 100000000m 20m", "60 9 0.000 N 24 39 0.000 E 10.00m 20m 20m 100000000m", ] for loc in bad_locs: with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.LOC, loc) def test_bad_LOC_wire(self): bad_locs = [ (0, 0, 0, 0x934FD901, 0x80000000, 100), (0, 0, 0, 0x6CB026FF, 0x80000000, 100), (0, 0, 0, 0x80000000, 0xA69FB201, 100), (0, 0, 0, 0x80000000, 0x59604DFF, 100), (0xA0, 0, 0, 0x80000000, 0x80000000, 100), (0x0A, 0, 0, 0x80000000, 0x80000000, 100), (0, 0xA0, 0, 0x80000000, 0x80000000, 100), (0, 0x0A, 0, 0x80000000, 0x80000000, 100), (0, 0, 0xA0, 0x80000000, 0x80000000, 100), (0, 0, 0x0A, 0x80000000, 0x80000000, 100), ] for t in bad_locs: with self.assertRaises(dns.exception.FormError): wire = struct.pack("!BBBBIII", 0, t[0], t[1], t[2], t[3], t[4], t[5]) dns.rdata.from_wire( dns.rdataclass.IN, dns.rdatatype.LOC, wire, 0, len(wire) ) with self.assertRaises(dns.exception.FormError): wire = struct.pack("!BBBBIII", 1, 0, 0, 0, 0, 0, 0) dns.rdata.from_wire( dns.rdataclass.IN, dns.rdatatype.LOC, wire, 0, len(wire) ) def equal_wks(self, a, b): rda = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.WKS, a) rdb = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.WKS, b) self.assertEqual(rda, rdb) def test_misc_good_WKS_text(self): self.equal_wks("10.0.0.1 tcp ( http )", "10.0.0.1 6 ( 80 )") self.equal_wks("10.0.0.1 udp ( domain )", "10.0.0.1 17 ( 53 )") def test_misc_bad_WKS_text(self): try: dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.WKS, "10.0.0.1 132 ( domain )" ) self.assertTrue(False) # should not happen except dns.exception.SyntaxError as e: self.assertIsInstance(e.__cause__, NotImplementedError) def test_GPOS_float_converters(self): rd = dns.rdata.from_text("in", "gpos", "49 0 0") self.assertEqual(rd.float_latitude, 49.0) self.assertEqual(rd.float_longitude, 0.0) self.assertEqual(rd.float_altitude, 0.0) def test_GPOS_constructor_conversion(self): rd = GPOS(dns.rdataclass.IN, dns.rdatatype.GPOS, 49.0, 0.0, 0.0) self.assertEqual(rd.float_latitude, 49.0) self.assertEqual(rd.float_longitude, 0.0) self.assertEqual(rd.float_altitude, 0.0) rd = GPOS(dns.rdataclass.IN, dns.rdatatype.GPOS, 49, 0, 0) self.assertEqual(rd.float_latitude, 49.0) self.assertEqual(rd.float_longitude, 0.0) self.assertEqual(rd.float_altitude, 0.0) def test_bad_GPOS_text(self): bad_gpos = [ '"-" "116.8652" "250"', '"+" "116.8652" "250"', '"" "116.8652" "250"', '"." "116.8652" "250"', '".a" "116.8652" "250"', '"a." "116.8652" "250"', '"a.a" "116.8652" "250"', # We don't need to test all the bad permutations again # but we do want to test that badness is detected # in the other strings '"0" "a" "250"', '"0" "0" "a"', # finally test bounds '"90.1" "0" "0"', '"-90.1" "0" "0"', '"0" "180.1" "0"', '"0" "-180.1" "0"', ] for gpos in bad_gpos: with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.GPOS, gpos) def test_bad_GPOS_wire(self): bad_gpos = [ b"\x01", b"\x01\x31\x01", b"\x01\x31\x01\x31\x01", ] for wire in bad_gpos: self.assertRaises( dns.exception.FormError, lambda: dns.rdata.from_wire( dns.rdataclass.IN, dns.rdatatype.GPOS, wire, 0, len(wire) ), ) def test_chaos(self): # avoid red spot on our coverage :) r1 = dns.rdata.from_text(dns.rdataclass.CH, dns.rdatatype.A, "chaos. 12345") w = r1.to_wire() r2 = dns.rdata.from_wire(dns.rdataclass.CH, dns.rdatatype.A, w, 0, len(w)) self.assertEqual(r1, r2) self.assertEqual(r1.domain, dns.name.from_text("chaos")) # the address input is octal self.assertEqual(r1.address, 0o12345) self.assertEqual(r1.to_text(), "chaos. 12345") def test_opt_repr(self): opt = OPT(4096, dns.rdatatype.OPT, ()) self.assertEqual(repr(opt), "") def test_opt_short_lengths(self): with self.assertRaises(dns.exception.FormError): parser = dns.wire.Parser(bytes.fromhex("f00102")) OPT.from_wire_parser(4096, dns.rdatatype.OPT, parser) with self.assertRaises(dns.exception.FormError): parser = dns.wire.Parser(bytes.fromhex("f00100030000")) OPT.from_wire_parser(4096, dns.rdatatype.OPT, parser) def test_from_wire_parser(self): wire = bytes.fromhex("01020304") rdata = dns.rdata.from_wire("in", "a", wire, 0, 4) self.assertEqual(rdata, dns.rdata.from_text("in", "a", "1.2.3.4")) def test_unpickle(self): expected_mx = dns.rdata.from_text("in", "mx", "10 mx.example.") with open(here("mx-2-0.pickle"), "rb") as f: mx = pickle.load(f) self.assertEqual(mx, expected_mx) self.assertIsNone(mx.rdcomment) def test_escaped_newline_in_quoted_string(self): rd = dns.rdata.from_text("in", "txt", '"foo\\\nbar"') self.assertEqual(rd.strings, (b"foo\nbar",)) self.assertEqual(rd.to_text(), '"foo\\010bar"') def test_escaped_newline_in_nonquoted_string(self): with self.assertRaises(dns.exception.UnexpectedEnd): dns.rdata.from_text("in", "txt", "foo\\\nbar") def test_wordbreak(self): text = b"abcdefgh" self.assertEqual(dns.rdata._wordbreak(text, 4), "abcd efgh") self.assertEqual(dns.rdata._wordbreak(text, 0), "abcdefgh") def test_escapify(self): self.assertEqual(dns.rdata._escapify("abc"), "abc") self.assertEqual(dns.rdata._escapify(b"abc"), "abc") self.assertEqual(dns.rdata._escapify(bytearray(b"abc")), "abc") self.assertEqual(dns.rdata._escapify(b'ab"c'), 'ab\\"c') self.assertEqual(dns.rdata._escapify(b"ab\\c"), "ab\\\\c") self.assertEqual(dns.rdata._escapify(b"ab\x01c"), "ab\\001c") def test_truncate_bitmap(self): self.assertEqual(dns.rdata._truncate_bitmap(b"\x00\x01\x00\x00"), b"\x00\x01") self.assertEqual( dns.rdata._truncate_bitmap(b"\x00\x01\x00\x01"), b"\x00\x01\x00\x01" ) self.assertEqual(dns.rdata._truncate_bitmap(b"\x00\x00\x00\x00"), b"\x00") def test_covers_and_extended_rdatatype(self): rd = dns.rdata.from_text("in", "a", "10.0.0.1") self.assertEqual(rd.covers(), dns.rdatatype.NONE) self.assertEqual(rd.extended_rdatatype(), 0x00000001) rd = dns.rdata.from_text( "in", "rrsig", "NSEC 1 3 3600 " + "20200101000000 20030101000000 " + "2143 foo Ym9ndXM=", ) self.assertEqual(rd.covers(), dns.rdatatype.NSEC) self.assertEqual(rd.extended_rdatatype(), 0x002F002E) def test_uncomparable(self): rd = dns.rdata.from_text("in", "a", "10.0.0.1") self.assertFalse(rd == "a") self.assertTrue(rd != "a") def test_bad_generic(self): # does not start with \# with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "type45678", "# 7 000a03666f6f00") # wrong length with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "type45678", "\\# 6 000a03666f6f00") def test_empty_generic(self): dns.rdata.from_text("in", "type45678", r"\# 0") def test_covered_repr(self): text = "NSEC 1 3 3600 20190101000000 20030101000000 " + "2143 foo Ym9ndXM=" rd = dns.rdata.from_text("in", "rrsig", text) self.assertEqual(repr(rd), "") def test_registration_implementing_known_and_implemented_type(self): # Try to register an implementation at the A codepoint. with self.assertRaises(dns.rdata.RdatatypeExists): dns.rdata.register_type(None, dns.rdatatype.A, "ANYTHING") def test_registration_of_known_but_unimplmented_type(self): # Try to register an implementation at the MD codepoint dns.rdata.register_type(tests.md_module, dns.rdatatype.MD, "MD") rd = dns.rdata.from_text("in", "md", "foo.") self.assertEqual(rd.target, dns.name.from_text("foo.")) def test_CERT_with_string_type(self): rd = dns.rdata.from_text("in", "cert", "SPKI 1 PRIVATEOID Ym9ndXM=") self.assertEqual(rd.to_text(), "SPKI 1 PRIVATEOID Ym9ndXM=") def test_CERT_algorithm(self): rd = dns.rdata.from_text("in", "cert", "SPKI 1 0 Ym9ndXM=") self.assertEqual(rd.algorithm, 0) with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "cert", "SPKI 1 -1 Ym9ndXM=") with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "cert", "SPKI 1 256 Ym9ndXM=") with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "cert", "SPKI 1 BOGUS Ym9ndXM=") def test_bad_URI_text(self): # empty target with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "uri", '10 1 ""') # no target with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "uri", "10 1") def test_bad_URI_wire(self): wire = bytes.fromhex("000a0001") with self.assertRaises(dns.exception.FormError): dns.rdata.from_wire("in", "uri", wire, 0, 4) def test_bad_NSAP_text(self): # does not start with 0x with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "nsap", "0y4700") # odd hex string length with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "nsap", "0x470") def test_bad_CAA_text(self): # tag too long with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "caa", "0 " + "a" * 256 + ' "ca.example.net"') # tag not alphanumeric with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "caa", '0 a-b "ca.example.net"') def test_bad_HIP_text(self): # hit too long with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "hip", "2 " + "00" * 256 + " Ym9ndXM=") def test_bad_sigtime(self): try: dns.rdata.from_text( "in", "rrsig", "NSEC 1 3 3600 " + "202001010000000 20030101000000 " + "2143 foo Ym9ndXM=", ) self.assertTrue(False) # should not happen except dns.exception.SyntaxError as e: self.assertIsInstance(e.__cause__, dns.rdtypes.ANY.RRSIG.BadSigTime) try: dns.rdata.from_text( "in", "rrsig", "NSEC 1 3 3600 " + "20200101000000 2003010100000 " + "2143 foo Ym9ndXM=", ) self.assertTrue(False) # should not happen except dns.exception.SyntaxError as e: self.assertIsInstance(e.__cause__, dns.rdtypes.ANY.RRSIG.BadSigTime) def test_empty_TXT(self): # hit too long with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "txt", "") def test_empty_TXT_wire(self): with self.assertRaises(dns.exception.FormError): dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.TXT, b"", 0, 0) def test_too_long_TXT(self): # hit too long with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "txt", "a" * 256) def equal_smimea(self, a, b): a = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SMIMEA, a) b = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SMIMEA, b) self.assertEqual(a, b) def test_good_SMIMEA(self): self.equal_smimea("3 0 1 aabbccddeeff", "3 0 01 AABBCCDDEEFF") def test_bad_SMIMEA(self): with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.SMIMEA, "1 1 1 aGVsbG8gd29ybGQh" ) def test_bad_APLItem_address_length(self): with self.assertRaises(ValueError): # 9999 is used in as an "unknown" address family. In the unlikely # event it is ever defined, we should switch the test to another # value. dns.rdtypes.IN.APL.APLItem(9999, False, b"0xff" * 128, 255) def test_DNSKEY_chunking(self): inputs = ( # each with chunking as given by dig, unusual chunking, and no chunking # example 1 ( "257 3 13 aCoEWYBBVsP9Fek2oC8yqU8ocKmnS1iDSFZNORnQuHKtJ9Wpyz+kNryq uB78Pyk/NTEoai5bxoipVQQXzHlzyg==", "257 3 13 aCoEWYBBVsP9Fek2oC8yqU8ocK mnS1iDSFZNORnQuHKtJ9Wpyz+kNryquB78Pyk/ NTEoai5bxoipVQQXzHlzyg==", "257 3 13 aCoEWYBBVsP9Fek2oC8yqU8ocKmnS1iDSFZNORnQuHKtJ9Wpyz+kNryquB78Pyk/NTEoai5bxoipVQQXzHlzyg==", ), # example 2 ( "257 3 8 AwEAAcw5QLr0IjC0wKbGoBPQv4qmeqHy9mvL5qGQTuaG5TSrNqEAR6b/ qvxDx6my4JmEmjUPA1JeEI9YfTUieMr2UZflu7aIbZFLw0vqiYrywCGr CHXLalOrEOmrvAxLvq4vHtuTlH7JIszzYBSes8g1vle6KG7xXiP3U5Ll 96Qiu6bZ31rlMQSPB20xbqJJh6psNSrQs41QvdcXAej+K2Hl1Wd8kPri ec4AgiBEh8sk5Pp8W9ROLQ7PcbqqttFaW2m7N/Wy4qcFU13roWKDEAst bxH5CHPoBfZSbIwK4KM6BK/uDHpSPIbiOvOCW+lvu9TAiZPc0oysY6as lO7jXv16Gws=", "257 3 8 AwEAAcw5QLr0IjC0wKbGoBPQv4qmeq Hy9mvL5qGQTuaG5TSrNqEA R6b/qvxDx6my4JmEmjUPA1JeEI9Y fTUieMr2UZflu7aIbZFLw0vqiYrywCGrC HXLalOrEOmrvAxLvq4vHtuTlH7JIszzYBSes8g1vle6KG7 xXiP3U5Ll 96Qiu6bZ31rlMQSPB20xbqJJh6psNSrQs41QvdcXAej+K2Hl1Wd8kPriec4AgiBEh8sk5Pp8W9ROLQ7PcbqqttFaW2m7N/Wy4qcFU13roWKDEAst bxH5CHPoBfZSbIwK4KM6BK/uDHpSPIbiOvOCW+lvu9TAiZPc0oysY6as lO7jXv16Gws=", "257 3 8 AwEAAcw5QLr0IjC0wKbGoBPQv4qmeqHy9mvL5qGQTuaG5TSrNqEAR6b/qvxDx6my4JmEmjUPA1JeEI9YfTUieMr2UZflu7aIbZFLw0vqiYrywCGrCHXLalOrEOmrvAxLvq4vHtuTlH7JIszzYBSes8g1vle6KG7xXiP3U5Ll96Qiu6bZ31rlMQSPB20xbqJJh6psNSrQs41QvdcXAej+K2Hl1Wd8kPriec4AgiBEh8sk5Pp8W9ROLQ7PcbqqttFaW2m7N/Wy4qcFU13roWKDEAstbxH5CHPoBfZSbIwK4KM6BK/uDHpSPIbiOvOCW+lvu9TAiZPc0oysY6aslO7jXv16Gws=", ), # example 3 ( "256 3 8 AwEAAday3UX323uVzQqtOMQ7EHQYfD5Ofv4akjQGN2zY5AgB/2jmdR/+ 1PvXFqzKCAGJv4wjABEBNWLLFm7ew1hHMDZEKVL17aml0EBKI6Dsz6Mx t6n7ScvLtHaFRKaxT4i2JxiuVhKdQR9XGMiWAPQKrRM5SLG0P+2F+TLK l3D0L/cD", "256 3 8 AwEAAday3UX323uVzQqtOMQ7EHQYfD5Ofv4akjQGN2zY5 AgB/2jmdR/+1PvXFqzKCAGJv4wjABEBNWLLFm7ew1hHMDZEKVL17aml0EBKI6Dsz6Mxt6n7ScvLtHaFRKaxT4i2JxiuVhKdQR9XGMiWAPQKrRM5SLG0P+2F+ TLKl3D0L/cD", "256 3 8 AwEAAday3UX323uVzQqtOMQ7EHQYfD5Ofv4akjQGN2zY5AgB/2jmdR/+1PvXFqzKCAGJv4wjABEBNWLLFm7ew1hHMDZEKVL17aml0EBKI6Dsz6Mxt6n7ScvLtHaFRKaxT4i2JxiuVhKdQR9XGMiWAPQKrRM5SLG0P+2F+TLKl3D0L/cD", ), ) output_map = { 32: ( "257 3 13 aCoEWYBBVsP9Fek2oC8yqU8ocKmnS1iD SFZNORnQuHKtJ9Wpyz+kNryquB78Pyk/ NTEoai5bxoipVQQXzHlzyg==", "257 3 8 AwEAAcw5QLr0IjC0wKbGoBPQv4qmeqHy 9mvL5qGQTuaG5TSrNqEAR6b/qvxDx6my 4JmEmjUPA1JeEI9YfTUieMr2UZflu7aI bZFLw0vqiYrywCGrCHXLalOrEOmrvAxL vq4vHtuTlH7JIszzYBSes8g1vle6KG7x XiP3U5Ll96Qiu6bZ31rlMQSPB20xbqJJ h6psNSrQs41QvdcXAej+K2Hl1Wd8kPri ec4AgiBEh8sk5Pp8W9ROLQ7PcbqqttFa W2m7N/Wy4qcFU13roWKDEAstbxH5CHPo BfZSbIwK4KM6BK/uDHpSPIbiOvOCW+lv u9TAiZPc0oysY6aslO7jXv16Gws=", "256 3 8 AwEAAday3UX323uVzQqtOMQ7EHQYfD5O fv4akjQGN2zY5AgB/2jmdR/+1PvXFqzK CAGJv4wjABEBNWLLFm7ew1hHMDZEKVL1 7aml0EBKI6Dsz6Mxt6n7ScvLtHaFRKax T4i2JxiuVhKdQR9XGMiWAPQKrRM5SLG0 P+2F+TLKl3D0L/cD", ), 56: (t[0] for t in inputs), 0: (t[0][:12] + t[0][12:].replace(" ", "") for t in inputs), } for chunksize, outputs in output_map.items(): for input, output in zip(inputs, outputs): for input_variation in input: rr = dns.rdata.from_text("IN", "DNSKEY", input_variation) new_text = rr.to_text(chunksize=chunksize) self.assertEqual(output, new_text) def test_simple_ordered_compare_when_equal(self): r1 = dns.rdata.from_text("IN", "AAAA", "::1") r2 = dns.rdata.from_text("IN", "AAAA", "0000:0000::1") self.assertFalse(r1 < r2) def test_relative_vs_absolute_compare_unstrict(self): try: saved = dns.rdata._allow_relative_comparisons dns.rdata._allow_relative_comparisons = True r1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "www.") r2 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "www") self.assertFalse(r1 == r2) self.assertTrue(r1 != r2) self.assertFalse(r1 < r2) self.assertFalse(r1 <= r2) self.assertTrue(r1 > r2) self.assertTrue(r1 >= r2) self.assertTrue(r2 < r1) self.assertTrue(r2 <= r1) self.assertFalse(r2 > r1) self.assertFalse(r2 >= r1) finally: dns.rdata._allow_relative_comparisons = saved def test_relative_vs_absolute_compare_strict(self): try: saved = dns.rdata._allow_relative_comparisons dns.rdata._allow_relative_comparisons = False r1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "www.") r2 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "www") self.assertFalse(r1 == r2) self.assertTrue(r1 != r2) def bad1(): r1 < r2 def bad2(): r1 <= r2 def bad3(): r1 > r2 def bad4(): r1 >= r2 self.assertRaises(dns.rdata.NoRelativeRdataOrdering, bad1) self.assertRaises(dns.rdata.NoRelativeRdataOrdering, bad2) self.assertRaises(dns.rdata.NoRelativeRdataOrdering, bad3) self.assertRaises(dns.rdata.NoRelativeRdataOrdering, bad4) finally: dns.rdata._allow_relative_comparisons = saved def test_absolute_vs_absolute_compare_unstrict(self): try: saved = dns.rdata._allow_relative_comparisons dns.rdata._allow_relative_comparisons = True r1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "www.") r2 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "xxx.") self.assertFalse(r1 == r2) self.assertTrue(r1 != r2) self.assertTrue(r1 < r2) self.assertTrue(r1 <= r2) self.assertFalse(r1 > r2) self.assertFalse(r1 >= r2) finally: dns.rdata._allow_relative_comparisons = saved def test_absolute_vs_absolute_compare_strict(self): try: saved = dns.rdata._allow_relative_comparisons dns.rdata._allow_relative_comparisons = False r1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "www.") r2 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "xxx.") self.assertFalse(r1 == r2) self.assertTrue(r1 != r2) self.assertTrue(r1 < r2) self.assertTrue(r1 <= r2) self.assertFalse(r1 > r2) self.assertFalse(r1 >= r2) finally: dns.rdata._allow_relative_comparisons = saved def test_relative_vs_relative_compare_unstrict(self): try: saved = dns.rdata._allow_relative_comparisons dns.rdata._allow_relative_comparisons = True r1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "www") r2 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "xxx") self.assertFalse(r1 == r2) self.assertTrue(r1 != r2) self.assertTrue(r1 < r2) self.assertTrue(r1 <= r2) self.assertFalse(r1 > r2) self.assertFalse(r1 >= r2) finally: dns.rdata._allow_relative_comparisons = saved def test_relative_vs_relative_compare_strict(self): try: saved = dns.rdata._allow_relative_comparisons dns.rdata._allow_relative_comparisons = False r1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "www") r2 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "xxx") self.assertFalse(r1 == r2) self.assertTrue(r1 != r2) def bad1(): r1 < r2 def bad2(): r1 <= r2 def bad3(): r1 > r2 def bad4(): r1 >= r2 self.assertRaises(dns.rdata.NoRelativeRdataOrdering, bad1) self.assertRaises(dns.rdata.NoRelativeRdataOrdering, bad2) self.assertRaises(dns.rdata.NoRelativeRdataOrdering, bad3) self.assertRaises(dns.rdata.NoRelativeRdataOrdering, bad4) finally: dns.rdata._allow_relative_comparisons = saved def test_nsec3_next_name(self): rdata = dns.rdata.from_text( "in", "nsec3", "1 1 0 - CK0Q2D6NI4I7EQH8NA30NS61O48UL8G5 NS SOA RRSIG DNSKEY NSEC3PARAM", ) origin = dns.name.from_text("com") expected_rel = dns.name.from_text( "ck0q2d6ni4i7eqh8na30ns61o48ul8g5", origin=None ) expected_abs = dns.name.from_text("ck0q2d6ni4i7eqh8na30ns61o48ul8g5.com.") self.assertEqual(rdata.next_name(), expected_rel) self.assertEqual(rdata.next_name(origin), expected_abs) class UtilTestCase(unittest.TestCase): def test_Gateway_bad_type0(self): with self.assertRaises(SyntaxError): dns.rdtypes.util.Gateway(0, "bad.") def test_Gateway_bad_type3(self): with self.assertRaises(SyntaxError): dns.rdtypes.util.Gateway(3, "bad.") def test_Gateway_type4(self): with self.assertRaises(SyntaxError): dns.rdtypes.util.Gateway(4) with self.assertRaises(dns.exception.FormError): dns.rdtypes.util.Gateway.from_wire_parser(4, None) def test_Bitmap(self): b = dns.rdtypes.util.Bitmap tok = dns.tokenizer.Tokenizer("A MX") windows = b.from_text(tok).windows ba = bytearray() ba.append(0x40) # bit 1, for A ba.append(0x01) # bit 15, for MX self.assertEqual(windows, [(0, bytes(ba))]) def test_Bitmap_with_duplicate_types(self): b = dns.rdtypes.util.Bitmap tok = dns.tokenizer.Tokenizer("A MX A A MX") windows = b.from_text(tok).windows ba = bytearray() ba.append(0x40) # bit 1, for A ba.append(0x01) # bit 15, for MX self.assertEqual(windows, [(0, bytes(ba))]) def test_Bitmap_with_out_of_order_types(self): b = dns.rdtypes.util.Bitmap tok = dns.tokenizer.Tokenizer("MX A") windows = b.from_text(tok).windows ba = bytearray() ba.append(0x40) # bit 1, for A ba.append(0x01) # bit 15, for MX self.assertEqual(windows, [(0, bytes(ba))]) def test_Bitmap_zero_padding_works(self): b = dns.rdtypes.util.Bitmap tok = dns.tokenizer.Tokenizer("SRV") windows = b.from_text(tok).windows ba = bytearray() ba.append(0) ba.append(0) ba.append(0) ba.append(0) ba.append(0x40) # bit 33, for SRV self.assertEqual(windows, [(0, bytes(ba))]) def test_Bitmap_has_type_0_set(self): b = dns.rdtypes.util.Bitmap with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer("NONE A MX") b.from_text(tok) def test_Bitmap_empty_window_not_written(self): b = dns.rdtypes.util.Bitmap tok = dns.tokenizer.Tokenizer("URI CAA") # types 256 and 257 windows = b.from_text(tok).windows ba = bytearray() ba.append(0xC0) # bits 0 and 1 in window 1 self.assertEqual(windows, [(1, bytes(ba))]) def test_Bitmap_ok_parse(self): parser = dns.wire.Parser(b"\x00\x01\x40") b = dns.rdtypes.util.Bitmap([]) windows = b.from_wire_parser(parser).windows self.assertEqual(windows, [(0, b"@")]) def test_Bitmap_0_length_window_parse(self): parser = dns.wire.Parser(b"\x00\x00") with self.assertRaises(ValueError): b = dns.rdtypes.util.Bitmap([]) b.from_wire_parser(parser) def test_Bitmap_too_long_parse(self): parser = dns.wire.Parser(b"\x00\x21" + b"\x01" * 33) with self.assertRaises(ValueError): b = dns.rdtypes.util.Bitmap([]) b.from_wire_parser(parser) def test_compressed_in_generic_is_bad(self): with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, r"\# 4 000aC000") def test_rdataset_ttl_conversion(self): rds1 = dns.rdataset.from_text("in", "a", 300, "10.0.0.1") self.assertEqual(rds1.ttl, 300) rds2 = dns.rdataset.from_text("in", "a", "5m", "10.0.0.1") self.assertEqual(rds2.ttl, 300) with self.assertRaises(ValueError): dns.rdataset.from_text("in", "a", 1.6, "10.0.0.1") with self.assertRaises(dns.ttl.BadTTL): dns.rdataset.from_text("in", "a", "10.0.0.1", "10.0.0.2") def test_nsap_ptr_type(self): # The NSAP-PTR type is special because it contains a dash, which means # that its enum value is not the same as its string value. self.assertEqual(dns.rdatatype.from_text("NSAP-PTR"), dns.rdatatype.NSAP_PTR) self.assertEqual( dns.rdatatype.RdataType.make("NSAP-PTR"), dns.rdatatype.NSAP_PTR ) Rdata = dns.rdata.Rdata class RdataConvertersTestCase(unittest.TestCase): def test_as_name(self): n = dns.name.from_text("hi") self.assertEqual(Rdata._as_name(n), n) self.assertEqual(Rdata._as_name("hi"), n) with self.assertRaises(ValueError): Rdata._as_name(100) def test_as_uint8(self): self.assertEqual(Rdata._as_uint8(0), 0) with self.assertRaises(ValueError): Rdata._as_uint8("hi") with self.assertRaises(ValueError): Rdata._as_uint8(-1) with self.assertRaises(ValueError): Rdata._as_uint8(256) def test_as_uint16(self): self.assertEqual(Rdata._as_uint16(0), 0) with self.assertRaises(ValueError): Rdata._as_uint16("hi") with self.assertRaises(ValueError): Rdata._as_uint16(-1) with self.assertRaises(ValueError): Rdata._as_uint16(65536) def test_as_uint32(self): self.assertEqual(Rdata._as_uint32(0), 0) with self.assertRaises(ValueError): Rdata._as_uint32("hi") with self.assertRaises(ValueError): Rdata._as_uint32(-1) with self.assertRaises(ValueError): Rdata._as_uint32(2**32) def test_as_uint48(self): self.assertEqual(Rdata._as_uint48(0), 0) with self.assertRaises(ValueError): Rdata._as_uint48("hi") with self.assertRaises(ValueError): Rdata._as_uint48(-1) with self.assertRaises(ValueError): Rdata._as_uint48(2**48) def test_as_int(self): self.assertEqual(Rdata._as_int(0, 0, 10), 0) with self.assertRaises(ValueError): Rdata._as_int("hi", 0, 10) with self.assertRaises(ValueError): Rdata._as_int(-1, 0, 10) with self.assertRaises(ValueError): Rdata._as_int(11, 0, 10) def test_as_bool(self): self.assertEqual(Rdata._as_bool(True), True) self.assertEqual(Rdata._as_bool(False), False) with self.assertRaises(ValueError): Rdata._as_bool("hi") def test_as_ttl(self): self.assertEqual(Rdata._as_ttl(300), 300) self.assertEqual(Rdata._as_ttl("5m"), 300) self.assertEqual(Rdata._as_ttl(dns.ttl.MAX_TTL), dns.ttl.MAX_TTL) with self.assertRaises(dns.ttl.BadTTL): Rdata._as_ttl("hi") with self.assertRaises(ValueError): Rdata._as_ttl(1.9) with self.assertRaises(ValueError): Rdata._as_ttl(dns.ttl.MAX_TTL + 1) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_rdataset.py0000644000000000000000000001377013615410400015060 0ustar00# -*- coding: utf-8 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import unittest import dns.name import dns.rdata import dns.rdataclass import dns.rdataset import dns.rdatatype class RdatasetTestCase(unittest.TestCase): def testCodec2003(self): r1 = dns.rdataset.from_text_list("in", "ns", 30, ["Königsgäßchen"]) r2 = dns.rdataset.from_text_list("in", "ns", 30, ["xn--knigsgsschen-lcb0w"]) self.assertEqual(r1, r2) @unittest.skipUnless( dns.name.have_idna_2008, "Python idna cannot be imported; no IDNA2008" ) def testCodec2008(self): r1 = dns.rdataset.from_text_list( "in", "ns", 30, ["Königsgäßchen"], idna_codec=dns.name.IDNA_2008 ) r2 = dns.rdataset.from_text_list( "in", "ns", 30, ["xn--knigsgchen-b4a3dun"], idna_codec=dns.name.IDNA_2008 ) self.assertEqual(r1, r2) def testCopy(self): r1 = dns.rdataset.from_text_list("in", "a", 30, ["10.0.0.1", "10.0.0.2"]) r2 = r1.copy() self.assertFalse(r1 is r2) self.assertTrue(r1 == r2) def testAddIncompatible(self): rds = dns.rdataset.Rdataset(dns.rdataclass.IN, dns.rdatatype.A) rd1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1") rd2 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.AAAA, "::1") rds.add(rd1, 30) self.assertRaises(dns.rdataset.IncompatibleTypes, lambda: rds.add(rd2, 30)) def testDifferingCovers(self): rds = dns.rdataset.Rdataset( dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.A ) rd1 = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.RRSIG, "A 1 3 3600 20200101000000 20030101000000 2143 foo Ym9ndXM=", ) rd2 = dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.RRSIG, "AAAA 1 3 3600 20200101000000 20030101000000 2143 foo Ym9ndXM=", ) rds.add(rd1, 30) self.assertRaises(dns.rdataset.DifferingCovers, lambda: rds.add(rd2, 30)) def testUnionUpdate(self): rds1 = dns.rdataset.from_text("in", "a", 300, "10.0.0.1") rds2 = dns.rdataset.from_text("in", "a", 30, "10.0.0.2") rdse = dns.rdataset.from_text("in", "a", 30, "10.0.0.1", "10.0.0.2") rds1.union_update(rds2) self.assertEqual(rds1, rdse) def testIntersectionUpdate(self): rds1 = dns.rdataset.from_text("in", "a", 300, "10.0.0.1", "10.0.0.2") rds2 = dns.rdataset.from_text("in", "a", 30, "10.0.0.2") rdse = dns.rdataset.from_text("in", "a", 30, "10.0.0.2") rds1.intersection_update(rds2) self.assertEqual(rds1, rdse) def testNoEqualToOther(self): rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.1") self.assertFalse(rds == 123) def testEmptyRdataList(self): self.assertRaises(ValueError, lambda: dns.rdataset.from_rdata_list(300, [])) def testToTextNoName(self): rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.1") text = rds.to_text() self.assertEqual(text, "300 IN A 10.0.0.1") def testToTextOverrideClass(self): rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.1") text = rds.to_text(override_rdclass=dns.rdataclass.NONE) self.assertEqual(text, "300 NONE A 10.0.0.1") def testRepr(self): rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.1") self.assertEqual(repr(rds), "]>") def testTruncatedRepr(self): rds = dns.rdataset.from_text("in", "txt", 300, "a" * 200) # * 99 not * 100 below as the " counts as one of the 100 chars self.assertEqual(repr(rds), ']>") def testStr(self): rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.1") self.assertEqual(str(rds), "300 IN A 10.0.0.1") def testMultilineToText(self): rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.1", "10.0.0.2") self.assertEqual(rds.to_text(), "300 IN A 10.0.0.1\n300 IN A 10.0.0.2") def testCoveredRepr(self): rds = dns.rdataset.from_text( "in", "rrsig", 300, "NSEC 1 3 3600 " + "20190101000000 20030101000000 " + "2143 foo Ym9ndXM=", ) # Using startswith as I don't care about the repr of the rdata, # just the covers self.assertTrue(repr(rds).startswith(" # # 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 RED HAT DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import unittest import dns.rrset import dns.rdtypes.ANY.DNSKEY from typing import Set # pylint: disable=unused-import class RdtypeAnyDnskeyTestCase(unittest.TestCase): def testFlagsAll(self): # type: () -> None """Test that all defined flags are recognized.""" good_s = {"SEP", "REVOKE", "ZONE"} good_f = 0x181 self.assertEqual( dns.rdtypes.ANY.DNSKEY.SEP | dns.rdtypes.ANY.DNSKEY.REVOKE | dns.rdtypes.ANY.DNSKEY.ZONE, good_f, ) def testFlagsRRToText(self): # type: () -> None """Test that RR method returns correct flags.""" rr = dns.rrset.from_text("foo", 300, "IN", "DNSKEY", "257 3 8 KEY=")[0] self.assertEqual( dns.rdtypes.ANY.DNSKEY.ZONE | dns.rdtypes.ANY.DNSKEY.SEP, rr.flags ) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_rdtypeanyeui.py0000644000000000000000000001616313615410400015772 0ustar00# Copyright (C) 2015 Red Hat, Inc. # Author: Petr Spacek # # 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 RED HAT DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import unittest import dns.rrset import dns.rdtypes.ANY.EUI48 import dns.rdtypes.ANY.EUI64 import dns.exception class RdtypeAnyEUI48TestCase(unittest.TestCase): def testInstOk(self): """Valid binary input.""" eui = b"\x01\x23\x45\x67\x89\xab" inst = dns.rdtypes.ANY.EUI48.EUI48(dns.rdataclass.IN, dns.rdatatype.EUI48, eui) self.assertEqual(inst.eui, eui) def testInstLength(self): """Incorrect input length.""" eui = b"\x01\x23\x45\x67\x89\xab\xcd" with self.assertRaises(dns.exception.FormError): dns.rdtypes.ANY.EUI48.EUI48(dns.rdataclass.IN, dns.rdatatype.EUI48, eui) def testFromTextOk(self): """Valid text input.""" r1 = dns.rrset.from_text("foo", 300, "IN", "EUI48", "01-23-45-67-89-ab") eui = b"\x01\x23\x45\x67\x89\xab" self.assertEqual(r1[0].eui, eui) def testFromTextLength(self): """Invalid input length.""" with self.assertRaises(dns.exception.SyntaxError): dns.rrset.from_text("foo", 300, "IN", "EUI48", "00-01-23-45-67-89-ab") def testFromTextDelim(self): """Invalid delimiter.""" with self.assertRaises(dns.exception.SyntaxError): dns.rrset.from_text("foo", 300, "IN", "EUI48", "01_23-45-67-89-ab") def testFromTextExtraDash(self): """Extra dash instead of hex digit.""" with self.assertRaises(dns.exception.SyntaxError): dns.rrset.from_text("foo", 300, "IN", "EUI48", "0--23-45-67-89-ab") def testFromTextMultipleTokens(self): """Invalid input divided to multiple tokens.""" with self.assertRaises(dns.exception.SyntaxError): dns.rrset.from_text("foo", 300, "IN", "EUI48", "01 23-45-67-89-ab") def testFromTextInvalidHex(self): """Invalid hexadecimal input.""" with self.assertRaises(dns.exception.SyntaxError): dns.rrset.from_text("foo", 300, "IN", "EUI48", "g0-23-45-67-89-ab") def testToTextOk(self): """Valid text output.""" eui = b"\x01\x23\x45\x67\x89\xab" exp_text = "01-23-45-67-89-ab" inst = dns.rdtypes.ANY.EUI48.EUI48(dns.rdataclass.IN, dns.rdatatype.EUI48, eui) text = inst.to_text() self.assertEqual(exp_text, text) def testToWire(self): """Valid wire format.""" eui = b"\x01\x23\x45\x67\x89\xab" inst = dns.rdtypes.ANY.EUI48.EUI48(dns.rdataclass.IN, dns.rdatatype.EUI48, eui) self.assertEqual(inst.to_wire(), eui) def testFromWireOk(self): """Valid wire format.""" eui = b"\x01\x23\x45\x67\x89\xab" pad_len = 100 wire = b"x" * pad_len + eui + b"y" * pad_len * 2 inst = dns.rdata.from_wire( dns.rdataclass.IN, dns.rdatatype.EUI48, wire, pad_len, len(eui) ) self.assertEqual(inst.eui, eui) def testFromWireLength(self): """Valid wire format.""" eui = b"\x01\x23\x45\x67\x89" pad_len = 100 wire = b"x" * pad_len + eui + b"y" * pad_len * 2 with self.assertRaises(dns.exception.FormError): dns.rdata.from_wire( dns.rdataclass.IN, dns.rdatatype.EUI48, wire, pad_len, len(eui) ) class RdtypeAnyEUI64TestCase(unittest.TestCase): def testInstOk(self): """Valid binary input.""" eui = b"\x01\x23\x45\x67\x89\xab\xcd\xef" inst = dns.rdtypes.ANY.EUI64.EUI64(dns.rdataclass.IN, dns.rdatatype.EUI64, eui) self.assertEqual(inst.eui, eui) def testInstLength(self): """Incorrect input length.""" eui = b"\x01\x23\x45\x67\x89\xab" with self.assertRaises(dns.exception.FormError): dns.rdtypes.ANY.EUI64.EUI64(dns.rdataclass.IN, dns.rdatatype.EUI64, eui) def testFromTextOk(self): """Valid text input.""" r1 = dns.rrset.from_text("foo", 300, "IN", "EUI64", "01-23-45-67-89-ab-cd-ef") eui = b"\x01\x23\x45\x67\x89\xab\xcd\xef" self.assertEqual(r1[0].eui, eui) def testFromTextLength(self): """Invalid input length.""" with self.assertRaises(dns.exception.SyntaxError): dns.rrset.from_text("foo", 300, "IN", "EUI64", "01-23-45-67-89-ab") def testFromTextDelim(self): """Invalid delimiter.""" with self.assertRaises(dns.exception.SyntaxError): dns.rrset.from_text("foo", 300, "IN", "EUI64", "01_23-45-67-89-ab-cd-ef") def testFromTextExtraDash(self): """Extra dash instead of hex digit.""" with self.assertRaises(dns.exception.SyntaxError): dns.rrset.from_text("foo", 300, "IN", "EUI64", "0--23-45-67-89-ab-cd-ef") def testFromTextMultipleTokens(self): """Invalid input divided to multiple tokens.""" with self.assertRaises(dns.exception.SyntaxError): dns.rrset.from_text("foo", 300, "IN", "EUI64", "01 23-45-67-89-ab-cd-ef") def testFromTextInvalidHex(self): """Invalid hexadecimal input.""" with self.assertRaises(dns.exception.SyntaxError): dns.rrset.from_text("foo", 300, "IN", "EUI64", "g0-23-45-67-89-ab-cd-ef") def testToTextOk(self): """Valid text output.""" eui = b"\x01\x23\x45\x67\x89\xab\xcd\xef" exp_text = "01-23-45-67-89-ab-cd-ef" inst = dns.rdtypes.ANY.EUI64.EUI64(dns.rdataclass.IN, dns.rdatatype.EUI64, eui) text = inst.to_text() self.assertEqual(exp_text, text) def testToWire(self): """Valid wire format.""" eui = b"\x01\x23\x45\x67\x89\xab\xcd\xef" inst = dns.rdtypes.ANY.EUI64.EUI64(dns.rdataclass.IN, dns.rdatatype.EUI64, eui) self.assertEqual(inst.to_wire(), eui) def testFromWireOk(self): """Valid wire format.""" eui = b"\x01\x23\x45\x67\x89\xab\xcd\xef" pad_len = 100 wire = b"x" * pad_len + eui + b"y" * pad_len * 2 inst = dns.rdata.from_wire( dns.rdataclass.IN, dns.rdatatype.EUI64, wire, pad_len, len(eui) ) self.assertEqual(inst.eui, eui) def testFromWireLength(self): """Valid wire format.""" eui = b"\x01\x23\x45\x67\x89" pad_len = 100 wire = b"x" * pad_len + eui + b"y" * pad_len * 2 with self.assertRaises(dns.exception.FormError): dns.rdata.from_wire( dns.rdataclass.IN, dns.rdatatype.EUI64, wire, pad_len, len(eui) ) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_rdtypeanyloc.py0000644000000000000000000000655513615410400015771 0ustar00# Copyright (C) 2014 Red Hat, Inc. # Author: Petr Spacek # # 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 RED HAT DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import unittest import dns.rrset import dns.rdtypes.ANY.LOC class RdtypeAnyLocTestCase(unittest.TestCase): def testEqual1(self): """Test default values for size, horizontal and vertical precision.""" r1 = dns.rrset.from_text( "foo", 300, "IN", "LOC", "49 11 42.400 N 16 36 29.600 E 227.64m" ) r2 = dns.rrset.from_text( "FOO", 600, "in", "loc", "49 11 42.400 N 16 36 29.600 E 227.64m " "1.00m 10000.00m 10.00m", ) self.assertEqual(r1, r2, '"{}" != "{}"'.format(r1, r2)) def testEqual2(self): """Test default values for size, horizontal and vertical precision.""" r1 = dns.rdtypes.ANY.LOC.LOC( 1, 29, (49, 11, 42, 400, 1), (16, 36, 29, 600, 1), 22764.0 ) # centimeters r2 = dns.rdtypes.ANY.LOC.LOC( 1, 29, (49, 11, 42, 400, 1), (16, 36, 29, 600, 1), 22764.0, # centimeters 100.0, 1000000.00, 1000.0, ) # centimeters self.assertEqual(r1, r2, '"{}" != "{}"'.format(r1, r2)) def testEqual3(self): """Test size, horizontal and vertical precision parsers: 100 cm == 1 m. Parsers in from_text() and __init__() have to produce equal results.""" r1 = dns.rdtypes.ANY.LOC.LOC( 1, 29, (49, 11, 42, 400, 1), (16, 36, 29, 600, 1), 22764.0, 200.0, 1000.00, 200.0, ) # centimeters r2 = dns.rrset.from_text( "FOO", 600, "in", "loc", "49 11 42.400 N 16 36 29.600 E 227.64m " "2.00m 10.00m 2.00m", )[0] self.assertEqual(r1, r2, '"{}" != "{}"'.format(r1, r2)) def testEqual4(self): """Test size, horizontal and vertical precision parsers without unit. Parsers in from_text() and __init__() have produce equal result for values with and without trailing "m".""" r1 = dns.rdtypes.ANY.LOC.LOC( 1, 29, (49, 11, 42, 400, 1), (16, 36, 29, 600, 1), 22764.0, 200.0, 1000.00, 200.0, ) # centimeters r2 = dns.rrset.from_text( "FOO", 600, "in", "loc", "49 11 42.400 N 16 36 29.600 E 227.64 " "2 10 2" )[ 0 ] # meters without explicit unit self.assertEqual(r1, r2, '"{}" != "{}"'.format(r1, r2)) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_rdtypeanytkey.py0000644000000000000000000001032513615410400016156 0ustar00# -*- coding: utf-8 # 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 unittest import base64 import dns.name import dns.zone import dns.rdtypes.ANY.TKEY from dns.rdataclass import RdataClass from dns.rdatatype import RdataType class RdtypeAnyTKeyTestCase(unittest.TestCase): tkey_rdata_text = "gss-tsig. 1594203795 1594206664 3 0 KEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEY OTHEROTHEROTHEROTHEROTHEROTHEROT" tkey_rdata_text_no_other = ( "gss-tsig. 1594203795 1594206664 3 0 KEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEY" ) def testTextOptionalData(self): # construct the rdata from text and extract the TKEY tkey = dns.rdata.from_text( RdataClass.ANY, RdataType.TKEY, RdtypeAnyTKeyTestCase.tkey_rdata_text, origin=".", ) self.assertEqual(type(tkey), dns.rdtypes.ANY.TKEY.TKEY) # go to text and compare tkey_out_text = tkey.to_text(relativize=False) self.assertEqual(tkey_out_text, RdtypeAnyTKeyTestCase.tkey_rdata_text) def testTextNoOptionalData(self): # construct the rdata from text and extract the TKEY tkey = dns.rdata.from_text( RdataClass.ANY, RdataType.TKEY, RdtypeAnyTKeyTestCase.tkey_rdata_text_no_other, origin=".", ) self.assertEqual(type(tkey), dns.rdtypes.ANY.TKEY.TKEY) # go to text and compare tkey_out_text = tkey.to_text(relativize=False) self.assertEqual(tkey_out_text, RdtypeAnyTKeyTestCase.tkey_rdata_text_no_other) def testWireOptionalData(self): key = base64.b64decode("KEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEY") other = base64.b64decode("OTHEROTHEROTHEROTHEROTHEROTHEROT") # construct the TKEY and compare the text output tkey = dns.rdtypes.ANY.TKEY.TKEY( dns.rdataclass.ANY, dns.rdatatype.TKEY, dns.name.from_text("gss-tsig."), 1594203795, 1594206664, 3, 0, key, other, ) self.assertEqual( tkey.to_text(relativize=False), RdtypeAnyTKeyTestCase.tkey_rdata_text ) # go to/from wire and compare the text output wire = tkey.to_wire() tkey_out_wire = dns.rdata.from_wire( dns.rdataclass.ANY, dns.rdatatype.TKEY, wire, 0, len(wire) ) self.assertEqual( tkey_out_wire.to_text(relativize=False), RdtypeAnyTKeyTestCase.tkey_rdata_text, ) def testWireNoOptionalData(self): key = base64.b64decode("KEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEY") # construct the TKEY with no 'other' data and compare the text output tkey = dns.rdtypes.ANY.TKEY.TKEY( dns.rdataclass.ANY, dns.rdatatype.TKEY, dns.name.from_text("gss-tsig."), 1594203795, 1594206664, 3, 0, key, ) self.assertEqual( tkey.to_text(relativize=False), RdtypeAnyTKeyTestCase.tkey_rdata_text_no_other, ) # go to/from wire and compare the text output wire = tkey.to_wire() tkey_out_wire = dns.rdata.from_wire( dns.rdataclass.ANY, dns.rdatatype.TKEY, wire, 0, len(wire) ) self.assertEqual( tkey_out_wire.to_text(relativize=False), RdtypeAnyTKeyTestCase.tkey_rdata_text_no_other, ) dnspython-2.7.0/tests/test_renderer.py0000644000000000000000000000760413615410400015056 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import unittest import dns.exception import dns.flags import dns.message import dns.renderer import dns.tsig import dns.tsigkeyring basic_answer = """flags QR edns 0 payload 4096 ;QUESTION foo.example. IN A ;ANSWER foo.example. 30 IN A 10.0.0.1 foo.example. 30 IN A 10.0.0.2 """ class RendererTestCase(unittest.TestCase): def test_basic(self): r = dns.renderer.Renderer(flags=dns.flags.QR, max_size=512) qname = dns.name.from_text("foo.example") r.add_question(qname, dns.rdatatype.A) rds = dns.rdataset.from_text("in", "a", 30, "10.0.0.1", "10.0.0.2") r.add_rdataset(dns.renderer.ANSWER, qname, rds) r.add_edns(0, 0, 4096) r.write_header() wire = r.get_wire() message = dns.message.from_wire(wire) expected = dns.message.from_text(basic_answer) # Our rendered message purposely has a random query id so we # exercise that code, so copy it into the expected message. expected.id = message.id self.assertEqual(message, expected) def test_tsig(self): r = dns.renderer.Renderer(flags=dns.flags.RD, max_size=512) qname = dns.name.from_text("foo.example") r.add_question(qname, dns.rdatatype.A) keyring = dns.tsigkeyring.from_text({"key": "12345678"}) keyname = next(iter(keyring)) r.write_header() r.add_tsig( keyname, keyring[keyname], 300, r.id, 0, b"", b"", dns.tsig.HMAC_SHA256 ) wire = r.get_wire() message = dns.message.from_wire(wire, keyring=keyring) expected = dns.message.make_query(qname, dns.rdatatype.A) expected.id = message.id self.assertEqual(message, expected) def test_multi_tsig(self): qname = dns.name.from_text("foo.example") keyring = dns.tsigkeyring.from_text({"key": "12345678"}) keyname = next(iter(keyring)) r = dns.renderer.Renderer(flags=dns.flags.RD, max_size=512) r.add_question(qname, dns.rdatatype.A) r.write_header() ctx = r.add_multi_tsig( None, keyname, keyring[keyname], 300, r.id, 0, b"", b"", dns.tsig.HMAC_SHA256, ) wire = r.get_wire() message = dns.message.from_wire(wire, keyring=keyring, multi=True) expected = dns.message.make_query(qname, dns.rdatatype.A) expected.id = message.id self.assertEqual(message, expected) r = dns.renderer.Renderer(flags=dns.flags.RD, max_size=512) r.add_question(qname, dns.rdatatype.A) r.write_header() ctx = r.add_multi_tsig( ctx, keyname, keyring[keyname], 300, r.id, 0, b"", b"", dns.tsig.HMAC_SHA256 ) wire = r.get_wire() message = dns.message.from_wire( wire, keyring=keyring, tsig_ctx=message.tsig_ctx, multi=True ) expected = dns.message.make_query(qname, dns.rdatatype.A) expected.id = message.id self.assertEqual(message, expected) def test_going_backwards_fails(self): r = dns.renderer.Renderer(flags=dns.flags.QR, max_size=512) qname = dns.name.from_text("foo.example") r.add_question(qname, dns.rdatatype.A) r.add_edns(0, 0, 4096) rds = dns.rdataset.from_text("in", "a", 30, "10.0.0.1", "10.0.0.2") def bad(): r.add_rdataset(dns.renderer.ANSWER, qname, rds) self.assertRaises(dns.exception.FormError, bad) def test_reservation(self): r = dns.renderer.Renderer(flags=dns.flags.QR, max_size=512) r.reserve(100) assert r.max_size == 412 r.release_reserved() assert r.max_size == 512 with self.assertRaises(ValueError): r.reserve(-1) with self.assertRaises(ValueError): r.reserve(513) dnspython-2.7.0/tests/test_resolution.py0000644000000000000000000005224013615410400015447 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import unittest import dns.flags import dns.message import dns.name import dns.rcode import dns.rdataclass import dns.rdatatype import dns.resolver import dns.tsigkeyring # Test the resolver's Resolution, i.e. the business logic of the resolver. class ResolutionTestCase(unittest.TestCase): def setUp(self): self.resolver = dns.resolver.Resolver(configure=False) self.resolver.nameservers = ["10.0.0.1", "10.0.0.2"] self.resolver.domain = dns.name.from_text("example") self.qname = dns.name.from_text("www.dnspython.org") self.resn = dns.resolver._Resolution( self.resolver, self.qname, "A", "IN", False, True, False ) def test_next_request_abs(self): (request, answer) = self.resn.next_request() self.assertTrue(answer is None) self.assertEqual(request.question[0].name, self.qname) self.assertEqual(request.question[0].rdtype, dns.rdatatype.A) def test_next_request_rel_with_search(self): qname = dns.name.from_text("www.dnspython.org", None) abs_qname_1 = dns.name.from_text("www.dnspython.org.example") self.resn = dns.resolver._Resolution( self.resolver, qname, "A", "IN", False, True, True ) (request, answer) = self.resn.next_request() self.assertTrue(answer is None) self.assertEqual(request.question[0].name, self.qname) self.assertEqual(request.question[0].rdtype, dns.rdatatype.A) (request, answer) = self.resn.next_request() self.assertTrue(answer is None) self.assertEqual(request.question[0].name, abs_qname_1) self.assertEqual(request.question[0].rdtype, dns.rdatatype.A) def bad(): (request, answer) = self.resn.next_request() self.assertRaises(dns.resolver.NXDOMAIN, bad) def test_next_request_rel_without_search(self): qname = dns.name.from_text("www.dnspython.org", None) abs_qname_1 = dns.name.from_text("www.dnspython.org.example") self.resn = dns.resolver._Resolution( self.resolver, qname, "A", "IN", False, True, False ) (request, answer) = self.resn.next_request() self.assertTrue(answer is None) self.assertEqual(request.question[0].name, self.qname) self.assertEqual(request.question[0].rdtype, dns.rdatatype.A) def bad(): (request, answer) = self.resn.next_request() self.assertRaises(dns.resolver.NXDOMAIN, bad) def test_next_request_exhaust_causes_nxdomain(self): def bad(): (request, answer) = self.resn.next_request() (request, answer) = self.resn.next_request() self.assertRaises(dns.resolver.NXDOMAIN, bad) def make_address_response(self, q): r = dns.message.make_response(q) rrs = r.get_rrset( r.answer, self.qname, dns.rdataclass.IN, dns.rdatatype.A, create=True ) rrs.add( dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1"), 300 ) return r def make_negative_response(self, q, nxdomain=False): r = dns.message.make_response(q) rrs = r.get_rrset( r.authority, q.question[0].name, dns.rdataclass.IN, dns.rdatatype.SOA, create=True, ) rrs.add( dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.SOA, ". . 1 2 3 4 300" ), 300, ) if nxdomain: r.set_rcode(dns.rcode.NXDOMAIN) return r def make_long_chain_response(self, q, count): r = dns.message.make_response(q) name = self.qname for i in range(count): rrs = r.get_rrset( r.answer, name, dns.rdataclass.IN, dns.rdatatype.CNAME, create=True ) tname = dns.name.from_text(f"target{i}.") rrs.add( dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME, str(tname)), 300, ) name = tname rrs = r.get_rrset( r.answer, name, dns.rdataclass.IN, dns.rdatatype.A, create=True ) rrs.add( dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1"), 300 ) return r def test_next_request_cache_hit(self): self.resolver.cache = dns.resolver.Cache() q = dns.message.make_query(self.qname, dns.rdatatype.A) r = self.make_address_response(q) cache_answer = dns.resolver.Answer( self.qname, dns.rdatatype.A, dns.rdataclass.IN, r ) self.resolver.cache.put( (self.qname, dns.rdatatype.A, dns.rdataclass.IN), cache_answer ) (request, answer) = self.resn.next_request() self.assertTrue(request is None) self.assertTrue(answer is cache_answer) def test_next_request_cached_no_answer(self): # In default mode, we should raise on a no-answer hit self.resolver.cache = dns.resolver.Cache() q = dns.message.make_query(self.qname, dns.rdatatype.A) # Note we need an SOA so the cache doesn't expire the answer # immediately, but our negative response code does that. r = self.make_negative_response(q) cache_answer = dns.resolver.Answer( self.qname, dns.rdatatype.A, dns.rdataclass.IN, r ) self.resolver.cache.put( (self.qname, dns.rdatatype.A, dns.rdataclass.IN), cache_answer ) def bad(): (request, answer) = self.resn.next_request() self.assertRaises(dns.resolver.NoAnswer, bad) # If raise_on_no_answer is False, we should get a cache hit. self.resn = dns.resolver._Resolution( self.resolver, self.qname, "A", "IN", False, False, False ) (request, answer) = self.resn.next_request() self.assertTrue(request is None) self.assertTrue(answer is cache_answer) def test_next_request_cached_nxdomain_without_search(self): # use a relative qname qname = dns.name.from_text("www.dnspython.org", None) self.resn = dns.resolver._Resolution( self.resolver, qname, "A", "IN", False, True, False ) qname1 = dns.name.from_text("www.dnspython.org.") # Arrange to get NXDOMAIN hits on it. self.resolver.cache = dns.resolver.Cache() q1 = dns.message.make_query(qname1, dns.rdatatype.A) r1 = self.make_negative_response(q1, True) cache_answer = dns.resolver.Answer( qname1, dns.rdatatype.ANY, dns.rdataclass.IN, r1 ) self.resolver.cache.put( (qname1, dns.rdatatype.ANY, dns.rdataclass.IN), cache_answer ) try: (request, answer) = self.resn.next_request() self.assertTrue(False) # should not happen! except dns.resolver.NXDOMAIN as nx: self.assertTrue(nx.response(qname1) is r1) def test_next_request_cached_nxdomain_with_search(self): # use a relative qname so we have two qnames to try qname = dns.name.from_text("www.dnspython.org", None) # also enable search mode or we'll only see www.dnspython.org. self.resn = dns.resolver._Resolution( self.resolver, qname, "A", "IN", False, True, True ) qname1 = dns.name.from_text("www.dnspython.org.example.") qname2 = dns.name.from_text("www.dnspython.org.") # Arrange to get NXDOMAIN hits on both of those qnames. self.resolver.cache = dns.resolver.Cache() q1 = dns.message.make_query(qname1, dns.rdatatype.A) r1 = self.make_negative_response(q1, True) cache_answer = dns.resolver.Answer( qname1, dns.rdatatype.ANY, dns.rdataclass.IN, r1 ) self.resolver.cache.put( (qname1, dns.rdatatype.ANY, dns.rdataclass.IN), cache_answer ) q2 = dns.message.make_query(qname2, dns.rdatatype.A) r2 = self.make_negative_response(q2, True) cache_answer = dns.resolver.Answer( qname2, dns.rdatatype.ANY, dns.rdataclass.IN, r2 ) self.resolver.cache.put( (qname2, dns.rdatatype.ANY, dns.rdataclass.IN), cache_answer ) try: (request, answer) = self.resn.next_request() self.assertTrue(False) # should not happen! except dns.resolver.NXDOMAIN as nx: self.assertTrue(nx.response(qname1) is r1) self.assertTrue(nx.response(qname2) is r2) def test_next_request_rotate(self): self.resolver.rotate = True order1 = ["Do53:10.0.0.1@53", "Do53:10.0.0.2@53"] order2 = ["Do53:10.0.0.2@53", "Do53:10.0.0.1@53"] seen1 = False seen2 = False # We're not interested in testing the randomness, but we'd # like to see some shuffling, so try up to 50 times to see # both orders at least once. This test can fail even with # correct code, but it is *extremely* unlikely. for count in range(0, 50): self.resn = dns.resolver._Resolution( self.resolver, self.qname, "A", "IN", False, True, False ) self.resn.next_request() text_form = [str(n) for n in self.resn.nameservers] print(text_form) if text_form == order1: seen1 = True elif text_form == order2: seen2 = True else: raise ValueError # should not happen! if seen1 and seen2: break self.assertTrue(seen1 and seen2) def test_next_request_TSIG(self): self.resolver.keyring = dns.tsigkeyring.from_text( {"keyname.": "NjHwPsMKjdN++dOfE5iAiQ=="} ) (keyname, secret) = next(iter(self.resolver.keyring.items())) self.resolver.keyname = dns.name.from_text("keyname.") (request, answer) = self.resn.next_request() self.assertFalse(request is None) self.assertEqual(request.keyring.name, keyname) self.assertEqual(request.keyring.secret, secret) def test_next_request_flags(self): self.resolver.flags = dns.flags.RD | dns.flags.CD (request, answer) = self.resn.next_request() self.assertFalse(request is None) self.assertEqual(request.flags, self.resolver.flags) def test_next_nameserver_udp(self): (request, answer) = self.resn.next_request() (nameserver1, tcp, backoff) = self.resn.next_nameserver() self.assertEqual(nameserver1.port, 53) self.assertFalse(tcp) self.assertEqual(backoff, 0.0) (nameserver2, tcp, backoff) = self.resn.next_nameserver() self.assertTrue(nameserver2 != nameserver1) self.assertEqual(nameserver2.port, 53) self.assertFalse(tcp) self.assertEqual(backoff, 0.0) (nameserver3, tcp, backoff) = self.resn.next_nameserver() self.assertTrue(nameserver3 is nameserver1) self.assertFalse(tcp) self.assertEqual(backoff, 0.1) (nameserver4, tcp, backoff) = self.resn.next_nameserver() self.assertTrue(nameserver4 is nameserver2) self.assertFalse(tcp) self.assertEqual(backoff, 0.0) (nameserver5, tcp, backoff) = self.resn.next_nameserver() self.assertTrue(nameserver5 is nameserver1) self.assertFalse(tcp) self.assertEqual(backoff, 0.2) def test_next_nameserver_retry_with_tcp(self): (request, answer) = self.resn.next_request() (nameserver1, tcp, backoff) = self.resn.next_nameserver() self.assertEqual(nameserver1.port, 53) self.assertFalse(tcp) self.assertEqual(backoff, 0.0) self.resn.retry_with_tcp = True (nameserver2, tcp, backoff) = self.resn.next_nameserver() self.assertTrue(nameserver2 is nameserver1) self.assertTrue(tcp) self.assertEqual(backoff, 0.0) (nameserver3, tcp, backoff) = self.resn.next_nameserver() self.assertTrue(nameserver3 != nameserver1) self.assertEqual(nameserver3.port, 53) self.assertFalse(tcp) self.assertEqual(backoff, 0.0) def test_next_nameserver_no_nameservers(self): (request, answer) = self.resn.next_request() (nameserver, _, _) = self.resn.next_nameserver() self.resn.nameservers.remove(nameserver) (nameserver, _, _) = self.resn.next_nameserver() self.resn.nameservers.remove(nameserver) def bad(): (nameserver, _, _) = self.resn.next_nameserver() self.assertRaises(dns.resolver.NoNameservers, bad) def test_next_nameserver_max_size_nameserver(self): # A query to a nameserver that always supports a maximum size query # always counts as a "tcp attempt" for the state machine self.resolver.nameservers = ["https://127.0.0.1:443/bogus"] (_, _) = self.resn.next_request() (nameserver, tcp_attempt, _) = self.resn.next_nameserver() print(nameserver) assert tcp_attempt def test_query_result_nameserver_removing_exceptions(self): # add some nameservers so we have enough to remove :) new_nameservers = list(self.resolver.nameservers[:]) new_nameservers.extend(["10.0.0.3", "10.0.0.4"]) self.resolver.nameservers = new_nameservers (request, _) = self.resn.next_request() exceptions = [ dns.exception.FormError(), EOFError(), NotImplementedError(), dns.message.Truncated(), ] for i in range(4): (nameserver, _, _) = self.resn.next_nameserver() if i == 3: # Truncated is only bad if we're doing TCP, make it look # like that's the case self.resn.tcp_attempt = True self.assertTrue(nameserver in self.resn.nameservers) (answer, done) = self.resn.query_result(None, exceptions[i]) self.assertTrue(answer is None) self.assertFalse(done) self.assertFalse(nameserver in self.resn.nameservers) self.assertEqual(len(self.resn.nameservers), 0) def test_query_result_nameserver_continuing_exception(self): # except for the exceptions tested in # test_query_result_nameserver_removing_exceptions(), we should # not remove any nameservers and just continue resolving. (_, _) = self.resn.next_request() (_, _, _) = self.resn.next_nameserver() nameservers = self.resn.nameservers[:] (answer, done) = self.resn.query_result(None, dns.exception.Timeout()) self.assertTrue(answer is None) self.assertFalse(done) self.assertEqual(nameservers, self.resn.nameservers) def test_query_result_retry_with_tcp(self): (request, _) = self.resn.next_request() (nameserver, tcp, _) = self.resn.next_nameserver() self.assertFalse(tcp) (answer, done) = self.resn.query_result(None, dns.message.Truncated()) self.assertTrue(answer is None) self.assertFalse(done) self.assertTrue(self.resn.retry_with_tcp) # The rest of TCP retry logic was tested above in # test_next_nameserver_retry_with_tcp(), so we do not repeat # it. def test_query_result_no_error_with_data(self): q = dns.message.make_query(self.qname, dns.rdatatype.A) r = self.make_address_response(q) (_, _) = self.resn.next_request() (_, _, _) = self.resn.next_nameserver() (answer, done) = self.resn.query_result(r, None) self.assertFalse(answer is None) self.assertTrue(done) self.assertEqual(answer.qname, self.qname) self.assertEqual(answer.rdtype, dns.rdatatype.A) def test_query_result_no_error_with_data_cached(self): self.resolver.cache = dns.resolver.Cache() q = dns.message.make_query(self.qname, dns.rdatatype.A) r = self.make_address_response(q) (_, _) = self.resn.next_request() (_, _, _) = self.resn.next_nameserver() (answer, done) = self.resn.query_result(r, None) self.assertFalse(answer is None) cache_answer = self.resolver.cache.get( (self.qname, dns.rdatatype.A, dns.rdataclass.IN) ) self.assertTrue(answer is cache_answer) def test_query_result_no_error_no_data(self): q = dns.message.make_query(self.qname, dns.rdatatype.A) r = self.make_negative_response(q) (_, _) = self.resn.next_request() (_, _, _) = self.resn.next_nameserver() def bad(): (answer, done) = self.resn.query_result(r, None) self.assertRaises(dns.resolver.NoAnswer, bad) def test_query_result_nxdomain(self): q = dns.message.make_query(self.qname, dns.rdatatype.A) r = self.make_negative_response(q, True) (_, _) = self.resn.next_request() (_, _, _) = self.resn.next_nameserver() (answer, done) = self.resn.query_result(r, None) self.assertTrue(answer is None) self.assertTrue(done) def test_query_result_nxdomain_but_has_answer(self): q = dns.message.make_query(self.qname, dns.rdatatype.A) r = self.make_address_response(q) r.set_rcode(dns.rcode.NXDOMAIN) (_, _) = self.resn.next_request() (nameserver, _, _) = self.resn.next_nameserver() (answer, done) = self.resn.query_result(r, None) self.assertIsNone(answer) self.assertFalse(done) self.assertTrue(nameserver not in self.resn.nameservers) def test_query_result_chain_not_too_long(self): q = dns.message.make_query(self.qname, dns.rdatatype.A) r = self.make_long_chain_response(q, 15) (_, _) = self.resn.next_request() (_, _, _) = self.resn.next_nameserver() (answer, done) = self.resn.query_result(r, None) self.assertIsNotNone(answer) self.assertTrue(done) def test_query_result_chain_too_long(self): q = dns.message.make_query(self.qname, dns.rdatatype.A) r = self.make_long_chain_response(q, 16) (_, _) = self.resn.next_request() (nameserver, _, _) = self.resn.next_nameserver() (answer, done) = self.resn.query_result(r, None) self.assertIsNone(answer) self.assertFalse(done) self.assertTrue(nameserver not in self.resn.nameservers) def test_query_result_nxdomain_cached(self): self.resolver.cache = dns.resolver.Cache() q = dns.message.make_query(self.qname, dns.rdatatype.A) r = self.make_negative_response(q, True) (_, _) = self.resn.next_request() (_, _, _) = self.resn.next_nameserver() (answer, done) = self.resn.query_result(r, None) self.assertTrue(answer is None) self.assertTrue(done) cache_answer = self.resolver.cache.get( (self.qname, dns.rdatatype.ANY, dns.rdataclass.IN) ) self.assertTrue(cache_answer.response is r) def test_query_result_yxdomain(self): q = dns.message.make_query(self.qname, dns.rdatatype.A) r = self.make_address_response(q) r.set_rcode(dns.rcode.YXDOMAIN) (_, _) = self.resn.next_request() (_, _, _) = self.resn.next_nameserver() def bad(): (answer, done) = self.resn.query_result(r, None) self.assertRaises(dns.resolver.YXDOMAIN, bad) def test_query_result_servfail_no_retry(self): q = dns.message.make_query(self.qname, dns.rdatatype.A) r = self.make_address_response(q) r.set_rcode(dns.rcode.SERVFAIL) (_, _) = self.resn.next_request() (nameserver, _, _) = self.resn.next_nameserver() (answer, done) = self.resn.query_result(r, None) self.assertTrue(answer is None) self.assertFalse(done) self.assertTrue(nameserver not in self.resn.nameservers) def test_query_result_servfail_with_retry(self): self.resolver.retry_servfail = True q = dns.message.make_query(self.qname, dns.rdatatype.A) r = self.make_address_response(q) r.set_rcode(dns.rcode.SERVFAIL) (_, _) = self.resn.next_request() (_, _, _) = self.resn.next_nameserver() nameservers = self.resn.nameservers[:] (answer, done) = self.resn.query_result(r, None) self.assertTrue(answer is None) self.assertFalse(done) self.assertEqual(nameservers, self.resn.nameservers) def test_query_result_other_unhappy_rcode(self): q = dns.message.make_query(self.qname, dns.rdatatype.A) r = self.make_address_response(q) r.set_rcode(dns.rcode.REFUSED) (_, _) = self.resn.next_request() (nameserver, _, _) = self.resn.next_nameserver() (answer, done) = self.resn.query_result(r, None) self.assertTrue(answer is None) self.assertFalse(done) self.assertTrue(nameserver not in self.resn.nameservers) def test_no_metaqueries(self): def bad1(): self.resn = dns.resolver._Resolution( self.resolver, self.qname, "ANY", "IN", False, True, False ) def bad2(): self.resn = dns.resolver._Resolution( self.resolver, self.qname, "A", "ANY", False, True, False ) self.assertRaises(dns.resolver.NoMetaqueries, bad1) self.assertRaises(dns.resolver.NoMetaqueries, bad2) dnspython-2.7.0/tests/test_resolver.py0000644000000000000000000012730413615410400015111 0ustar00# 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 # 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 selectors import socket import sys import time import unittest from io import StringIO from unittest.mock import patch import pytest import dns.e164 import dns.message import dns.name import dns.quic import dns.rdataclass import dns.rdatatype import dns.resolver import dns.reversename import dns.tsig import dns.tsigkeyring import tests.util # Some tests use a "nano nameserver" for testing. It requires trio # and threading, so try to import it and if it doesn't work, skip # those tests. try: from .nanonameserver import Server _nanonameserver_available = True except ImportError: _nanonameserver_available = False class Server(object): pass # Look for systemd-resolved, as it does dangling CNAME responses incorrectly. # # Currently we simply check if the nameserver is 127.0.0.53. _systemd_resolved_present = False try: _resolver = dns.resolver.Resolver() if _resolver.nameservers == ["127.0.0.53"]: _systemd_resolved_present = True except Exception: pass # Docker too! It not only has problems with dangling CNAME, but also says NXDOMAIN when # it should say no error no data. _is_docker = tests.util.is_docker() resolv_conf = """ /t/t # comment 1 ; comment 2 domain foo nameserver 10.0.0.1 nameserver 10.0.0.2 """ resolv_conf_options1 = """ nameserver 10.0.0.1 nameserver 10.0.0.2 search search1 search2 options rotate timeout:1 edns0 ndots:2 """ bad_timeout_1 = """ nameserver 10.0.0.1 nameserver 10.0.0.2 options rotate timeout """ bad_timeout_2 = """ nameserver 10.0.0.1 nameserver 10.0.0.2 options rotate timeout:bogus """ bad_ndots_1 = """ nameserver 10.0.0.1 nameserver 10.0.0.2 options rotate ndots """ bad_ndots_2 = """ nameserver 10.0.0.1 nameserver 10.0.0.2 options rotate ndots:bogus """ no_nameservers = """ options rotate """ unknown_and_bad_directives = """ nameserver 10.0.0.1 foo bar bad """ unknown_option = """ nameserver 10.0.0.1 option foobar """ message_text = """id 1234 opcode QUERY rcode NOERROR flags QR AA RD ;QUESTION example. IN A ;ANSWER example. 1 IN A 10.0.0.1 ;AUTHORITY ;ADDITIONAL """ message_text_mx = """id 1234 opcode QUERY rcode NOERROR flags QR AA RD ;QUESTION example. IN MX ;ANSWER example. 1 IN A 10.0.0.1 ;AUTHORITY ;ADDITIONAL """ dangling_cname_0_message_text = """id 10000 opcode QUERY rcode NOERROR flags QR AA RD RA ;QUESTION 91.11.17.172.in-addr.arpa.none. IN PTR ;ANSWER ;AUTHORITY ;ADDITIONAL """ dangling_cname_1_message_text = """id 10001 opcode QUERY rcode NOERROR flags QR AA RD RA ;QUESTION 91.11.17.172.in-addr.arpa. IN PTR ;ANSWER 11.17.172.in-addr.arpa. 86400 IN DNAME 11.8-22.17.172.in-addr.arpa. 91.11.17.172.in-addr.arpa. 86400 IN CNAME 91.11.8-22.17.172.in-addr.arpa. ;AUTHORITY ;ADDITIONAL """ dangling_cname_2_message_text = """id 10002 opcode QUERY rcode NOERROR flags QR AA RD RA ;QUESTION 91.11.17.172.in-addr.arpa.example. IN PTR ;ANSWER 91.11.17.172.in-addr.arpa.example. 86400 IN CNAME 91.11.17.172.in-addr.arpa.base. 91.11.17.172.in-addr.arpa.base. 86400 IN CNAME 91.11.17.172.clients.example. 91.11.17.172.clients.example. 86400 IN CNAME 91-11-17-172.dynamic.example. ;AUTHORITY ;ADDITIONAL """ class FakeAnswer(object): def __init__(self, expiration): self.expiration = expiration class FakeTime: # Mock the clock! def __init__(self, now=None, want_fake=True): if now is None: now = time.time() self.now = now self.saved_time = time.time self.want_fake = want_fake def __enter__(self): if self.want_fake: time.time = self.time return self def __exit__(self, exc_type, exc_value, traceback): if self.want_fake: time.time = self.saved_time return False def time(self): if self.want_fake: return self.now else: return time.time() def sleep(self, offset): if self.want_fake: self.now += offset else: time.sleep(offset) class BaseResolverTests(unittest.TestCase): def testRead(self): f = StringIO(resolv_conf) r = dns.resolver.Resolver(configure=False) r.read_resolv_conf(f) self.assertEqual(r.nameservers, ["10.0.0.1", "10.0.0.2"]) self.assertEqual(r.domain, dns.name.from_text("foo")) def testReadOptions(self): f = StringIO(resolv_conf_options1) r = dns.resolver.Resolver(configure=False) r.read_resolv_conf(f) self.assertEqual(r.nameservers, ["10.0.0.1", "10.0.0.2"]) self.assertTrue(r.rotate) self.assertEqual(r.timeout, 1) self.assertEqual(r.ndots, 2) self.assertEqual(r.edns, 0) self.assertEqual(r.payload, dns.message.DEFAULT_EDNS_PAYLOAD) def testReadOptionsBadTimeouts(self): f = StringIO(bad_timeout_1) r = dns.resolver.Resolver(configure=False) r.read_resolv_conf(f) # timeout should still be default self.assertEqual(r.timeout, 2.0) f = StringIO(bad_timeout_2) r = dns.resolver.Resolver(configure=False) r.read_resolv_conf(f) # timeout should still be default self.assertEqual(r.timeout, 2.0) def testReadOptionsBadNdots(self): f = StringIO(bad_ndots_1) r = dns.resolver.Resolver(configure=False) r.read_resolv_conf(f) # ndots should still be default self.assertEqual(r.ndots, None) f = StringIO(bad_ndots_2) r = dns.resolver.Resolver(configure=False) r.read_resolv_conf(f) # ndots should still be default self.assertEqual(r.ndots, None) def testReadNoNameservers(self): f = StringIO(no_nameservers) r = dns.resolver.Resolver(configure=False) with self.assertRaises(dns.resolver.NoResolverConfiguration): r.read_resolv_conf(f) def testReadUnknownDirective(self): # The real test here is ignoring the unknown directive and the bad # directive. f = StringIO(unknown_and_bad_directives) r = dns.resolver.Resolver(configure=False) r.read_resolv_conf(f) self.assertEqual(r.nameservers, ["10.0.0.1"]) def testReadUnknownOption(self): # The real test here is ignoring the unknown option f = StringIO(unknown_option) r = dns.resolver.Resolver(configure=False) r.read_resolv_conf(f) self.assertEqual(r.nameservers, ["10.0.0.1"]) def testCacheExpiration(self): with FakeTime() as fake_time: message = dns.message.from_text(message_text) name = dns.name.from_text("example.") answer = dns.resolver.Answer( name, dns.rdatatype.A, dns.rdataclass.IN, message ) cache = dns.resolver.Cache() cache.put((name, dns.rdatatype.A, dns.rdataclass.IN), answer) fake_time.sleep(2) self.assertTrue( cache.get((name, dns.rdatatype.A, dns.rdataclass.IN)) is None ) def testCacheCleaning(self): with FakeTime() as fake_time: message = dns.message.from_text(message_text) name = dns.name.from_text("example.") answer = dns.resolver.Answer( name, dns.rdatatype.A, dns.rdataclass.IN, message ) cache = dns.resolver.Cache(cleaning_interval=1.0) cache.put((name, dns.rdatatype.A, dns.rdataclass.IN), answer) fake_time.sleep(2) cache._maybe_clean() self.assertTrue( cache.data.get((name, dns.rdatatype.A, dns.rdataclass.IN)) is None ) def testCacheNonCleaning(self): with FakeTime() as fake_time: message = dns.message.from_text(message_text) name = dns.name.from_text("example.") answer = dns.resolver.Answer( name, dns.rdatatype.A, dns.rdataclass.IN, message ) # override TTL as we're testing non-cleaning answer.expiration = fake_time.time() + 100 cache = dns.resolver.Cache(cleaning_interval=1.0) cache.put((name, dns.rdatatype.A, dns.rdataclass.IN), answer) fake_time.sleep(1.1) self.assertEqual( cache.get((name, dns.rdatatype.A, dns.rdataclass.IN)), answer ) def testIndexErrorOnEmptyRRsetAccess(self): def bad(): message = dns.message.from_text(message_text_mx) name = dns.name.from_text("example.") answer = dns.resolver.Answer( name, dns.rdatatype.MX, dns.rdataclass.IN, message ) return answer[0] self.assertRaises(IndexError, bad) def testIndexErrorOnEmptyRRsetDelete(self): def bad(): message = dns.message.from_text(message_text_mx) name = dns.name.from_text("example.") answer = dns.resolver.Answer( name, dns.rdatatype.MX, dns.rdataclass.IN, message ) del answer[0] self.assertRaises(IndexError, bad) def testRRsetDelete(self): message = dns.message.from_text(message_text) name = dns.name.from_text("example.") answer = dns.resolver.Answer(name, dns.rdatatype.A, dns.rdataclass.IN, message) del answer[0] self.assertEqual(len(answer), 0) def testLRUReplace(self): cache = dns.resolver.LRUCache(4) for i in range(0, 5): name = dns.name.from_text("example%d." % i) answer = FakeAnswer(time.time() + 1) cache.put((name, dns.rdatatype.A, dns.rdataclass.IN), answer) for i in range(0, 5): name = dns.name.from_text("example%d." % i) if i == 0: self.assertTrue( cache.get((name, dns.rdatatype.A, dns.rdataclass.IN)) is None ) else: self.assertTrue( not cache.get((name, dns.rdatatype.A, dns.rdataclass.IN)) is None ) def testLRUDoesLRU(self): cache = dns.resolver.LRUCache(4) for i in range(0, 4): name = dns.name.from_text("example%d." % i) answer = FakeAnswer(time.time() + 1) cache.put((name, dns.rdatatype.A, dns.rdataclass.IN), answer) name = dns.name.from_text("example0.") cache.get((name, dns.rdatatype.A, dns.rdataclass.IN)) # The LRU is now example1. name = dns.name.from_text("example4.") answer = FakeAnswer(time.time() + 1) cache.put((name, dns.rdatatype.A, dns.rdataclass.IN), answer) for i in range(0, 5): name = dns.name.from_text("example%d." % i) if i == 1: self.assertTrue( cache.get((name, dns.rdatatype.A, dns.rdataclass.IN)) is None ) else: self.assertTrue( not cache.get((name, dns.rdatatype.A, dns.rdataclass.IN)) is None ) def testLRUExpiration(self): with FakeTime() as fake_time: cache = dns.resolver.LRUCache(4) for i in range(0, 4): name = dns.name.from_text("example%d." % i) answer = FakeAnswer(time.time() + 1) cache.put((name, dns.rdatatype.A, dns.rdataclass.IN), answer) fake_time.sleep(2) for i in range(0, 4): name = dns.name.from_text("example%d." % i) self.assertTrue( cache.get((name, dns.rdatatype.A, dns.rdataclass.IN)) is None ) def test_cache_flush(self): name1 = dns.name.from_text("name1") name2 = dns.name.from_text("name2") name3 = dns.name.from_text("name3") basic_cache = dns.resolver.Cache() lru_cache = dns.resolver.LRUCache(100) for cache in [basic_cache, lru_cache]: answer1 = FakeAnswer(time.time() + 10) answer2 = FakeAnswer(time.time() + 10) cache.put((name1, dns.rdatatype.A, dns.rdataclass.IN), answer1) cache.put((name2, dns.rdatatype.A, dns.rdataclass.IN), answer2) canswer = cache.get((name1, dns.rdatatype.A, dns.rdataclass.IN)) self.assertTrue(canswer is answer1) canswer = cache.get((name2, dns.rdatatype.A, dns.rdataclass.IN)) self.assertTrue(canswer is answer2) # explicit flush of nonexistent key, just to exercise the branch cache.flush((name3, dns.rdatatype.A, dns.rdataclass.IN)) canswer = cache.get((name1, dns.rdatatype.A, dns.rdataclass.IN)) self.assertTrue(canswer is answer1) canswer = cache.get((name2, dns.rdatatype.A, dns.rdataclass.IN)) self.assertTrue(canswer is answer2) # explicit flush cache.flush((name1, dns.rdatatype.A, dns.rdataclass.IN)) canswer = cache.get((name1, dns.rdatatype.A, dns.rdataclass.IN)) self.assertTrue(canswer is None) canswer = cache.get((name2, dns.rdatatype.A, dns.rdataclass.IN)) self.assertTrue(canswer is answer2) # flush all cache.flush() canswer = cache.get((name1, dns.rdatatype.A, dns.rdataclass.IN)) self.assertTrue(canswer is None) canswer = cache.get((name2, dns.rdatatype.A, dns.rdataclass.IN)) self.assertTrue(canswer is None) def test_LRUCache_set_max_size(self): cache = dns.resolver.LRUCache(4) self.assertEqual(cache.max_size, 4) cache.set_max_size(0) self.assertEqual(cache.max_size, 1) def test_LRUCache_overwrite(self): def on_lru_list(cache, key, value): cnode = cache.sentinel.next while cnode != cache.sentinel: if cnode.key == key and cnode.value is value: return True cnode = cnode.next return False cache = dns.resolver.LRUCache(4) answer1 = FakeAnswer(time.time() + 10) answer2 = FakeAnswer(time.time() + 10) key = (dns.name.from_text("key."), dns.rdatatype.A, dns.rdataclass.IN) cache.put(key, answer1) canswer = cache.get(key) self.assertTrue(canswer is answer1) self.assertTrue(on_lru_list(cache, key, answer1)) cache.put(key, answer2) canswer = cache.get(key) self.assertTrue(canswer is answer2) self.assertFalse(on_lru_list(cache, key, answer1)) self.assertTrue(on_lru_list(cache, key, answer2)) def test_cache_stats(self): caches = [dns.resolver.Cache(), dns.resolver.LRUCache(4)] key1 = (dns.name.from_text("key1."), dns.rdatatype.A, dns.rdataclass.IN) key2 = (dns.name.from_text("key2."), dns.rdatatype.A, dns.rdataclass.IN) for cache in caches: answer1 = FakeAnswer(time.time() + 10) answer2 = FakeAnswer(10) # expired! a = cache.get(key1) self.assertIsNone(a) self.assertEqual(cache.hits(), 0) self.assertEqual(cache.misses(), 1) if isinstance(cache, dns.resolver.LRUCache): self.assertEqual(cache.get_hits_for_key(key1), 0) cache.put(key1, answer1) a = cache.get(key1) self.assertIs(a, answer1) self.assertEqual(cache.hits(), 1) self.assertEqual(cache.misses(), 1) if isinstance(cache, dns.resolver.LRUCache): self.assertEqual(cache.get_hits_for_key(key1), 1) cache.put(key2, answer2) a = cache.get(key2) self.assertIsNone(a) self.assertEqual(cache.hits(), 1) self.assertEqual(cache.misses(), 2) if isinstance(cache, dns.resolver.LRUCache): self.assertEqual(cache.get_hits_for_key(key2), 0) stats = cache.get_statistics_snapshot() self.assertEqual(stats.hits, 1) self.assertEqual(stats.misses, 2) cache.reset_statistics() stats = cache.get_statistics_snapshot() self.assertEqual(stats.hits, 0) self.assertEqual(stats.misses, 0) def testEmptyAnswerSection(self): # TODO: dangling_cname_0_message_text was the only sample message # with an empty answer section. Other than that it doesn't # apply. message = dns.message.from_text(dangling_cname_0_message_text) name = dns.name.from_text("example.") answer = dns.resolver.Answer(name, dns.rdatatype.A, dns.rdataclass.IN, message) def test_python_internal_truth(answer): if answer: return True else: return False self.assertFalse(test_python_internal_truth(answer)) for a in answer: pass def testSearchListsRelative(self): res = dns.resolver.Resolver(configure=False) res.domain = dns.name.from_text("example") res.search = [dns.name.from_text(x) for x in ["dnspython.org", "dnspython.net"]] qname = dns.name.from_text("www", None) qnames = res._get_qnames_to_try(qname, True) self.assertEqual( qnames, [ dns.name.from_text(x) for x in ["www.dnspython.org", "www.dnspython.net", "www."] ], ) qnames = res._get_qnames_to_try(qname, False) self.assertEqual(qnames, [dns.name.from_text("www.")]) qnames = res._get_qnames_to_try(qname, None) self.assertEqual(qnames, [dns.name.from_text("www.")]) # # Now change search default on resolver to True # res.use_search_by_default = True qnames = res._get_qnames_to_try(qname, None) self.assertEqual( qnames, [ dns.name.from_text(x) for x in ["www.dnspython.org", "www.dnspython.net", "www."] ], ) # # Now test ndots # qname = dns.name.from_text("a.b", None) res.ndots = 1 qnames = res._get_qnames_to_try(qname, True) self.assertEqual( qnames, [ dns.name.from_text(x) for x in ["a.b", "a.b.dnspython.org", "a.b.dnspython.net"] ], ) res.ndots = 2 qnames = res._get_qnames_to_try(qname, True) self.assertEqual( qnames, [ dns.name.from_text(x) for x in ["a.b.dnspython.org", "a.b.dnspython.net", "a.b"] ], ) qname = dns.name.from_text("a.b.c", None) qnames = res._get_qnames_to_try(qname, True) self.assertEqual( qnames, [ dns.name.from_text(x) for x in ["a.b.c", "a.b.c.dnspython.org", "a.b.c.dnspython.net"] ], ) def testSearchListsAbsolute(self): res = dns.resolver.Resolver(configure=False) qname = dns.name.from_text("absolute") qnames = res._get_qnames_to_try(qname, True) self.assertEqual(qnames, [qname]) qnames = res._get_qnames_to_try(qname, False) self.assertEqual(qnames, [qname]) qnames = res._get_qnames_to_try(qname, None) self.assertEqual(qnames, [qname]) def testUseEDNS(self): r = dns.resolver.Resolver(configure=False) r.use_edns(None) self.assertEqual(r.edns, -1) r.use_edns(False) self.assertEqual(r.edns, -1) r.use_edns(True) self.assertEqual(r.edns, 0) def testSetFlags(self): flags = dns.flags.CD | dns.flags.RD r = dns.resolver.Resolver(configure=False) r.set_flags(flags) self.assertEqual(r.flags, flags) def testUseTSIG(self): keyring = dns.tsigkeyring.from_text({"keyname.": "NjHwPsMKjdN++dOfE5iAiQ=="}) r = dns.resolver.Resolver(configure=False) r.use_tsig(keyring) self.assertEqual(r.keyring, keyring) self.assertEqual(r.keyname, None) self.assertEqual(r.keyalgorithm, dns.tsig.default_algorithm) keyname = dns.name.from_text("keyname") @unittest.skipIf(not tests.util.is_internet_reachable(), "Internet not reachable") class LiveResolverTests(unittest.TestCase): def testZoneForName1(self): name = dns.name.from_text("www.dnspython.org.") ezname = dns.name.from_text("dnspython.org.") zname = dns.resolver.zone_for_name(name) self.assertEqual(zname, ezname) def testZoneForName2(self): name = dns.name.from_text("a.b.www.dnspython.org.") ezname = dns.name.from_text("dnspython.org.") zname = dns.resolver.zone_for_name(name) self.assertEqual(zname, ezname) def testZoneForName3(self): ezname = dns.name.from_text("dnspython.org.") zname = dns.resolver.zone_for_name("dnspython.org.") self.assertEqual(zname, ezname) def testZoneForName4(self): def bad(): name = dns.name.from_text("dnspython.org", None) dns.resolver.zone_for_name(name) self.assertRaises(dns.resolver.NotAbsolute, bad) def testResolve(self): answer = dns.resolver.resolve("dns.google.", "A") seen = set([rdata.address for rdata in answer]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) def testResolveTCP(self): answer = dns.resolver.resolve("dns.google.", "A", tcp=True) seen = set([rdata.address for rdata in answer]) self.assertTrue("8.8.8.8" in seen) self.assertTrue("8.8.4.4" in seen) def testResolveAddress(self): answer = dns.resolver.resolve_address("8.8.8.8") dnsgoogle = dns.name.from_text("dns.google.") self.assertEqual(answer[0].target, dnsgoogle) def testResolveName(self): answers = dns.resolver.resolve_name("dns.google.") seen = set(answers.addresses()) self.assertEqual(len(seen), 4) self.assertIn("8.8.8.8", seen) self.assertIn("8.8.4.4", seen) self.assertIn("2001:4860:4860::8844", seen) self.assertIn("2001:4860:4860::8888", seen) answers = dns.resolver.resolve_name("dns.google.", socket.AF_INET) seen = set(answers.addresses()) self.assertEqual(len(seen), 2) self.assertIn("8.8.8.8", seen) self.assertIn("8.8.4.4", seen) answers = dns.resolver.resolve_name("dns.google.", socket.AF_INET6) seen = set(answers.addresses()) self.assertEqual(len(seen), 2) self.assertIn("2001:4860:4860::8844", seen) self.assertIn("2001:4860:4860::8888", seen) with self.assertRaises(dns.resolver.NXDOMAIN): dns.resolver.resolve_name("nxdomain.dnspython.org") if not _is_docker: # We skip docker as it says NXDOMAIN! with self.assertRaises(dns.resolver.NoAnswer): dns.resolver.resolve_name(dns.reversename.from_address("8.8.8.8")) @patch.object(dns.message.Message, "use_edns") def testResolveEdnsOptions(self, message_use_edns_mock): resolver = dns.resolver.Resolver() options = [dns.edns.ECSOption("1.1.1.1")] resolver.use_edns(True, options=options) resolver.resolve("dns.google.", "A") assert {"options": options} in message_use_edns_mock.call_args def testResolveNodataException(self): def bad(): dns.resolver.resolve("dnspython.org.", "SRV") self.assertRaises(dns.resolver.NoAnswer, bad) def testResolveNodataAnswer(self): qname = dns.name.from_text("dnspython.org") qclass = dns.rdataclass.from_text("IN") qtype = dns.rdatatype.from_text("SRV") answer = dns.resolver.resolve(qname, qtype, raise_on_no_answer=False) self.assertRaises( KeyError, lambda: answer.response.find_rrset( answer.response.answer, qname, qclass, qtype ), ) def testResolveNXDOMAIN(self): qname = dns.name.from_text("nxdomain.dnspython.org") qclass = dns.rdataclass.from_text("IN") qtype = dns.rdatatype.from_text("A") def bad(): answer = dns.resolver.resolve(qname, qtype) try: dns.resolver.resolve(qname, qtype) self.assertTrue(False) # should not happen! except dns.resolver.NXDOMAIN as nx: self.assertIn(qname, nx.qnames()) self.assertGreaterEqual(len(nx.responses()), 1) @unittest.skipIf(not tests.util.have_ipv4(), "IPv4 not reachable") def testResolveCacheHit(self): res = dns.resolver.Resolver(configure=False) res.nameservers = ["8.8.8.8"] res.cache = dns.resolver.Cache() answer1 = res.resolve("dns.google.", "A") seen = set([rdata.address for rdata in answer1]) self.assertIn("8.8.8.8", seen) self.assertIn("8.8.4.4", seen) answer2 = res.resolve("dns.google.", "A") self.assertIs(answer2, answer1) @unittest.skipIf(not tests.util.have_ipv4(), "IPv4 not reachable") def testTLSNameserver(self): res = dns.resolver.Resolver(configure=False) res.nameservers = [dns.nameserver.DoTNameserver("8.8.8.8", 853)] answer = res.resolve("dns.google.", "A") seen = set([rdata.address for rdata in answer]) self.assertIn("8.8.8.8", seen) self.assertIn("8.8.4.4", seen) @unittest.skipIf( not (tests.util.have_ipv4() and dns.quic.have_quic), "IPv4 not reachable or QUIC not available", ) def testQuicNameserver(self): res = dns.resolver.Resolver(configure=False) res.nameservers = [dns.nameserver.DoQNameserver("94.140.14.14", 784)] answer = res.resolve("dns.adguard.com.", "A") seen = set([rdata.address for rdata in answer]) self.assertIn("94.140.14.14", seen) self.assertIn("94.140.15.15", seen) @unittest.skipIf(not tests.util.have_ipv4(), "IPv4 not reachable") def testResolveAtAddress(self): answer = dns.resolver.resolve_at("8.8.8.8", "dns.google.", "A") seen = set([rdata.address for rdata in answer]) self.assertIn("8.8.8.8", seen) self.assertIn("8.8.4.4", seen) @unittest.skipIf(not tests.util.have_ipv4(), "IPv4 not reachable") def testResolveAtName(self): answer = dns.resolver.resolve_at( "dns.google", "dns.google.", "A", family=socket.AF_INET ) seen = set([rdata.address for rdata in answer]) self.assertIn("8.8.8.8", seen) self.assertIn("8.8.4.4", seen) def testCanonicalNameNoCNAME(self): cname = dns.name.from_text("www.google.com") self.assertEqual(dns.resolver.canonical_name("www.google.com"), cname) def testCanonicalNameCNAME(self): name = dns.name.from_text("www.dnspython.org") cname = dns.name.from_text("dmfrjf4ips8xa.cloudfront.net") self.assertEqual(dns.resolver.canonical_name(name), cname) @unittest.skipIf( _systemd_resolved_present or _is_docker, "systemd-resolved or docker in use" ) def testCanonicalNameDangling(self): name = dns.name.from_text("dangling-cname.dnspython.org") cname = dns.name.from_text("dangling-target.dnspython.org") self.assertEqual(dns.resolver.canonical_name(name), cname) def testNameserverSetting(self): res = dns.resolver.Resolver(configure=False) ns = ["1.2.3.4", "::1", "https://ns.example"] res.nameservers = ns[:] self.assertEqual(res.nameservers, ns) for ns in ["999.999.999.999", "ns.example.", "bogus://ns.example"]: with self.assertRaises(ValueError): res.nameservers = [ns] class NXDOMAINExceptionTestCase(unittest.TestCase): # pylint: disable=broad-except def test_nxdomain_compatible(self): n1 = dns.name.Name(("a", "b", "")) n2 = dns.name.Name(("a", "b", "s", "")) try: raise dns.resolver.NXDOMAIN except dns.exception.DNSException as e: self.assertEqual(e.args, (e.__doc__,)) self.assertTrue(("kwargs" in dir(e))) self.assertEqual(str(e), e.__doc__, str(e)) self.assertTrue(("qnames" not in e.kwargs)) self.assertTrue(("responses" not in e.kwargs)) try: raise dns.resolver.NXDOMAIN("errmsg") except dns.exception.DNSException as e: self.assertEqual(e.args, ("errmsg",)) self.assertTrue(("kwargs" in dir(e))) self.assertEqual(str(e), "errmsg", str(e)) self.assertTrue(("qnames" not in e.kwargs)) self.assertTrue(("responses" not in e.kwargs)) try: raise dns.resolver.NXDOMAIN("errmsg", -1) except dns.exception.DNSException as e: self.assertEqual(e.args, ("errmsg", -1)) self.assertTrue(("kwargs" in dir(e))) self.assertEqual(str(e), "('errmsg', -1)", str(e)) self.assertTrue(("qnames" not in e.kwargs)) self.assertTrue(("responses" not in e.kwargs)) try: raise dns.resolver.NXDOMAIN(qnames=None) except Exception as e: self.assertTrue((isinstance(e, AttributeError))) try: raise dns.resolver.NXDOMAIN(qnames=n1) except Exception as e: self.assertTrue((isinstance(e, AttributeError))) try: raise dns.resolver.NXDOMAIN(qnames=[]) except Exception as e: self.assertTrue((isinstance(e, AttributeError))) try: raise dns.resolver.NXDOMAIN(qnames=[n1]) except dns.exception.DNSException as e: MSG = "The DNS query name does not exist: a.b." self.assertEqual(e.args, (MSG,), repr(e.args)) self.assertTrue(("kwargs" in dir(e))) self.assertEqual(str(e), MSG, str(e)) self.assertTrue(("qnames" in e.kwargs)) self.assertEqual(e.kwargs["qnames"], [n1]) self.assertTrue(("responses" in e.kwargs)) self.assertEqual(e.kwargs["responses"], {}) try: raise dns.resolver.NXDOMAIN(qnames=[n2, n1]) except dns.resolver.NXDOMAIN as e: e0 = dns.resolver.NXDOMAIN("errmsg") e = e0 + e MSG = "None of DNS query names exist: a.b.s., a.b." self.assertEqual(e.args, (MSG,), repr(e.args)) self.assertTrue(("kwargs" in dir(e))) self.assertEqual(str(e), MSG, str(e)) self.assertTrue(("qnames" in e.kwargs)) self.assertEqual(e.kwargs["qnames"], [n2, n1]) self.assertTrue(("responses" in e.kwargs)) self.assertEqual(e.kwargs["responses"], {}) try: raise dns.resolver.NXDOMAIN(qnames=[n1], responses=["r1.1"]) except Exception as e: self.assertTrue((isinstance(e, AttributeError))) try: raise dns.resolver.NXDOMAIN(qnames=[n1], responses={n1: "r1.1"}) except dns.resolver.NXDOMAIN as e: MSG = "The DNS query name does not exist: a.b." self.assertEqual(e.args, (MSG,), repr(e.args)) self.assertTrue(("kwargs" in dir(e))) self.assertEqual(str(e), MSG, str(e)) self.assertTrue(("qnames" in e.kwargs)) self.assertEqual(e.kwargs["qnames"], [n1]) self.assertTrue(("responses" in e.kwargs)) self.assertEqual(e.kwargs["responses"], {n1: "r1.1"}) def test_nxdomain_merge(self): n1 = dns.name.Name(("a", "b", "")) n2 = dns.name.Name(("a", "b", "")) n3 = dns.name.Name(("a", "b", "c", "")) n4 = dns.name.Name(("a", "b", "d", "")) responses1 = {n1: "r1.1", n2: "r1.2", n4: "r1.4"} qnames1 = [n1, n4] # n2 == n1 responses2 = {n2: "r2.2", n3: "r2.3"} qnames2 = [n2, n3] e0 = dns.resolver.NXDOMAIN() e1 = dns.resolver.NXDOMAIN(qnames=qnames1, responses=responses1) e2 = dns.resolver.NXDOMAIN(qnames=qnames2, responses=responses2) e = e1 + e0 + e2 self.assertRaises(AttributeError, lambda: e0 + e0) self.assertEqual(e.kwargs["qnames"], [n1, n4, n3], repr(e.kwargs["qnames"])) self.assertTrue(e.kwargs["responses"][n1].startswith("r2.")) self.assertTrue(e.kwargs["responses"][n2].startswith("r2.")) self.assertTrue(e.kwargs["responses"][n3].startswith("r2.")) self.assertTrue(e.kwargs["responses"][n4].startswith("r1.")) def test_nxdomain_canonical_name(self): cname1 = "91.11.8-22.17.172.in-addr.arpa." cname2 = "91-11-17-172.dynamic.example." message0 = dns.message.from_text(dangling_cname_0_message_text) message1 = dns.message.from_text(dangling_cname_1_message_text) message2 = dns.message.from_text(dangling_cname_2_message_text) qname0 = message0.question[0].name qname1 = message1.question[0].name qname2 = message2.question[0].name responses = {qname0: message0, qname1: message1, qname2: message2} eX = dns.resolver.NXDOMAIN() e0 = dns.resolver.NXDOMAIN(qnames=[qname0], responses=responses) e1 = dns.resolver.NXDOMAIN(qnames=[qname0, qname1, qname2], responses=responses) e2 = dns.resolver.NXDOMAIN(qnames=[qname0, qname2, qname1], responses=responses) self.assertRaises(TypeError, lambda: eX.canonical_name) self.assertEqual(e0.canonical_name, qname0) self.assertEqual(e1.canonical_name, dns.name.from_text(cname1)) self.assertEqual(e2.canonical_name, dns.name.from_text(cname2)) class ResolverMiscTestCase(unittest.TestCase): if sys.platform != "win32": def test_read_nonexistent_config(self): res = dns.resolver.Resolver(configure=False) pathname = "/etc/nonexistent-resolv.conf" self.assertRaises( dns.resolver.NoResolverConfiguration, lambda: res.read_resolv_conf(pathname), ) def test_compute_timeout(self): res = dns.resolver.Resolver(configure=False) now = time.time() self.assertRaises( dns.resolver.Timeout, lambda: res._compute_timeout(now + 10000) ) self.assertRaises(dns.resolver.Timeout, lambda: res._compute_timeout(0)) # not raising is the test res._compute_timeout(now + 0.5) if sys.platform == "win32": def test_configure_win32_domain(self): n = dns.name.from_text("home.") self.assertEqual(n, dns.win32util._config_domain("home")) self.assertEqual(n, dns.win32util._config_domain(".home")) class ResolverNameserverValidTypeTestCase(unittest.TestCase): def test_set_nameservers_to_list(self): resolver = dns.resolver.Resolver(configure=False) resolver.nameservers = ["1.2.3.4"] self.assertEqual(resolver.nameservers, ["1.2.3.4"]) def test_set_namservers_to_empty_list(self): resolver = dns.resolver.Resolver(configure=False) resolver.nameservers = [] self.assertEqual(resolver.nameservers, []) def test_set_nameservers_invalid_type(self): resolver = dns.resolver.Resolver(configure=False) invalid_nameservers = [ None, "1.2.3.4", 1234, (1, 2, 3, 4), (), {"invalid": "nameserver"}, ] for invalid_nameserver in invalid_nameservers: with self.assertRaises(ValueError): resolver.nameservers = invalid_nameserver class NaptrNanoNameserver(Server): def handle(self, request): response = dns.message.make_response(request.message) response.set_rcode(dns.rcode.REFUSED) response.flags |= dns.flags.RA try: zero_subdomain = dns.e164.from_e164("0") if request.qname.is_subdomain(zero_subdomain): response.set_rcode(dns.rcode.NXDOMAIN) response.flags |= dns.flags.AA elif ( request.qtype == dns.rdatatype.NAPTR and request.qclass == dns.rdataclass.IN ): rrs = dns.rrset.from_text( request.qname, 300, "IN", "NAPTR", '0 0 "" "" "" .' ) response.answer.append(rrs) response.set_rcode(dns.rcode.NOERROR) response.flags |= dns.flags.AA except Exception: pass return response @unittest.skipIf( not (tests.util.is_internet_reachable() and _nanonameserver_available), "Internet and NanoAuth required", ) class NanoTests(unittest.TestCase): def testE164Query(self): with NaptrNanoNameserver() as na: res = dns.resolver.Resolver(configure=False) res.port = na.udp_address[1] res.nameservers = [na.udp_address[0]] answer = dns.e164.query("1650551212", ["e164.arpa"], res) self.assertEqual(answer[0].order, 0) self.assertEqual(answer[0].preference, 0) self.assertEqual(answer[0].flags, b"") self.assertEqual(answer[0].service, b"") self.assertEqual(answer[0].regexp, b"") self.assertEqual(answer[0].replacement, dns.name.root) with self.assertRaises(dns.resolver.NXDOMAIN): dns.e164.query("0123456789", ["e164.arpa"], res) class AlwaysType3NXDOMAINNanoNameserver(Server): def handle(self, request): response = dns.message.make_response(request.message) response.set_rcode(dns.rcode.NXDOMAIN) response.flags |= dns.flags.RA return response class AlwaysNXDOMAINNanoNameserver(Server): def handle(self, request): response = dns.message.make_response(request.message) response.set_rcode(dns.rcode.NXDOMAIN) response.flags |= dns.flags.RA origin = dns.name.from_text("example.") soa_rrset = response.find_rrset( response.authority, origin, dns.rdataclass.IN, dns.rdatatype.SOA, create=True, ) rdata = dns.rdata.from_text("IN", "SOA", "ns.example. root.example. 1 2 3 4 5") soa_rrset.add(rdata) soa_rrset.update_ttl(300) return response class AlwaysNoErrorNoDataNanoNameserver(Server): def handle(self, request): response = dns.message.make_response(request.message) response.set_rcode(dns.rcode.NOERROR) response.flags |= dns.flags.RA origin = dns.name.from_text("example.") soa_rrset = response.find_rrset( response.authority, origin, dns.rdataclass.IN, dns.rdatatype.SOA, create=True, ) rdata = dns.rdata.from_text("IN", "SOA", "ns.example. root.example. 1 2 3 4 5") soa_rrset.add(rdata) soa_rrset.update_ttl(300) return response @unittest.skipIf( not (tests.util.is_internet_reachable() and _nanonameserver_available), "Internet and NanoAuth required", ) class ZoneForNameTests(unittest.TestCase): def testNoRootSOA(self): with AlwaysType3NXDOMAINNanoNameserver() as na: res = dns.resolver.Resolver(configure=False) res.port = na.udp_address[1] res.nameservers = [na.udp_address[0]] with self.assertRaises(dns.resolver.NoRootSOA): dns.resolver.zone_for_name("www.foo.bar.", resolver=res) def testHelpfulNXDOMAIN(self): with AlwaysNXDOMAINNanoNameserver() as na: res = dns.resolver.Resolver(configure=False) res.port = na.udp_address[1] res.nameservers = [na.udp_address[0]] expected = dns.name.from_text("example.") name = dns.resolver.zone_for_name( "1.2.3.4.5.6.7.8.9.10.example.", resolver=res ) self.assertEqual(name, expected) def testHelpfulNoErrorNoData(self): with AlwaysNoErrorNoDataNanoNameserver() as na: res = dns.resolver.Resolver(configure=False) res.port = na.udp_address[1] res.nameservers = [na.udp_address[0]] expected = dns.name.from_text("example.") name = dns.resolver.zone_for_name( "1.2.3.4.5.6.7.8.9.10.example.", resolver=res ) self.assertEqual(name, expected) class DroppingNanoNameserver(Server): def handle(self, request): return None class FormErrNanoNameserver(Server): def handle(self, request): r = dns.message.make_response(request.message) r.set_rcode(dns.rcode.FORMERR) return r # we use pytest for these so we can have a "slow" mark later if we want to # (right now it's still fast enough we don't really need it) @pytest.mark.skipif( not (tests.util.is_internet_reachable() and _nanonameserver_available), reason="Internet and NanoAuth required", ) def testResolverTimeout(): with DroppingNanoNameserver() as na: res = dns.resolver.Resolver(configure=False) res.port = na.udp_address[1] res.nameservers = [na.udp_address[0]] res.timeout = 0.2 try: lifetime = 1.0 a = res.resolve("www.dnspython.org", lifetime=lifetime) assert False # should never happen except dns.resolver.LifetimeTimeout as e: assert e.kwargs["timeout"] >= lifetime # The length of errors can vary based on how slow things are, # but it ought to be > 1, so we assert that. errors = e.kwargs["errors"] assert len(errors) > 1 for error in errors: assert str(error[0]) == f"Do53:{na.udp_address[0]}@{na.udp_address[1]}" assert not error[1] # not TCP assert error[2] == na.udp_address[1] # port assert isinstance(error[3], dns.exception.Timeout) # exception @pytest.mark.skipif( not (tests.util.is_internet_reachable() and _nanonameserver_available), reason="Internet and NanoAuth required", ) def testResolverNoNameservers(): with FormErrNanoNameserver() as na: res = dns.resolver.Resolver(configure=False) res.port = na.udp_address[1] res.nameservers = [na.udp_address[0]] try: a = res.resolve("www.dnspython.org") assert False # should never happen except dns.resolver.NoNameservers as e: errors = e.kwargs["errors"] assert len(errors) == 1 for error in errors: assert error[0] == f"Do53:{na.udp_address[0]}@{na.udp_address[1]}" assert not error[1] # not TCP assert error[2] == na.udp_address[1] # port assert error[3] == "FORMERR" class SlowAlwaysType3NXDOMAINNanoNameserver(Server): def handle(self, request): response = dns.message.make_response(request.message) response.set_rcode(dns.rcode.NXDOMAIN) response.flags |= dns.flags.RA time.sleep(0.2) return response @pytest.mark.skipif( not (tests.util.is_internet_reachable() and _nanonameserver_available), reason="Internet and NanoAuth required", ) def testZoneForNameLifetimeTimeout(): with SlowAlwaysType3NXDOMAINNanoNameserver() as na: res = dns.resolver.Resolver(configure=False) res.port = na.udp_address[1] res.nameservers = [na.udp_address[0]] with pytest.raises(dns.resolver.LifetimeTimeout): dns.resolver.zone_for_name( "1.2.3.4.5.6.7.8.9.10.example.", resolver=res, lifetime=1.0 ) dnspython-2.7.0/tests/test_resolver_override.py0000644000000000000000000002176513615410400017014 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import socket import sys import unittest import dns.name import dns.rdataclass import dns.rdatatype import dns.resolver import tests.util @unittest.skipIf(not tests.util.is_internet_reachable(), "Internet not reachable") class OverrideSystemResolverTestCase(unittest.TestCase): def setUp(self): self.res = dns.resolver.Resolver(configure=False) self.res.nameservers = ["8.8.8.8"] self.res.cache = dns.resolver.LRUCache() dns.resolver.override_system_resolver(self.res) def tearDown(self): dns.resolver.restore_system_resolver() self.res = None def test_override(self): self.assertTrue(socket.getaddrinfo is dns.resolver._getaddrinfo) socket.gethostbyname("www.dnspython.org") answer = self.res.cache.get( ( dns.name.from_text("www.dnspython.org."), dns.rdatatype.A, dns.rdataclass.IN, ) ) self.assertTrue(answer is not None) self.res.cache.flush() socket.gethostbyname_ex("www.dnspython.org") answer = self.res.cache.get( ( dns.name.from_text("www.dnspython.org."), dns.rdatatype.A, dns.rdataclass.IN, ) ) self.assertTrue(answer is not None) self.res.cache.flush() socket.getfqdn("8.8.8.8") answer = self.res.cache.get( ( dns.name.from_text("8.8.8.8.in-addr.arpa."), dns.rdatatype.PTR, dns.rdataclass.IN, ) ) self.assertTrue(answer is not None) self.res.cache.flush() socket.gethostbyaddr("8.8.8.8") answer = self.res.cache.get( ( dns.name.from_text("8.8.8.8.in-addr.arpa."), dns.rdatatype.PTR, dns.rdataclass.IN, ) ) self.assertTrue(answer is not None) # restoring twice is harmless, so we restore now instead of # waiting for tearDown so we can assert that it worked dns.resolver.restore_system_resolver() self.assertTrue(socket.getaddrinfo is dns.resolver._original_getaddrinfo) def equivalent_info(self, a, b, q): if len(a) != len(b): return False for x in a: if x not in b: # Windows does not set the protocol to non-zero, so try # looking for a zero protocol. y = (x[0], x[1], 0, x[3], x[4]) if y not in b: # musl libc insists on always providing a canonical name, so # accept that too. y = (x[0], x[1], x[2], q, x[4]) if y not in b: print("NOT EQUIVALENT") print(a) print(b) return False return True def equivalent(self, *args, **kwargs): q = args[0] a = socket.getaddrinfo(*args, **kwargs) b = dns.resolver._original_getaddrinfo(*args, **kwargs) return self.equivalent_info(a, b, q) @unittest.skipIf( sys.platform == "win32", "avoid windows original getaddrinfo issues" ) def test_basic_getaddrinfo(self): self.assertTrue( self.equivalent("dns.google", 53, socket.AF_INET, socket.SOCK_DGRAM) ) self.assertTrue( self.equivalent("dns.google", 53, socket.AF_INET6, socket.SOCK_DGRAM) ) self.assertTrue( self.equivalent("dns.google", None, socket.AF_UNSPEC, socket.SOCK_DGRAM) ) self.assertTrue( self.equivalent("8.8.8.8", 53, socket.AF_INET, socket.SOCK_DGRAM) ) self.assertTrue( self.equivalent( "2001:4860:4860::8888", 53, socket.AF_INET6, socket.SOCK_DGRAM ) ) self.assertTrue( self.equivalent( "8.8.8.8", 53, socket.AF_INET, socket.SOCK_DGRAM, flags=socket.AI_NUMERICHOST, ) ) self.assertTrue( self.equivalent( "2001:4860:4860::8888", 53, socket.AF_INET6, socket.SOCK_DGRAM, flags=socket.AI_NUMERICHOST, ) ) def test_getaddrinfo_nxdomain(self): try: socket.getaddrinfo("nxdomain.dnspython.org.", 53) self.assertTrue(False) # should not happen! except socket.gaierror as e: self.assertEqual(e.errno, socket.EAI_NONAME) def test_getaddrinfo_service(self): a = socket.getaddrinfo("dns.google", "domain") b = socket.getaddrinfo("dns.google", 53) self.assertTrue(self.equivalent_info(a, b, "dns.google")) try: socket.getaddrinfo("dns.google", "domain", flags=socket.AI_NUMERICSERV) self.assertTrue(False) # should not happen! except socket.gaierror as e: self.assertEqual(e.errno, socket.EAI_NONAME) def test_getaddrinfo_only_service(self): infos = socket.getaddrinfo( service=53, family=socket.AF_INET, socktype=socket.SOCK_DGRAM, proto=socket.IPPROTO_UDP, ) self.assertEqual(len(infos), 1) info = infos[0] self.assertEqual(info[0], socket.AF_INET) self.assertEqual(info[1], socket.SOCK_DGRAM) self.assertEqual(info[2], socket.IPPROTO_UDP) self.assertEqual(info[4], ("127.0.0.1", 53)) def test_unknown_service_fails(self): with self.assertRaises(socket.gaierror): socket.getaddrinfo("dns.google.", "bogus-service") def test_getnameinfo_tcp(self): info = socket.getnameinfo(("8.8.8.8", 53)) self.assertEqual(info, ("dns.google", "domain")) def test_getnameinfo_udp(self): info = socket.getnameinfo(("8.8.8.8", 53), socket.NI_DGRAM) self.assertEqual(info, ("dns.google", "domain")) # Give up on testing this for now as all of the names I've considered # using for testing are part of CDNs and there is deep magic in # gethostbyaddr() that python's getfqdn() is using. At any rate, # the problem is that dnspython just gives up whereas the native python # code is looking up www.dnspython.org, picking a CDN IPv4 address # (sometimes) and returning the reverse lookup of that address (i.e. # the domain name of the CDN server). This isn't what I'd consider the # FQDN of www.dnspython.org to be! # # def test_getfqdn(self): # b = socket.getfqdn('www.dnspython.org') # # we do this now because python's original getfqdn calls # # gethostbyaddr() and we don't want it to call us! # dns.resolver.restore_system_resolver() # a = dns.resolver._original_getfqdn('www.dnspython.org') # self.assertEqual(dns.name.from_text(a), dns.name.from_text(b)) def test_gethostbyaddr(self): a = dns.resolver._original_gethostbyaddr("8.8.8.8") b = socket.gethostbyaddr("8.8.8.8") # We only test elements 0 and 2 as we don't set aliases currently! self.assertEqual(a[0], b[0]) self.assertEqual(a[2], b[2]) a = dns.resolver._original_gethostbyaddr("2001:4860:4860::8888") b = socket.gethostbyaddr("2001:4860:4860::8888") self.assertEqual(a[0], b[0]) self.assertEqual(a[2], b[2]) class FakeResolver: def resolve(self, *args, **kwargs): raise dns.exception.Timeout class OverrideSystemResolverUsingFakeResolverTestCase(unittest.TestCase): def setUp(self): self.res = FakeResolver() dns.resolver.override_system_resolver(self.res) def tearDown(self): dns.resolver.restore_system_resolver() self.res = None def test_temporary_failure(self): with self.assertRaises(socket.gaierror): socket.getaddrinfo("dns.google") # We don't need the fake resolver for the following tests, but we # don't need the live network either, so we're testing here. def test_no_host_or_service_fails(self): with self.assertRaises(socket.gaierror): socket.getaddrinfo() def test_AI_ADDRCONFIG_fails(self): with self.assertRaises(socket.gaierror): socket.getaddrinfo("dns.google", flags=socket.AI_ADDRCONFIG) def test_gethostbyaddr_of_name_fails(self): with self.assertRaises(socket.gaierror): socket.gethostbyaddr("bogus") @unittest.skipIf(not tests.util.is_internet_reachable(), "Internet not reachable") class OverrideSystemResolverUsingDefaultResolverTestCase(unittest.TestCase): def setUp(self): self.res = FakeResolver() dns.resolver.override_system_resolver() def tearDown(self): dns.resolver.restore_system_resolver() self.res = None def test_override(self): self.assertEqual(dns.resolver._resolver, dns.resolver.default_resolver) dnspython-2.7.0/tests/test_rrset.py0000644000000000000000000002003413615410400014377 0ustar00# -*- coding: utf-8 # 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 unittest import dns.name import dns.rrset class RRsetTestCase(unittest.TestCase): def testEqual1(self): r1 = dns.rrset.from_text("foo", 300, "in", "a", "10.0.0.1", "10.0.0.2") r2 = dns.rrset.from_text("FOO", 300, "in", "a", "10.0.0.2", "10.0.0.1") self.assertEqual(r1, r2) def testEqual2(self): r1 = dns.rrset.from_text("foo", 300, "in", "a", "10.0.0.1", "10.0.0.2") r2 = dns.rrset.from_text("FOO", 600, "in", "a", "10.0.0.2", "10.0.0.1") self.assertEqual(r1, r2) def testNotEqual1(self): r1 = dns.rrset.from_text("fooa", 30, "in", "a", "10.0.0.1", "10.0.0.2") r2 = dns.rrset.from_text("FOO", 30, "in", "a", "10.0.0.2", "10.0.0.1") self.assertNotEqual(r1, r2) def testNotEqual2(self): r1 = dns.rrset.from_text("foo", 30, "in", "a", "10.0.0.1", "10.0.0.3") r2 = dns.rrset.from_text("FOO", 30, "in", "a", "10.0.0.2", "10.0.0.1") self.assertNotEqual(r1, r2) def testNotEqual3(self): r1 = dns.rrset.from_text( "foo", 30, "in", "a", "10.0.0.1", "10.0.0.2", "10.0.0.3" ) r2 = dns.rrset.from_text("FOO", 30, "in", "a", "10.0.0.2", "10.0.0.1") self.assertNotEqual(r1, r2) def testNotEqual4(self): r1 = dns.rrset.from_text("foo", 30, "in", "a", "10.0.0.1") r2 = dns.rrset.from_text("FOO", 30, "in", "a", "10.0.0.2", "10.0.0.1") self.assertNotEqual(r1, r2) def testCodec2003(self): r1 = dns.rrset.from_text_list( "Königsgäßchen", 30, "in", "ns", ["Königsgäßchen"] ) r2 = dns.rrset.from_text_list( "xn--knigsgsschen-lcb0w", 30, "in", "ns", ["xn--knigsgsschen-lcb0w"] ) self.assertEqual(r1, r2) @unittest.skipUnless( dns.name.have_idna_2008, "Python idna cannot be imported; no IDNA2008" ) def testCodec2008(self): r1 = dns.rrset.from_text_list( "Königsgäßchen", 30, "in", "ns", ["Königsgäßchen"], idna_codec=dns.name.IDNA_2008, ) r2 = dns.rrset.from_text_list( "xn--knigsgchen-b4a3dun", 30, "in", "ns", ["xn--knigsgchen-b4a3dun"], idna_codec=dns.name.IDNA_2008, ) self.assertEqual(r1, r2) def testCopy(self): r1 = dns.rrset.from_text_list("foo", 30, "in", "a", ["10.0.0.1", "10.0.0.2"]) r2 = r1.copy() self.assertFalse(r1 is r2) self.assertTrue(r1 == r2) def testFullMatch1(self): r1 = dns.rrset.from_text_list("foo", 30, "in", "a", ["10.0.0.1", "10.0.0.2"]) self.assertTrue( r1.full_match( r1.name, dns.rdataclass.IN, dns.rdatatype.A, dns.rdatatype.NONE ) ) def testFullMatch2(self): r1 = dns.rrset.from_text_list("foo", 30, "in", "a", ["10.0.0.1", "10.0.0.2"]) r1.deleting = dns.rdataclass.NONE self.assertTrue( r1.full_match( r1.name, dns.rdataclass.IN, dns.rdatatype.A, dns.rdatatype.NONE, dns.rdataclass.NONE, ) ) def testNoFullMatch1(self): n = dns.name.from_text("bar", None) r1 = dns.rrset.from_text_list("foo", 30, "in", "a", ["10.0.0.1", "10.0.0.2"]) self.assertFalse( r1.full_match( n, dns.rdataclass.IN, dns.rdatatype.A, dns.rdatatype.NONE, dns.rdataclass.ANY, ) ) def testNoFullMatch2(self): r1 = dns.rrset.from_text_list("foo", 30, "in", "a", ["10.0.0.1", "10.0.0.2"]) r1.deleting = dns.rdataclass.NONE self.assertFalse( r1.full_match( r1.name, dns.rdataclass.IN, dns.rdatatype.A, dns.rdatatype.NONE, dns.rdataclass.ANY, ) ) def testNoFullMatch3(self): r1 = dns.rrset.from_text_list("foo", 30, "in", "a", ["10.0.0.1", "10.0.0.2"]) self.assertFalse( r1.full_match( r1.name, dns.rdataclass.IN, dns.rdatatype.MX, dns.rdatatype.NONE, dns.rdataclass.ANY, ) ) def testMatchCompatibilityWithFullMatch(self): r1 = dns.rrset.from_text_list("foo", 30, "in", "a", ["10.0.0.1", "10.0.0.2"]) self.assertTrue( r1.match(r1.name, dns.rdataclass.IN, dns.rdatatype.A, dns.rdatatype.NONE) ) def testMatchCompatibilityWithRdatasetMatch(self): r1 = dns.rrset.from_text_list("foo", 30, "in", "a", ["10.0.0.1", "10.0.0.2"]) self.assertTrue( r1.match(dns.rdataclass.IN, dns.rdatatype.A, dns.rdatatype.NONE) ) def testToRdataset(self): r1 = dns.rrset.from_text_list("foo", 30, "in", "a", ["10.0.0.1", "10.0.0.2"]) r2 = dns.rdataset.from_text_list("in", "a", 30, ["10.0.0.1", "10.0.0.2"]) self.assertEqual(r1.to_rdataset(), r2) def testFromRdata(self): rdata1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1") rdata2 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.2") expected_rrs = dns.rrset.from_text( "foo", 300, "in", "a", "10.0.0.1", "10.0.0.2" ) rrs = dns.rrset.from_rdata("foo", 300, rdata1, rdata2) self.assertEqual(rrs, expected_rrs) def testEmptyList(self): def bad(): rrs = dns.rrset.from_rdata_list("foo", 300, []) self.assertRaises(ValueError, bad) def testTTLMinimization(self): rrs = dns.rrset.RRset( dns.name.from_text("foo"), dns.rdataclass.IN, dns.rdatatype.A ) rdata1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1") rdata2 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.2") rrs.add(rdata1, 300) self.assertEqual(rrs.ttl, 300) rrs.add(rdata2, 30) self.assertEqual(rrs.ttl, 30) # adding the same thing with a smaller TTL also minimizes rrs.add(rdata2, 3) self.assertEqual(rrs.ttl, 3) def testNotEqualOtherType(self): rrs = dns.rrset.RRset( dns.name.from_text("foo"), dns.rdataclass.IN, dns.rdatatype.A ) self.assertFalse(rrs == 123) def testRepr(self): rrset = dns.rrset.from_text("foo", 30, "in", "a", "10.0.0.1", "10.0.0.2") self.assertEqual(repr(rrset), ", <10.0.0.2>]>") rrset.deleting = dns.rdataclass.NONE self.assertEqual( repr(rrset), ", <10.0.0.2>]>", ) rrset = dns.rrset.from_text( "foo", 30, "in", "rrsig", "A 1 3 3600 20200701000000 20200601000000 1 NAME Ym9ndXM=", ) self.assertEqual( repr(rrset), "]>", ) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_rrset_reader.py0000644000000000000000000000752013615410400015726 0ustar00import pytest import dns.rrset from dns.zonefile import read_rrsets expected_mx_1 = dns.rrset.from_text("name.", 300, "in", "mx", "10 a.", "20 b.") expected_mx_2 = dns.rrset.from_text("name.", 10, "in", "mx", "10 a.", "20 b.") expected_mx_3 = dns.rrset.from_text("foo.", 10, "in", "mx", "10 a.") expected_mx_4 = dns.rrset.from_text("bar.", 10, "in", "mx", "20 b.") expected_mx_5 = dns.rrset.from_text("foo.example.", 10, "in", "mx", "10 a.example.") expected_mx_6 = dns.rrset.from_text("bar.example.", 10, "in", "mx", "20 b.") expected_mx_7 = dns.rrset.from_text("foo", 10, "in", "mx", "10 a") expected_mx_8 = dns.rrset.from_text("bar", 10, "in", "mx", "20 b.") expected_ns_1 = dns.rrset.from_text("name.", 300, "in", "ns", "hi.") expected_ns_2 = dns.rrset.from_text("name.", 300, "ch", "ns", "hi.") def equal_rrsets(a, b): # return True iff. a and b have the same rrsets regardless of order if len(a) != len(b): return False for rrset in a: if not rrset in b: return False return True def test_name_ttl_rdclass_forced(): input = """; mx 10 a mx 20 b. ns hi""" rrsets = read_rrsets(input, name="name", ttl=300) assert equal_rrsets(rrsets, [expected_mx_1, expected_ns_1]) assert rrsets[0].ttl == 300 assert rrsets[1].ttl == 300 def test_name_ttl_rdclass_forced_rdata_split(): input = """; mx 10 a ns hi mx 20 b.""" rrsets = read_rrsets(input, name="name", ttl=300) assert equal_rrsets(rrsets, [expected_mx_1, expected_ns_1]) def test_name_ttl_rdclass_rdtype_forced(): input = """; 10 a 20 b.""" rrsets = read_rrsets(input, name="name", ttl=300, rdtype="mx") assert equal_rrsets(rrsets, [expected_mx_1]) def test_name_rdclass_forced(): input = """30 mx 10 a 10 mx 20 b. """ rrsets = read_rrsets(input, name="name") assert equal_rrsets(rrsets, [expected_mx_2]) assert rrsets[0].ttl == 10 def test_rdclass_forced(): input = """; foo 20 mx 10 a bar 30 mx 20 b. """ rrsets = read_rrsets(input) assert equal_rrsets(rrsets, [expected_mx_3, expected_mx_4]) def test_rdclass_forced_with_origin(): input = """; foo 20 mx 10 a bar.example. 30 mx 20 b. """ rrsets = read_rrsets(input, origin="example") assert equal_rrsets(rrsets, [expected_mx_5, expected_mx_6]) def test_rdclass_forced_with_origin_relativized(): input = """; foo 20 mx 10 a.example. bar.example. 30 mx 20 b. """ rrsets = read_rrsets(input, origin="example", relativize=True) assert equal_rrsets(rrsets, [expected_mx_7, expected_mx_8]) def test_rdclass_matching_default_tolerated(): input = """; foo 20 mx 10 a.example. bar.example. 30 in mx 20 b. """ rrsets = read_rrsets(input, origin="example", relativize=True, rdclass=None) assert equal_rrsets(rrsets, [expected_mx_7, expected_mx_8]) def test_rdclass_not_matching_default_rejected(): input = """; foo 20 mx 10 a.example. bar.example. 30 ch mx 20 b. """ with pytest.raises(dns.exception.SyntaxError): rrsets = read_rrsets(input, origin="example", relativize=True, rdclass=None) def test_default_rdclass_is_none(): input = "" with pytest.raises(TypeError): rrsets = read_rrsets( input, default_rdclass=None, origin="example", relativize=True ) def test_name_rdclass_rdtype_force(): # No real-world usage should do this, but it can be specified so we test it. input = """; 30 10 a 10 20 b. """ rrsets = read_rrsets(input, name="name", rdtype="mx") assert equal_rrsets(rrsets, [expected_mx_1]) assert rrsets[0].ttl == 10 def test_rdclass_rdtype_force(): # No real-world usage should do this, but it can be specified so we test it. input = """; foo 30 10 a bar 30 20 b. """ rrsets = read_rrsets(input, rdtype="mx") assert equal_rrsets(rrsets, [expected_mx_3, expected_mx_4]) # also weird but legal # input5 = '''foo 30 10 a # bar 10 20 foo. #''' dnspython-2.7.0/tests/test_serial.py0000644000000000000000000000730013615410400014520 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import unittest import dns.serial def S2(v): return dns.serial.Serial(v, bits=2) def S8(v): return dns.serial.Serial(v, bits=8) class SerialTestCase(unittest.TestCase): def test_rfc_1982_2_bit_cases(self): self.assertEqual(S2(0) + S2(1), S2(1)) self.assertEqual(S2(1) + S2(1), S2(2)) self.assertEqual(S2(2) + S2(1), S2(3)) self.assertEqual(S2(3) + S2(1), S2(0)) self.assertTrue(S2(1) > S2(0)) self.assertTrue(S2(2) > S2(1)) self.assertTrue(S2(3) > S2(2)) self.assertTrue(S2(0) > S2(3)) self.assertFalse(S2(2) > S2(0)) self.assertFalse(S2(0) > S2(2)) self.assertFalse(S2(2) < S2(0)) self.assertFalse(S2(0) < S2(2)) def test_rfc_1982_8_bit_cases(self): self.assertEqual(S8(255) + S8(1), S8(0)) self.assertEqual(S8(100) + S8(100), S8(200)) self.assertEqual(S8(200) + S8(100), S8(44)) self.assertTrue(S8(1) > S8(0)) self.assertTrue(S8(44) > S8(0)) self.assertTrue(S8(100) > S8(0)) self.assertTrue(S8(100) > S8(44)) self.assertTrue(S8(200) > S8(100)) self.assertTrue(S8(255) > S8(200)) self.assertTrue(S8(0) > S8(255)) self.assertTrue(S8(255) < S8(0)) self.assertTrue(S8(100) > S8(255)) self.assertTrue(S8(0) > S8(200)) self.assertTrue(S8(44) > S8(200)) self.assertFalse(S8(0) > S8(128)) self.assertFalse(S8(128) > S8(0)) self.assertFalse(S8(0) < S8(128)) self.assertFalse(S8(128) < S8(0)) self.assertFalse(S8(1) > S8(129)) self.assertFalse(S8(129) > S8(1)) def test_incremental_ops(self): v = S8(255) v += 1 self.assertEqual(v, 0) v = S8(255) v += S8(1) self.assertEqual(v, 0) v = S8(0) v -= 1 self.assertEqual(v, 255) v = S8(0) v -= S8(1) self.assertEqual(v, 255) def test_sub(self): self.assertEqual(S8(0) - S8(1), S8(255)) def test_addition_bounds(self): self.assertRaises(ValueError, lambda: S8(0) + 128) self.assertRaises(ValueError, lambda: S8(0) - 128) def bad1(): v = S8(0) v += 128 self.assertRaises(ValueError, bad1) def bad2(): v = S8(0) v -= 128 self.assertRaises(ValueError, bad2) def test_casting(self): self.assertTrue(S8(0) == 0) self.assertTrue(S8(0) != 1) self.assertTrue(S8(0) < 1) self.assertTrue(S8(0) <= 1) self.assertTrue(S8(0) > 255) self.assertTrue(S8(0) >= 255) def test_uncastable(self): self.assertRaises(ValueError, lambda: S8(0) + "a") self.assertRaises(ValueError, lambda: S8(0) - "a") def bad1(): v = S8(0) v += "a" self.assertRaises(ValueError, bad1) def bad2(): v = S8(0) v -= "a" self.assertRaises(ValueError, bad2) def test_uncomparable(self): self.assertFalse(S8(0) == S2(0)) self.assertFalse(S8(0) == "a") self.assertTrue(S8(0) != "a") self.assertRaises(TypeError, lambda: S8(0) < "a") self.assertRaises(TypeError, lambda: S8(0) <= "a") self.assertRaises(TypeError, lambda: S8(0) > "a") self.assertRaises(TypeError, lambda: S8(0) >= "a") def test_modulo(self): self.assertEqual(S8(-1), 255) self.assertEqual(S8(257), 1) def test_repr(self): self.assertEqual(repr(S8(1)), "dns.serial.Serial(1, 8)") def test_not_equal(self): self.assertNotEqual(S8(0), S8(1)) self.assertNotEqual(S8(0), S2(0)) dnspython-2.7.0/tests/test_set.py0000644000000000000000000002125013615410400014034 0ustar00# 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 copy import unittest import dns.set # for convenience S = dns.set.Set class SetTestCase(unittest.TestCase): def testLen1(self): s1 = S() self.assertEqual(len(s1), 0) def testLen2(self): s1 = S([1, 2, 3]) self.assertEqual(len(s1), 3) def testLen3(self): s1 = S([1, 2, 3, 3, 3]) self.assertEqual(len(s1), 3) def testUnion1(self): s1 = S([1, 2, 3]) s2 = S([1, 2, 3]) e = S([1, 2, 3]) self.assertEqual(s1 | s2, e) def testUnion2(self): s1 = S([1, 2, 3]) s2 = S([]) e = S([1, 2, 3]) self.assertEqual(s1 | s2, e) def testUnion3(self): s1 = S([1, 2, 3]) s2 = S([3, 4]) e = S([1, 2, 3, 4]) self.assertEqual(s1 | s2, e) def testUnionPlusSyntax(self): s1 = S([1, 2, 3]) s2 = S([3, 4]) e = S([1, 2, 3, 4]) self.assertEqual(s1 + s2, e) def testIntersection1(self): s1 = S([1, 2, 3]) s2 = S([1, 2, 3]) e = S([1, 2, 3]) self.assertEqual(s1 & s2, e) def testIntersection2(self): s1 = S([0, 1, 2, 3]) s2 = S([1, 2, 3, 4]) e = S([1, 2, 3]) self.assertEqual(s1 & s2, e) def testIntersection3(self): s1 = S([1, 2, 3]) s2 = S([]) e = S([]) self.assertEqual(s1 & s2, e) def testIntersection4(self): s1 = S([1, 2, 3]) s2 = S([5, 4]) e = S([]) self.assertEqual(s1 & s2, e) def testDifference1(self): s1 = S([1, 2, 3]) s2 = S([5, 4]) e = S([1, 2, 3]) self.assertEqual(s1 - s2, e) def testDifference2(self): s1 = S([1, 2, 3]) s2 = S([]) e = S([1, 2, 3]) self.assertEqual(s1 - s2, e) def testDifference3(self): s1 = S([1, 2, 3]) s2 = S([3, 2]) e = S([1]) self.assertEqual(s1 - s2, e) def testDifference4(self): s1 = S([1, 2, 3]) s2 = S([3, 2, 1]) e = S([]) self.assertEqual(s1 - s2, e) def testSymmetricDifference1(self): s1 = S([1, 2, 3]) s2 = S([5, 4]) e = S([1, 2, 3, 4, 5]) self.assertEqual(s1 ^ s2, e) def testSymmetricDifference2(self): s1 = S([1, 2, 3]) s2 = S([]) e = S([1, 2, 3]) self.assertEqual(s1 ^ s2, e) def testSymmetricDifference3(self): s1 = S([1, 2, 3]) s2 = S([3, 2]) e = S([1]) self.assertEqual(s1 ^ s2, e) def testSymmetricDifference4(self): s1 = S([1, 2, 3]) s2 = S([3, 2, 1]) e = S([]) self.assertEqual(s1 ^ s2, e) def testSymmetricDifference5(self): s1 = S([1, 2, 3]) s2 = S([2, 4]) s1 ^= s2 e = S([1, 3, 4]) self.assertEqual(s1, e) def testSymmetricDifference6(self): s1 = S([1, 2, 3]) s1 ^= s1 e = S([]) self.assertEqual(s1, e) def testSubset1(self): s1 = S([1, 2, 3]) s2 = S([3, 2, 1]) self.assertTrue(s1.issubset(s2)) def testSubset2(self): s1 = S([1, 2, 3]) self.assertTrue(s1.issubset(s1)) def testSubset3(self): s1 = S([]) s2 = S([1, 2, 3]) self.assertTrue(s1.issubset(s2)) def testSubset4(self): s1 = S([1]) s2 = S([1, 2, 3]) self.assertTrue(s1.issubset(s2)) def testSubset5(self): s1 = S([]) s2 = S([]) self.assertTrue(s1.issubset(s2)) def testSubset6(self): s1 = S([1, 4]) s2 = S([1, 2, 3]) self.assertTrue(not s1.issubset(s2)) def testSuperset1(self): s1 = S([1, 2, 3]) s2 = S([3, 2, 1]) self.assertTrue(s1.issuperset(s2)) def testSuperset2(self): s1 = S([1, 2, 3]) self.assertTrue(s1.issuperset(s1)) def testSuperset3(self): s1 = S([1, 2, 3]) s2 = S([]) self.assertTrue(s1.issuperset(s2)) def testSuperset4(self): s1 = S([1, 2, 3]) s2 = S([1]) self.assertTrue(s1.issuperset(s2)) def testSuperset5(self): s1 = S([]) s2 = S([]) self.assertTrue(s1.issuperset(s2)) def testSuperset6(self): s1 = S([1, 2, 3]) s2 = S([1, 4]) self.assertTrue(not s1.issuperset(s2)) def testDisjoint1(self): s1 = S([1, 2, 3]) s2 = S([4]) self.assertTrue(s1.isdisjoint(s2)) def testDisjoint2(self): s1 = S([1, 2, 3]) s2 = S([2, 4]) self.assertTrue(not s1.isdisjoint(s2)) def testDisjoint3(self): s1 = S([1, 2, 3]) s2 = S([]) self.assertTrue(s1.isdisjoint(s2)) def testPop(self): original = S([1, 2, 3]) s1 = original.copy() item = s1.pop() self.assertTrue(len(s1) == 2) self.assertTrue(item in original) item = s1.pop() self.assertTrue(len(s1) == 1) self.assertTrue(item in original) item = s1.pop() self.assertTrue(len(s1) == 0) self.assertTrue(item in original) self.assertRaises(KeyError, lambda: s1.pop()) def testUpdate1(self): s1 = S([1, 2, 3]) u = (4, 5, 6) e = S([1, 2, 3, 4, 5, 6]) s1.update(u) self.assertEqual(s1, e) def testUpdate2(self): s1 = S([1, 2, 3]) u = [] e = S([1, 2, 3]) s1.update(u) self.assertEqual(s1, e) def testGetitem(self): s1 = S([1, 2, 3]) i0 = s1[0] i1 = s1[1] i2 = s1[2] s2 = S([i0, i1, i2]) self.assertEqual(s1, s2) def testGetslice(self): s1 = S([1, 2, 3]) slice = s1[0:2] self.assertEqual(len(slice), 2) item = s1[2] slice.append(item) s2 = S(slice) self.assertEqual(s1, s2) def testDelitem(self): s1 = S([1, 2, 3]) del s1[0] self.assertEqual(list(s1), [2, 3]) def testDelslice(self): s1 = S([1, 2, 3]) del s1[0:2] self.assertEqual(list(s1), [3]) def testRemoveNonexistent(self): s1 = S([1, 2, 3]) s2 = S([1, 2, 3]) with self.assertRaises(ValueError): s1.remove(4) self.assertEqual(s1, s2) def testDiscardNonexistent(self): s1 = S([1, 2, 3]) s2 = S([1, 2, 3]) s1.discard(4) self.assertEqual(s1, s2) def testCopy(self): s1 = S([1, 2, 3]) s2 = s1.copy() s1.remove(1) self.assertNotEqual(s1, s2) s1.add(1) self.assertEqual(s1, s2) s2 = copy.copy(s1) self.assertEqual(s1, s2) def testBadUpdates(self): s = S([1, 2, 3]) self.assertRaises(ValueError, lambda: s.union_update(1)) self.assertRaises(ValueError, lambda: s.intersection_update(1)) self.assertRaises(ValueError, lambda: s.difference_update(1)) self.assertRaises(ValueError, lambda: s.symmetric_difference_update(1)) def testSelfUpdates(self): expected = S([1, 2, 3]) s = S([1, 2, 3]) s.union_update(s) self.assertEqual(s, expected) s.intersection_update(s) self.assertEqual(s, expected) s.difference_update(s) self.assertTrue(len(s) == 0) def testBadSubsetSuperset(self): s = S([1, 2, 3]) self.assertRaises(ValueError, lambda: s.issubset(123)) self.assertRaises(ValueError, lambda: s.issuperset(123)) def testBadDisjoint(self): s = S([1, 2, 3]) self.assertRaises(ValueError, lambda: s.isdisjoint(123)) def testIncrementalOperators(self): s = S([1, 2, 3]) s += S([5, 4]) self.assertEqual(s, S([1, 2, 3, 4, 5])) s -= S([1, 2]) self.assertEqual(s, S([3, 4, 5])) s |= S([1, 2]) self.assertEqual(s, S([1, 2, 3, 4, 5])) s &= S([1, 2]) self.assertEqual(s, S([1, 2])) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_svcb.py0000644000000000000000000003353513615410400014207 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import io import unittest import dns.rdata import dns.rdtypes.svcbbase import dns.rrset from dns.tokenizer import Tokenizer from tests.util import here class SVCBTestCase(unittest.TestCase): def check_valid_inputs(self, inputs): expected = inputs[0] for text in inputs: rr = dns.rdata.from_text("IN", "SVCB", text) new_text = rr.to_text() self.assertEqual(expected, new_text) def check_invalid_inputs(self, inputs): for text in inputs: with self.assertRaises((dns.exception.SyntaxError, ValueError)): dns.rdata.from_text("IN", "SVCB", text) def test_svcb_general_invalid(self): invalid_inputs = ( # Duplicate keys "1 . alpn=h2 alpn=h3", "1 . alpn=h2 key1=h3", # Quoted keys '1 . "alpn=h2"', # Invalid space "1 . alpn= h2", "1 . alpn =h2", "1 . alpn = h2", '1 . alpn= "h2"', "1 . =alpn", ) self.check_invalid_inputs(invalid_inputs) def test_svcb_mandatory(self): valid_inputs = ( '1 . mandatory="alpn,no-default-alpn" alpn="h2" no-default-alpn', "1 . mandatory=alpn,no-default-alpn alpn=h2 no-default-alpn", "1 . mandatory=key1,key2 alpn=h2 no-default-alpn", "1 . mandatory=alpn,no-default-alpn key1=\\002h2 key2", "1 . key0=\\000\\001\\000\\002 alpn=h2 no-default-alpn", "1 . alpn=h2 no-default-alpn mandatory=alpn,no-default-alpn", ) self.check_valid_inputs(valid_inputs) invalid_inputs = ( # empty "1 . mandatory=", "1 . mandatory", # unknown key "1 . mandatory=foo", # key 0 "1 . mandatory=key0", "1 . mandatory=key0,alpn", # missing key "1 . mandatory=alpn", # duplicate "1 . mandatory=alpn,alpn alpn=h2", # invalid escaping "1 . mandatory=\\alpn alpn=h2", # empty wire format "1 . key0", "1 . key0=", # 0 in wire format "1 . key0=\\000\\000", # invalid length in wire format "1 . key0=\\000", # out of order in wire format "1 . key0=\\000\\002\\000\\001 alpn=h2 no-default-alpn", # leading zeros "1 . mandatory=key1,key002 alpn=h2 no-default-alpn", ) self.check_invalid_inputs(invalid_inputs) def test_svcb_alpn(self): valid_inputs_two_items = ( '1 . alpn="h2,h3"', "1 . alpn=h2,h3", "1 . alpn=h\\050,h3", '1 . alpn="h\\050,h3"', "1 . alpn=\\h2,h3", '1 . alpn="h2\\,h3"', "1 . alpn=h2\\,h3", "1 . alpn=h2\\044h3", "1 . key1=\\002h2\\002h3", ) self.check_valid_inputs(valid_inputs_two_items) valid_inputs_one_item = ( '1 . alpn="h2\\\\,h3"', "1 . alpn=h2\\\\,h3", "1 . alpn=h2\\092\\044h3", "1 . key1=\\005h2,h3", ) self.check_valid_inputs(valid_inputs_one_item) invalid_inputs = ( "1 . alpn", "1 . alpn=", "1 . alpn=h2,,h3", "1 . alpn=01234567890abcdef01234567890abcdef01234567890abcdef" "01234567890abcdef01234567890abcdef01234567890abcdef" "01234567890abcdef01234567890abcdef01234567890abcdef" "01234567890abcdef01234567890abcdef01234567890abcdef" "01234567890abcdef01234567890abcdef01234567890abcdef" "01234567890abcdef", '1 . alpn=",h2,h3"', '1 . alpn="h2,h3,"', "1 . key1", "1 . key1=", "1 . key1=\\000", "1 . key1=\\002x", ) self.check_invalid_inputs(invalid_inputs) def test_svcb_no_default_alpn(self): valid_inputs = ( '1 . alpn="h2" no-default-alpn', '1 . alpn="h2" no-default-alpn=""', '1 . alpn="h2" key2', '1 . alpn="h2" key2=""', ) self.check_valid_inputs(valid_inputs) invalid_inputs = ( "1 . no-default-alpn", '1 . no-default-alpn=""', "1 . key2", '1 . key2=""', "1 . alpn=h2 no-default-alpn=foo", "1 . alpn=h2 no-default-alpn=", "1 . alpn=h2 key2=foo", "1 . alpn=h2 key2=", ) self.check_invalid_inputs(invalid_inputs) def test_svcb_port(self): valid_inputs = ( '1 . port="53"', "1 . port=53", "1 . key3=\\000\\053", ) self.check_valid_inputs(valid_inputs) invalid_inputs = ( "1 . port", "1 . port=", "1 . port=53x", "1 . port=x53", "1 . port=53,54", "1 . port=53\\,54", "1 . port=65536", "1 . key3", "1 . key3=", "1 . key3=\\000", ) self.check_invalid_inputs(invalid_inputs) def test_svcb_ipv4hint(self): valid_inputs = ( '1 . ipv4hint="0.0.0.0,1.1.1.1"', "1 . ipv4hint=0.0.0.0,1.1.1.1", "1 . key4=\\000\\000\\000\\000\\001\\001\\001\\001", ) self.check_valid_inputs(valid_inputs) invalid_inputs = ( "1 . ipv4hint", "1 . ipv4hint=", "1 . ipv4hint=1234", "1 . ipv4hint=1\\.2.3.4", "1 . ipv4hint=1.2.3.4\\,2.3.4.5", "1 . key4=", "1 . key4=123", ) self.check_invalid_inputs(invalid_inputs) def test_svcb_ech(self): valid_inputs = ( '1 . ech="Zm9vMA=="', "1 . ech=Zm9vMA==", "1 . key5=foo0", "1 . key5=\\102\\111\\111\\048", ) self.check_valid_inputs(valid_inputs) invalid_inputs = ( "1 . ech", "1 . ech=", "1 . ech=Zm9vMA", "1 . ech=\\090m9vMA==", "1 . key5", "1 . key5=", ) self.check_invalid_inputs(invalid_inputs) def test_svcb_ipv6hint(self): valid_inputs = ( '1 . ipv6hint="::4,1::"', "1 . ipv6hint=::4,1::", "1 . key6=\\000\\000\\000\\000\\000\\000\\000\\000" "\\000\\000\\000\\000\\000\\000\\000\\004" "\\000\\001\\000\\000\\000\\000\\000\\000" "\\000\\000\\000\\000\\000\\000\\000\\000", ) self.check_valid_inputs(valid_inputs) invalid_inputs = ( "1 . ipv6hint", "1 . ipv6hint=", "1 . ipv6hint=1234", "1 . ipv6hint=1\\::2", "1 . ipv6hint=::1\\,::2", "1 . ipv6hint", "1 . key6", "1 . key6=", "1 . key6=123", ) self.check_invalid_inputs(invalid_inputs) def test_svcb_unknown(self): valid_inputs_one_key = ( '1 . key23="key45"', "1 . key23=key45", "1 . key23=key\\052\\053", '1 . key23="key\\052\\053"', "1 . key23=\\107\\101\\121\\052\\053", ) self.check_valid_inputs(valid_inputs_one_key) valid_inputs_one_key_empty = ( "1 . key23", '1 . key23=""', ) self.check_valid_inputs(valid_inputs_one_key_empty) invalid_inputs_one_key = ( "1 . key65536=foo", "1 . key24= key48", ) self.check_invalid_inputs(invalid_inputs_one_key) valid_inputs_two_keys = ( "1 . key24 key48", '1 . key24="" key48', ) self.check_valid_inputs(valid_inputs_two_keys) def test_svcb_wire(self): valid_inputs = ( '1 . mandatory="alpn,port" alpn="h2" port="257"', "\\# 24 0001 00 0000000400010003 00010003026832 000300020101", ) self.check_valid_inputs(valid_inputs) everything = ( '100 foo.com. mandatory="alpn,port" alpn="h2,h3" ' ' no-default-alpn port="12345" ech="abcd" ' " ipv4hint=1.2.3.4,4.3.2.1 ipv6hint=1::2,3::4" ' key12345="foo"' ) rr = dns.rdata.from_text("IN", "SVCB", everything) rr2 = dns.rdata.from_text("IN", "SVCB", rr.to_generic().to_text()) self.assertEqual(rr, rr2) invalid_inputs = ( # As above, but the keys are out of order. "\\# 24 0001 00 0000000400010003 000300020101 00010003026832", # As above, but the mandatory keys don't match "\\# 24 0001 00 0000000400010002 00010003026832 000300020101", "\\# 24 0001 00 0000000400010004 00010003026832 000300020101", # Alias form shouldn't have parameters. "\\# 08 0000 000300020101", # no-default-alpn requires alpn "\\# 07 0001 00 00020000", ) self.check_invalid_inputs(invalid_inputs) def test_misc_escape(self): rdata = dns.rdata.from_text("in", "svcb", "1 . alpn=\\010\\010") expected = '1 . alpn="\\\\010\\\\010"' self.assertEqual(rdata.to_text(), expected) with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "svcb", "1 . alpn=\\0") with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "svcb", "1 . alpn=\\00") with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "svcb", "1 . alpn=\\00q") with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "svcb", "1 . alpn=\\256") # This doesn't usually get exercised, so we do it directly. gp = dns.rdtypes.svcbbase.GenericParam.from_value("\\001\\002") expected = '"\\001\\002"' self.assertEqual(gp.to_text(), expected) def test_svcb_spec_test_vectors(self): text_file = here("svcb_test_vectors.text") text_tokenizer = Tokenizer(open(text_file), filename=text_file) generic_file = here("svcb_test_vectors.generic") generic_tokenizer = Tokenizer(open(generic_file), filename=generic_file) while True: while True: text_token = text_tokenizer.get() if text_token.is_eol(): continue break while True: generic_token = generic_tokenizer.get() if generic_token.is_eol(): continue break self.assertEqual(text_token.ttype, generic_token.ttype) if text_token.is_eof(): break self.assertTrue(text_token.is_identifier) text_tokenizer.unget(text_token) generic_tokenizer.unget(generic_token) text_rdata = dns.rdata.from_text("IN", "SVCB", text_tokenizer) generic_rdata = dns.rdata.from_text("IN", "SVCB", generic_tokenizer) self.assertEqual(text_rdata, generic_rdata) def test_svcb_spec_failure_cases(self): failure_cases = ( # This example has multiple instances of the same SvcParamKey "1 foo.example.com. key123=abc key123=def", # In the next examples the SvcParamKeys are missing their values. "1 foo.example.com. mandatory", "1 foo.example.com. alpn", "1 foo.example.com. port", "1 foo.example.com. ipv4hint", "1 foo.example.com. ipv6hint", # The "no-default-alpn" SvcParamKey value MUST be empty (Section 6.1). "1 foo.example.com. no-default-alpn=abc", # In this record a mandatory SvcParam is missing (Section 7). "1 foo.example.com. mandatory=key123", # The "mandatory" SvcParamKey MUST not be included in mandatory list # (Section 7). "1 foo.example.com. mandatory=mandatory", # Here there are multiple instances of the same SvcParamKey in the # mandatory list (Section 7). "1 foo.example.com. mandatory=key123,key123 key123=abc", ) self.check_invalid_inputs(failure_cases) def test_alias_mode(self): rd = dns.rdata.from_text("in", "svcb", "0 .") self.assertEqual(len(rd.params), 0) self.assertEqual(rd.target, dns.name.root) self.assertEqual(rd.to_text(), "0 .") rd = dns.rdata.from_text("in", "svcb", "0 elsewhere.") self.assertEqual(rd.target, dns.name.from_text("elsewhere.")) self.assertEqual(len(rd.params), 0) # provoke 'parameters in AliasMode' from text. with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("in", "svcb", "0 elsewhere. alpn=h2") # provoke 'parameters in AliasMode' from wire too. wire = bytes.fromhex("0000000000000400010003") with self.assertRaises(dns.exception.FormError): dns.rdata.from_wire("in", "svcb", wire, 0, len(wire)) def test_immutability(self): alpn = dns.rdtypes.svcbbase.ALPNParam.from_value(["h2", "h3"]) with self.assertRaises(TypeError): alpn.ids[0] = "foo" with self.assertRaises(TypeError): del alpn.ids[0] with self.assertRaises(TypeError): alpn.ids = "foo" with self.assertRaises(TypeError): del alpn.ids def test_alias_not_compressed(self): rrs = dns.rrset.from_text("elsewhere.", 300, "in", "svcb", "0 elseWhere.") output = io.BytesIO() compress = {} rrs.to_wire(output, compress) wire = output.getvalue() # Just one of these assertions is enough, but we do both to show # the bug we're checking is fixed. assert not wire.endswith(b"\xc0\x00") assert wire.endswith(b"\x09elseWhere\x00") dnspython-2.7.0/tests/test_tokenizer.py0000644000000000000000000003223113615410400015254 0ustar00# 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 unittest import dns.exception import dns.tokenizer Token = dns.tokenizer.Token class TokenizerTestCase(unittest.TestCase): def testStr(self): tok = dns.tokenizer.Tokenizer("foo") token = tok.get() self.assertEqual(token, Token(dns.tokenizer.IDENTIFIER, "foo")) def testQuotedString1(self): tok = dns.tokenizer.Tokenizer(r'"foo"') token = tok.get() self.assertEqual(token, Token(dns.tokenizer.QUOTED_STRING, "foo")) def testQuotedString2(self): tok = dns.tokenizer.Tokenizer(r'""') token = tok.get() self.assertEqual(token, Token(dns.tokenizer.QUOTED_STRING, "")) def testQuotedString3(self): tok = dns.tokenizer.Tokenizer(r'"\"foo\""') token = tok.get() self.assertEqual(token, Token(dns.tokenizer.QUOTED_STRING, '\\"foo\\"')) def testQuotedString4(self): tok = dns.tokenizer.Tokenizer(r'"foo\010bar"') token = tok.get() self.assertEqual(token, Token(dns.tokenizer.QUOTED_STRING, "foo\\010bar")) def testQuotedString5(self): with self.assertRaises(dns.exception.UnexpectedEnd): tok = dns.tokenizer.Tokenizer(r'"foo') tok.get() def testQuotedString6(self): with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer(r'"foo\01') tok.get() def testQuotedString7(self): with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer('"foo\nbar"') tok.get() def testEmpty1(self): tok = dns.tokenizer.Tokenizer("") token = tok.get() self.assertTrue(token.is_eof()) def testEmpty2(self): tok = dns.tokenizer.Tokenizer("") token1 = tok.get() token2 = tok.get() self.assertTrue(token1.is_eof() and token2.is_eof()) def testEOL(self): tok = dns.tokenizer.Tokenizer("\n") token1 = tok.get() token2 = tok.get() self.assertTrue(token1.is_eol() and token2.is_eof()) def testWS1(self): tok = dns.tokenizer.Tokenizer(" \n") token1 = tok.get() self.assertTrue(token1.is_eol()) def testWS2(self): tok = dns.tokenizer.Tokenizer(" \n") token1 = tok.get(want_leading=True) self.assertTrue(token1.is_whitespace()) def testComment1(self): tok = dns.tokenizer.Tokenizer(" ;foo\n") token1 = tok.get() self.assertTrue(token1.is_eol()) def testComment2(self): tok = dns.tokenizer.Tokenizer(" ;foo\n") token1 = tok.get(want_comment=True) token2 = tok.get() self.assertEqual(token1, Token(dns.tokenizer.COMMENT, "foo")) self.assertTrue(token2.is_eol()) def testComment3(self): tok = dns.tokenizer.Tokenizer(" ;foo bar\n") token1 = tok.get(want_comment=True) token2 = tok.get() self.assertEqual(token1, Token(dns.tokenizer.COMMENT, "foo bar")) self.assertTrue(token2.is_eol()) def testMultiline1(self): tok = dns.tokenizer.Tokenizer("( foo\n\n bar\n)") tokens = list(iter(tok)) self.assertEqual( tokens, [ Token(dns.tokenizer.IDENTIFIER, "foo"), Token(dns.tokenizer.IDENTIFIER, "bar"), ], ) def testMultiline2(self): tok = dns.tokenizer.Tokenizer("( foo\n\n bar\n)\n") tokens = list(iter(tok)) self.assertEqual( tokens, [ Token(dns.tokenizer.IDENTIFIER, "foo"), Token(dns.tokenizer.IDENTIFIER, "bar"), Token(dns.tokenizer.EOL, "\n"), ], ) def testMultiline3(self): with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer("foo)") list(iter(tok)) def testMultiline4(self): with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer("((foo)") list(iter(tok)) def testUnget1(self): tok = dns.tokenizer.Tokenizer("foo") t1 = tok.get() tok.unget(t1) t2 = tok.get() self.assertEqual(t1, t2) self.assertEqual(t1.ttype, dns.tokenizer.IDENTIFIER) self.assertEqual(t1.value, "foo") def testUnget2(self): with self.assertRaises(dns.tokenizer.UngetBufferFull): tok = dns.tokenizer.Tokenizer("foo") t1 = tok.get() tok.unget(t1) tok.unget(t1) def testGetEOL1(self): tok = dns.tokenizer.Tokenizer("\n") t = tok.get_eol() self.assertEqual(t, "\n") def testGetEOL2(self): tok = dns.tokenizer.Tokenizer("") t = tok.get_eol() self.assertEqual(t, "") def testEscapedDelimiter1(self): tok = dns.tokenizer.Tokenizer(r"ch\ ld") t = tok.get() self.assertEqual(t.ttype, dns.tokenizer.IDENTIFIER) self.assertEqual(t.value, r"ch\ ld") def testEscapedDelimiter2(self): tok = dns.tokenizer.Tokenizer(r"ch\032ld") t = tok.get() self.assertEqual(t.ttype, dns.tokenizer.IDENTIFIER) self.assertEqual(t.value, r"ch\032ld") def testEscapedDelimiter3(self): tok = dns.tokenizer.Tokenizer(r"ch\ild") t = tok.get() self.assertEqual(t.ttype, dns.tokenizer.IDENTIFIER) self.assertEqual(t.value, r"ch\ild") def testEscapedDelimiter1u(self): tok = dns.tokenizer.Tokenizer(r"ch\ ld") t = tok.get().unescape() self.assertEqual(t.ttype, dns.tokenizer.IDENTIFIER) self.assertEqual(t.value, r"ch ld") def testEscapedDelimiter2u(self): tok = dns.tokenizer.Tokenizer(r"ch\032ld") t = tok.get().unescape() self.assertEqual(t.ttype, dns.tokenizer.IDENTIFIER) self.assertEqual(t.value, "ch ld") def testEscapedDelimiter3u(self): tok = dns.tokenizer.Tokenizer(r"ch\ild") t = tok.get().unescape() self.assertEqual(t.ttype, dns.tokenizer.IDENTIFIER) self.assertEqual(t.value, r"child") def testGetUInt(self): tok = dns.tokenizer.Tokenizer("1234") v = tok.get_int() self.assertEqual(v, 1234) with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer('"1234"') tok.get_int() with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer("q1234") tok.get_int() with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer("281474976710656") tok.get_uint48() with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer("4294967296") tok.get_uint32() with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer("65536") tok.get_uint16() with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer("256") tok.get_uint8() # Even though it is badly named get_int(), it's really get_unit! with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer("-1234") tok.get_int() # get_uint16 can do other bases too, and has a custom error # for base 8. tok = dns.tokenizer.Tokenizer("177777") self.assertEqual(tok.get_uint16(base=8), 65535) with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer("200000") tok.get_uint16(base=8) def testGetString(self): tok = dns.tokenizer.Tokenizer("foo") v = tok.get_string() self.assertEqual(v, "foo") tok = dns.tokenizer.Tokenizer('"foo"') v = tok.get_string() self.assertEqual(v, "foo") tok = dns.tokenizer.Tokenizer("abcdefghij") v = tok.get_string(max_length=10) self.assertEqual(v, "abcdefghij") with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer("abcdefghij") tok.get_string(max_length=9) tok = dns.tokenizer.Tokenizer("") with self.assertRaises(dns.exception.SyntaxError): tok.get_string() def testMultiLineWithComment(self): tok = dns.tokenizer.Tokenizer("( ; abc\n)") tok.get_eol() # Nothing to assert here, as we're testing tok.get_eol() does NOT # raise. def testEOLAfterComment(self): tok = dns.tokenizer.Tokenizer("; abc\n") t = tok.get() self.assertTrue(t.is_eol()) def testEOFAfterComment(self): tok = dns.tokenizer.Tokenizer("; abc") t = tok.get() self.assertTrue(t.is_eof()) def testMultiLineWithEOFAfterComment(self): with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer("( ; abc") tok.get_eol() def testEscapeUnexpectedEnd(self): with self.assertRaises(dns.exception.UnexpectedEnd): tok = dns.tokenizer.Tokenizer("\\") tok.get() def testEscapeBounds(self): with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer("\\256") tok.get().unescape() with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer("\\256") tok.get().unescape_to_bytes() def testGetUngetRegetComment(self): tok = dns.tokenizer.Tokenizer(";comment") t1 = tok.get(want_comment=True) tok.unget(t1) t2 = tok.get(want_comment=True) self.assertEqual(t1, t2) def testBadAsName(self): with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer('"not an identifier"') t = tok.get() tok.as_name(t) def testBadGetTTL(self): with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer('"not an identifier"') tok.get_ttl() def testBadGetEOL(self): with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer('"not an identifier"') tok.get_eol_as_token() def testDanglingEscapes(self): for text in ['"\\"', '"\\0"', '"\\00"', '"\\00a"']: with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer(text) tok.get().unescape() with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer(text) tok.get().unescape_to_bytes() def testTokenMisc(self): t1 = dns.tokenizer.Token(dns.tokenizer.IDENTIFIER, "hi") t2 = dns.tokenizer.Token(dns.tokenizer.IDENTIFIER, "hi") t3 = dns.tokenizer.Token(dns.tokenizer.IDENTIFIER, "there") self.assertEqual(t1, t2) self.assertFalse(t1 == "hi") # not NotEqual because we want to use == self.assertNotEqual(t1, "hi") self.assertNotEqual(t1, t3) self.assertEqual(str(t1), '3 "hi"') def testBadConcatenateRemaining(self): with self.assertRaises(dns.exception.SyntaxError): tok = dns.tokenizer.Tokenizer('a b "not an identifier" c') tok.concatenate_remaining_identifiers() def testStdinFilename(self): tok = dns.tokenizer.Tokenizer() self.assertEqual(tok.filename, "") def testBytesLiteral(self): tok = dns.tokenizer.Tokenizer(b"this is input") self.assertEqual(tok.get().value, "this") self.assertEqual(tok.filename, "") tok = dns.tokenizer.Tokenizer(b"this is input", "myfilename") self.assertEqual(tok.filename, "myfilename") def testUngetBranches(self): tok = dns.tokenizer.Tokenizer(b" this is input") t = tok.get(want_leading=True) tok.unget(t) t = tok.get(want_leading=True) self.assertEqual(t.ttype, dns.tokenizer.WHITESPACE) tok.unget(t) t = tok.get() self.assertEqual(t.ttype, dns.tokenizer.IDENTIFIER) self.assertEqual(t.value, "this") tok = dns.tokenizer.Tokenizer(b"; this is input\n") t = tok.get(want_comment=True) tok.unget(t) t = tok.get(want_comment=True) self.assertEqual(t.ttype, dns.tokenizer.COMMENT) tok.unget(t) t = tok.get() self.assertEqual(t.ttype, dns.tokenizer.EOL) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_transaction.py0000644000000000000000000005622413615410400015577 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import time import pytest import dns.name import dns.rdataclass import dns.rdataset import dns.rdatatype import dns.rrset import dns.transaction import dns.versioned import dns.zone class DB(dns.transaction.TransactionManager): def __init__(self): self.rdatasets = {} def reader(self): return Transaction(self, False, True) def writer(self, replacement=False): return Transaction(self, replacement, False) def origin_information(self): return (dns.name.from_text("example"), True, dns.name.empty) def get_class(self): return dns.rdataclass.IN class Transaction(dns.transaction.Transaction): def __init__(self, db, replacement, read_only): super().__init__(db, replacement, read_only) self.rdatasets = {} if not replacement: self.rdatasets.update(db.rdatasets) @property def db(self): return self.manager def _get_rdataset(self, name, rdtype, covers): return self.rdatasets.get((name, rdtype, covers)) def _put_rdataset(self, name, rdataset): self.rdatasets[(name, rdataset.rdtype, rdataset.covers)] = rdataset def _delete_name(self, name): remove = [] for key in self.rdatasets.keys(): if key[0] == name: remove.append(key) if len(remove) > 0: for key in remove: del self.rdatasets[key] def _delete_rdataset(self, name, rdtype, covers): del self.rdatasets[(name, rdtype, covers)] def _name_exists(self, name): for key in self.rdatasets.keys(): if key[0] == name: return True return False def _changed(self): if self.read_only: return False else: return len(self.rdatasets) > 0 def _end_transaction(self, commit): if commit: self.db.rdatasets = self.rdatasets def _set_origin(self, origin): pass @pytest.fixture def db(): db = DB() rrset = dns.rrset.from_text("content", 300, "in", "txt", "content") db.rdatasets[(rrset.name, rrset.rdtype, 0)] = rrset return db def test_basic(db): # successful txn with db.writer() as txn: rrset = dns.rrset.from_text("foo", 300, "in", "a", "10.0.0.1", "10.0.0.2") txn.add(rrset) assert txn.name_exists(rrset.name) assert db.rdatasets[(rrset.name, rrset.rdtype, 0)] == rrset # rollback with pytest.raises(Exception): with db.writer() as txn: rrset2 = dns.rrset.from_text("foo", 300, "in", "a", "10.0.0.3", "10.0.0.4") txn.add(rrset2) raise Exception() assert db.rdatasets[(rrset.name, rrset.rdtype, 0)] == rrset with db.writer() as txn: txn.delete(rrset.name) assert db.rdatasets.get((rrset.name, rrset.rdtype, 0)) is None def test_get(db): with db.writer() as txn: content = dns.name.from_text("content", None) rdataset = txn.get(content, dns.rdatatype.TXT) assert rdataset is not None assert rdataset[0].strings == (b"content",) assert isinstance(rdataset, dns.rdataset.ImmutableRdataset) def test_add(db): with db.writer() as txn: rrset = dns.rrset.from_text("foo", 300, "in", "a", "10.0.0.1", "10.0.0.2") txn.add(rrset) rrset2 = dns.rrset.from_text("foo", 300, "in", "a", "10.0.0.3", "10.0.0.4") txn.add(rrset2) expected = dns.rrset.from_text( "foo", 300, "in", "a", "10.0.0.1", "10.0.0.2", "10.0.0.3", "10.0.0.4" ) assert db.rdatasets[(rrset.name, rrset.rdtype, 0)] == expected def test_replacement(db): with db.writer() as txn: rrset = dns.rrset.from_text("foo", 300, "in", "a", "10.0.0.1", "10.0.0.2") txn.add(rrset) rrset2 = dns.rrset.from_text("foo", 300, "in", "a", "10.0.0.3", "10.0.0.4") txn.replace(rrset2) assert db.rdatasets[(rrset.name, rrset.rdtype, 0)] == rrset2 def test_delete(db): with db.writer() as txn: txn.delete(dns.name.from_text("nonexistent", None)) content = dns.name.from_text("content", None) content2 = dns.name.from_text("content2", None) txn.delete(content) assert not txn.name_exists(content) txn.delete(content2, dns.rdatatype.TXT) rrset = dns.rrset.from_text("content", 300, "in", "txt", "new-content") txn.add(rrset) assert txn.name_exists(content) txn.delete(content, dns.rdatatype.TXT) assert not txn.name_exists(content) rrset = dns.rrset.from_text("content2", 300, "in", "txt", "new-content") txn.delete(rrset) content_keys = [k for k in db.rdatasets if k[0] == content] assert len(content_keys) == 0 def test_delete_exact(db): with db.writer() as txn: rrset = dns.rrset.from_text("content", 300, "in", "txt", "bad-content") with pytest.raises(dns.transaction.DeleteNotExact): txn.delete_exact(rrset) rrset = dns.rrset.from_text("content2", 300, "in", "txt", "bad-content") with pytest.raises(dns.transaction.DeleteNotExact): txn.delete_exact(rrset) with pytest.raises(dns.transaction.DeleteNotExact): txn.delete_exact(rrset.name) with pytest.raises(dns.transaction.DeleteNotExact): txn.delete_exact(rrset.name, dns.rdatatype.TXT) rrset = dns.rrset.from_text("content", 300, "in", "txt", "content") txn.delete_exact(rrset) assert db.rdatasets.get((rrset.name, rrset.rdtype, 0)) is None def test_parameter_forms(db): with db.writer() as txn: foo = dns.name.from_text("foo", None) rdataset = dns.rdataset.from_text("in", "a", 300, "10.0.0.1", "10.0.0.2") rdata1 = dns.rdata.from_text("in", "a", "10.0.0.3") rdata2 = dns.rdata.from_text("in", "a", "10.0.0.4") txn.add(foo, rdataset) txn.add(foo, 100, rdata1) txn.add(foo, 30, rdata2) expected = dns.rrset.from_text( "foo", 30, "in", "a", "10.0.0.1", "10.0.0.2", "10.0.0.3", "10.0.0.4" ) assert db.rdatasets[(foo, rdataset.rdtype, 0)] == expected with db.writer() as txn: txn.delete(foo, rdataset) txn.delete(foo, rdata1) txn.delete(foo, rdata2) assert db.rdatasets.get((foo, rdataset.rdtype, 0)) is None def test_bad_parameters(db): with db.writer() as txn: with pytest.raises(TypeError): txn.add(1) with pytest.raises(TypeError): rrset = dns.rrset.from_text("bar", 300, "in", "txt", "bar") txn.add(rrset, 1) with pytest.raises(ValueError): foo = dns.name.from_text("foo", None) rdata = dns.rdata.from_text("in", "a", "10.0.0.3") txn.add(foo, 0x100000000, rdata) with pytest.raises(TypeError): txn.add(foo) with pytest.raises(TypeError): txn.add() with pytest.raises(TypeError): txn.add(foo, 300) with pytest.raises(TypeError): txn.add(foo, 300, "hi") with pytest.raises(TypeError): txn.add(foo, "hi") with pytest.raises(TypeError): txn.delete() with pytest.raises(TypeError): txn.delete(1) def test_cannot_store_non_origin_soa(db): with pytest.raises(ValueError): with db.writer() as txn: rrset = dns.rrset.from_text("foo", 300, "in", "SOA", ". . 1 2 3 4 5") txn.add(rrset) def test_checks(db): called = set() with db.writer() as txn: txn.check_put_rdataset(lambda t, n, r: called.add("put_rdataset")) txn.check_delete_rdataset(lambda t, n, r, c: called.add("delete_rdataset")) txn.check_delete_name(lambda t, n: called.add("delete_name")) rrset = dns.rrset.from_text("foo", 300, "in", "A", "10.0.0.1", "10.0.0.2") txn.add(rrset) rrset = dns.rrset.from_text("foo", 300, "in", "AAAA", "::1") txn.add(rrset) assert "put_rdataset" in called rrset = dns.rrset.from_text("foo", 300, "in", "txt", "foo") txn.add(rrset) called.clear() txn.delete("foo", "txt") assert "delete_rdataset" in called called.clear() rdata = dns.rdata.from_text("in", "a", "10.0.0.2") txn.delete("foo", rdata) # we get put here as we're storing an updated rrset, not deleting it assert "put_rdataset" in called called.clear() rdata = dns.rdata.from_text("in", "a", "10.0.0.1") # now we are deleting txn.delete("foo", rdata) assert "delete_rdataset" in called # non-match calls nothing called.clear() txn.delete("foo", "rrsig", "a") assert len(called) == 0 # delete the name txn.delete("foo") assert "delete_name" in called example_text = """$TTL 3600 $ORIGIN example. @ soa foo bar 1 2 3 4 5 @ ns ns1 @ ns ns2 ns1 a 10.0.0.1 ns2 a 10.0.0.2 $TTL 300 $ORIGIN foo.example. bar mx 0 blaz """ example_text_output = """@ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 @ 3600 IN NS ns3 ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.2 ns3 3600 IN A 10.0.0.3 """ @pytest.fixture(params=[dns.zone.Zone, dns.versioned.Zone]) def zone(request): return dns.zone.from_text(example_text, zone_factory=request.param) def test_zone_basic(zone): with zone.writer() as txn: txn.delete(dns.name.from_text("bar.foo", None)) rd = dns.rdata.from_text("in", "ns", "ns3") txn.add(dns.name.empty, 3600, rd) rd = dns.rdata.from_text("in", "a", "10.0.0.3") txn.add(dns.name.from_text("ns3", None), 3600, rd) output = zone.to_text() assert output == example_text_output def test_explicit_rollback_and_commit(zone): with zone.writer() as txn: assert not txn.changed() txn.delete(dns.name.from_text("bar.foo", None)) txn.rollback() assert zone.get_node("bar.foo") is not None with zone.writer() as txn: assert not txn.changed() txn.delete(dns.name.from_text("bar.foo", None)) txn.commit() assert zone.get_node("bar.foo") is None with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.delete(dns.name.from_text("bar.foo", None)) with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.add("bar.foo", 300, dns.rdata.from_text("in", "txt", "hi")) with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.replace("bar.foo", 300, dns.rdata.from_text("in", "txt", "hi")) with pytest.raises(dns.transaction.AlreadyEnded): with zone.reader() as txn: txn.rollback() txn.get("bar.foo", "in", "mx") with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.delete_exact("bar.foo") with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.name_exists("bar.foo") with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.update_serial() with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.changed() with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.rollback() with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.commit() with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() for rdataset in txn: pass def test_zone_changed(zone): # Read-only is not changed! with zone.reader() as txn: assert not txn.changed() # delete an existing name with zone.writer() as txn: assert not txn.changed() txn.delete(dns.name.from_text("bar.foo", None)) assert txn.changed() # delete a nonexistent name with zone.writer() as txn: assert not txn.changed() txn.delete(dns.name.from_text("unknown.bar.foo", None)) assert not txn.changed() # delete a nonexistent rdataset from an extant node with zone.writer() as txn: assert not txn.changed() txn.delete(dns.name.from_text("bar.foo", None), "txt") assert not txn.changed() # add an rdataset to an extant Node with zone.writer() as txn: assert not txn.changed() txn.add("bar.foo", 300, dns.rdata.from_text("in", "txt", "hi")) assert txn.changed() # add an rdataset to a nonexistent Node with zone.writer() as txn: assert not txn.changed() txn.add("foo.foo", 300, dns.rdata.from_text("in", "txt", "hi")) assert txn.changed() def test_zone_base_layer(zone): with zone.writer() as txn: # Get a set from the zone layer rdataset = txn.get(dns.name.empty, dns.rdatatype.NS, dns.rdatatype.NONE) expected = dns.rdataset.from_text("in", "ns", 300, "ns1", "ns2") assert rdataset == expected def test_zone_transaction_layer(zone): with zone.writer() as txn: # Make a change rd = dns.rdata.from_text("in", "ns", "ns3") txn.add(dns.name.empty, 3600, rd) # Get a set from the transaction layer expected = dns.rdataset.from_text("in", "ns", 300, "ns1", "ns2", "ns3") rdataset = txn.get(dns.name.empty, dns.rdatatype.NS, dns.rdatatype.NONE) assert rdataset == expected assert txn.name_exists(dns.name.empty) ns1 = dns.name.from_text("ns1", None) assert txn.name_exists(ns1) ns99 = dns.name.from_text("ns99", None) assert not txn.name_exists(ns99) def test_zone_add_and_delete(zone): with zone.writer() as txn: a99 = dns.name.from_text("a99", None) a100 = dns.name.from_text("a100", None) a101 = dns.name.from_text("a101", None) rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.99") txn.add(a99, rds) txn.delete(a99, dns.rdatatype.A) txn.delete(a100, dns.rdatatype.A) txn.delete(a101) assert not txn.name_exists(a99) assert not txn.name_exists(a100) assert not txn.name_exists(a101) ns1 = dns.name.from_text("ns1", None) txn.delete(ns1, dns.rdatatype.A) assert not txn.name_exists(ns1) with zone.writer() as txn: txn.add(a99, rds) txn.delete(a99) assert not txn.name_exists(a99) with zone.writer() as txn: txn.add(a100, rds) txn.delete(a99) assert not txn.name_exists(a99) assert txn.name_exists(a100) def test_write_after_rollback(zone): with pytest.raises(ExpectedException): with zone.writer() as txn: a99 = dns.name.from_text("a99", None) rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.99") txn.add(a99, rds) raise ExpectedException with zone.writer() as txn: a99 = dns.name.from_text("a99", None) rds = dns.rdataset.from_text("in", "a", 300, "10.99.99.99") txn.add(a99, rds) assert zone.get_rdataset("a99", "a") == rds def test_zone_get_deleted(zone): with zone.writer() as txn: ns1 = dns.name.from_text("ns1", None) assert txn.get(ns1, dns.rdatatype.A) is not None txn.delete(ns1) assert txn.get(ns1, dns.rdatatype.A) is None ns2 = dns.name.from_text("ns2", None) txn.delete(ns2, dns.rdatatype.A) assert txn.get(ns2, dns.rdatatype.A) is None def test_zone_bad_class(zone): with zone.writer() as txn: rds = dns.rdataset.from_text("ch", "ns", 300, "ns1", "ns2") with pytest.raises(ValueError): txn.add(dns.name.empty, rds) with pytest.raises(ValueError): txn.replace(dns.name.empty, rds) with pytest.raises(ValueError): txn.delete(dns.name.empty, rds) def test_update_serial(zone): # basic with zone.writer() as txn: txn.update_serial() rdataset = zone.find_rdataset("@", "soa") assert rdataset[0].serial == 2 # max with zone.writer() as txn: txn.update_serial(0xFFFFFFFF, False) rdataset = zone.find_rdataset("@", "soa") assert rdataset[0].serial == 0xFFFFFFFF # wraparound to 1 with zone.writer() as txn: txn.update_serial() rdataset = zone.find_rdataset("@", "soa") assert rdataset[0].serial == 1 # trying to set to zero sets to 1 with zone.writer() as txn: txn.update_serial(0, False) rdataset = zone.find_rdataset("@", "soa") assert rdataset[0].serial == 1 # specifying the name explicitly works with zone.writer() as txn: txn.update_serial(1, True, "@") rdataset = zone.find_rdataset("@", "soa") assert rdataset[0].serial == 2 with pytest.raises(KeyError): with zone.writer() as txn: txn.update_serial(name=dns.name.from_text("unknown", None)) with pytest.raises(ValueError): with zone.writer() as txn: txn.update_serial(-1) with pytest.raises(ValueError): with zone.writer() as txn: txn.update_serial(2**31) class ExpectedException(Exception): pass def test_zone_rollback(zone): a99 = dns.name.from_text("a99.example.") try: with zone.writer() as txn: rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.99") txn.add(a99, rds) assert txn.name_exists(a99) raise ExpectedException except ExpectedException: pass assert not zone.get_node(a99) def test_zone_ooz_name(zone): with zone.writer() as txn: with pytest.raises(KeyError): a99 = dns.name.from_text("a99.not-example.") assert txn.name_exists(a99) def test_zone_iteration(zone): expected = {} for name, rdataset in zone.iterate_rdatasets(): expected[(name, rdataset.rdtype, rdataset.covers)] = rdataset with zone.writer() as txn: actual1 = {} for name, rdataset in txn: actual1[(name, rdataset.rdtype, rdataset.covers)] = rdataset actual2 = {} for name, rdataset in txn.iterate_rdatasets(): actual2[(name, rdataset.rdtype, rdataset.covers)] = rdataset assert actual1 == expected assert actual2 == expected def test_zone_name_iteration(zone): expected = list(zone.keys()) with zone.writer() as txn: actual = list(txn.iterate_names()) assert actual == expected def test_iteration_in_replacement_txn(zone): rds = dns.rdataset.from_text("in", "a", 300, "1.2.3.4", "5.6.7.8") expected = {} expected[(dns.name.empty, rds.rdtype, rds.covers)] = rds with zone.writer(True) as txn: txn.replace(dns.name.empty, rds) actual = {} for name, rdataset in txn: actual[(name, rdataset.rdtype, rdataset.covers)] = rdataset assert actual == expected def test_replacement_commit(zone): rds = dns.rdataset.from_text("in", "a", 300, "1.2.3.4", "5.6.7.8") expected = {} expected[(dns.name.empty, rds.rdtype, rds.covers)] = rds with zone.writer(True) as txn: txn.replace(dns.name.empty, rds) with zone.reader() as txn: actual = {} for name, rdataset in txn: actual[(name, rdataset.rdtype, rdataset.covers)] = rdataset assert actual == expected def test_replacement_get(zone): with zone.writer(True) as txn: rds = txn.get(dns.name.empty, "soa") assert rds is None @pytest.fixture def vzone(): return dns.zone.from_text(example_text, zone_factory=dns.versioned.Zone) def test_vzone_read_only(vzone): with vzone.reader() as txn: rdataset = txn.get(dns.name.empty, dns.rdatatype.NS, dns.rdatatype.NONE) expected = dns.rdataset.from_text("in", "ns", 300, "ns1", "ns2") assert rdataset == expected with pytest.raises(dns.transaction.ReadOnly): txn.replace(dns.name.empty, expected) def test_vzone_multiple_versions(vzone): assert len(vzone._versions) == 1 vzone.set_max_versions(None) # unlimited! with vzone.writer() as txn: txn.update_serial() with vzone.writer() as txn: txn.update_serial() with vzone.writer() as txn: txn.update_serial(1000, False) rdataset = vzone.find_rdataset("@", "soa") assert rdataset[0].serial == 1000 assert len(vzone._versions) == 4 with vzone.reader(id=5) as txn: assert txn.version.id == 5 rdataset = txn.get("@", "soa") assert rdataset[0].serial == 1000 with vzone.reader(serial=1000) as txn: assert txn.version.id == 5 rdataset = txn.get("@", "soa") assert rdataset[0].serial == 1000 vzone.set_max_versions(2) assert len(vzone._versions) == 2 # The ones that survived should be 3 and 1000 rdataset = vzone._versions[0].get_rdataset( dns.name.empty, dns.rdatatype.SOA, dns.rdatatype.NONE ) assert rdataset[0].serial == 3 rdataset = vzone._versions[1].get_rdataset( dns.name.empty, dns.rdatatype.SOA, dns.rdatatype.NONE ) assert rdataset[0].serial == 1000 with pytest.raises(ValueError): vzone.set_max_versions(0) # for debugging if needed def _dump(zone): for v in zone._versions: print("VERSION", v.id) for name, n in v.nodes.items(): for rdataset in n: print(rdataset.to_text(name)) def test_vzone_open_txn_pins_versions(vzone): assert len(vzone._versions) == 1 vzone.set_max_versions(None) # unlimited! with vzone.writer() as txn: txn.update_serial() with vzone.writer() as txn: txn.update_serial() with vzone.writer() as txn: txn.update_serial() with vzone.reader(id=2) as txn: vzone.set_max_versions(1) with vzone.reader(id=3) as txn: rdataset = txn.get("@", "soa") assert rdataset[0].serial == 2 assert len(vzone._versions) == 4 assert len(vzone._versions) == 1 rdataset = vzone.find_rdataset("@", "soa") assert vzone._versions[0].id == 5 assert rdataset[0].serial == 4 try: import threading one_got_lock = threading.Event() def run_one(zone): with zone.writer() as txn: one_got_lock.set() # wait until two blocks while len(zone._write_waiters) == 0: time.sleep(0.01) rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.98") txn.add("a98", rds) def run_two(zone): # wait until one has the lock so we know we will block if we # get the call done before the sleep in one completes one_got_lock.wait() with zone.writer() as txn: rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.99") txn.add("a99", rds) def test_vzone_concurrency(vzone): t1 = threading.Thread(target=run_one, args=(vzone,)) t1.start() t2 = threading.Thread(target=run_two, args=(vzone,)) t2.start() t1.join() t2.join() with vzone.reader() as txn: assert txn.name_exists("a98") assert txn.name_exists("a99") except ImportError: # pragma: no cover pass dnspython-2.7.0/tests/test_tsig.py0000644000000000000000000003225513615410400014216 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import base64 import time import unittest from unittest.mock import Mock import dns.message import dns.rcode import dns.rdtypes.ANY.TKEY import dns.tsig import dns.tsigkeyring keyring = dns.tsigkeyring.from_text({"keyname.": "NjHwPsMKjdN++dOfE5iAiQ=="}) keyname = dns.name.from_text("keyname") class TSIGTestCase(unittest.TestCase): def test_get_context(self): key = dns.tsig.Key("foo.com", "abcd", "hmac-sha256") ctx = dns.tsig.get_context(key) self.assertEqual(ctx.name, "hmac-sha256") key = dns.tsig.Key("foo.com", "abcd", "hmac-sha512") ctx = dns.tsig.get_context(key) self.assertEqual(ctx.name, "hmac-sha512") bogus = dns.tsig.Key("foo.com", "abcd", "bogus") with self.assertRaises(NotImplementedError): dns.tsig.get_context(bogus) def test_tsig_message_properties(self): m = dns.message.make_query("example", "a") self.assertIsNone(m.keyname) self.assertIsNone(m.keyalgorithm) self.assertIsNone(m.tsig_error) m.use_tsig(keyring, keyname) self.assertEqual(m.keyname, keyname) self.assertEqual(m.keyalgorithm, dns.tsig.default_algorithm) self.assertEqual(m.tsig_error, dns.rcode.NOERROR) m = dns.message.make_query("example", "a") m.use_tsig(keyring, keyname, tsig_error=dns.rcode.BADKEY) self.assertEqual(m.tsig_error, dns.rcode.BADKEY) def test_verify_mac_for_context(self): key = dns.tsig.Key("foo.com", "abcd", "hmac-sha512") ctx = dns.tsig.get_context(key) bad_expected = b"xxxxxxxxxx" with self.assertRaises(dns.tsig.BadSignature): ctx.verify(bad_expected) def test_validate(self): # make message and grab the TSIG m = dns.message.make_query("example", "a") m.use_tsig(keyring, keyname, algorithm=dns.tsig.HMAC_SHA256) w = m.to_wire() tsig = m.tsig[0] # get the time and create a key with matching characteristics now = int(time.time()) key = dns.tsig.Key("foo.com", "abcd", "hmac-sha256") # add enough to the time to take it over the fudge amount with self.assertRaises(dns.tsig.BadTime): dns.tsig.validate( w, key, dns.name.from_text("foo.com"), tsig, now + 1000, b"", 0 ) # change the key name with self.assertRaises(dns.tsig.BadKey): dns.tsig.validate(w, key, dns.name.from_text("bar.com"), tsig, now, b"", 0) # change the key algorithm key = dns.tsig.Key("foo.com", "abcd", "hmac-sha512") with self.assertRaises(dns.tsig.BadAlgorithm): dns.tsig.validate(w, key, dns.name.from_text("foo.com"), tsig, now, b"", 0) def test_gssapi_context(self): def verify_signature(data, mac): if data == b"throw": raise Exception return None # mock out the gssapi context to return some dummy values gssapi_context_mock = Mock() gssapi_context_mock.get_signature.return_value = b"xxxxxxxxxxx" gssapi_context_mock.verify_signature.side_effect = verify_signature # create the key and add it to the keyring keyname = "gsstsigtest" key = dns.tsig.Key(keyname, gssapi_context_mock, "gss-tsig") ctx = dns.tsig.get_context(key) self.assertEqual(ctx.name, "gss-tsig") gsskeyname = dns.name.from_text(keyname) keyring[gsskeyname] = key # make sure we can get the keyring (no exception == success) text = dns.tsigkeyring.to_text(keyring) self.assertNotEqual(text, "") # test exceptional case for _verify_mac_for_context with self.assertRaises(dns.tsig.BadSignature): ctx.update(b"throw") ctx.verify(b"bogus") gssapi_context_mock.verify_signature.assert_called() self.assertEqual(gssapi_context_mock.verify_signature.call_count, 1) # simulate case where TKEY message is used to establish the context; # first, the query from the client tkey_message = dns.message.make_query(keyname, "tkey", "any") # test existent/non-existent keys in the keyring adapted_keyring = dns.tsig.GSSTSigAdapter(keyring) fetched_key = adapted_keyring(tkey_message, gsskeyname) self.assertEqual(fetched_key, key) key = adapted_keyring(None, gsskeyname) self.assertEqual(fetched_key, key) key = adapted_keyring(tkey_message, "dummy") self.assertEqual(key, None) # create a response, TKEY and turn it into bytes, simulating the server # sending the response to the query tkey_response = dns.message.make_response(tkey_message) key = base64.b64decode("KEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEY") tkey = dns.rdtypes.ANY.TKEY.TKEY( dns.rdataclass.ANY, dns.rdatatype.TKEY, dns.name.from_text("gss-tsig."), 1594203795, 1594206664, 3, 0, key, ) # add the TKEY answer and sign it tkey_response.set_rcode(dns.rcode.NOERROR) tkey_response.answer = [ dns.rrset.from_rdata(dns.name.from_text(keyname), 0, tkey) ] tkey_response.use_tsig( keyring=dns.tsig.GSSTSigAdapter(keyring), keyname=gsskeyname, algorithm=dns.tsig.GSS_TSIG, ) # "send" it to the client tkey_wire = tkey_response.to_wire() # grab the response from the "server" and simulate the client side dns.message.from_wire(tkey_wire, dns.tsig.GSSTSigAdapter(keyring)) # assertions to make sure the "gssapi" functions were called gssapi_context_mock.get_signature.assert_called() self.assertEqual(gssapi_context_mock.get_signature.call_count, 1) gssapi_context_mock.verify_signature.assert_called() self.assertEqual(gssapi_context_mock.verify_signature.call_count, 2) gssapi_context_mock.step.assert_called() self.assertEqual(gssapi_context_mock.step.call_count, 1) # create example message and go to/from wire to simulate sign/verify # of regular messages a_message = dns.message.make_query("example", "a") a_message.use_tsig(dns.tsig.GSSTSigAdapter(keyring), gsskeyname) a_wire = a_message.to_wire() # not raising is passing dns.message.from_wire(a_wire, dns.tsig.GSSTSigAdapter(keyring)) # assertions to make sure the "gssapi" functions were called again gssapi_context_mock.get_signature.assert_called() self.assertEqual(gssapi_context_mock.get_signature.call_count, 2) gssapi_context_mock.verify_signature.assert_called() self.assertEqual(gssapi_context_mock.verify_signature.call_count, 3) def test_sign_and_validate(self): m = dns.message.make_query("example", "a") m.use_tsig(keyring, keyname) w = m.to_wire() # not raising is passing dns.message.from_wire(w, keyring) def test_signature_is_invalid(self): m = dns.message.make_query("example", "a") m.use_tsig(keyring, keyname) w = m.to_wire() b = bytearray(w) # corrupt hash b[-7] = (b[-7] + 1) & 0xFF w = bytes(b) with self.assertRaises(dns.tsig.BadSignature): dns.message.from_wire(w, keyring) def test_signature_is_invalid_and_ignored(self): m = dns.message.make_query("example", "a") m.use_tsig(keyring, keyname) w = m.to_wire() b = bytearray(w) # corrupt hash b[-7] = (b[-7] + 1) & 0xFF w = bytes(b) m2 = dns.message.from_wire(w, False) self.assertIsNotNone(m2.tsig) def test_validate_with_bad_keyring(self): m = dns.message.make_query("example", "a") m.use_tsig(keyring, keyname) w = m.to_wire() # keyring == None is an error with self.assertRaises(dns.message.UnknownTSIGKey): dns.message.from_wire(w, None) # callable keyring that returns None is an error with self.assertRaises(dns.message.UnknownTSIGKey): dns.message.from_wire(w, lambda m, n: None) def test_sign_and_validate_with_other_data(self): m = dns.message.make_query("example", "a") m.use_tsig(keyring, keyname, other_data=b"other") w = m.to_wire() # not raising is passing dns.message.from_wire(w, keyring) def test_sign_respond_and_validate(self): mq = dns.message.make_query("example", "a") mq.use_tsig(keyring, keyname) wq = mq.to_wire() mq_with_tsig = dns.message.from_wire(wq, keyring) mr = dns.message.make_response(mq) mr.use_tsig(keyring, keyname) wr = mr.to_wire() dns.message.from_wire(wr, keyring, request_mac=mq_with_tsig.mac) def make_message_pair(self, qname="example", rdtype="A", tsig_error=0): q = dns.message.make_query(qname, rdtype) q.use_tsig(keyring=keyring, keyname=keyname) q.to_wire() # to set q.mac r = dns.message.make_response(q, tsig_error=tsig_error) return (q, r) def test_peer_errors(self): items = [ (dns.rcode.BADSIG, dns.tsig.PeerBadSignature), (dns.rcode.BADKEY, dns.tsig.PeerBadKey), (dns.rcode.BADTIME, dns.tsig.PeerBadTime), (dns.rcode.BADTRUNC, dns.tsig.PeerBadTruncation), (99, dns.tsig.PeerError), ] for err, ex in items: q, r = self.make_message_pair(tsig_error=err) w = r.to_wire() def bad(): dns.message.from_wire(w, keyring=keyring, request_mac=q.mac) self.assertRaises(ex, bad) def _test_truncated_algorithm(self, alg, length): key = dns.tsig.Key("foo", b"abcdefg", algorithm=alg) q = dns.message.make_query("example", "a") q.use_tsig(key) q2 = dns.message.from_wire(q.to_wire(), keyring=key) self.assertTrue(q2.had_tsig) self.assertEqual(q2.tsig[0].algorithm, q.tsig[0].algorithm) self.assertEqual(len(q2.tsig[0].mac), length // 8) def test_hmac_sha256_128(self): self._test_truncated_algorithm(dns.tsig.HMAC_SHA256_128, 128) def test_hmac_sha384_192(self): self._test_truncated_algorithm(dns.tsig.HMAC_SHA384_192, 192) def test_hmac_sha512_256(self): self._test_truncated_algorithm(dns.tsig.HMAC_SHA512_256, 256) def _test_text_format(self, alg): key = dns.tsig.Key("foo", b"abcdefg", algorithm=alg) q = dns.message.make_query("example", "a") q.use_tsig(key) _ = q.to_wire() text = q.tsig[0].to_text() tsig2 = dns.rdata.from_text("ANY", "TSIG", text) self.assertEqual(tsig2, q.tsig[0]) q = dns.message.make_query("example", "a") q.use_tsig(key, other_data=b"abc") q.use_tsig(key) _ = q.to_wire() text = q.tsig[0].to_text() tsig2 = dns.rdata.from_text("ANY", "TSIG", text) self.assertEqual(tsig2, q.tsig[0]) def test_text_hmac_sha256_128(self): self._test_text_format(dns.tsig.HMAC_SHA256_128) def test_text_hmac_sha384_192(self): self._test_text_format(dns.tsig.HMAC_SHA384_192) def test_text_hmac_sha512_256(self): self._test_text_format(dns.tsig.HMAC_SHA512_256) def test_non_gss_key_repr(self): key = dns.tsig.Key( "foo", b"0123456789abcdef" * 2, algorithm=dns.tsig.HMAC_SHA256 ) self.assertEqual( repr(key), "") def _test_multi(self, pad): tm0 = """id 1 ;QUESTION example. IN AXFR """ tm1 = """id 1 flags QR ;QUESTION example. IN AXFR ;ANSWER example. 300 IN SOA . . 1 2 3 4 5 example. 300 IN NS ns1.tld. example. 300 IN NS ns2.tld. """ tm2 = """id 1 flags QR ;ANSWER example. 300 IN MX 10 mail.tld. example. 300 IN SOA . . 1 2 3 4 5 """ m0 = dns.message.from_text(tm0) m0.use_edns(0, pad=pad) m0.use_tsig(keyring) w0 = m0.to_wire() m1 = dns.message.from_text(tm1) m1.use_edns(0, pad=pad) m1.use_tsig(keyring) m1.request_mac = m0.mac w1 = m1.to_wire(multi=True) if pad != 0: self.assertEqual(len(w1) % pad, 0) m2 = dns.message.from_text(tm2) m2.use_edns(0, pad=pad) m2.use_tsig(keyring) w2 = m2.to_wire(multi=True, tsig_ctx=m1.tsig_ctx) if pad != 0: self.assertEqual(len(w2) % pad, 0) m3 = dns.message.from_wire(w1, keyring=keyring, request_mac=m0.mac, multi=True) m4 = dns.message.from_wire( w2, keyring=keyring, multi=True, tsig_ctx=m3.tsig_ctx ) # Not raising means we passed validation self.assertEqual(m1, m3) self.assertEqual(m2, m4) def test_multi(self): self._test_multi(0) def test_multi_with_pad(self): self._test_multi(468) dnspython-2.7.0/tests/test_tsigkeyring.py0000644000000000000000000000403013615410400015575 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import base64 import unittest import dns.tsig import dns.tsigkeyring text_keyring = {"keyname.": ("hmac-sha256.", "NjHwPsMKjdN++dOfE5iAiQ==")} alt_text_keyring = {"keyname.": (dns.tsig.HMAC_SHA256, "NjHwPsMKjdN++dOfE5iAiQ==")} old_text_keyring = {"keyname.": "NjHwPsMKjdN++dOfE5iAiQ=="} key = dns.tsig.Key("keyname.", "NjHwPsMKjdN++dOfE5iAiQ==") rich_keyring = {key.name: key} old_rich_keyring = {key.name: key.secret} class TSIGKeyRingTestCase(unittest.TestCase): def test_from_text(self): """text keyring -> rich keyring""" rkeyring = dns.tsigkeyring.from_text(text_keyring) self.assertEqual(rkeyring, rich_keyring) def test_from_alt_text(self): """alternate format text keyring -> rich keyring""" rkeyring = dns.tsigkeyring.from_text(alt_text_keyring) self.assertEqual(rkeyring, rich_keyring) def test_from_old_text(self): """old format text keyring -> rich keyring""" rkeyring = dns.tsigkeyring.from_text(old_text_keyring) self.assertEqual(rkeyring, old_rich_keyring) def test_to_text(self): """text keyring -> rich keyring -> text keyring""" tkeyring = dns.tsigkeyring.to_text(rich_keyring) self.assertEqual(tkeyring, text_keyring) def test_old_to_text(self): """text keyring -> rich keyring -> text keyring""" tkeyring = dns.tsigkeyring.to_text(old_rich_keyring) self.assertEqual(tkeyring, old_text_keyring) def test_from_and_to_text(self): """text keyring -> rich keyring -> text keyring""" rkeyring = dns.tsigkeyring.from_text(text_keyring) tkeyring = dns.tsigkeyring.to_text(rkeyring) self.assertEqual(tkeyring, text_keyring) def test_old_from_and_to_text(self): """text keyring -> rich keyring -> text keyring""" rkeyring = dns.tsigkeyring.from_text(old_text_keyring) tkeyring = dns.tsigkeyring.to_text(rkeyring) self.assertEqual(tkeyring, old_text_keyring) dnspython-2.7.0/tests/test_ttl.py0000644000000000000000000000256313615410400014052 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import unittest import dns.ttl class TTLTestCase(unittest.TestCase): def test_bind_style_ok(self): ttl = dns.ttl.from_text("2w1d1h1m1s") self.assertEqual(ttl, 2 * 604800 + 86400 + 3600 + 60 + 1) def test_bind_style_ok2(self): # no one should do this, but it is legal! :) ttl = dns.ttl.from_text("1s2w1m1d1h") self.assertEqual(ttl, 2 * 604800 + 86400 + 3600 + 60 + 1) def test_bind_style_bad_unit(self): with self.assertRaises(dns.ttl.BadTTL): dns.ttl.from_text("5y") def test_bind_style_no_unit(self): with self.assertRaises(dns.ttl.BadTTL): dns.ttl.from_text("1d5") def test_bind_style_leading_unit(self): with self.assertRaises(dns.ttl.BadTTL): dns.ttl.from_text("s") def test_bind_style_unit_without_digits(self): with self.assertRaises(dns.ttl.BadTTL): dns.ttl.from_text("1mw") def test_ttl_technically_too_big_but_tolerated(self): ttl = dns.ttl.from_text("4294967295") assert ttl == 4294967295 def test_ttl_technically_too_big(self): with self.assertRaises(dns.ttl.BadTTL): dns.ttl.from_text("4294967296") def test_empty(self): with self.assertRaises(dns.ttl.BadTTL): dns.ttl.from_text("") dnspython-2.7.0/tests/test_update.py0000644000000000000000000002623713615410400014535 0ustar00# 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 unittest import binascii import dns.update import dns.rdata import dns.rdataset import dns.tsigkeyring def hextowire(hex): return binascii.unhexlify(hex.replace(" ", "").encode()) goodwire = hextowire( "0001 2800 0001 0005 0007 0000" "076578616d706c6500 0006 0001" "03666f6fc00c 00ff 00ff 00000000 0000" "c019 0001 00ff 00000000 0000" "03626172c00c 0001 0001 00000000 0004 0a000005" "05626c617a32c00c 00ff 00fe 00000000 0000" "c049 0001 00fe 00000000 0000" "c019 0001 00ff 00000000 0000" "c019 0001 0001 0000012c 0004 0a000001" "c019 0001 0001 0000012c 0004 0a000002" "c035 0001 0001 0000012c 0004 0a000003" "c035 0001 00fe 00000000 0004 0a000004" "04626c617ac00c 0001 00ff 00000000 0000" "c049 00ff 00ff 00000000 0000" ) goodwirenone = hextowire( "0001 2800 0001 0000 0001 0000" "076578616d706c6500 0006 0001" "03666f6fc00c 0001 00fe 00000000 0004 01020304" ) badwirenone = hextowire( "0001 2800 0001 0003 0000 0000" "076578616d706c6500 0006 0001" "03666f6fc00c 00ff 00ff 00000000 0000" "c019 0001 00ff 00000000 0000" "c019 0001 00fe 00000000 0004 01020304" ) badwireany = hextowire( "0001 2800 0001 0002 0000 0000" "076578616d706c6500 0006 0001" "03666f6fc00c 00ff 00ff 00000000 0000" "c019 0001 00ff 00000000 0004 01020304" ) badwireanyany = hextowire( "0001 2800 0001 0001 0000 0000" "076578616d706c6500 0006 0001" "03666f6fc00c 00ff 00ff 00000000 0004 01020304" ) badwirezonetype = hextowire( "0001 2800 0001 0000 0000 0000" "076578616d706c6500 0001 0001" ) badwirezoneclass = hextowire( "0001 2800 0001 0000 0000 0000" "076578616d706c6500 0006 00ff" ) badwirezonemulti = hextowire( "0001 2800 0002 0000 0000 0000" "076578616d706c6500 0006 0001" "c019 0006 0001" ) badwirenozone = hextowire( "0001 2800 0000 0000 0001 0000" "03666f6f076578616d706c6500 0001 0001 00000030 0004 01020304" ) update_text = """id 1 opcode UPDATE rcode NOERROR ;ZONE example. IN SOA ;PREREQ foo ANY ANY foo ANY A bar 0 IN A 10.0.0.5 blaz2 NONE ANY blaz2 NONE A ;UPDATE foo ANY A foo 300 IN A 10.0.0.1 foo 300 IN A 10.0.0.2 bar 300 IN A 10.0.0.3 bar 0 NONE A 10.0.0.4 blaz ANY A blaz2 ANY ANY """ added_text = """id 1 opcode UPDATE ;ZONE example. IN SOA ;UPDATE foo 300 IN A 10.0.0.1 foo 300 IN A 10.0.0.2 """ replaced_text = """id 1 opcode UPDATE ;ZONE example. IN SOA ;UPDATE foo ANY A foo 300 IN A 10.0.0.1 foo 300 IN A 10.0.0.2 """ deleted_text = """id 1 opcode UPDATE ;ZONE example. IN SOA ;UPDATE foo 0 NONE A 10.0.0.1 foo 0 NONE A 10.0.0.2 """ class UpdateTestCase(unittest.TestCase): def test_to_wire1(self): # type: () -> None update = dns.update.Update("example") update.id = 1 update.present("foo") update.present("foo", "a") update.present("bar", "a", "10.0.0.5") update.absent("blaz2") update.absent("blaz2", "a") update.replace("foo", 300, "a", "10.0.0.1", "10.0.0.2") update.add("bar", 300, "a", "10.0.0.3") update.delete("bar", "a", "10.0.0.4") update.delete("blaz", "a") update.delete("blaz2") self.assertEqual(update.to_wire(), goodwire) def test_to_wire2(self): # type: () -> None update = dns.update.Update("example") update.id = 1 update.present("foo") update.present("foo", "a") update.present("bar", "a", "10.0.0.5") update.absent("blaz2") update.absent("blaz2", "a") update.replace("foo", 300, "a", "10.0.0.1", "10.0.0.2") update.add("bar", 300, dns.rdata.from_text(1, 1, "10.0.0.3")) update.delete("bar", "a", "10.0.0.4") update.delete("blaz", "a") update.delete("blaz2") self.assertEqual(update.to_wire(), goodwire) def test_to_wire3(self): # type: () -> None update = dns.update.Update("example") update.id = 1 update.present("foo") update.present("foo", "a") update.present("bar", "a", "10.0.0.5") update.absent("blaz2") update.absent("blaz2", "a") update.replace("foo", 300, "a", "10.0.0.1", "10.0.0.2") update.add("bar", dns.rdataset.from_text(1, 1, 300, "10.0.0.3")) update.delete("bar", "a", "10.0.0.4") update.delete("blaz", "a") update.delete("blaz2") self.assertEqual(update.to_wire(), goodwire) def test_from_text1(self): # type: () -> None update = dns.message.from_text(update_text) self.assertTrue(isinstance(update, dns.update.UpdateMessage)) w = update.to_wire(origin=dns.name.from_text("example"), want_shuffle=False) self.assertEqual(w, goodwire) def test_from_wire(self): origin = dns.name.from_text("example") u1 = dns.message.from_wire(goodwire, origin=origin) u2 = dns.message.from_text(update_text, origin=origin) self.assertEqual(u1, u2) def test_good_explicit_delete_wire(self): name = dns.name.from_text("foo.example") u = dns.message.from_wire(goodwirenone) self.assertEqual(u.update[0].name, name) self.assertEqual(u.update[0].rdtype, dns.rdatatype.A) self.assertEqual(u.update[0].rdclass, dns.rdataclass.IN) self.assertTrue(u.update[0].deleting) self.assertEqual(u.update[0][0].address, "1.2.3.4") def test_none_with_rdata_from_wire(self): def bad(): dns.message.from_wire(badwirenone) self.assertRaises(dns.exception.FormError, bad) def test_any_with_rdata_from_wire(self): def bad(): dns.message.from_wire(badwireany) self.assertRaises(dns.exception.FormError, bad) def test_any_any_with_rdata_from_wire(self): def bad(): dns.message.from_wire(badwireanyany) self.assertRaises(dns.exception.FormError, bad) def test_bad_zone_type_from_wire(self): def bad(): dns.message.from_wire(badwirezonetype) self.assertRaises(dns.exception.FormError, bad) def test_bad_zone_class_from_wire(self): def bad(): dns.message.from_wire(badwirezoneclass) self.assertRaises(dns.exception.FormError, bad) def test_bad_zone_multi_from_wire(self): def bad(): dns.message.from_wire(badwirezonemulti) self.assertRaises(dns.exception.FormError, bad) def test_no_zone_section_from_wire(self): def bad(): dns.message.from_wire(badwirenozone) self.assertRaises(dns.exception.FormError, bad) def test_TSIG(self): keyring = dns.tsigkeyring.from_text({"keyname.": "NjHwPsMKjdN++dOfE5iAiQ=="}) update = dns.update.Update("example.", keyring=keyring) update.replace("host.example.", 300, "A", "1.2.3.4") wire = update.to_wire() update2 = dns.message.from_wire(wire, keyring) self.assertEqual(update, update2) def test_is_response(self): update = dns.message.from_text(update_text) self.assertTrue(isinstance(update, dns.update.UpdateMessage)) r = dns.message.make_response(update) self.assertTrue(isinstance(r, dns.update.UpdateMessage)) self.assertTrue(update.is_response(r)) def test_making_UpdateSection(self): self.assertEqual( dns.update.UpdateSection.make(0), dns.update.UpdateSection.make("ZONE") ) with self.assertRaises(ValueError): dns.update.UpdateSection.make(99) def test_setters(self): u = dns.update.UpdateMessage(id=1) qrrset = dns.rrset.RRset( dns.name.from_text("example"), dns.rdataclass.IN, dns.rdatatype.SOA ) rrset = dns.rrset.from_text("foo", 300, "in", "a", "10.0.0.1") u.zone = [qrrset] self.assertEqual(u.sections[0], [qrrset]) self.assertEqual(u.sections[1], []) self.assertEqual(u.sections[2], []) self.assertEqual(u.sections[3], []) u.prerequisite = [rrset] self.assertEqual(u.sections[0], [qrrset]) self.assertEqual(u.sections[1], [rrset]) self.assertEqual(u.sections[2], []) self.assertEqual(u.sections[3], []) u.update = [rrset] self.assertEqual(u.sections[0], [qrrset]) self.assertEqual(u.sections[1], [rrset]) self.assertEqual(u.sections[2], [rrset]) self.assertEqual(u.sections[3], []) def test_added_rdataset(self): u = dns.update.UpdateMessage("example.", id=1) rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.1", "10.0.0.2") u.add("foo", rds) expected = dns.message.from_text(added_text) self.assertEqual(u, expected) def test_replaced_rdataset(self): u = dns.update.UpdateMessage("example.", id=1) rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.1", "10.0.0.2") u.replace("foo", rds) expected = dns.message.from_text(replaced_text) self.assertEqual(u, expected) def test_delete_rdataset(self): u = dns.update.UpdateMessage("example.", id=1) rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.1", "10.0.0.2") u.delete("foo", rds) expected = dns.message.from_text(deleted_text) self.assertEqual(u, expected) def test_added_rdata(self): u = dns.update.UpdateMessage("example.", id=1) rd1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1") rd2 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.2") u.add("foo", 300, rd1) u.add("foo", 300, rd2) expected = dns.message.from_text(added_text) self.assertEqual(u, expected) def test_replaced_rdata(self): u = dns.update.UpdateMessage("example.", id=1) rd1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1") rd2 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.2") u.replace("foo", 300, rd1) u.add("foo", 300, rd2) expected = dns.message.from_text(replaced_text) self.assertEqual(u, expected) def test_deleted_rdata(self): u = dns.update.UpdateMessage("example.", id=1) rd1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1") rd2 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.2") u.delete("foo", rd1) u.delete("foo", rd2) expected = dns.message.from_text(deleted_text) self.assertEqual(u, expected) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_wire.py0000644000000000000000000000635413615410400014217 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import unittest import dns.exception import dns.wire import dns.name class BinaryTestCase(unittest.TestCase): def test_basic(self): wire = bytes.fromhex("0102010203040102") p = dns.wire.Parser(wire) self.assertEqual(p.get_uint16(), 0x0102) with p.restrict_to(5): self.assertEqual(p.get_uint32(), 0x01020304) self.assertEqual(p.get_uint8(), 0x01) self.assertEqual(p.remaining(), 0) with self.assertRaises(dns.exception.FormError): p.get_uint16() self.assertEqual(p.remaining(), 1) self.assertEqual(p.get_uint8(), 0x02) with self.assertRaises(dns.exception.FormError): p.get_uint8() def test_name(self): # www.dnspython.org NS IN question wire = b"\x03www\x09dnspython\x03org\x00\x00\x02\x00\x01" expected = dns.name.from_text("www.dnspython.org") p = dns.wire.Parser(wire) self.assertEqual(p.get_name(), expected) self.assertEqual(p.get_uint16(), 2) self.assertEqual(p.get_uint16(), 1) self.assertEqual(p.remaining(), 0) def test_relativized_name(self): # www.dnspython.org NS IN question wire = b"\x03www\x09dnspython\x03org\x00\x00\x02\x00\x01" origin = dns.name.from_text("dnspython.org") expected = dns.name.from_text("www", None) p = dns.wire.Parser(wire) self.assertEqual(p.get_name(origin), expected) self.assertEqual(p.remaining(), 4) def test_compressed_name(self): # www.dnspython.org NS IN question wire = b"\x09dnspython\x03org\x00\x03www\xc0\x00" expected1 = dns.name.from_text("dnspython.org") expected2 = dns.name.from_text("www.dnspython.org") p = dns.wire.Parser(wire) self.assertEqual(p.get_name(), expected1) self.assertEqual(p.get_name(), expected2) self.assertEqual(p.remaining(), 0) # verify the restore_furthest() self.assertEqual(p.current, len(wire)) def test_seek(self): wire = b"\x09dnspython\x03org\x00" p = dns.wire.Parser(wire) p.seek(10) self.assertEqual(p.get_uint8(), 3) # seeking to the end index is OK p.seek(len(wire)) self.assertEqual(p.current, p.end) with self.assertRaises(dns.exception.FormError): # but reading there will not succeed p.get_uint8() with self.assertRaises(dns.exception.FormError): p.seek(-1) with self.assertRaises(dns.exception.FormError): p.seek(len(wire) + 1) def test_not_reading_everything_in_restriction(self): wire = bytes.fromhex("0102010203040102") p = dns.wire.Parser(wire) with self.assertRaises(dns.exception.FormError): with p.restrict_to(5): v = p.get_uint8() self.assertEqual(v, 1) # don't read the other 4 bytes def test_restriction_does_not_mask_exception(self): wire = bytes.fromhex("0102010203040102") p = dns.wire.Parser(wire) with self.assertRaises(NotImplementedError): with p.restrict_to(5): raise NotImplementedError dnspython-2.7.0/tests/test_xfr.py0000644000000000000000000005202413615410400014043 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import asyncio import sys import pytest import dns.asyncbackend import dns.asyncquery import dns.message import dns.query import dns.tsigkeyring import dns.versioned import dns.xfr # Some tests use a "nano nameserver" for testing. It requires trio # and threading, so try to import it and if it doesn't work, skip # those tests. try: from .nanonameserver import Server _nanonameserver_available = True except ImportError: _nanonameserver_available = False class Server(object): pass axfr = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN AXFR ;ANSWER @ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 bar.foo 300 IN MX 0 blaz.foo ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.2 @ 3600 IN SOA foo bar 1 2 3 4 5 """ axfr1 = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN AXFR ;ANSWER @ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 """ axfr2 = """id 1 opcode QUERY rcode NOERROR flags AA ;ANSWER bar.foo 300 IN MX 0 blaz.foo ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.2 @ 3600 IN SOA foo bar 1 2 3 4 5 """ base = """@ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 bar.foo 300 IN MX 0 blaz.foo ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.2 """ axfr_unexpected_origin = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN AXFR ;ANSWER @ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN SOA foo bar 1 2 3 4 7 """ ixfr = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN IXFR ;ANSWER @ 3600 IN SOA foo bar 4 2 3 4 5 @ 3600 IN SOA foo bar 1 2 3 4 5 bar.foo 300 IN MX 0 blaz.foo ns2 3600 IN A 10.0.0.2 @ 3600 IN SOA foo bar 2 2 3 4 5 ns2 3600 IN A 10.0.0.4 @ 3600 IN SOA foo bar 2 2 3 4 5 @ 3600 IN SOA foo bar 3 2 3 4 5 ns3 3600 IN A 10.0.0.3 @ 3600 IN SOA foo bar 3 2 3 4 5 @ 3600 IN NS ns2 @ 3600 IN SOA foo bar 4 2 3 4 5 @ 3600 IN SOA foo bar 4 2 3 4 5 """ compressed_ixfr = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN IXFR ;ANSWER @ 3600 IN SOA foo bar 4 2 3 4 5 @ 3600 IN SOA foo bar 1 2 3 4 5 bar.foo 300 IN MX 0 blaz.foo ns2 3600 IN A 10.0.0.2 @ 3600 IN NS ns2 @ 3600 IN SOA foo bar 4 2 3 4 5 ns2 3600 IN A 10.0.0.4 ns3 3600 IN A 10.0.0.3 @ 3600 IN SOA foo bar 4 2 3 4 5 """ ixfr_expected = """@ 3600 IN SOA foo bar 4 2 3 4 5 @ 3600 IN NS ns1 ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.4 ns3 3600 IN A 10.0.0.3 """ ixfr_first_message = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN IXFR ;ANSWER @ 3600 IN SOA foo bar 4 2 3 4 5 """ ixfr_header = """id 1 opcode QUERY rcode NOERROR flags AA ;ANSWER """ ixfr_body = [ "@ 3600 IN SOA foo bar 1 2 3 4 5", "bar.foo 300 IN MX 0 blaz.foo", "ns2 3600 IN A 10.0.0.2", "@ 3600 IN SOA foo bar 2 2 3 4 5", "ns2 3600 IN A 10.0.0.4", "@ 3600 IN SOA foo bar 2 2 3 4 5", "@ 3600 IN SOA foo bar 3 2 3 4 5", "ns3 3600 IN A 10.0.0.3", "@ 3600 IN SOA foo bar 3 2 3 4 5", "@ 3600 IN NS ns2", "@ 3600 IN SOA foo bar 4 2 3 4 5", "@ 3600 IN SOA foo bar 4 2 3 4 5", ] ixfrs = [ixfr_first_message] ixfrs.extend([ixfr_header + l for l in ixfr_body]) good_empty_ixfr = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN IXFR ;ANSWER @ 3600 IN SOA foo bar 1 2 3 4 5 """ retry_tcp_ixfr = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN IXFR ;ANSWER @ 3600 IN SOA foo bar 5 2 3 4 5 """ bad_empty_ixfr = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN IXFR ;ANSWER @ 3600 IN SOA foo bar 4 2 3 4 5 @ 3600 IN SOA foo bar 4 2 3 4 5 """ unexpected_end_ixfr = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN IXFR ;ANSWER @ 3600 IN SOA foo bar 4 2 3 4 5 @ 3600 IN SOA foo bar 1 2 3 4 5 bar.foo 300 IN MX 0 blaz.foo ns2 3600 IN A 10.0.0.2 @ 3600 IN NS ns2 @ 3600 IN SOA foo bar 3 2 3 4 5 ns2 3600 IN A 10.0.0.4 ns3 3600 IN A 10.0.0.3 @ 3600 IN SOA foo bar 4 2 3 4 5 """ unexpected_end_ixfr_2 = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN IXFR ;ANSWER @ 3600 IN SOA foo bar 4 2 3 4 5 @ 3600 IN SOA foo bar 1 2 3 4 5 bar.foo 300 IN MX 0 blaz.foo ns2 3600 IN A 10.0.0.2 @ 3600 IN NS ns2 """ bad_serial_ixfr = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN IXFR ;ANSWER @ 3600 IN SOA foo bar 4 2 3 4 5 @ 3600 IN SOA foo bar 2 2 3 4 5 bar.foo 300 IN MX 0 blaz.foo ns2 3600 IN A 10.0.0.2 @ 3600 IN NS ns2 @ 3600 IN SOA foo bar 4 2 3 4 5 ns2 3600 IN A 10.0.0.4 ns3 3600 IN A 10.0.0.3 @ 3600 IN SOA foo bar 4 2 3 4 5 """ ixfr_axfr = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN IXFR ;ANSWER @ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 bar.foo 300 IN MX 0 blaz.foo ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.2 @ 3600 IN SOA foo bar 1 2 3 4 5 """ def test_basic_axfr(): z = dns.versioned.Zone("example.") m = dns.message.from_text(axfr, origin=z.origin, one_rr_per_rrset=True) with dns.xfr.Inbound(z, dns.rdatatype.AXFR) as xfr: done = xfr.process_message(m) assert done ez = dns.zone.from_text(base, "example.") assert z == ez def test_basic_axfr_unversioned(): z = dns.zone.Zone("example.") m = dns.message.from_text(axfr, origin=z.origin, one_rr_per_rrset=True) with dns.xfr.Inbound(z, dns.rdatatype.AXFR) as xfr: done = xfr.process_message(m) assert done ez = dns.zone.from_text(base, "example.") assert z == ez def test_basic_axfr_two_parts(): z = dns.versioned.Zone("example.") m1 = dns.message.from_text(axfr1, origin=z.origin, one_rr_per_rrset=True) m2 = dns.message.from_text(axfr2, origin=z.origin, one_rr_per_rrset=True) with dns.xfr.Inbound(z, dns.rdatatype.AXFR) as xfr: done = xfr.process_message(m1) assert not done done = xfr.process_message(m2) assert done ez = dns.zone.from_text(base, "example.") assert z == ez def test_axfr_unexpected_origin(): z = dns.versioned.Zone("example.") m = dns.message.from_text( axfr_unexpected_origin, origin=z.origin, one_rr_per_rrset=True ) with dns.xfr.Inbound(z, dns.rdatatype.AXFR) as xfr: with pytest.raises(dns.exception.FormError): xfr.process_message(m) def test_basic_ixfr(): z = dns.zone.from_text(base, "example.", zone_factory=dns.versioned.Zone) m = dns.message.from_text(ixfr, origin=z.origin, one_rr_per_rrset=True) with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr: done = xfr.process_message(m) assert done ez = dns.zone.from_text(ixfr_expected, "example.") assert z == ez def test_basic_ixfr_unversioned(): z = dns.zone.from_text(base, "example.") m = dns.message.from_text(ixfr, origin=z.origin, one_rr_per_rrset=True) with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr: done = xfr.process_message(m) assert done ez = dns.zone.from_text(ixfr_expected, "example.") assert z == ez def test_compressed_ixfr(): z = dns.zone.from_text(base, "example.", zone_factory=dns.versioned.Zone) m = dns.message.from_text(compressed_ixfr, origin=z.origin, one_rr_per_rrset=True) with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr: done = xfr.process_message(m) assert done ez = dns.zone.from_text(ixfr_expected, "example.") assert z == ez def test_basic_ixfr_many_parts(): z = dns.zone.from_text(base, "example.", zone_factory=dns.versioned.Zone) with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr: done = False for text in ixfrs: assert not done m = dns.message.from_text(text, origin=z.origin, one_rr_per_rrset=True) done = xfr.process_message(m) assert done ez = dns.zone.from_text(ixfr_expected, "example.") assert z == ez def test_good_empty_ixfr(): z = dns.zone.from_text(ixfr_expected, "example.", zone_factory=dns.versioned.Zone) m = dns.message.from_text(good_empty_ixfr, origin=z.origin, one_rr_per_rrset=True) with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr: done = xfr.process_message(m) assert done ez = dns.zone.from_text(ixfr_expected, "example.") assert z == ez def test_retry_tcp_ixfr(): z = dns.zone.from_text(ixfr_expected, "example.", zone_factory=dns.versioned.Zone) m = dns.message.from_text(retry_tcp_ixfr, origin=z.origin, one_rr_per_rrset=True) with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1, is_udp=True) as xfr: with pytest.raises(dns.xfr.UseTCP): xfr.process_message(m) def test_bad_empty_ixfr(): z = dns.zone.from_text(ixfr_expected, "example.", zone_factory=dns.versioned.Zone) m = dns.message.from_text(bad_empty_ixfr, origin=z.origin, one_rr_per_rrset=True) with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=3) as xfr: with pytest.raises(dns.exception.FormError): xfr.process_message(m) def test_serial_went_backwards_ixfr(): z = dns.zone.from_text(ixfr_expected, "example.", zone_factory=dns.versioned.Zone) m = dns.message.from_text(bad_empty_ixfr, origin=z.origin, one_rr_per_rrset=True) with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=5) as xfr: with pytest.raises(dns.xfr.SerialWentBackwards): xfr.process_message(m) def test_ixfr_is_axfr(): z = dns.zone.from_text(base, "example.", zone_factory=dns.versioned.Zone) m = dns.message.from_text(ixfr_axfr, origin=z.origin, one_rr_per_rrset=True) with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=0xFFFFFFFF) as xfr: done = xfr.process_message(m) assert done ez = dns.zone.from_text(base, "example.") assert z == ez def test_ixfr_requires_serial(): z = dns.zone.from_text(base, "example.", zone_factory=dns.versioned.Zone) with pytest.raises(ValueError): dns.xfr.Inbound(z, dns.rdatatype.IXFR) def test_ixfr_unexpected_end_bad_diff_sequence(): # This is where we get the end serial, but haven't seen all of # the expected diffs z = dns.zone.from_text(base, "example.", zone_factory=dns.versioned.Zone) m = dns.message.from_text( unexpected_end_ixfr, origin=z.origin, one_rr_per_rrset=True ) with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr: with pytest.raises(dns.exception.FormError): xfr.process_message(m) def test_udp_ixfr_unexpected_end_just_stops(): # This is where everything looks good, but the IXFR just stops # in the middle. z = dns.zone.from_text(base, "example.", zone_factory=dns.versioned.Zone) m = dns.message.from_text( unexpected_end_ixfr_2, origin=z.origin, one_rr_per_rrset=True ) with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1, is_udp=True) as xfr: with pytest.raises(dns.exception.FormError): xfr.process_message(m) def test_ixfr_bad_serial(): z = dns.zone.from_text(base, "example.", zone_factory=dns.versioned.Zone) m = dns.message.from_text(bad_serial_ixfr, origin=z.origin, one_rr_per_rrset=True) with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr: with pytest.raises(dns.exception.FormError): xfr.process_message(m) def test_no_udp_with_axfr(): z = dns.versioned.Zone("example.") with pytest.raises(ValueError): with dns.xfr.Inbound(z, dns.rdatatype.AXFR, is_udp=True) as xfr: pass refused = """id 1 opcode QUERY rcode REFUSED flags AA ;QUESTION example. IN AXFR """ bad_qname = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION not-example. IN IXFR """ bad_qtype = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN AXFR """ soa_not_first = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN IXFR ;ANSWER bar.foo 300 IN MX 0 blaz.foo """ soa_not_first_2 = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN IXFR ;ANSWER @ 300 IN MX 0 blaz.foo """ no_answer = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN IXFR ;ADDITIONAL bar.foo 300 IN MX 0 blaz.foo """ axfr_answers_after_final_soa = """id 1 opcode QUERY rcode NOERROR flags AA ;QUESTION example. IN AXFR ;ANSWER @ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 bar.foo 300 IN MX 0 blaz.foo ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.2 @ 3600 IN SOA foo bar 1 2 3 4 5 ns3 3600 IN A 10.0.0.3 """ def test_refused(): z = dns.zone.from_text(base, "example.", zone_factory=dns.versioned.Zone) m = dns.message.from_text(refused, origin=z.origin, one_rr_per_rrset=True) with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr: with pytest.raises(dns.xfr.TransferError): xfr.process_message(m) def test_bad_qname(): z = dns.zone.from_text(base, "example.", zone_factory=dns.versioned.Zone) m = dns.message.from_text(bad_qname, origin=z.origin, one_rr_per_rrset=True) with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr: with pytest.raises(dns.exception.FormError): xfr.process_message(m) def test_bad_qtype(): z = dns.zone.from_text(base, "example.", zone_factory=dns.versioned.Zone) m = dns.message.from_text(bad_qtype, origin=z.origin, one_rr_per_rrset=True) with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr: with pytest.raises(dns.exception.FormError): xfr.process_message(m) def test_soa_not_first(): z = dns.zone.from_text(base, "example.", zone_factory=dns.versioned.Zone) m = dns.message.from_text(soa_not_first, origin=z.origin, one_rr_per_rrset=True) with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr: with pytest.raises(dns.exception.FormError): xfr.process_message(m) m = dns.message.from_text(soa_not_first_2, origin=z.origin, one_rr_per_rrset=True) with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr: with pytest.raises(dns.exception.FormError): xfr.process_message(m) def test_no_answer(): z = dns.zone.from_text(base, "example.", zone_factory=dns.versioned.Zone) m = dns.message.from_text(no_answer, origin=z.origin, one_rr_per_rrset=True) with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr: with pytest.raises(dns.exception.FormError): xfr.process_message(m) def test_axfr_answers_after_final_soa(): z = dns.versioned.Zone("example.") m = dns.message.from_text( axfr_answers_after_final_soa, origin=z.origin, one_rr_per_rrset=True ) with dns.xfr.Inbound(z, dns.rdatatype.AXFR) as xfr: with pytest.raises(dns.exception.FormError): xfr.process_message(m) keyring = dns.tsigkeyring.from_text({"keyname.": "NjHwPsMKjdN++dOfE5iAiQ=="}) keyname = dns.name.from_text("keyname") def test_make_query_basic(): z = dns.versioned.Zone("example.") (q, s) = dns.xfr.make_query(z) assert q.question[0].rdtype == dns.rdatatype.AXFR assert s is None (q, s) = dns.xfr.make_query(z, serial=None) assert q.question[0].rdtype == dns.rdatatype.AXFR assert s is None (q, s) = dns.xfr.make_query(z, serial=10) assert q.question[0].rdtype == dns.rdatatype.IXFR assert q.authority[0].rdtype == dns.rdatatype.SOA assert q.authority[0][0].serial == 10 assert s == 10 with z.writer() as txn: txn.add("@", 300, dns.rdata.from_text("in", "soa", ". . 1 2 3 4 5")) (q, s) = dns.xfr.make_query(z) assert q.question[0].rdtype == dns.rdatatype.IXFR assert q.authority[0].rdtype == dns.rdatatype.SOA assert q.authority[0][0].serial == 1 assert s == 1 (q, s) = dns.xfr.make_query(z, keyring=keyring, keyname=keyname) assert q.question[0].rdtype == dns.rdatatype.IXFR assert q.authority[0].rdtype == dns.rdatatype.SOA assert q.authority[0][0].serial == 1 assert s == 1 assert q.keyname == keyname def test_make_query_bad_serial(): z = dns.versioned.Zone("example.") with pytest.raises(ValueError): dns.xfr.make_query(z, serial="hi") with pytest.raises(ValueError): dns.xfr.make_query(z, serial=-1) with pytest.raises(ValueError): dns.xfr.make_query(z, serial=4294967296) def test_extract_serial_from_query(): z = dns.versioned.Zone("example.") (q, s) = dns.xfr.make_query(z) xs = dns.xfr.extract_serial_from_query(q) assert s is None assert s == xs (q, s) = dns.xfr.make_query(z, serial=10) xs = dns.xfr.extract_serial_from_query(q) assert s == 10 assert s == xs q = dns.message.make_query("example", "a") with pytest.raises(ValueError): dns.xfr.extract_serial_from_query(q) class XFRNanoNameserver(Server): def __init__(self): super().__init__(origin=dns.name.from_text("example")) def handle(self, request): try: if request.message.question[0].rdtype == dns.rdatatype.IXFR: text = ixfr else: text = axfr r = dns.message.from_text(text, one_rr_per_rrset=True, origin=self.origin) r.id = request.message.id return r except Exception: pass @pytest.mark.skipif(not _nanonameserver_available, reason="requires nanonameserver") def test_sync_inbound_xfr(): with XFRNanoNameserver() as ns: zone = dns.versioned.Zone("example") dns.query.inbound_xfr( ns.tcp_address[0], zone, port=ns.tcp_address[1], udp_mode=dns.query.UDPMode.TRY_FIRST, ) dns.query.inbound_xfr( ns.tcp_address[0], zone, port=ns.tcp_address[1], udp_mode=dns.query.UDPMode.TRY_FIRST, ) expected = dns.zone.from_text(ixfr_expected, "example") assert zone == expected async def async_inbound_xfr(): with XFRNanoNameserver() as ns: zone = dns.versioned.Zone("example") await dns.asyncquery.inbound_xfr( ns.tcp_address[0], zone, port=ns.tcp_address[1], udp_mode=dns.query.UDPMode.TRY_FIRST, ) await dns.asyncquery.inbound_xfr( ns.tcp_address[0], zone, port=ns.tcp_address[1], udp_mode=dns.query.UDPMode.TRY_FIRST, ) expected = dns.zone.from_text(ixfr_expected, "example") assert zone == expected @pytest.mark.skipif(not _nanonameserver_available, reason="requires nanonameserver") def test_asyncio_inbound_xfr(): dns.asyncbackend.set_default_backend("asyncio") async def run(): await async_inbound_xfr() asyncio.run(run()) # # We don't need to do this as it's all generic code, but # just for extra caution we do it for each backend. # try: import trio @pytest.mark.skipif(not _nanonameserver_available, reason="requires nanonameserver") def test_trio_inbound_xfr(): dns.asyncbackend.set_default_backend("trio") async def run(): await async_inbound_xfr() trio.run(run) except ImportError: pass class UDPXFRNanoNameserver(Server): def __init__(self): super().__init__(origin=dns.name.from_text("example")) self.did_truncation = False def handle(self, request): try: if request.message.question[0].rdtype == dns.rdatatype.IXFR: if self.did_truncation: text = ixfr else: text = retry_tcp_ixfr self.did_truncation = True else: text = axfr r = dns.message.from_text(text, one_rr_per_rrset=True, origin=self.origin) r.id = request.message.id return r except Exception: pass @pytest.mark.skipif(not _nanonameserver_available, reason="requires nanonameserver") def test_sync_retry_tcp_inbound_xfr(): with UDPXFRNanoNameserver() as ns: zone = dns.versioned.Zone("example") dns.query.inbound_xfr( ns.tcp_address[0], zone, port=ns.tcp_address[1], udp_mode=dns.query.UDPMode.TRY_FIRST, ) dns.query.inbound_xfr( ns.tcp_address[0], zone, port=ns.tcp_address[1], udp_mode=dns.query.UDPMode.TRY_FIRST, ) expected = dns.zone.from_text(ixfr_expected, "example") assert zone == expected async def udp_async_inbound_xfr(): with UDPXFRNanoNameserver() as ns: zone = dns.versioned.Zone("example") await dns.asyncquery.inbound_xfr( ns.tcp_address[0], zone, port=ns.tcp_address[1], udp_mode=dns.query.UDPMode.TRY_FIRST, ) await dns.asyncquery.inbound_xfr( ns.tcp_address[0], zone, port=ns.tcp_address[1], udp_mode=dns.query.UDPMode.TRY_FIRST, ) expected = dns.zone.from_text(ixfr_expected, "example") assert zone == expected @pytest.mark.skipif(not _nanonameserver_available, reason="requires nanonameserver") def test_asyncio_retry_tcp_inbound_xfr(): dns.asyncbackend.set_default_backend("asyncio") async def run(): await udp_async_inbound_xfr() try: runner = asyncio.run except AttributeError: def old_runner(awaitable): loop = asyncio.get_event_loop() return loop.run_until_complete(awaitable) runner = old_runner runner(run()) dnspython-2.7.0/tests/test_zone.py0000644000000000000000000013214113615410400014216 0ustar00# -*- coding: utf-8 # 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 difflib import os import sys import unittest from io import BytesIO, StringIO from typing import cast import dns.exception import dns.message import dns.name import dns.node import dns.rdata import dns.rdataclass import dns.rdataset import dns.rdatatype import dns.rrset import dns.versioned import dns.zone from tests.util import here example_text = """$TTL 3600 $ORIGIN example. @ soa foo bar 1 2 3 4 5 @ ns ns1 @ ns ns2 ns1 a 10.0.0.1 ns2 a 10.0.0.2 $TTL 300 $ORIGIN foo.example. bar mx 0 blaz """ example_text_output = """@ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 bar.foo 300 IN MX 0 blaz.foo ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.2 """ example_text_output_class_before_ttl = """@ IN 3600 SOA foo bar 1 2 3 4 5 @ 3600 NS ns1 ; no class @ NS ns2 ; no class or TTL, TTL inferred from prior record bar.foo IN 300 MX 0 blaz.foo ns1 IN 3600 A 10.0.0.1 ns2 IN 3600 A 10.0.0.2 """ example_generate = """@ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns $GENERATE 9-12 a.$ A 10.0.0.$ $GENERATE 80-254/173 b.${0,5,d} A 10.0.1.$ $GENERATE 80-254/173 c.${0,5,o} A 10.0.2.$ $GENERATE 80-254/173 d.${0,5,x} A 10.0.3.$ $GENERATE 80-254/173 e.${0,5,X} A 10.0.4.$ $GENERATE 80-254/173 f.${0,5,n} A 10.0.5.$ $GENERATE 80-254/173 g.${0,5,N} A 10.0.6.$ $GENERATE 218-218/1 h.${0,4,N} A 10.0.7.$ $GENERATE 218-218/1 i.${0,4,N}j A 10.0.8.$ $GENERATE 23-24 k.${1,2,d} A 10.0.9.${-1,2,d} """ example_generate_output = """@ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns a.9 5 IN A 10.0.0.9 a.10 5 IN A 10.0.0.10 a.11 5 IN A 10.0.0.11 a.12 5 IN A 10.0.0.12 b.00080 5 IN A 10.0.1.80 b.00253 5 IN A 10.0.1.253 c.00120 5 IN A 10.0.2.80 c.00375 5 IN A 10.0.2.253 d.00050 5 IN A 10.0.3.80 d.000fd 5 IN A 10.0.3.253 e.00050 5 IN A 10.0.4.80 e.000FD 5 IN A 10.0.4.253 f.0.5.0 5 IN A 10.0.5.80 f.d.f.0 5 IN A 10.0.5.253 g.0.5.0 5 IN A 10.0.6.80 g.D.F.0 5 IN A 10.0.6.253 i.A.D.j 5 IN A 10.0.8.218 k.24 5 IN A 10.0.9.22 k.25 5 IN A 10.0.9.23 """ something_quite_similar = """@ 3600 IN SOA foo bar 1 2 3 4 5 @ 3600 IN NS ns1 @ 3600 IN NS ns2 bar.foo 300 IN MX 0 blaz.foo ns1 3600 IN A 10.0.0.1 ns2 3600 IN A 10.0.0.3 """ something_different = """@ 3600 IN SOA fooa bar 1 2 3 4 5 @ 3600 IN NS ns11 @ 3600 IN NS ns21 bar.fooa 300 IN MX 0 blaz.fooa ns11 3600 IN A 10.0.0.11 ns21 3600 IN A 10.0.0.21 """ ttl_example_text = """$TTL 1h $ORIGIN example. @ soa foo bar 1 2 3 4 5 @ ns ns1 @ ns ns2 ns1 1d1s a 10.0.0.1 ns2 1w1D1h1m1S a 10.0.0.2 """ # No $TTL so default TTL for RRs should be inherited from SOA minimum TTL ( # not from the last explicit RR TTL). ttl_from_soa_text = """$ORIGIN example. @ 1h soa foo bar 1 2 3 4 5 @ 1h ns ns1 @ 1h ns ns2 ns1 1w1D1h1m1S a 10.0.0.2 ns2 a 10.0.0.1 """ # No $TTL and no SOA, so default TTL for RRs should be inherited from last # explicit RR TTL. ttl_from_last_text = """$ORIGIN example. @ 1h ns ns1 @ 1h ns ns2 ns1 a 10.0.0.1 ns2 1w1D1h1m1S a 10.0.0.2 """ # No $TTL and no SOA should raise SyntaxError as no TTL can be determined. no_ttl_text = """$ORIGIN example. @ ns ns1 @ ns ns2 ns1 a 10.0.0.1 ns2 a 10.0.0.2 """ no_soa_text = """$TTL 1h $ORIGIN example. @ ns ns1 @ ns ns2 ns1 1d1s a 10.0.0.1 ns2 1w1D1h1m1S a 10.0.0.2 """ no_ns_text = """$TTL 1h $ORIGIN example. @ soa foo bar 1 2 3 4 5 """ include_text = """$INCLUDE "%s" """ % here( "example" ) bad_directive_text = """$FOO bar $ORIGIN example. @ soa foo bar 1 2 3 4 5 @ ns ns1 @ ns ns2 ns1 1d1s a 10.0.0.1 ns2 1w1D1h1m1S a 10.0.0.2 """ codec_text = """ @ soa foo bar 1 2 3 4 5 @ ns ns1 @ ns ns2 Königsgäßchen 300 NS Königsgäßchen """ misc_cases_input = """ $ORIGIN example. $TTL 300 @ soa foo bar 1 2 3 4 5 @ ns ns1 @ ns ns2 out-of-zone. in a 10.0.0.1 """ misc_cases_expected = """ $ORIGIN example. $TTL 300 @ soa foo bar 1 2 3 4 5 @ ns ns1 @ ns ns2 """ last_ttl_input = """ $ORIGIN example. @ 300 ns ns1 @ 300 ns ns2 foo a 10.0.0.1 @ soa foo bar 1 2 3 4 5 """ origin_sets_input = """ $ORIGIN example. @ soa foo bar 1 2 3 4 5 @ 300 ns ns1 @ 300 ns ns2 """ example_comments_text = """$TTL 3600 $ORIGIN example. @ soa foo bar (1 ; not kept 2 3 4 5) ; kept @ ns ns1 @ ns ns2 ns1 a 10.0.0.1 ; comment1 ns2 a 10.0.0.2 ; comment2 """ example_comments_text_output = """@ 3600 IN SOA foo bar 1 2 3 4 5 ; kept @ 3600 IN NS ns1 @ 3600 IN NS ns2 ns1 3600 IN A 10.0.0.1 ; comment1 ns2 3600 IN A 10.0.0.2 ; comment2 """ example_cname = """$TTL 3600 $ORIGIN example. @ soa foo bar (1 2 3 4 5) @ ns ns1 @ ns ns2 ns1 a 10.0.0.1 ns2 a 10.0.0.2 www a 10.0.0.3 web cname www nsec @ CNAME RRSIG rrsig NSEC 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= rrsig CNAME 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= web2 cname www nsec3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr CNAME RRSIG rrsig NSEC3 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= rrsig CNAME 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= """ example_other_data = """$TTL 3600 $ORIGIN example. @ soa foo bar (1 2 3 4 5) @ ns ns1 @ ns ns2 ns1 a 10.0.0.1 ns2 a 10.0.0.2 www a 10.0.0.3 web a 10.0.0.4 nsec @ A RRSIG rrsig A 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= rrsig NSEC 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= """ example_cname_and_other_data = """$TTL 3600 $ORIGIN example. @ soa foo bar (1 2 3 4 5) @ ns ns1 @ ns ns2 ns1 a 10.0.0.1 ns2 a 10.0.0.2 www a 10.0.0.3 web a 10.0.0.4 cname www nsec @ A RRSIG rrsig A 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= rrsig NSEC 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= """ _keep_output = True def _rdata_sort(a): return (a[0], a[2].rdclass, a[2].to_text()) def add_rdataset(msg, name, rds): rrset = msg.get_rrset( msg.answer, name, rds.rdclass, rds.rdtype, create=True, force_unique=True ) for rd in rds: rrset.add(rd, ttl=rds.ttl) def make_xfr(zone): q = dns.message.make_query(zone.origin, "AXFR") msg = dns.message.make_response(q) if zone.relativize: msg.origin = zone.origin soa_name = dns.name.empty else: soa_name = zone.origin soa = zone.find_rdataset(soa_name, "SOA") add_rdataset(msg, soa_name, soa) for name, rds in zone.iterate_rdatasets(): if rds.rdtype == dns.rdatatype.SOA: continue add_rdataset(msg, name, rds) add_rdataset(msg, soa_name, soa) return [msg] def compare_files(test_name, a_name, b_name): with open(a_name, "r") as a: with open(b_name, "r") as b: differences = list(difflib.unified_diff(a.readlines(), b.readlines())) if len(differences) == 0: return True else: print(f"{test_name} differences:") sys.stdout.writelines(differences) return False class ZoneTestCase(unittest.TestCase): def testFromFile1(self): z = dns.zone.from_file(here("example"), "example") ok = False try: z.to_file(here("example1.out"), nl=b"\x0a") ok = compare_files( "testFromFile1", here("example1.out"), here("example1.good") ) finally: if not _keep_output: os.unlink(here("example1.out")) self.assertTrue(ok) def testFromFile2(self): z = dns.zone.from_file(here("example"), "example", relativize=False) ok = False try: z.to_file(here("example2.out"), relativize=False, nl=b"\x0a") ok = compare_files( "testFromFile2", here("example2.out"), here("example2.good") ) finally: if not _keep_output: os.unlink(here("example2.out")) self.assertTrue(ok) def testToFileTextualStream(self): z = dns.zone.from_text(example_text, "example.", relativize=True) f = StringIO() z.to_file(f) out = f.getvalue() f.close() self.assertEqual(out, example_text_output) def testToFileBinaryStream(self): z = dns.zone.from_text(example_text, "example.", relativize=True) f = BytesIO() z.to_file(f, nl=b"\n") out = f.getvalue() f.close() self.assertEqual(out, example_text_output.encode()) def testToFileTextual(self): z = dns.zone.from_file(here("example"), "example") try: f = open(here("example3-textual.out"), "w") z.to_file(f) f.close() ok = compare_files( "testToFileTextual", here("example3-textual.out"), here("example3.good") ) finally: if not _keep_output: os.unlink(here("example3-textual.out")) self.assertTrue(ok) def testToFileBinary(self): z = dns.zone.from_file(here("example"), "example") try: f = open(here("example3-binary.out"), "wb") z.to_file(f) f.close() ok = compare_files( "testToFileBinary", here("example3-binary.out"), here("example3.good") ) finally: if not _keep_output: os.unlink(here("example3-binary.out")) self.assertTrue(ok) def testToFileFilename(self): z = dns.zone.from_file(here("example"), "example") try: z.to_file(here("example3-filename.out")) ok = compare_files( "testToFileFilename", here("example3-filename.out"), here("example3.good"), ) finally: if not _keep_output: os.unlink(here("example3-filename.out")) self.assertTrue(ok) def testToText(self): z = dns.zone.from_file(here("example"), "example") ok = False try: text_zone = z.to_text(nl="\x0a") f = open(here("example3.out"), "w") f.write(text_zone) f.close() ok = compare_files( "testToText", here("example3.out"), here("example3.good") ) finally: if not _keep_output: os.unlink(here("example3.out")) self.assertTrue(ok) def testToFileTextualWithOrigin(self): z = dns.zone.from_file(here("example"), "example") try: f = open(here("example4-textual.out"), "w") z.to_file(f, want_origin=True) f.close() ok = compare_files( "testToFileTextualWithOrigin", here("example4-textual.out"), here("example4.good"), ) finally: if not _keep_output: os.unlink(here("example4-textual.out")) self.assertTrue(ok) def testToFileBinaryWithOrigin(self): z = dns.zone.from_file(here("example"), "example") try: f = open(here("example4-binary.out"), "wb") z.to_file(f, want_origin=True) f.close() ok = compare_files( "testToFileBinaryWithOrigin", here("example4-binary.out"), here("example4.good"), ) finally: if not _keep_output: os.unlink(here("example4-binary.out")) self.assertTrue(ok) def testFromText(self): z = dns.zone.from_text(example_text, "example.", relativize=True) f = StringIO() names = list(z.nodes.keys()) names.sort() for n in names: f.write(z[n].to_text(n)) f.write("\n") self.assertEqual(f.getvalue(), example_text_output) def testGenerate(self): z = dns.zone.from_text(example_generate, "example.", relativize=True) f = StringIO() expected = dns.zone.from_text( example_generate_output, "example.", relativize=True ) self.assertEqual(z, expected) def testTorture1(self): # # Read a zone containing all our supported RR types, and # for each RR in the zone, convert the rdata into wire format # and then back out, and see if we get equal rdatas. # f = BytesIO() o = dns.name.from_text("example.") z = dns.zone.from_file(here("example"), o) for node in z.values(): for rds in node: for rd in rds: f.seek(0) f.truncate() rd.to_wire(f, origin=o) wire = f.getvalue() rd2 = dns.rdata.from_wire( rds.rdclass, rds.rdtype, wire, 0, len(wire), origin=o ) self.assertEqual(rd, rd2) def testEqual(self): z1 = dns.zone.from_text(example_text, "example.", relativize=True) z2 = dns.zone.from_text(example_text_output, "example.", relativize=True) self.assertEqual(z1, z2) def testEqualClassBeforeTTL(self): z1 = dns.zone.from_text( example_text_output_class_before_ttl, "example.", relativize=True ) z2 = dns.zone.from_text(example_text_output, "example.", relativize=True) self.assertEqual(z1, z2) def testNotEqual1(self): z1 = dns.zone.from_text(example_text, "example.", relativize=True) z2 = dns.zone.from_text(something_quite_similar, "example.", relativize=True) self.assertNotEqual(z1, z2) def testNotEqual2(self): z1 = dns.zone.from_text(example_text, "example.", relativize=True) z2 = dns.zone.from_text(something_different, "example.", relativize=True) self.assertNotEqual(z1, z2) def testNotEqual3(self): z1 = dns.zone.from_text(example_text, "example.", relativize=True) z2 = dns.zone.from_text(something_different, "example2.", relativize=True) self.assertNotEqual(z1, z2) def testFindRdataset1(self): z = dns.zone.from_text(example_text, "example.", relativize=True) rds = z.find_rdataset("@", "soa") exrds = dns.rdataset.from_text("IN", "SOA", 300, "foo bar 1 2 3 4 5") self.assertEqual(rds, exrds) def testFindRdataset2(self): def bad(): z = dns.zone.from_text(example_text, "example.", relativize=True) z.find_rdataset("@", "loc") self.assertRaises(KeyError, bad) def testFindRRset1(self): z = dns.zone.from_text(example_text, "example.", relativize=True) rrs = z.find_rrset("@", "soa") exrrs = dns.rrset.from_text("@", 300, "IN", "SOA", "foo bar 1 2 3 4 5") self.assertEqual(rrs, exrrs) def testFindRRset2(self): def bad(): z = dns.zone.from_text(example_text, "example.", relativize=True) z.find_rrset("@", "loc") self.assertRaises(KeyError, bad) def testGetRdataset1(self): z = dns.zone.from_text(example_text, "example.", relativize=True) rds = z.get_rdataset("@", "soa") exrds = dns.rdataset.from_text("IN", "SOA", 300, "foo bar 1 2 3 4 5") self.assertEqual(rds, exrds) def testGetRdataset2(self): z = dns.zone.from_text(example_text, "example.", relativize=True) rds = z.get_rdataset("@", "loc") self.assertTrue(rds is None) def testGetRdatasetWithRelativeNameFromAbsoluteZone(self): z = dns.zone.from_text(example_text, "example.", relativize=False) rds = z.get_rdataset(dns.name.empty, "soa") self.assertIsNotNone(rds) exrds = dns.rdataset.from_text( "IN", "SOA", 300, "foo.example. bar.example. 1 2 3 4 5" ) self.assertEqual(rds, exrds) def testGetRRset1(self): z = dns.zone.from_text(example_text, "example.", relativize=True) rrs = z.get_rrset("@", "soa") exrrs = dns.rrset.from_text("@", 300, "IN", "SOA", "foo bar 1 2 3 4 5") self.assertEqual(rrs, exrrs) def testGetRRset2(self): z = dns.zone.from_text(example_text, "example.", relativize=True) rrs = z.get_rrset("@", "loc") self.assertTrue(rrs is None) def testReplaceRdataset1(self): z = dns.zone.from_text(example_text, "example.", relativize=True) rdataset = dns.rdataset.from_text("in", "ns", 300, "ns3", "ns4") z.replace_rdataset("@", rdataset) rds = z.get_rdataset("@", "ns") self.assertTrue(rds is rdataset) def testReplaceRdataset2(self): z = dns.zone.from_text(example_text, "example.", relativize=True) rdataset = dns.rdataset.from_text("in", "txt", 300, '"foo"') z.replace_rdataset("@", rdataset) rds = z.get_rdataset("@", "txt") self.assertTrue(rds is rdataset) def testDeleteRdataset1(self): z = dns.zone.from_text(example_text, "example.", relativize=True) z.delete_rdataset("@", "ns") rds = z.get_rdataset("@", "ns") self.assertTrue(rds is None) def testDeleteRdataset2(self): z = dns.zone.from_text(example_text, "example.", relativize=True) z.delete_rdataset("ns1", "a") node = z.get_node("ns1") self.assertTrue(node is None) def testNodeFindRdataset1(self): z = dns.zone.from_text(example_text, "example.", relativize=True) node = z["@"] rds = node.find_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA) exrds = dns.rdataset.from_text("IN", "SOA", 300, "foo bar 1 2 3 4 5") self.assertEqual(rds, exrds) def testNodeFindRdataset2(self): def bad(): z = dns.zone.from_text(example_text, "example.", relativize=True) node = z["@"] node.find_rdataset(dns.rdataclass.IN, dns.rdatatype.LOC) self.assertRaises(KeyError, bad) def testNodeFindRdataset3(self): z = dns.zone.from_text(example_text, "example.", relativize=True) node = z["@"] rds = node.find_rdataset( dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.A, create=True ) self.assertEqual(rds.rdclass, dns.rdataclass.IN) self.assertEqual(rds.rdtype, dns.rdatatype.RRSIG) self.assertEqual(rds.covers, dns.rdatatype.A) def testNodeGetRdataset1(self): z = dns.zone.from_text(example_text, "example.", relativize=True) node = z["@"] rds = node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA) exrds = dns.rdataset.from_text("IN", "SOA", 300, "foo bar 1 2 3 4 5") self.assertEqual(rds, exrds) def testNodeGetRdataset2(self): z = dns.zone.from_text(example_text, "example.", relativize=True) node = z["@"] rds = node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.LOC) self.assertTrue(rds is None) def testNodeDeleteRdataset1(self): z = dns.zone.from_text(example_text, "example.", relativize=True) node = z["@"] node.delete_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA) rds = node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA) self.assertTrue(rds is None) def testNodeDeleteRdataset2(self): z = dns.zone.from_text(example_text, "example.", relativize=True) node = z["@"] node.delete_rdataset(dns.rdataclass.IN, dns.rdatatype.LOC) rds = node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.LOC) self.assertTrue(rds is None) def testIterateNodes(self): z = dns.zone.from_text(example_text, "example.", relativize=True) count = 0 for n in z: count += 1 self.assertEqual(count, 4) def testIterateRdatasets(self): z = dns.zone.from_text(example_text, "example.", relativize=True) ns = [n for n, r in z.iterate_rdatasets("A")] ns.sort() self.assertEqual( ns, [dns.name.from_text("ns1", None), dns.name.from_text("ns2", None)] ) def testIterateAllRdatasets(self): z = dns.zone.from_text(example_text, "example.", relativize=True) ns = [n for n, r in z.iterate_rdatasets()] ns.sort() self.assertEqual( ns, [ dns.name.from_text("@", None), dns.name.from_text("@", None), dns.name.from_text("bar.foo", None), dns.name.from_text("ns1", None), dns.name.from_text("ns2", None), ], ) def testIterateRdatas(self): z = dns.zone.from_text(example_text, "example.", relativize=True) l = list(z.iterate_rdatas("A")) l.sort() exl = [ ( dns.name.from_text("ns1", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1"), ), ( dns.name.from_text("ns2", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.2"), ), ] self.assertEqual(l, exl) def testIterateAllRdatas(self): z = dns.zone.from_text(example_text, "example.", relativize=True) l = list(z.iterate_rdatas()) l.sort(key=_rdata_sort) exl = [ ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns1"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS, "ns2"), ), ( dns.name.from_text("@", None), 3600, dns.rdata.from_text( dns.rdataclass.IN, dns.rdatatype.SOA, "foo bar 1 2 3 4 5" ), ), ( dns.name.from_text("bar.foo", None), 300, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX, "0 blaz.foo"), ), ( dns.name.from_text("ns1", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.1"), ), ( dns.name.from_text("ns2", None), 3600, dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "10.0.0.2"), ), ] exl.sort(key=_rdata_sort) self.assertEqual(l, exl) def testNodeGetSetDel(self): z = dns.zone.from_text(example_text, "example.", relativize=True) n = z.node_factory() rds = dns.rdataset.from_text("IN", "A", 300, "10.0.0.1") n.replace_rdataset(rds) z["foo"] = n self.assertTrue(z.find_rdataset("foo", "A") is rds) self.assertEqual(z["foo"], n) self.assertEqual(z.get("foo"), n) del z["foo"] self.assertEqual(z.get("foo"), None) with self.assertRaises(KeyError): z[123] = n with self.assertRaises(KeyError): z["foo."] = n with self.assertRaises(KeyError): bn = z.find_node("bar") bn = z.find_node("bar", True) self.assertTrue(isinstance(bn, dns.node.Node)) # The next two tests pass by not raising KeyError z.delete_node("foo") z.delete_node("bar") def testBadReplacement(self): z = dns.zone.from_text(example_text, "example.", relativize=True) rds = dns.rdataset.from_text("CH", "TXT", 300, "hi") def bad(): z.replace_rdataset("foo", rds) self.assertRaises(ValueError, bad) def testTTLs(self): z = dns.zone.from_text(ttl_example_text, "example.", relativize=True) n = z["@"] # type: dns.node.Node rds = cast( dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA) ) self.assertEqual(rds.ttl, 3600) n = z["ns1"] rds = cast( dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A) ) self.assertEqual(rds.ttl, 86401) n = z["ns2"] rds = cast( dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A) ) self.assertEqual(rds.ttl, 694861) def testTTLFromSOA(self): z = dns.zone.from_text(ttl_from_soa_text, "example.", relativize=True) n = z["@"] rds = cast( dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA) ) self.assertEqual(rds.ttl, 3600) soa_rd = rds[0] n = z["ns1"] rds = cast( dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A) ) self.assertEqual(rds.ttl, 694861) n = z["ns2"] rds = cast( dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A) ) self.assertEqual(rds.ttl, soa_rd.minimum) def testTTLFromLast(self): z = dns.zone.from_text(ttl_from_last_text, "example.", check_origin=False) n = z["@"] rds = cast( dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.NS) ) self.assertEqual(rds.ttl, 3600) n = z["ns1"] rds = cast( dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A) ) self.assertEqual(rds.ttl, 3600) n = z["ns2"] rds = cast( dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A) ) self.assertEqual(rds.ttl, 694861) def testNoTTL(self): def bad(): dns.zone.from_text(no_ttl_text, "example.", check_origin=False) self.assertRaises(dns.exception.SyntaxError, bad) def testNoSOA(self): def bad(): dns.zone.from_text(no_soa_text, "example.", relativize=True) self.assertRaises(dns.zone.NoSOA, bad) def testNoNS(self): def bad(): dns.zone.from_text(no_ns_text, "example.", relativize=True) self.assertRaises(dns.zone.NoNS, bad) def testInclude(self): z1 = dns.zone.from_text( include_text, "example.", relativize=True, allow_include=True ) z2 = dns.zone.from_file(here("example"), "example.", relativize=True) self.assertEqual(z1, z2) def testNoInclude(self): def bad(): dns.zone.from_text( include_text, "example.", relativize=True, allow_include=False ) self.assertRaises(dns.exception.SyntaxError, bad) def testExplicitInclude(self): z1 = dns.zone.from_text( include_text, "example.", relativize=True, allow_directives={"$INCLUDE", "$ORIGIN", "$TTL"}, ) z2 = dns.zone.from_file(here("example"), "example.", relativize=True) self.assertEqual(z1, z2) def testExplicitIncludeNotUpperNoDollar(self): z1 = dns.zone.from_text( include_text, "example.", relativize=True, allow_directives={"InClUdE", "origin", "TTL"}, ) z2 = dns.zone.from_file(here("example"), "example.", relativize=True) self.assertEqual(z1, z2) def testExplicitLowerCase(self): z1 = dns.zone.from_text( include_text, "example.", relativize=True, allow_directives={"$include", "$origin", "$ttl"}, ) z2 = dns.zone.from_file(here("example"), "example.", relativize=True) self.assertEqual(z1, z2) def testExplicitWithoutInclude1(self): def bad(): dns.zone.from_text( include_text, "example.", relativize=True, allow_include=False, allow_directives={"$ORIGIN", "$TTL"}, ) self.assertRaises(dns.exception.SyntaxError, bad) def testExplicitWithoutInclude2(self): def bad(): dns.zone.from_text( include_text, "example.", relativize=True, allow_include=True, allow_directives={"$ORIGIN", "$TTL"}, ) self.assertRaises(dns.exception.SyntaxError, bad) def testBadDirective(self): def bad(): dns.zone.from_text(bad_directive_text, "example.", relativize=True) self.assertRaises(dns.exception.SyntaxError, bad) def testAllowedButNotImplementedDirective(self): def bad(): dns.zone.from_text( bad_directive_text, "example.", relativize=True, allow_directives={"$FOO", "$ORIGIN"}, ) self.assertRaises(dns.exception.SyntaxError, bad) def testFirstRRStartsWithWhitespace(self): # no name is specified, so default to the initial origin z = dns.zone.from_text( " 300 IN A 10.0.0.1", origin="example.", check_origin=False ) n = z["@"] rds = cast( dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A) ) self.assertEqual(rds.ttl, 300) def testZoneOrigin(self): z = dns.zone.Zone("example.") self.assertEqual(z.origin, dns.name.from_text("example.")) def bad1(): o = dns.name.from_text("example", None) dns.zone.Zone(o) self.assertRaises(ValueError, bad1) def bad2(): dns.zone.Zone(cast(str, 1.0)) self.assertRaises(ValueError, bad2) def testZoneOriginNone(self): dns.zone.Zone(cast(str, None)) def testZoneFromXFR(self): z1_abs = dns.zone.from_text(example_text, "example.", relativize=False) z2_abs = dns.zone.from_xfr(make_xfr(z1_abs), relativize=False) self.assertEqual(z1_abs, z2_abs) z1_rel = dns.zone.from_text(example_text, "example.", relativize=True) z2_rel = dns.zone.from_xfr(make_xfr(z1_rel), relativize=True) self.assertEqual(z1_rel, z2_rel) def testCodec2003(self): z = dns.zone.from_text(codec_text, "example.", relativize=True) n2003 = dns.name.from_text("xn--knigsgsschen-lcb0w", None) n2008 = dns.name.from_text("xn--knigsgchen-b4a3dun", None) self.assertTrue(n2003 in z) self.assertFalse(n2008 in z) rrs = z.find_rrset(n2003, "NS") self.assertEqual(rrs[0].target, n2003) @unittest.skipUnless( dns.name.have_idna_2008, "Python idna cannot be imported; no IDNA2008" ) def testCodec2008(self): z = dns.zone.from_text( codec_text, "example.", relativize=True, idna_codec=dns.name.IDNA_2008 ) n2003 = dns.name.from_text("xn--knigsgsschen-lcb0w", None) n2008 = dns.name.from_text("xn--knigsgchen-b4a3dun", None) self.assertFalse(n2003 in z) self.assertTrue(n2008 in z) rrs = z.find_rrset(n2008, "NS") self.assertEqual(rrs[0].target, n2008) def testZoneMiscCases(self): # test that leading whitespace followed by EOL is treated like # a blank line, and that out-of-zone names are dropped. z1 = dns.zone.from_text(misc_cases_input, "example.") z2 = dns.zone.from_text(misc_cases_expected, "example.") self.assertEqual(z1, z2) def testUnknownOrigin(self): def bad(): dns.zone.from_text("foo 300 in a 10.0.0.1") self.assertRaises(dns.zone.UnknownOrigin, bad) def testBadClass(self): def bad(): dns.zone.from_text("foo 300 ch txt hi", "example.") self.assertRaises(dns.exception.SyntaxError, bad) def testUnknownRdatatype(self): def bad(): dns.zone.from_text("foo 300 BOGUSTYPE hi", "example.") self.assertRaises(dns.exception.SyntaxError, bad) def testDangling(self): def bad1(): dns.zone.from_text("foo", "example.") self.assertRaises(dns.exception.SyntaxError, bad1) def bad2(): dns.zone.from_text("foo 300", "example.") self.assertRaises(dns.exception.SyntaxError, bad2) def bad3(): dns.zone.from_text("foo 300 in", "example.") self.assertRaises(dns.exception.SyntaxError, bad3) def bad4(): dns.zone.from_text("foo 300 in a", "example.") self.assertRaises(dns.exception.SyntaxError, bad4) def bad5(): dns.zone.from_text("$TTL", "example.") self.assertRaises(dns.exception.SyntaxError, bad5) def bad6(): dns.zone.from_text("$ORIGIN", "example.") self.assertRaises(dns.exception.SyntaxError, bad6) def testUseLastTTL(self): z = dns.zone.from_text(last_ttl_input, "example.") rds = z.find_rdataset("foo", "A") self.assertEqual(rds.ttl, 300) def testDollarOriginSetsZoneOriginIfUnknown(self): z = dns.zone.from_text(origin_sets_input) self.assertEqual(z.origin, dns.name.from_text("example")) def testValidateNameRelativizesNameInZone(self): z = dns.zone.from_text(example_text, "example.", relativize=True) self.assertEqual( z._validate_name("foo.bar.example."), dns.name.from_text("foo.bar", None) ) def testComments(self): z = dns.zone.from_text(example_comments_text, "example.", relativize=True) f = StringIO() z.to_file(f, want_comments=True) out = f.getvalue() f.close() self.assertEqual(out, example_comments_text_output) def testUncomparable(self): z = dns.zone.from_text(example_comments_text, "example.", relativize=True) self.assertFalse(z == "a") def testUnsorted(self): z = dns.zone.from_text(example_text, "example.", relativize=True) f = StringIO() z.to_file(f, sorted=False) out = f.getvalue() f.close() z2 = dns.zone.from_text(out, "example.", relativize=True) self.assertEqual(z, z2) def testNodeReplaceRdatasetConvertsRRsets(self): node = dns.node.Node() rrs = dns.rrset.from_text("foo", 300, "in", "a", "10.0.0.1") node.replace_rdataset(rrs) rds = node.find_rdataset(dns.rdataclass.IN, dns.rdatatype.A) self.assertEqual(rds, rrs) self.assertTrue(rds is not rrs) self.assertFalse(isinstance(rds, dns.rrset.RRset)) def testCnameAndOtherDataAddOther(self): z = dns.zone.from_text(example_cname, "example.", relativize=True) rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.1") z.replace_rdataset("web", rds) z.replace_rdataset("web2", rds.copy()) n = z.find_node("web") self.assertEqual(len(n.rdatasets), 3) self.assertEqual(n.find_rdataset(dns.rdataclass.IN, dns.rdatatype.A), rds) self.assertIsNotNone(n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.NSEC)) self.assertIsNotNone( n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.NSEC) ) n = z.find_node("web2") self.assertEqual(len(n.rdatasets), 3) self.assertEqual(n.find_rdataset(dns.rdataclass.IN, dns.rdatatype.A), rds) self.assertIsNotNone(n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.NSEC3)) self.assertIsNotNone( n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.NSEC3) ) def testCnameAndOtherDataAddCname(self): z = dns.zone.from_text(example_other_data, "example.", relativize=True) rds = dns.rdataset.from_text("in", "cname", 300, "www") z.replace_rdataset("web", rds) n = z.find_node("web") self.assertEqual(len(n.rdatasets), 3) self.assertEqual(n.find_rdataset(dns.rdataclass.IN, dns.rdatatype.CNAME), rds) self.assertIsNotNone(n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.NSEC)) self.assertIsNotNone( n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.NSEC) ) def testCnameAndOtherDataInZonefile(self): with self.assertRaises(dns.zonefile.CNAMEAndOtherData): dns.zone.from_text( example_cname_and_other_data, "example.", relativize=True ) def testNameInZoneWithStr(self): z = dns.zone.from_text(example_text, "example.", relativize=False) self.assertTrue("ns1.example." in z) self.assertTrue("bar.foo.example." in z) def testNameInZoneWhereNameIsNotValid(self): z = dns.zone.from_text(example_text, "example.", relativize=False) with self.assertRaises(KeyError): self.assertTrue(1 in z) def testRelativeNameLengthChecks(self): z = dns.zone.from_text(example_cname, "example.", relativize=True) rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.1") # This name is 246 bytes long, which along with the 8 bytes for label "example" # and 1 byte for the root name is 246 + 8 + 1 = 255 bytes long, the maximum # legal wire format name length. ok_long_relative = dns.name.Name(["a" * 63, "a" * 63, "a" * 63, "a" * 53]) z.replace_rdataset(ok_long_relative, rds) self.assertEqual(z.find_rdataset(ok_long_relative, "A"), rds) # This is the longest possible relative name and won't work in any zone # as there is no space left for any origin labels. too_long_relative = dns.name.Name(["a" * 63, "a" * 63, "a" * 63, "a" * 62]) with self.assertRaises(KeyError): z.replace_rdataset(too_long_relative, rds) class VersionedZoneTestCase(unittest.TestCase): def testUseTransaction(self): z = dns.zone.from_text( example_text, "example.", relativize=True, zone_factory=dns.versioned.Zone ) with self.assertRaises(dns.versioned.UseTransaction): z.find_node("not_there", True) with self.assertRaises(dns.versioned.UseTransaction): z.delete_node("not_there") with self.assertRaises(dns.versioned.UseTransaction): z.find_rdataset("not_there", "a", create=True) with self.assertRaises(dns.versioned.UseTransaction): z.get_rdataset("not_there", "a", create=True) with self.assertRaises(dns.versioned.UseTransaction): z.delete_rdataset("not_there", "a") with self.assertRaises(dns.versioned.UseTransaction): z.replace_rdataset("not_there", None) def testImmutableNodes(self): z = dns.zone.from_text( example_text, "example.", relativize=True, zone_factory=dns.versioned.Zone ) node = z.find_node("@") with self.assertRaises(TypeError): node.find_rdataset(dns.rdataclass.IN, dns.rdatatype.RP, create=True) with self.assertRaises(TypeError): node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.RP, create=True) with self.assertRaises(TypeError): node.delete_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA) with self.assertRaises(TypeError): node.replace_rdataset(None) def testSelectDefaultPruningPolicy(self): z = dns.zone.from_text( example_text, "example.", relativize=True, zone_factory=dns.versioned.Zone ) z.set_pruning_policy(None) self.assertEqual(z._pruning_policy, z._default_pruning_policy) def testSetAlternatePruningPolicyInConstructor(self): def never_prune(version): return False z = dns.versioned.Zone("example", pruning_policy=never_prune) self.assertEqual(z._pruning_policy, never_prune) def testCannotSpecifyBothSerialAndVersionIdToReader(self): z = dns.zone.from_text( example_text, "example.", relativize=True, zone_factory=dns.versioned.Zone ) with self.assertRaises(ValueError): z.reader(1, 1) def testUnknownVersion(self): z = dns.zone.from_text( example_text, "example.", relativize=True, zone_factory=dns.versioned.Zone ) with self.assertRaises(KeyError): z.reader(99999) def testUnknownSerial(self): z = dns.zone.from_text( example_text, "example.", relativize=True, zone_factory=dns.versioned.Zone ) with self.assertRaises(KeyError): z.reader(serial=99999) def testNoRelativizeReader(self): z = dns.zone.from_text( example_text, "example.", relativize=False, zone_factory=dns.versioned.Zone ) with z.reader(serial=1) as txn: rds = txn.get("example.", "soa") self.assertEqual(rds[0].serial, 1) def testNoRelativizeReaderOriginInText(self): z = dns.zone.from_text( example_text, relativize=False, zone_factory=dns.versioned.Zone ) with z.reader(serial=1) as txn: rds = txn.get("example.", "soa") self.assertEqual(rds[0].serial, 1) def testNoRelativizeReaderAbsoluteGet(self): z = dns.zone.from_text( example_text, "example.", relativize=False, zone_factory=dns.versioned.Zone ) with z.reader(serial=1) as txn: rds = txn.get(dns.name.empty, "soa") self.assertEqual(rds[0].serial, 1) def testCnameAndOtherDataAddOther(self): z = dns.zone.from_text( example_cname, "example.", relativize=True, zone_factory=dns.versioned.Zone ) rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.1") with z.writer() as txn: txn.replace("web", rds) txn.replace("web2", rds.copy()) n = z.find_node("web") self.assertEqual(len(n.rdatasets), 3) self.assertEqual(n.find_rdataset(dns.rdataclass.IN, dns.rdatatype.A), rds) self.assertIsNotNone(n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.NSEC)) self.assertIsNotNone( n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.NSEC) ) n = z.find_node("web2") self.assertEqual(len(n.rdatasets), 3) self.assertEqual(n.find_rdataset(dns.rdataclass.IN, dns.rdatatype.A), rds) self.assertIsNotNone(n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.NSEC3)) self.assertIsNotNone( n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.NSEC3) ) def testCnameAndOtherDataAddCname(self): z = dns.zone.from_text( example_other_data, "example.", relativize=True, zone_factory=dns.versioned.Zone, ) rds = dns.rdataset.from_text("in", "cname", 300, "www") with z.writer() as txn: txn.replace("web", rds) n = z.find_node("web") self.assertEqual(len(n.rdatasets), 3) self.assertEqual(n.find_rdataset(dns.rdataclass.IN, dns.rdatatype.CNAME), rds) self.assertIsNotNone(n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.NSEC)) self.assertIsNotNone( n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.NSEC) ) def testGetSoa(self): z = dns.zone.from_text( example_text, "example.", relativize=True, zone_factory=dns.versioned.Zone ) soa = z.get_soa() self.assertTrue(soa.rdtype, dns.rdatatype.SOA) self.assertEqual(soa.serial, 1) def testGetSoaTxn(self): z = dns.zone.from_text( example_text, "example.", relativize=True, zone_factory=dns.versioned.Zone ) with z.reader(serial=1) as txn: soa = z.get_soa(txn) self.assertTrue(soa.rdtype, dns.rdatatype.SOA) self.assertEqual(soa.serial, 1) def testGetSoaEmptyZone(self): z = dns.zone.Zone("example.") with self.assertRaises(dns.zone.NoSOA): soa = z.get_soa() def testGetRdataset1(self): z = dns.zone.from_text( example_text, "example.", relativize=True, zone_factory=dns.versioned.Zone ) rds = z.get_rdataset("@", "soa") exrds = dns.rdataset.from_text("IN", "SOA", 300, "foo bar 1 2 3 4 5") self.assertEqual(rds, exrds) def testGetRdataset2(self): z = dns.zone.from_text( example_text, "example.", relativize=True, zone_factory=dns.versioned.Zone ) rds = z.get_rdataset("@", "loc") self.assertTrue(rds is None) if __name__ == "__main__": unittest.main() dnspython-2.7.0/tests/test_zonedigest.py0000644000000000000000000002163713615410400015425 0ustar00# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import io import textwrap import unittest import dns.rdata import dns.rrset import dns.zone class ZoneDigestTestCase(unittest.TestCase): # Examples from RFC 8976, fixed per errata. simple_example = textwrap.dedent( """ example. 86400 IN SOA ns1 admin 2018031900 ( 1800 900 604800 86400 ) 86400 IN NS ns1 86400 IN NS ns2 86400 IN ZONEMD 2018031900 1 1 ( c68090d90a7aed71 6bc459f9340e3d7c 1370d4d24b7e2fc3 a1ddc0b9a87153b9 a9713b3c9ae5cc27 777f98b8e730044c ) ns1 3600 IN A 203.0.113.63 ns2 3600 IN AAAA 2001:db8::63 """ ) complex_example = textwrap.dedent( """ example. 86400 IN SOA ns1 admin 2018031900 ( 1800 900 604800 86400 ) 86400 IN NS ns1 86400 IN NS ns2 86400 IN ZONEMD 2018031900 1 1 ( a3b69bad980a3504 e1cffcb0fd6397f9 3848071c93151f55 2ae2f6b1711d4bd2 d8b39808226d7b9d b71e34b72077f8fe ) ns1 3600 IN A 203.0.113.63 NS2 3600 IN AAAA 2001:db8::63 occluded.sub 7200 IN TXT "I'm occluded but must be digested" sub 7200 IN NS ns1 duplicate 300 IN TXT "I must be digested just once" duplicate 300 IN TXT "I must be digested just once" foo.test. 555 IN TXT "out-of-zone data must be excluded" UPPERCASE 3600 IN TXT "canonicalize uppercase owner names" * 777 IN PTR dont-forget-about-wildcards mail 3600 IN MX 20 MAIL1 mail 3600 IN MX 10 Mail2.Example. sortme 3600 IN AAAA 2001:db8::5:61 sortme 3600 IN AAAA 2001:db8::3:62 sortme 3600 IN AAAA 2001:db8::4:63 sortme 3600 IN AAAA 2001:db8::1:65 sortme 3600 IN AAAA 2001:db8::2:64 non-apex 900 IN ZONEMD 2018031900 1 1 ( 616c6c6f77656420 6275742069676e6f 7265642e20616c6c 6f77656420627574 2069676e6f726564 2e20616c6c6f7765 ) """ ) multiple_digests_example = textwrap.dedent( """ example. 86400 IN SOA ns1 admin 2018031900 ( 1800 900 604800 86400 ) example. 86400 IN NS ns1.example. example. 86400 IN NS ns2.example. example. 86400 IN ZONEMD 2018031900 1 1 ( 62e6cf51b02e54b9 b5f967d547ce4313 6792901f9f88e637 493daaf401c92c27 9dd10f0edb1c56f8 080211f8480ee306 ) example. 86400 IN ZONEMD 2018031900 1 2 ( 08cfa1115c7b948c 4163a901270395ea 226a930cd2cbcf2f a9a5e6eb85f37c8a 4e114d884e66f176 eab121cb02db7d65 2e0cc4827e7a3204 f166b47e5613fd27 ) example. 86400 IN ZONEMD 2018031900 1 240 ( e2d523f654b9422a 96c5a8f44607bbee ) example. 86400 IN ZONEMD 2018031900 241 1 ( e1846540e33a9e41 89792d18d5d131f6 05fc283eaaaaaaaa aaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaa) ns1.example. 3600 IN A 203.0.113.63 ns2.example. 86400 IN TXT "This example has multiple digests" NS2.EXAMPLE. 3600 IN AAAA 2001:db8::63 """ ) def _get_zonemd(self, zone): return zone.get_rdataset(zone.origin, "ZONEMD") def test_zonemd_simple(self): zone = dns.zone.from_text(self.simple_example, origin="example") zone.verify_digest() zonemd = self._get_zonemd(zone) self.assertEqual(zonemd[0], zone.compute_digest(zonemd[0].hash_algorithm)) def test_zonemd_simple_absolute(self): zone = dns.zone.from_text( self.simple_example, origin="example", relativize=False ) zone.verify_digest() zonemd = self._get_zonemd(zone) self.assertEqual(zonemd[0], zone.compute_digest(zonemd[0].hash_algorithm)) def test_zonemd_complex(self): zone = dns.zone.from_text(self.complex_example, origin="example") zone.verify_digest() zonemd = self._get_zonemd(zone) self.assertEqual(zonemd[0], zone.compute_digest(zonemd[0].hash_algorithm)) def test_zonemd_multiple_digests(self): zone = dns.zone.from_text(self.multiple_digests_example, origin="example") zone.verify_digest() zonemd = self._get_zonemd(zone) for rr in zonemd: if rr.scheme == 1 and rr.hash_algorithm in (1, 2): zone.verify_digest(rr) self.assertEqual(rr, zone.compute_digest(rr.hash_algorithm)) else: with self.assertRaises(dns.zone.DigestVerificationFailure): zone.verify_digest(rr) def test_zonemd_no_digest(self): zone = dns.zone.from_text(self.simple_example, origin="example") zone.delete_rdataset(dns.name.empty, "ZONEMD") with self.assertRaises(dns.zone.NoDigest): zone.verify_digest() sha384_hash = "ab" * 48 sha512_hash = "ab" * 64 def test_zonemd_parse_rdata(self): dns.rdata.from_text("IN", "ZONEMD", "100 1 1 " + self.sha384_hash) dns.rdata.from_text("IN", "ZONEMD", "100 1 2 " + self.sha512_hash) dns.rdata.from_text("IN", "ZONEMD", "100 100 1 " + self.sha384_hash) dns.rdata.from_text("IN", "ZONEMD", "100 1 100 abcd") def test_zonemd_unknown_scheme(self): zone = dns.zone.from_text(self.simple_example, origin="example") with self.assertRaises(dns.zone.UnsupportedDigestScheme): zone.compute_digest(dns.zone.DigestHashAlgorithm.SHA384, 2) def test_zonemd_unknown_hash_algorithm(self): zone = dns.zone.from_text(self.simple_example, origin="example") with self.assertRaises(dns.zone.UnsupportedDigestHashAlgorithm): zone.compute_digest(5) def test_zonemd_invalid_digest_length(self): with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("IN", "ZONEMD", "100 1 2 " + self.sha384_hash) with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("IN", "ZONEMD", "100 2 1 " + self.sha512_hash) def test_zonemd_parse_rdata_reserved(self): with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("IN", "ZONEMD", "100 0 1 " + self.sha384_hash) with self.assertRaises(dns.exception.SyntaxError): dns.rdata.from_text("IN", "ZONEMD", "100 1 0 " + self.sha384_hash) sorting_zone = textwrap.dedent( """ @ 86400 IN SOA ns1 admin 2018031900 ( 1800 900 604800 86400 ) 86400 IN NS ns1 86400 IN NS ns2 86400 IN RP n1.example. a. 86400 IN RP n1. b. """ ) def test_relative_zone_sorting(self): z1 = dns.zone.from_text(self.sorting_zone, "example.", relativize=True) z2 = dns.zone.from_text(self.sorting_zone, "example.", relativize=False) zmd1 = z1.compute_digest(dns.zone.DigestHashAlgorithm.SHA384) zmd2 = z2.compute_digest(dns.zone.DigestHashAlgorithm.SHA384) self.assertEqual(zmd1, zmd2) dnspython-2.7.0/tests/ttxt_module.py0000644000000000000000000000014513615410400014552 0ustar00import dns.rdtypes.txtbase class TTXT(dns.rdtypes.txtbase.TXTBase): """Test TXT-like record""" dnspython-2.7.0/tests/utest.py0000644000000000000000000000056413615410400013353 0ustar00import os.path import sys import unittest if __name__ == "__main__": sys.path.insert(0, os.path.realpath("..")) if len(sys.argv) > 1: pattern = sys.argv[1] else: pattern = "test*.py" suites = unittest.defaultTestLoader.discover(".", pattern) if not unittest.TextTestRunner(verbosity=2).run(suites).wasSuccessful(): sys.exit(1) dnspython-2.7.0/tests/util.py0000644000000000000000000001060013615410400013154 0ustar00# 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 enum import inspect import os import dns.message import dns.name import dns.query import dns.rdataclass import dns.rdatatype # Cache for is_internet_reachable() _internet_reachable = None _have_ipv4 = False _have_ipv6 = False def here(filename): return os.path.join(os.path.dirname(__file__), filename) def check_networking(addresses): """Can we do a DNS resolution via UDP and TCP to at least one of the addresses?""" for address in addresses: try: q = dns.message.make_query(dns.name.root, dns.rdatatype.NS) ok = False # We try UDP a few times in case we get unlucky and a packet is lost. for i in range(5): # We don't check the answer other than make sure there is one. try: r = dns.query.udp(q, address, timeout=4) ns = r.find_rrset( r.answer, dns.name.root, dns.rdataclass.IN, dns.rdatatype.NS ) ok = True break except Exception: continue # UDP try loop if not ok: continue # addresses loop try: r = dns.query.tcp(q, address, timeout=4) ns = r.find_rrset( r.answer, dns.name.root, dns.rdataclass.IN, dns.rdatatype.NS ) # UDP and TCP both work! return True except Exception: continue except Exception as e: pass return False def is_internet_reachable(): """Check if the Internet is reachable. Setting the environment variable `NO_INTERNET` will let this function always return False. The result is cached. We check using the Google and Cloudflare public resolvers as they are highly available and have well-known stable addresses. """ global _internet_reachable if _internet_reachable is None: if os.environ.get("NO_INTERNET"): _internet_reachable = False else: global _have_ipv4 _have_ipv4 = check_networking(["8.8.8.8", "1.1.1.1"]) global _have_ipv6 _have_ipv6 = check_networking( ["2001:4860:4860::8888", "2606:4700:4700::1111"] ) _internet_reachable = _have_ipv4 or _have_ipv6 return _internet_reachable def have_ipv4(): if not is_internet_reachable(): return False return _have_ipv4 def have_ipv6(): if not is_internet_reachable(): return False return _have_ipv6 def enumerate_module(module, super_class): """Yield module attributes which are subclasses of given class""" for attr_name in dir(module): attr = getattr(module, attr_name) if inspect.isclass(attr) and issubclass(attr, super_class): yield attr def check_enum_exports(module, eq_callback, only=None): """Make sure module exports all mnemonics from enums""" for attr in enumerate_module(module, enum.Enum): if only is not None and attr not in only: # print('SKIP', attr) continue for flag, value in attr.__members__.items(): # print(module, flag, value) eq_callback(getattr(module, flag), value) def is_docker() -> bool: # There are a lot of answers to "am I runnning in a container" and none appear # to work reliably, so we're settling for "am I running in docker?" try: return os.path.isfile("/.dockerenv") except Exception: return False dnspython-2.7.0/tests/tls/ca.crt0000644000000000000000000000217313615410400013532 0ustar00-----BEGIN CERTIFICATE----- MIIDIzCCAgugAwIBAgIURNfnZHaOJeyZ0osGiwu3X/YDFL0wDQYJKoZIhvcNAQEL BQAwGTEXMBUGA1UEAxMOZG5zcHl0aG9uIHJvb3QwHhcNMjQwNTAzMTkyNTE5WhcN MzQwNDMwMTkyNTQ5WjAZMRcwFQYDVQQDEw5kbnNweXRob24gcm9vdDCCASIwDQYJ KoZIhvcNAQEBBQADggEPADCCAQoCggEBAOaghN4Rkw+sG9yPsfRNXhdq7Hz5TzDx rHGSq8ptIqFc0vysW/DOBrMiJGBb7EfokHewEXo6R1FXhDDElwAi3b6Z57vQb67y i1cdxxxb/AGghwUoclnKXKalon4hPU2Roo5nqx3k4UDdSa7k1hhGD0VvExommgLH 9JmXBxlQxSm4fO3h05CRZlhWOdyKbdSX5qsGhB0qEZ8pUMnar/ETmBmYtAzwHWkc eiyoxi/bVZltTuNmxn5CBUiWMgQwCM5vay95C6UxJVvxGXb/5GMrBTl7BWv+JtKz sUHyICrpPodBAianh0oF5AuCnB/Wc+a4VqYmq3kBlOX/2nvh/XQ9sxUCAwEAAaNj MGEwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHHQ vbgcx+Af7H2IiW12df1L6j+PMB8GA1UdIwQYMBaAFHHQvbgcx+Af7H2IiW12df1L 6j+PMA0GCSqGSIb3DQEBCwUAA4IBAQCdsOcxHQD3d7azspc+SerwVYXYARo3XPjb EuimRbz6iBOPUwcIOKtmTVUILv42SrsSFQX0yzpEgvre3r6PavbZA3e2/+wMP/5b NYAZvew6zzH0YekPaia8nWdLHYNF5ejsGfc469X+YZbo6idjR/fE8BoNraguJJa5 9z2PPHS0ljETBwlomEcat16r7SWxIh2Gp4e0bmIu3h+pes467Dylru//w1kfsI3f kyxmiKy+2/2hB+6fEee47FYL8SYi3eFwTWy9zfjRe1zbOJYwRCj16petvecAeJol lewwEjNUdLtMcqkz0nrW/bRK6XONDhcjyIzhphT3SyZSFI742RJd -----END CERTIFICATE----- dnspython-2.7.0/tests/tls/private.pem0000644000000000000000000000321713615410400014612 0ustar00-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA7XpdDZHVwG40RRKIgkoVUVNmVoRQNPuBFfyDZu+PS6iLjZ56 9JZIXb9gbu8yEyXbZRpLzscAdm+n3oLq/DDEjjse1NLYAxhMJhpbvF2hGYQCbyk5 PN8hooYDcYhgGCINqLbODyh3CI6+OYDSFKZtowyahzeYaCl56vAanGjogUPmrfpE /9raO3jn2BumCTMLA4KgXUv4bo6ytt0W9JlWPPZklLD3a0Wt+b6cYqzxVNq6ZFze veBL3AWn/YgsTOQah0MYpI2Kcqiu3pyDGqwtipiewF2UwtB49AewiPjtNpJg3dDT jwAb1xpJQCibJl9MNWK0oCqw5DY/DqhTCbm5fwIDAQABAoIBAA9NZjecoRhwQX/9 7LvwiCUgaaTDi+OOHUhMaC822pX2e1+ZybR/BpS74zviv+hJ9TQzmJTvb9Ex6rBE 4V10ywWbO6ZdG7w/UZDW+/hMVDgvsHR3RI/ulN1DI2+qaP7FGvo3JVoGsnzjrdBI w8C+5KuwATkbVP/JRGjmJeDURb8mgCmmZMFZ5P3FJTtU6G857Ppknbs1EMqdJUFd /wuh39ek4ht63al74hAhgToAoyiDcGJWyQjMHTsrUOKr3y5hdGMgp0WlhJGmYzU2 We0XGDmt/keKJwy2oTNLpStfdrCI0rjFkaw6Drex1dnZ1/xB9JXur6i5e74EKF6m fcsCKfECgYEA73/XpLY5Mt4emK1K952JBCkp0LSKE1KO3cqszSX7Sz2uDyO9tISH 3ZjV8awfa9+Ww6jJlJ8tRlJjE3bZ3idj87MTFql/vUlffta1+JahvqwZIwFG5T2y avI2cDIVgWqTq1ptuG8Q387l44+y5JydRrk1o1sZ+iggaCGhVVJI4lcCgYEA/dbe Z1Hlpiwua4I4qNXZa2ch0ifE/JbCbmHl8VLlXUnNOpB5d2Cdtm6ioOwmRmdY4lEt HxrPgvGv6dHdao/pV5fOoIuldesXjFHDT+4BBI37F0QfzbeSsZDFCdcu80iZdRkp nGE9Mhr2pw6fL/RkjeNGXvXTZfVBpQwDRDAT+RkCgYBJuWLaUzW8UN++Q+oF2zqo SzuSAWFXnVxZLeCPrhdzC+OeyXPGM93W+wswv7lUbZltVXHoDC/Vq9rPzRA38Iw2 InPH0BmAXSvGUbXmdggIN9XtDqGSQ7G+dh0H4IL6OaKHZujLk3TwUDI4IK4C7asE cFLwnESc2ulO5LtTFmdVNQKBgQDw1z6bXzv6R28fuCVUI9qvKeIQCIXBdrUxkBdg uds/gPeE+nEVEAlzf1M0Rfsmz4Wo7YZXPdPnlLLosaNYKQUng7Sb5QNvd0gXbbhX jSVQEfpLn+phACzh5s7cwFsenyJi1SYC5svuT2D5LklhcYs6iIpLLQiPc3z+QQCU YN2CeQKBgQCKUuqYg3PxD2+FZiPdH17aMvL3IPbW04+uZpB6zUdYGBSAswpMf9im bxYRSDbAuxMj2bFcHOpKCuwoP8k1ssNsYHebFwggiWt0kLjoHXSGVuat+1hMslA0 GOJccxYBG3kAVxhdYfNVLiqn+jhF4fA3tufesqkDzC1N9TD2Lu8SHA== -----END RSA PRIVATE KEY----- dnspython-2.7.0/tests/tls/public.crt0000644000000000000000000000231013615410400014416 0ustar00-----BEGIN CERTIFICATE----- MIIDXDCCAkSgAwIBAgIUSm6amJOT3rbMGuGmOKP0AvKsT98wDQYJKoZIhvcNAQEL BQAwGTEXMBUGA1UEAxMOZG5zcHl0aG9uIHJvb3QwHhcNMjQwNTAzMTkyNzU3WhcN MzQwNDI4MTkyODI3WjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQDtel0NkdXAbjRFEoiCShVRU2ZWhFA0+4EV/INm 749LqIuNnnr0lkhdv2Bu7zITJdtlGkvOxwB2b6fegur8MMSOOx7U0tgDGEwmGlu8 XaEZhAJvKTk83yGihgNxiGAYIg2ots4PKHcIjr45gNIUpm2jDJqHN5hoKXnq8Bqc aOiBQ+at+kT/2to7eOfYG6YJMwsDgqBdS/hujrK23Rb0mVY89mSUsPdrRa35vpxi rPFU2rpkXN694EvcBaf9iCxM5BqHQxikjYpyqK7enIMarC2KmJ7AXZTC0Hj0B7CI +O02kmDd0NOPABvXGklAKJsmX0w1YrSgKrDkNj8OqFMJubl/AgMBAAGjgaAwgZ0w DgYDVR0PAQH/BAQDAgOoMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAd BgNVHQ4EFgQUUL5BQaDG8uCqe/vRZzEzjsrAXV8wHwYDVR0jBBgwFoAUcdC9uBzH 4B/sfYiJbXZ1/UvqP48wLAYDVR0RBCUwI4IJbG9jYWxob3N0hwR/AAABhxAAAAAA AAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQAN//a9IwM2+kfCiT8n0xbu F3Euh2OSRtjOPSg4NF/Lf4aDXxMgj1mw63qFN0CarWMk3uPnixhmQl7xeS2rEw9U LxJ6wvwT4aFCcyCycuW8cGz3Gc3ZHIUg+k1+4Kiv7gVdjgJftFgbfuzNyyju0HgH 0znDTUlKy3AAL72JELFVV3Ky6PXDXypeQ1m0wyTMcIzaOkGdilYlKebMkvXtawj1 nCtUL+kgfTT8Pfpxw9TLQz7ZYs8XG3vgv4JkQfUYkX8/tBJTOejXiSTx7Rm1iwDd /1W7HpckdfpBOQPglyKjJvARCnterBVPBYTr6RkK7thZORHUWUriKotVCr2gzNWy -----END CERTIFICATE----- dnspython-2.7.0/util/constants-tool0000755000000000000000000000606613615410400014370 0ustar00#!/usr/bin/env python3 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license from importlib import import_module import os import sys enum_names = [ 'dns.dnssec.Algorithm', 'dns.edns.OptionType', 'dns.flags.Flag', 'dns.flags.EDNSFlag', 'dns.message.MessageSection', 'dns.opcode.Opcode', 'dns.rcode.Rcode', 'dns.rdataclass.RdataClass', 'dns.rdatatype.RdataType', 'dns.rdtypes.dnskeybase.Flag', 'dns.update.UpdateSection', ] def generate(): for enum_name in enum_names: dot = enum_name.rindex('.') module_name = enum_name[:dot] type_name = enum_name[dot + 1:] lname = type_name.lower() mod = import_module(module_name) enum = getattr(mod, type_name) filename = module_name.replace('.', '/') + '.py' new_filename = filename + '.new' with open(filename) as f: with open(new_filename, 'w') as nf: lines = f.readlines() found = False i = 0 length = len(lines) while i < length: l = lines[i].rstrip() i += 1 if l.startswith(f'### BEGIN generated {type_name} ' + 'constants') or \ l.startswith(f'### BEGIN generated {lname} constants'): found = True break else: print(l, file=nf) if found: found = False while i < length: l = lines[i].rstrip() i += 1 if l.startswith(f'### END generated {type_name} ' + 'constants') or \ l.startswith(f'### END generated {lname} constants'): found = True break if not found: print(f'Found begin marker for {type_name} but did ' + 'not find end marker!', file=sys.stderr) sys.exit(1) if not found: print('', file=nf) print(f'### BEGIN generated {type_name} constants', file=nf) print('', file=nf) # We have to use __members__.items() and not "in enum" because # otherwise we miss values that have multiple names (e.g. NONE # and TYPE0 for rdatatypes). for name, value in enum.__members__.items(): print(f'{name} = {type_name}.{name}', file=nf) print('', file=nf) print(f'### END generated {type_name} constants', file=nf) # Copy remaining lines (if any) while i < length: l = lines[i].rstrip() i += 1 print(l, file=nf) os.rename(new_filename, filename) def main(): generate() if __name__ == '__main__': main() dnspython-2.7.0/util/generate-features0000755000000000000000000000206213615410400014777 0ustar00#!/usr/bin/env python3 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license import os import tomllib with open("pyproject.toml", "rb") as pp: pyproject = tomllib.load(pp) FEATURES = "dns/_features.py" NEW_FEATURES = FEATURES + ".new" skip = False with open(FEATURES, "r") as input: with open(NEW_FEATURES, "w") as output: for l in input.readlines(): l = l.rstrip() if l.startswith(" ### BEGIN generated requirements"): print(l, file=output) for name, deps in pyproject["project"]["optional-dependencies"].items(): if name == "dev": continue print( f" {repr(name)}: {repr(deps)},".replace("'", '"'), file=output, ) skip = True elif l.startswith(" ### END generated requirements"): skip = False if not skip: print(l, file=output) os.rename(NEW_FEATURES, FEATURES) dnspython-2.7.0/util/generate-mx-pickle.py0000644000000000000000000000066513615410400015505 0ustar00import pickle import sys import dns.rdata import dns.version # Generate a pickled mx RR for the current dnspython version mx = dns.rdata.from_text("in", "mx", "10 mx.example.") filename = f"pickled-{dns.version.MAJOR}-{dns.version.MINOR}.pickle" with open(filename, "wb") as f: pickle.dump(mx, f) with open(filename, "rb") as f: mx2 = pickle.load(f) if mx == mx2: print("ok") else: print("DIFFERENT!") sys.exit(1) dnspython-2.7.0/util/generate-rdatatype-doc.py0000644000000000000000000000054613615410400016352 0ustar00import dns.rdatatype print("Rdatatypes") print("----------") print() by_name = {} for rdtype in dns.rdatatype.RdataType: short_name = dns.rdatatype.to_text(rdtype).replace("-", "_") by_name[short_name] = int(rdtype) for k in sorted(by_name.keys()): v = by_name[k] print(f".. py:data:: dns.rdatatype.{k}") print(f" :annotation: = {v}") dnspython-2.7.0/.gitignore0000644000000000000000000000031213615410400012452 0ustar00build dist MANIFEST html html.zip html.tar.gz tests/*.out *.pyc .coverage .tox dnspython.egg-info/ .eggs/ .mypy_cache/ .python-version poetry.lock htmlcov coverage.xml .dir-locals.el .vscode/ doc/_builddnspython-2.7.0/LICENSE0000644000000000000000000000276613615410400011506 0ustar00ISC License Copyright (C) Dnspython Contributors Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. Copyright (C) 2001-2017 Nominum, Inc. Copyright (C) Google 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. dnspython-2.7.0/README.md0000644000000000000000000000653313615410400011754 0ustar00# dnspython [![Build Status](https://github.com/rthalley/dnspython/actions/workflows/ci.yml/badge.svg)](https://github.com/rthalley/dnspython/actions/) [![Documentation Status](https://readthedocs.org/projects/dnspython/badge/?version=latest)](https://dnspython.readthedocs.io/en/latest/?badge=latest) [![PyPI version](https://badge.fury.io/py/dnspython.svg)](https://badge.fury.io/py/dnspython) [![License: ISC](https://img.shields.io/badge/License-ISC-brightgreen.svg)](https://opensource.org/licenses/ISC) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) ## INTRODUCTION dnspython is a DNS toolkit for Python. It supports almost all record types. It can be used for queries, zone transfers, and dynamic updates. It supports TSIG authenticated messages and EDNS0. dnspython provides both high and low level access to DNS. The high level classes perform queries for data of a given name, type, and class, and return an answer set. The low level classes allow direct manipulation of DNS zones, messages, names, and records. To see a few of the ways dnspython can be used, look in the `examples/` directory. dnspython is a utility to work with DNS, `/etc/hosts` is thus not used. For simple forward DNS lookups, it's better to use `socket.getaddrinfo()` or `socket.gethostbyname()`. dnspython originated at Nominum where it was developed to facilitate the testing of DNS software. ## ABOUT THIS RELEASE This is dnspython 2.7.0. Please read [What's New](https://dnspython.readthedocs.io/en/stable/whatsnew.html) for information about the changes in this release. ## INSTALLATION * Many distributions have dnspython packaged for you, so you should check there first. * To use a wheel downloaded from PyPi, run: pip install dnspython * To install from the source code, go into the top-level of the source code and run: ``` pip install --upgrade pip build python -m build pip install dist/*.whl ``` * To install the latest from the main branch, run `pip install git+https://github.com/rthalley/dnspython.git` Dnspython's default installation does not depend on any modules other than those in the Python standard library. To use some features, additional modules must be installed. For convenience, pip options are defined for the requirements. If you want to use DNS-over-HTTPS, run `pip install dnspython[doh]`. If you want to use DNSSEC functionality, run `pip install dnspython[dnssec]`. If you want to use internationalized domain names (IDNA) functionality, you must run `pip install dnspython[idna]` If you want to use the Trio asynchronous I/O package, run `pip install dnspython[trio]`. If you want to use WMI on Windows to determine the active DNS settings instead of the default registry scanning method, run `pip install dnspython[wmi]`. If you want to try the experimental DNS-over-QUIC code, run `pip install dnspython[doq]`. Note that you can install any combination of the above, e.g.: `pip install dnspython[doh,dnssec,idna]` ### Notices Python 2.x support ended with the release of 1.16.0. Dnspython 2.6.x supports Python 3.8 and later, though support for 3.8 ends on October 14, 2024. Dnspython 2.7.x supports Python 3.9 and later. Future support is aligned with the lifetime of the Python 3 versions. Documentation has moved to [dnspython.readthedocs.io](https://dnspython.readthedocs.io). dnspython-2.7.0/pyproject.toml0000644000000000000000000000547313615410400013413 0ustar00[build-system] requires = ["hatchling>=1.21.0"] build-backend = "hatchling.build" [project] name = "dnspython" description = "DNS toolkit" authors = [{ name = "Bob Halley", email = "halley@dnspython.org" }] license = { text = "ISC" } classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: System Administrators", "License :: OSI Approved :: ISC License (ISCL)", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Programming Language :: Python", "Topic :: Internet :: Name Service (DNS)", "Topic :: Software Development :: Libraries :: Python Modules", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ] readme = "README.md" requires-python = ">=3.9" dependencies = [] dynamic = ["version"] [project.optional-dependencies] dev = [ "black>=23.1.0", "coverage>=7.0", "hypercorn>=0.16.0", "flake8>=7", "mypy>=1.8", "pylint>=3", "pytest>=7.4", "pytest-cov>=4.1.0", "quart-trio>=0.11.0", "sphinx>=7.2.0", "sphinx-rtd-theme>=2.0.0", "twine>=4.0.0", "wheel>=0.42.0", ] dnssec = ["cryptography>=43"] doh = ["httpcore>=1.0.0", "httpx>=0.26.0", "h2>=4.1.0"] doq = ["aioquic>=1.0.0"] idna = ["idna>=3.7"] trio = ["trio>=0.23"] wmi = ["wmi>=1.5.1"] [project.urls] homepage = "https://www.dnspython.org" repository = "https://github.com/rthalley/dnspython.git" documentation = "https://dnspython.readthedocs.io/en/stable/" issues = "https://github.com/rthalley/dnspython/issues" [tool.hatch.build.targets.sdist] include = [ "/dns/*.py", "/dns/**/*.py", "/dns/py.typed", "/examples/*.txt", "/examples/*.py", "/tests/*.txt", "/tests/*.py", "/tests/*.good", "/tests/example", "/tests/query", "/tests/*.pickle", "/tests/*.text", "/tests/*.generic", "/tests/tls/*.crt", "/tests/tls/*.pem", "/util/**", ] [tool.hatch.build.targets.wheel] include = ["dns/*.py", "dns/**/*.py", "dns/py.typed"] [tool.hatch.envs.default] features = ["trio", "dnssec", "idna", "doh", "doq", "dev"] #installer = "uv" [tool.hatch.version] source = "code" path = "dns/version.py" expression = "version" [tool.ruff] lint.select = [ # pycodestyle "E", # Pyflakes "F", # pyupgrade "UP", # flake8-bugbear "B", # isort "I", ] lint.ignore = ["E501", "E741", "F401", "I001", "B904", "B011", "UP006", "UP035"] lint.exclude = ["tests/*"] [tool.isort] profile = "black" [[tool.mypy.overrides]] module = "pythoncom" ignore_missing_imports = true [[tool.mypy.overrides]] module = "wmi" ignore_missing_imports = true dnspython-2.7.0/PKG-INFO0000644000000000000000000001320313615410400011562 0ustar00Metadata-Version: 2.3 Name: dnspython Version: 2.7.0 Summary: DNS toolkit Project-URL: homepage, https://www.dnspython.org Project-URL: repository, https://github.com/rthalley/dnspython.git Project-URL: documentation, https://dnspython.readthedocs.io/en/stable/ Project-URL: issues, https://github.com/rthalley/dnspython/issues Author-email: Bob Halley License: ISC License-File: LICENSE Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: ISC License (ISCL) Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Topic :: Internet :: Name Service (DNS) Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Python: >=3.9 Provides-Extra: dev Requires-Dist: black>=23.1.0; extra == 'dev' Requires-Dist: coverage>=7.0; extra == 'dev' Requires-Dist: flake8>=7; extra == 'dev' Requires-Dist: hypercorn>=0.16.0; extra == 'dev' Requires-Dist: mypy>=1.8; extra == 'dev' Requires-Dist: pylint>=3; extra == 'dev' Requires-Dist: pytest-cov>=4.1.0; extra == 'dev' Requires-Dist: pytest>=7.4; extra == 'dev' Requires-Dist: quart-trio>=0.11.0; extra == 'dev' Requires-Dist: sphinx-rtd-theme>=2.0.0; extra == 'dev' Requires-Dist: sphinx>=7.2.0; extra == 'dev' Requires-Dist: twine>=4.0.0; extra == 'dev' Requires-Dist: wheel>=0.42.0; extra == 'dev' Provides-Extra: dnssec Requires-Dist: cryptography>=43; extra == 'dnssec' Provides-Extra: doh Requires-Dist: h2>=4.1.0; extra == 'doh' Requires-Dist: httpcore>=1.0.0; extra == 'doh' Requires-Dist: httpx>=0.26.0; extra == 'doh' Provides-Extra: doq Requires-Dist: aioquic>=1.0.0; extra == 'doq' Provides-Extra: idna Requires-Dist: idna>=3.7; extra == 'idna' Provides-Extra: trio Requires-Dist: trio>=0.23; extra == 'trio' Provides-Extra: wmi Requires-Dist: wmi>=1.5.1; extra == 'wmi' Description-Content-Type: text/markdown # dnspython [![Build Status](https://github.com/rthalley/dnspython/actions/workflows/ci.yml/badge.svg)](https://github.com/rthalley/dnspython/actions/) [![Documentation Status](https://readthedocs.org/projects/dnspython/badge/?version=latest)](https://dnspython.readthedocs.io/en/latest/?badge=latest) [![PyPI version](https://badge.fury.io/py/dnspython.svg)](https://badge.fury.io/py/dnspython) [![License: ISC](https://img.shields.io/badge/License-ISC-brightgreen.svg)](https://opensource.org/licenses/ISC) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) ## INTRODUCTION dnspython is a DNS toolkit for Python. It supports almost all record types. It can be used for queries, zone transfers, and dynamic updates. It supports TSIG authenticated messages and EDNS0. dnspython provides both high and low level access to DNS. The high level classes perform queries for data of a given name, type, and class, and return an answer set. The low level classes allow direct manipulation of DNS zones, messages, names, and records. To see a few of the ways dnspython can be used, look in the `examples/` directory. dnspython is a utility to work with DNS, `/etc/hosts` is thus not used. For simple forward DNS lookups, it's better to use `socket.getaddrinfo()` or `socket.gethostbyname()`. dnspython originated at Nominum where it was developed to facilitate the testing of DNS software. ## ABOUT THIS RELEASE This is dnspython 2.7.0. Please read [What's New](https://dnspython.readthedocs.io/en/stable/whatsnew.html) for information about the changes in this release. ## INSTALLATION * Many distributions have dnspython packaged for you, so you should check there first. * To use a wheel downloaded from PyPi, run: pip install dnspython * To install from the source code, go into the top-level of the source code and run: ``` pip install --upgrade pip build python -m build pip install dist/*.whl ``` * To install the latest from the main branch, run `pip install git+https://github.com/rthalley/dnspython.git` Dnspython's default installation does not depend on any modules other than those in the Python standard library. To use some features, additional modules must be installed. For convenience, pip options are defined for the requirements. If you want to use DNS-over-HTTPS, run `pip install dnspython[doh]`. If you want to use DNSSEC functionality, run `pip install dnspython[dnssec]`. If you want to use internationalized domain names (IDNA) functionality, you must run `pip install dnspython[idna]` If you want to use the Trio asynchronous I/O package, run `pip install dnspython[trio]`. If you want to use WMI on Windows to determine the active DNS settings instead of the default registry scanning method, run `pip install dnspython[wmi]`. If you want to try the experimental DNS-over-QUIC code, run `pip install dnspython[doq]`. Note that you can install any combination of the above, e.g.: `pip install dnspython[doh,dnssec,idna]` ### Notices Python 2.x support ended with the release of 1.16.0. Dnspython 2.6.x supports Python 3.8 and later, though support for 3.8 ends on October 14, 2024. Dnspython 2.7.x supports Python 3.9 and later. Future support is aligned with the lifetime of the Python 3 versions. Documentation has moved to [dnspython.readthedocs.io](https://dnspython.readthedocs.io).