aboutsummaryrefslogtreecommitdiff
path: root/pyfingerd/core.py
diff options
context:
space:
mode:
Diffstat (limited to 'pyfingerd/core.py')
-rwxr-xr-xpyfingerd/core.py250
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. """