diff options
Diffstat (limited to 'pyfingerd/core.py')
-rwxr-xr-x | pyfingerd/core.py | 250 |
1 files changed, 5 insertions, 245 deletions
diff --git a/pyfingerd/core.py b/pyfingerd/core.py index b9e8b57..8a3d65b 100755 --- a/pyfingerd/core.py +++ b/pyfingerd/core.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # ***************************************************************************** -# Copyright (C) 2017-2021 Thomas Touhey <thomas@touhey.fr> +# Copyright (C) 2017-2022 Thomas Touhey <thomas@touhey.fr> # This file is part of the pyfingerd project, which is MIT-licensed. # ***************************************************************************** """ Main classes for the finger server, interfaces and formatters. @@ -13,21 +13,18 @@ import asyncio as _asyncio import copy as _copy import multiprocessing as _multip import signal as _signal -import socket as _socket import string as _string -from collections.abc import Sequence as _Sequence from datetime import datetime as _dt, timedelta as _td, tzinfo as _tzinfo -from enum import Enum as _Enum from errno import errorcode as _errorcode -from typing import Optional as _Optional +from typing import Optional as _Optional, Sequence as _Sequence from croniter import croniter as _croniter from pytz import utc as _utc +from .binds import FingerBindsDecoder as _FingerBindsDecoder from .errors import ( HostnameError as _HostnameError, - InvalidBindError as _InvalidBindError, MalformedQueryError as _MalformedQueryError, NoBindsError as _NoBindsError, ) @@ -757,243 +754,6 @@ class FingerInterface: # --- -# Bind-related configuration. -# --- - - -class _BindAddress: - """ Bind address for pyfingerd. """ - - class Type(_Enum): - """ Bind address type. """ - - """ TCP on IPv4 bind. """ - TCP_IPv4 = 1 - - """ TCP on IPv6 bind. """ - TCP_IPv6 = 2 - - def __init__(self, family): - self._family = self.Type(family) - - def __repr__(self): - return f'{self._class__.__name__}(family={self._family!r})' - - @property - def family(self): - """ Family as one of the `BindAddress.Type` enumeration values. """ - - return self._family - - -class _TCP4Address(_BindAddress): - """ IPv4 TCP Address. """ - - def __init__(self, address, port): - super().__init__(_BindAddress.Type.TCP_IPv4) - - try: - self._addr = _socket.inet_pton(_socket.AF_INET, address) - except Exception: - self._addr = address - - self._port = port - - @property - def runserver_params(self): - """ Return the data as `_runserver` parameters. """ - - return ( - _socket.AF_INET, - _socket.inet_ntop(_socket.AF_INET, self._addr), - self._port) - - -class _TCP6Address(_BindAddress): - """ IPv6 TCP Address. """ - - def __init__(self, address, port): - super().__init__(_BindAddress.Type.TCP_IPv6) - - try: - self._addr = _socket.inet_pton(_socket.AF_INET6, address) - except Exception: - self._addr = address - - self._port = port - - @property - def runserver_params(self): - """ Return the data as `_runserver` parameters. """ - - return ( - _socket.AF_INET6, - _socket.inet_ntop(_socket.AF_INET6, self._addr), - self._port, - ) - - -class _BindsDecoder: - """ Binds decoder for pyfingerd. - - Takes a raw string and the protocol name, either 'finger' (the base - protocol managed by the class) or 'pyfingerd-control' (the protocol - used for controlling the live pyfingerd interface). - """ - - def __init__(self, raw, proto='finger'): - proto = proto.casefold() - if proto not in ('finger',): - raise ValueError(f'unsupported protocol {proto}') - - self._binds = set() - - for x in map(lambda x: x.strip(), raw.split(',')): - addr = x - - # Try to find a scheme. - - scheme, *rest = x.split(':/') - if not rest: - # No scheme found, let's just guess the scheme based on - # the situation. - - x = scheme - scheme = {'finger': 'tcp'}[proto] - else: - # just don't add the ':' of ':/' again - x = '/' + ':/'.join(rest) - - if ( - (proto == 'finger' and scheme != 'tcp') - or scheme not in ('tcp',) - ): - raise _InvalidBindError( - addr, - f'Unsupported scheme {scheme!r} for protocol {proto!r}', - ) - - # Decode the address data. - - if scheme == 'tcp': - self._binds.update(self._decode_tcp_host(x)) - - self._binds = tuple(self._binds) - - def __iter__(self): - return iter(self._binds) - - def __repr__(self): - return f'{self._class__.__name__}(binds={self._binds})' - - def _decode_tcp_host(self, x): - """ Decode suitable hosts for a TCP bind. """ - - addrs = () - addr = x - - # TODO: manage the '*' case. - - # Get the host part first, we'll decode it later. - - if x[0] == '[': - # The host part is an IPv6, look for the closing ']' and - # decode it later. - - to = x.find(']') - if to < 0: - raise _InvalidBindError( - addr, "Expected closing ']'") - - host = x[1:to] - x = x[to + 1:] - - is_ipv6 = True - else: - # The host part is either an IPv4 or a host name, look for - # the ':' and decode it later. - - host, *x = x.split(':') - x = ':' + ':'.join(x) - - is_ipv6 = False - - # Decode the port part. - - if x == '': - port = 79 - elif x[0] == ':': - try: - port = int(x[1:]) - except ValueError: - try: - if x[1:] != '': - raise AssertionError('Expected a port number') - port = _socket.getservbyname(x[1:]) - except Exception: - raise _InvalidBindError( - addr, - 'Expected a valid port number or name ' - f'(got {x[1:]!r})', - ) from None - else: - raise _InvalidBindError( - addr, 'Garbage found after the host', - ) - - # Decode the host part and get the addresses. - - addrs = () - if is_ipv6: - # Decode the IPv6 address (validate it using `_socket.inet_pton`). - - ip6 = host - _socket.inet_pton(_socket.AF_INET6, host) - addrs += (_TCP6Address(ip6, port),) - else: - # Decode the host (try IPv4, otherwise, resolve domain). - - try: - ip = host.split('.') - if len(ip) < 2 or len(ip) > 4: - raise AssertionError('2 <= len(ip) <= 4') - - ip = list(map(int, ip)) - if not all(lambda x: 0 <= x < 256, ip): - raise AssertionError('non-8-bit component') - - if len(ip) == 2: - ip = [ip[0], 0, 0, ip[1]] - elif len(ip) == 3: - ip = [ip[0], 0, ip[1], ip[2]] - - addrs += (_TCP4Address(ip, port),) - except Exception: - entries = _socket.getaddrinfo( - host, port, - proto=_socket.IPPROTO_TCP, - type=_socket.SOCK_STREAM) - - for ent in entries: - if ( - ent[0] not in (_socket.AF_INET, _socket.AF_INET6) - or ent[1] not in (_socket.SOCK_STREAM,) - ): - continue - - if ent[0] == _socket.AF_INET: - ip = ent[4][0] - _socket.inet_pton(_socket.AF_INET, ent[4][0]) - addrs += (_TCP4Address(ip, port),) - else: - ip6 = ent[4][0] - _socket.inet_pton(_socket.AF_INET6, ent[4][0]) - addrs += (_TCP6Address(ip6, port),) - - return addrs - - -# --- # Finger/TCP server implementation. # --- @@ -1117,7 +877,7 @@ class FingerServer: # Check the binds. - self._binds = [b for b in _BindsDecoder(binds)] + self._binds = [b for b in _FingerBindsDecoder().decode(binds)] if not self._binds: raise _NoBindsError() @@ -1290,7 +1050,7 @@ class FingerServer: if seconds >= 0: break - await _asyncio.sleep(seconds) # TODO + await _asyncio.sleep(seconds) async def start_servers(): """ Start the servers. """ |