From 354200f05d18ba5f6de7be9af81117bf125b3fd5 Mon Sep 17 00:00:00 2001 From: Affe Null Date: Thu, 11 Jan 2024 14:02:32 +0100 Subject: [PATCH] Allow serving static files from a fallback directory --- src/cobbler_tftp/server/__init__.py | 9 ++++- src/cobbler_tftp/server/tftp.py | 42 +++++++++++++++++--- src/cobbler_tftp/settings/__init__.py | 10 ++++- src/cobbler_tftp/settings/data/settings.yml | 1 + src/cobbler_tftp/settings/migrations/v1_0.py | 1 + 5 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/cobbler_tftp/server/__init__.py b/src/cobbler_tftp/server/__init__.py index ccc1f68..cea3f7b 100644 --- a/src/cobbler_tftp/server/__init__.py +++ b/src/cobbler_tftp/server/__init__.py @@ -30,7 +30,14 @@ def run_server(application_settings: Settings): timeout = application_settings.tftp_timeout # TODO: use username and password api = xmlrpc.client.Server(application_settings.uri) - server = TFTPServer(address, port, retries, timeout, api) + server = TFTPServer( + address, + port, + retries, + timeout, + api, + application_settings.static_fallback_dir, + ) except: # pylint: disable=bare-except logging.exception("Fatal exception while setting up server") return diff --git a/src/cobbler_tftp/server/tftp.py b/src/cobbler_tftp/server/tftp.py index 9be269f..84fb618 100644 --- a/src/cobbler_tftp/server/tftp.py +++ b/src/cobbler_tftp/server/tftp.py @@ -3,6 +3,7 @@ """ import logging +import xmlrpc.client from io import BytesIO from fbtftp import BaseHandler, BaseServer, ResponseData @@ -25,6 +26,23 @@ def close(self): self._io.close() +class FileResponseData(ResponseData): + """Object representing a static file response from the TFTP server.""" + + def __init__(self, path): + self._io = open(path, "rb") + self._size = path.stat().st_size + + def read(self, n): + return self._io.read(n) + + def size(self): + return self._size + + def close(self): + self._io.close() + + def handler_stats_cb(stats): duration = stats.duration() * 1000 logging.info( @@ -52,7 +70,7 @@ class CobblerRequestHandler(BaseHandler): Handles TFTP requests using the Cobbler API. """ - def __init__(self, server_addr, peer, path, options, api): + def __init__(self, server_addr, peer, path, options, api, static_fallback_dir): """ Initialize a handler for a specific request. @@ -61,12 +79,21 @@ def __init__(self, server_addr, peer, path, options, api): :param path: Request file path. :param options: Options requested by the client. :param api: The Cobbler API object. + :param static_fallback_dir: Path to directory with static TFTP files. """ self._api = api + self._static_fallback_dir = static_fallback_dir super().__init__(server_addr, peer, path, options, handler_stats_cb) def get_response_data(self): - return CobblerResponseData(self._api.get_tftp_file(self._path).data) + try: + return CobblerResponseData(self._api.get_tftp_file(self._path).data) + except xmlrpc.client.Error as err: + if self._static_fallback_dir is not None: + return FileResponseData( + self._static_fallback_dir / self._path.strip("/") + ) + raise err class TFTPServer(BaseServer): @@ -74,7 +101,7 @@ class TFTPServer(BaseServer): Implements a TFTP server for the Cobbler API using the CobblerRequestHandler. """ - def __init__(self, address, port, retries, timeout, api): + def __init__(self, address, port, retries, timeout, api, static_fallback_dir): """ Initialize the TFTP server. @@ -83,10 +110,13 @@ def __init__(self, address, port, retries, timeout, api): :param retries: Maximum number of retries when sending a packet fails. :param timeout: Timeout for sending packets. :param api: The Cobbler API object. + :param static_fallback_dir: Path to directory with static TFTP files. """ - self.api = api - self.handler_stats_cb = handler_stats_cb + self._api = api + self._static_fallback_dir = static_fallback_dir super().__init__(address, port, retries, timeout, server_stats_cb) def get_handler(self, server_addr, peer, path, options): - return CobblerRequestHandler(server_addr, peer, path, options, self.api) + return CobblerRequestHandler( + server_addr, peer, path, options, self._api, self._static_fallback_dir + ) diff --git a/src/cobbler_tftp/settings/__init__.py b/src/cobbler_tftp/settings/__init__.py index cb205fe..c8b089d 100644 --- a/src/cobbler_tftp/settings/__init__.py +++ b/src/cobbler_tftp/settings/__init__.py @@ -36,6 +36,7 @@ def __init__( tftp_retries: int, tftp_timeout: int, logging_conf: Optional[Path], + static_fallback_dir: Optional[Path], ) -> None: """ Initialize a new instance of the Settings. @@ -46,6 +47,7 @@ def __init__( :param username: Username to authenticate at Cobbler's API. :param password: Password for authentication with Cobbler. :param password_file: Path to the file containing the password. + :param static_fallback_dir: Path to the directory with static TFTP files. """ # pylint: disable=R0913 @@ -59,6 +61,7 @@ def __init__( self.tftp_retries: int = tftp_retries self.tftp_timeout: int = tftp_timeout self.logging_conf: Optional[Path] = logging_conf + self.static_fallback_dir: Optional[Path] = static_fallback_dir self.__password: Optional[str] = password self.__password_file: Optional[Path] = password_file @@ -147,11 +150,15 @@ def build_settings( password_file: Optional[Path] = Path(cobbler_settings.get("password_file", None)) # type: ignore else: password_file = None - tftp_settings = self._settings_dict.get("tftp", None) + tftp_settings = self._settings_dict.get("tftp", {}) tftp_addr: str = tftp_settings.get("address", "127.0.0.1") # type: ignore tftp_port: int = tftp_settings.get("port", 69) # type: ignore tftp_retries: int = tftp_settings.get("retries", 5) # type: ignore tftp_timeout: int = tftp_settings.get("timeout", 2) # type: ignore + if tftp_settings.get("static_fallback_dir", None) is not None: # type: ignore + static_fallback_dir: Optional[Path] = Path(tftp_settings.get("static_fallback_dir", None)) # type: ignore + else: + static_fallback_dir = None if self._settings_dict.get("logging_conf", None) is not None: # type: ignore logging_conf: Optional[Path] = Path(self._settings_dict.get("logging_conf", None)) # type: ignore else: @@ -171,6 +178,7 @@ def build_settings( tftp_retries, tftp_timeout, logging_conf, + static_fallback_dir, ) return settings diff --git a/src/cobbler_tftp/settings/data/settings.yml b/src/cobbler_tftp/settings/data/settings.yml index 0339be9..e00f7ed 100644 --- a/src/cobbler_tftp/settings/data/settings.yml +++ b/src/cobbler_tftp/settings/data/settings.yml @@ -16,4 +16,5 @@ tftp: port: 69 retries: 5 timeout: 2 + static_fallback_dir: "/srv/tftpboot" logging_conf: "/etc/cobbler-tftp/logging.conf" diff --git a/src/cobbler_tftp/settings/migrations/v1_0.py b/src/cobbler_tftp/settings/migrations/v1_0.py index e449d08..a5252da 100644 --- a/src/cobbler_tftp/settings/migrations/v1_0.py +++ b/src/cobbler_tftp/settings/migrations/v1_0.py @@ -22,6 +22,7 @@ Optional("port"): int, Optional("retries"): int, Optional("timeout"): int, + Optional("static_fallback_dir"): str, }, Optional("logging_conf"): str, }