From bb5f07dbade474fde7859c8acf739f407aa9b8e1 Mon Sep 17 00:00:00 2001 From: Michael Woods Date: Sat, 4 Jan 2025 12:12:23 -0500 Subject: [PATCH] Added a better connection approval mechanism than query_accept. --- src/packetserver/common/__init__.py | 11 +++++- src/packetserver/server/__init__.py | 60 ++++++++++++++++++++++++----- src/packetserver/server/requests.py | 33 ++++++++++++---- 3 files changed, 86 insertions(+), 18 deletions(-) diff --git a/src/packetserver/common/__init__.py b/src/packetserver/common/__init__.py index b0cbd33..e3f05bf 100644 --- a/src/packetserver/common/__init__.py +++ b/src/packetserver/common/__init__.py @@ -6,6 +6,8 @@ from enum import Enum import bz2 from typing import Union, Self import datetime +import logging +import ax25 class PacketServerConnection(Connection): @@ -20,6 +22,7 @@ class PacketServerConnection(Connection): self.data_lock = Lock() self.connection_created = datetime.datetime.now(datetime.UTC) self.connection_last_activity = datetime.datetime.now(datetime.UTC) + self.closing = False @property @@ -38,20 +41,26 @@ class PacketServerConnection(Connection): def connected(self): print("connected") + logging.debug(f"new connection from {self.call_from} to {self.call_to}") for fn in PacketServerConnection.connection_subscribers: fn(self) def disconnected(self): - pass + logging.debug(f"connection disconnected: {self.call_from} -> {self.call_to}") def data_received(self, pid, data): self.connection_last_activity = datetime.datetime.now(datetime.UTC) + logging.debug(f"received data: {data}") with self.data_lock: + logging.debug(f"fed received data to unpacker {data}") self.data.feed(data) for fn in PacketServerConnection.receive_subscribers: + logging.debug("found function to notify about received data") fn(self) + logging.debug("notified function about received data") def send_data(self, data: Union[bytes, bytearray]): + logging.debug(f"sending data: {data}") self.connection_last_activity = datetime.datetime.now(datetime.UTC) super().send_data(data) diff --git a/src/packetserver/server/__init__.py b/src/packetserver/server/__init__.py index d518e08..2d2cc47 100644 --- a/src/packetserver/server/__init__.py +++ b/src/packetserver/server/__init__.py @@ -1,13 +1,18 @@ import pe.app -from ..common import PacketServerConnection -from .constants import default_server_config +import packetserver.common +from packetserver.server.constants import default_server_config from copy import deepcopy import ax25 from pathlib import Path import ZODB, ZODB.FileStorage from BTrees.OOBTree import OOBTree -from .requests import process_incoming_data -from .requests import standard_handlers +from persistent.mapping import PersistentMapping +from persistent.list import PersistentList +from packetserver.server.requests import process_incoming_data +from packetserver.server.requests import standard_handlers +import logging +import signal +import time class Server: @@ -26,34 +31,69 @@ class Server: if data_path.joinpath("data.zopedb").exists(): if not data_path.joinpath("data.zopedb").is_file(): raise FileExistsError("data.zopedb exists as non-file in specified path") - self.home_dir = data_dir + self.home_dir = data_path else: if data_path.exists(): raise FileExistsError(f"Non-Directory path '{data_dir}' already exists.") else: data_path.mkdir() - self.home_dir = data_dir + self.home_dir = data_path self.storage = ZODB.FileStorage.FileStorage(self.data_file) self.db = ZODB.DB(self.storage) with self.db.transaction() as conn: if 'config' not in conn.root(): - conn.root.config = deepcopy(default_server_config) + conn.root.config = PersistentMapping(deepcopy(default_server_config)) + conn.root.config['blacklist'] = PersistentList() if 'users' not in conn.root(): conn.root.users = OOBTree() self.app = pe.app.Application() - PacketServerConnection.receive_subscribers.append(lambda x: self.server_receiver(x)) + packetserver.common.PacketServerConnection.receive_subscribers.append(lambda x: self.server_receiver(x)) + packetserver.common.PacketServerConnection.connection_subscribers.append(lambda x: self.server_connection_bouncer(x)) + signal.signal(signal.SIGINT, self.exit_gracefully) + signal.signal(signal.SIGTERM, self.exit_gracefully) @property def data_file(self) -> str: return str(Path(self.home_dir).joinpath('data.zopedb')) - def server_receiver(self, conn: PacketServerConnection): + def server_connection_bouncer(self, conn: packetserver.common.PacketServerConnection): + logging.debug("new connection bouncer checking for blacklist") + # blacklist check + blacklisted = False + with self.db.transaction() as storage: + if 'blacklist' in storage.root.config: + bl = storage.root.config['blacklist'] + logging.debug(f"A blacklist exists: {bl}") + base = ax25.Address(conn.remote_callsign).call + logging.debug(f"Checking callsign {base.upper()}") + if base.upper() in bl: + logging.debug(f"Connection from blacklisted callsign {base}") + conn.closing = True + blacklisted = True + if blacklisted: + count = 0 + while count < 10: + time.sleep(.5) + if conn.state.name == "CONNECTED": + break + conn.close() + + def server_receiver(self, conn: packetserver.common.PacketServerConnection): + logging.debug("running server receiver") process_incoming_data(conn, self) def start(self): self.app.start(self.pe_server, self.pe_port) self.app.register_callsigns(self.callsign) + def exit_gracefully(self, signum, frame): + self.stop() + def stop(self): - self.app.stop() \ No newline at end of file + cm = self.app._engine._active_handler._handlers[1]._connection_map + for key in cm._connections.keys(): + cm._connections[key].close() + self.app.stop() + self.storage.close() + self.db.close() diff --git a/src/packetserver/server/requests.py b/src/packetserver/server/requests.py index f0b5831..b74f0b8 100644 --- a/src/packetserver/server/requests.py +++ b/src/packetserver/server/requests.py @@ -1,11 +1,12 @@ """Module for handling requests as they arrive to connection objects and servers.""" -from . import PacketServerConnection -from . import Server from msgpack.exceptions import OutOfData -from ..common import Message, Request, Response +from packetserver.common import Message, Request, Response, PacketServerConnection +import logging -def handle_root_get(req: Request, conn: PacketServerConnection, server: Server): +def handle_root_get(req: Request, conn: PacketServerConnection, + server: 'packetserver.server.Server'): + logging.debug(f"Received request: {req}") response = Response.blank() response.compression = Message.CompressionType.BZIP2 operator = "" @@ -22,7 +23,9 @@ def handle_root_get(req: Request, conn: PacketServerConnection, server: Server): } if conn.state.name == "CONNECTED": + logging.debug(f"sending response: {response}, {response.compression}, {response.payload}") conn.send_data(response.pack()) + logging.debug("response sent successfully") standard_handlers = { "": { @@ -30,24 +33,37 @@ standard_handlers = { } } -def handle_request(req: Request, conn: PacketServerConnection, server: Server): +def handle_request(req: Request, conn: PacketServerConnection, + server: 'packetserver.server.Server'): """Handles a proper request by handing off to the appropriate function depending on method and Path.""" + logging.debug(f"asked to handle request: {req}") + if conn.closing: + logging.debug("Connection marked as closing. Ignoring it.") + return if req.path in server.handlers: if req.method.name in server.handlers[req.path]: + logging.debug(f"found handler for req {req}") server.handlers[req.path][req.method.name](req, conn, server) return + logging.warning(f"unhandled request found: {req}") response_404 = Response.blank() response_404.status_code = 404 if conn.state.name == "CONNECTED": conn.send_data(response_404.pack()) + logging.debug(f"Sent 404 in response to {req}") -def process_incoming_data(connection: PacketServerConnection, server: Server): +def process_incoming_data(connection: 'packetserver.common.PacketServerConnection', + server: 'packetserver.server.Server'): """Handles incoming data.""" + logging.debug("Running process_incoming_data on connection") with connection.data_lock: + logging.debug("Data lock acquired") while True: try: msg = Message.partial_unpack(connection.data.unpack()) + logging.debug(f"parsed a Message from data received") except OutOfData: + logging.debug("no complete message yet, done until more data arrives") break except ValueError: r = Response.blank() @@ -55,12 +71,15 @@ def process_incoming_data(connection: PacketServerConnection, server: Server): r.payload = "BAD REQUEST. COULD NOT PARSE INCOMING DATA AS PACKETSERVER MESSAGE" connection.send_data(r.pack()) connection.send_data(b"BAD REQUEST. COULD NOT PARSE INCOMING DATA AS PACKETSERVER MESSAGE") - try: request = Request(msg) + logging.debug(f"parsed Message into request {request}") except ValueError: r = Response.blank() r.status_code = 400 r.payload = "BAD REQUEST. DID NOT RECEIVE A REQUEST MESSAGE." connection.send_data(r.pack()) connection.send_data(b"BAD REQUEST. DID NOT RECEIVE A REQUEST MESSAGE.") + logging.debug(f"attempting to handle request {request}") + handle_request(request, connection, server) + logging.debug("request handled") \ No newline at end of file