diff options
author | Thomas "Cakeisalie5" Touhey <thomas@touhey.fr> | 2019-02-07 11:54:48 +0100 |
---|---|---|
committer | Thomas "Cakeisalie5" Touhey <thomas@touhey.fr> | 2019-02-07 11:54:48 +0100 |
commit | 464e337b4446f8d37a37218bb5f4a4d4f7463a5f (patch) | |
tree | d3ba3bbc8eebc9021c4f1d74b4e731be913bfee2 | |
parent | 425681857d9d2af3d2e27994b8a3a00439c579f5 (diff) |
Corrected port decoding a little
-rwxr-xr-x | fingerd/__init__.py | 2 | ||||
-rwxr-xr-x | fingerd/__main__.py | 2 | ||||
-rw-r--r-- | fingerd/_binds.py | 232 | ||||
-rw-r--r-- | fingerd/_exceptions.py | 26 | ||||
-rwxr-xr-x | fingerd/_fiction.py | 2 | ||||
-rwxr-xr-x | fingerd/_server.py | 31 | ||||
-rwxr-xr-x | fingerd/_util.py | 206 | ||||
-rw-r--r-- | fingerd/client/__init__.py | 6 |
8 files changed, 281 insertions, 226 deletions
diff --git a/fingerd/__init__.py b/fingerd/__init__.py index 018fa7c..bcf392e 100755 --- a/fingerd/__init__.py +++ b/fingerd/__init__.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 #****************************************************************************** -# Copyright (C) 2017-2018 Thomas Touhey <thomas@touhey.fr> +# Copyright (C) 2017-2019 Thomas Touhey <thomas@touhey.fr> # This file is part of the fingerd Python 3.x module, which is MIT-licensed. #****************************************************************************** """ finger is both a protocol and a utility to get the information and diff --git a/fingerd/__main__.py b/fingerd/__main__.py index 3910283..2d8b442 100755 --- a/fingerd/__main__.py +++ b/fingerd/__main__.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 #****************************************************************************** -# Copyright (C) 2017-2018 Thomas Touhey <thomas@touhey.fr> +# Copyright (C) 2017-2019 Thomas Touhey <thomas@touhey.fr> # This file is part of the fingerd Python 3.x module, which is MIT-licensed. #****************************************************************************** """ Main script of the module. diff --git a/fingerd/_binds.py b/fingerd/_binds.py new file mode 100644 index 0000000..1885ed0 --- /dev/null +++ b/fingerd/_binds.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +#****************************************************************************** +# Copyright (C) 2017-2019 Thomas Touhey <thomas@touhey.fr> +# This file is part of the fingerd Python 3.x module, which is MIT-licensed. +#****************************************************************************** +""" This file serves for managing binds. """ + +from enum import Enum as _Enum + +import socket as _socket + +from ._exceptions import InvalidBindAddressError as _InvalidBindAddressError + +__all__ = ["BindAddressType", "BindsDecoder"] + +# --- +# Bind adresses and address types. +# --- + +class BindAddressType(_Enum): + """ Bind address type. """ + + """ TCP on IPv4 bind. """ + TCP_IPv4 = 1 + + """ TCP on IPv6 bind. """ + TCP_IPv6 = 2 + + """ IPC bind. """ + IPC = 3 + +class _BindAddress: + """ Bind address for fingerd. """ + + def __init__(self, family): + self.__family = BindAddressType(family) + + def __repr__(self): + return f"{self.__class__.__name__}(family = {self.__family})" + + @property + def family(self): + """ Family as one of the `BindAddressType` enumeration values. """ + + return self.__family + +class _TCP4Address(_BindAddress): + """ IPv4 TCP Address. """ + + def __init__(self, address, port): + # XXX: normalize the address to the binary representation in a + # better way. + + try: + self.__addr = _socket.inet_pton(_socket.AF_INET, address) + except: + 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): + # XXX: normalize the address to the binary representation in a + # better way. + + try: + self.__addr = _socket.inet_pton(_socket.AF_INET6, address) + except: + 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) + +# --- +# Decoding from strings. +# --- + +def _decode_tcp_host(x): + """ Decode suitable hosts for a TCP bind. """ + + addrs = () + addr = x + + # 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 _InvalidBindAddressError(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: + try: + assert x[1:] != '' + port = _socket.getservbyname(x[1:]) + except: + raise _InvalidBindAddressError(addr, + "expected a valid port number or name " \ + f"(got {repr(x[1:])})") from None + else: + raise _InvalidBindAddressError(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('.') + assert 2 <= len(ip) <= 4 + + ip = list(map(int, ip)) + assert all(lambda x: 0 <= x < 256, ip) + + 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: + 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 + +class BindsDecoder: + """ Binds decoder for fingerd. + Takes a raw string and the protocol name, either 'finger' (the base + protocol managed by the class) or 'fingerd-control' (the protocol used + for controlling the live fingerd interface). """ + + def __init__(self, raw, proto = 'finger'): + proto = proto.casefold() + if proto not in ('finger', 'fingerd-control'): + raise NotImplementedError(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: + x = scheme + scheme = {'finger': 'tcp', 'fingerd-control': 'ipc'}[proto] + else: + # just don't add the ':' of ':/' again + x = '/' + ':/'.join(rest) + + if (proto == 'finger' and scheme != 'tcp') \ + or scheme not in ('tcp', 'ipc'): + raise _InvalidBindError("unsupported scheme {repr(scheme)} " \ + f"for protocol {repr(proto)}") + + # Decode the address data. + + if scheme == "tcp": + self.__binds.update(_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})" + +# End of file. diff --git a/fingerd/_exceptions.py b/fingerd/_exceptions.py new file mode 100644 index 0000000..0d03e6f --- /dev/null +++ b/fingerd/_exceptions.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +#****************************************************************************** +# Copyright (C) 2017-2019 Thomas Touhey <thomas@touhey.fr> +# This file is part of the fingerd Python 3.x module, which is MIT-licensed. +#****************************************************************************** +""" This file defines the exceptions used throughout the module. """ + +class ConfigurationError(Exception): + """ Generic exception for when an invalid configuration + exception occurs. """ + + pass + +class BindError(ConfigurationError): + """ Exception raised when there were errors in the binds. """ + + def __init__(self): + super().__init__("invalid bind") + +class InvalidBindAddressError(ConfigurationError): + """ Exception raised when a bind could not be understood. """ + + def __init__(self, addr, text): + super().__init__(f"{text} for address {addr}") + +# End of file. diff --git a/fingerd/_fiction.py b/fingerd/_fiction.py index 84262b6..2b71640 100755 --- a/fingerd/_fiction.py +++ b/fingerd/_fiction.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 #****************************************************************************** -# Copyright (C) 2017-2018 Thomas Touhey <thomas@touhey.fr> +# Copyright (C) 2017-2019 Thomas Touhey <thomas@touhey.fr> # This file is part of the fingerd Python 3.x module, which is MIT-licensed. #****************************************************************************** """ Definitions for the finger server fiction interface. diff --git a/fingerd/_server.py b/fingerd/_server.py index 2772586..3833837 100755 --- a/fingerd/_server.py +++ b/fingerd/_server.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 #****************************************************************************** -# Copyright (C) 2017-2018 Thomas Touhey <thomas@touhey.fr> +# Copyright (C) 2017-2019 Thomas Touhey <thomas@touhey.fr> # This file is part of the fingerd Python 3.x module, which is MIT-licensed. #****************************************************************************** """ The main server class for the server, with the related utilities that @@ -237,7 +237,7 @@ _ldh = _string.ascii_letters + _string.digits + '.-' class FingerServer: """ The Finger Server class. """ - def __init__(self, bind = 'localhost:79', host = 'EXAMPLE.ORG', + def __init__(self, bind = 'localhost:79', host = 'LOCALHOST', interface = _FingerInterface(), formatter = _FingerFormatter(), logger = _FingerLogger()): @@ -284,12 +284,7 @@ class FingerServer: self._servers = [] for bind in _BindsDecoder(addresses): - if bind.family == _BindAddressType.TCP_IPv4: - fam = _socket.AF_INET - elif bind.family == _BindAddressType.TCP_IPv6: - fam = _socket.AF_INET6 - - self._servers.append([fam, bind.data[0], bind.data[1], None]) + self._servers.append([bind.runserver_params, None]) return bool(self._servers) @@ -297,18 +292,16 @@ class FingerServer: """ Bind and start the underlying servers. """ for entry in self._servers: - family, addr, port, _ = entry - # Check if the thread already exists and runs. - if entry[3] != None and not entry[3].is_alive(): + if entry[1] != None and not entry[1].is_alive(): continue # Start the thread. - entry[3] = _multip.Process(target = _runserver, - args = (family, addr, port, self._params)) - entry[3].start() + entry[1] = _multip.Process(target = _runserver, + args = entry[0] + (self._params,)) + entry[1].start() def stop(self): """ Stop the underlying servers. """ @@ -316,16 +309,16 @@ class FingerServer: for entry in self._servers: # Check if the thread is still here. - if entry[3] == None: + if entry[1] == None: continue - if not entry[3].is_alive(): - entry[3] = None + if not entry[1].is_alive(): + entry[1] = None continue # Stop it. - entry[3].join() - entry[3] = None + entry[1].join() + entry[1] = None def serve_forever(self): """ Serve forever. """ diff --git a/fingerd/_util.py b/fingerd/_util.py index 9e8a971..8454827 100755 --- a/fingerd/_util.py +++ b/fingerd/_util.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 #****************************************************************************** -# Copyright (C) 2017-2018 Thomas Touhey <thomas@touhey.fr> +# Copyright (C) 2017-2019 Thomas Touhey <thomas@touhey.fr> # This file is part of the fingerd Python 3.x module, which is MIT-licensed. #****************************************************************************** """ This file defines the base classes for what is used by the @@ -8,39 +8,18 @@ import sys as _sys, copy as _copy import multiprocessing as _multip -import socket as _socket -from enum import Enum as _Enum from datetime import datetime as _dt, timedelta as _td +from ._exceptions import ConfigurationError, BindError +from ._binds import BindAddressType, BindsDecoder + __all__ = ["ConfigurationError", "BindError", "FingerUser", "FingerSession", "FingerFormatter", "FingerInterface", "FingerLogger" "BindAddressType", "BindsDecoder"] # --- -# Exceptions. -# --- - -class ConfigurationError(Exception): - """ Generic exception for when an invalid configuration - exception occurs. """ - - pass - -class BindError(ConfigurationError): - """ Exception raised when there were errors in the binds. """ - - def __init__(self): - super().__init__("invalid bind") - -class InvalidBindAddressError(ConfigurationError): - """ Exception raised when a bind could not be understood. """ - - def __init__(self, addr, text): - super().__init__(f"{text} for address {addr}") - -# --- # User-related classes. # --- @@ -525,181 +504,4 @@ class FingerLogger: def list(self, source): self._logr(source, "list connected users.") -# --- -# Bind decoders. -# --- - -class BindAddressType(_Enum): - """ Bind address type. """ - - """ TCP on IPv4 bind. """ - TCP_IPv4 = 1 - - """ TCP on IPv6 bind. """ - TCP_IPv6 = 2 - - """ IPC bind. """ - IPC = 3 - -class _BindAddress: - """ Bind address for fingerd. """ - - def __init__(self, family, data): - self.__family = BindAddressType(family) - self.__data = data - - def __repr__(self): - return f"{self.__class__.__name__}(family = {self.__family}, " \ - f"data = {self.__data})" - - @property - def family(self): - """ Family as one of the `BindAddressType` enumeration values. """ - - return self.__family - - @property - def data(self): - """ Data to be sent for binding. """ - - return self.__data - -def _decode_tcp_host(x): - """ Decode suitable hosts for a TCP bind. """ - - addrs = () - addr = x - - # 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 InvalidBindAddressError(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: - port = None if x[1:] == "" else _socket.getservbyname(x[1:]) - if port is None: - raise InvalidBindAddressError(addr, - "expected a port number or name") from None - else: - raise InvalidBindAddressError(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 += (_BindAddress(BindAddressType.TCP_IPv6, (ip6, port)),) - else: - # Decode the host (try IPv4, otherwise, resolve domain). - - try: - ip = host.split('.') - assert 2 <= len(ip) <= 4 - - ip = list(map(int, ip)) - assert all(lambda x: 0 <= x < 256, ip) - - if len(ip) == 2: - ip = [ip[0], 0, 0, ip[1]] - elif len(ip) == 3: - ip = [ip[0], 0, ip[1], ip[2]] - - addrs += (_BindAddress(BindAddressType.TCP_IPv4, (ip, port)),) - except: - 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 += (_BindAddress(BindAddressType.TCP_IPv4, - (ip, port)),) - else: - ip6 = ent[4][0] - _socket.inet_pton(_socket.AF_INET6, ent[4][0]) - addrs += (_BindAddress(BindAddressType.TCP_IPv6, - (ip6, port)),) - - return addrs - -class BindsDecoder: - """ Binds decoder for fingerd. - Takes a raw string and the protocol name, either 'finger' (the base - protocol managed by the class) or 'fingerd-control' (the protocol used - for controlling the live fingerd interface). """ - - def __init__(self, raw, proto = 'finger'): - proto = proto.casefold() - if proto not in ('finger', 'fingerd-control'): - raise NotImplementedError(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: - x = scheme - scheme = {'finger': 'tcp', 'fingerd-control': 'ipc'}[proto] - else: - # just don't add the ':' of ':/' again - x = '/' + ':/'.join(rest) - - if (proto == 'finger' and scheme != 'tcp') \ - or scheme not in ('tcp', 'ipc'): - raise _InvalidBindError("unsupported scheme {repr(scheme)} " \ - f"for protocol {repr(proto)}") - - # Decode the address data. - - if scheme == "tcp": - self.__binds.update(_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})" - # End of file. diff --git a/fingerd/client/__init__.py b/fingerd/client/__init__.py index ca92dca..a008005 100644 --- a/fingerd/client/__init__.py +++ b/fingerd/client/__init__.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 #****************************************************************************** -# Copyright (C) 2017-2018 Thomas Touhey <thomas@touhey.fr> +# Copyright (C) 2017-2019 Thomas Touhey <thomas@touhey.fr> # This file is part of the fingerd Python 3.x module, which is MIT-licensed. #****************************************************************************** """ The `fingerd.client` submodule is a module for adding actions to the @@ -23,7 +23,7 @@ DELETE = _DeleteClass() # The client. # --- -class FingerLiveClient: +class FingerControlClient: """ Client for sending actions to the fingerd live interface. """ def __init__(self, url): @@ -45,4 +45,6 @@ class FingerLiveClient: def login(self, login, session): """ Login to a finger session. """ + raise NotImplementedError + # End of file. |