From 3f2760ba2ddb81b8c82cc711d58dfb9c341b9e44 Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Thu, 30 Jun 2022 21:03:22 +0200 Subject: [PATCH] Create skeleton of the Lacre daemon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also: - Expose a function to read mail relay configuration. - Replace tabs with 4 spaces in lacre.config. --- lacre/config.py | 102 ++++++++++++++++++++++++++-------------------- lacre/daemon.py | 34 ++++++++++++++++ lacre/mailgate.py | 2 +- 3 files changed, 93 insertions(+), 45 deletions(-) create mode 100644 lacre/daemon.py diff --git a/lacre/config.py b/lacre/config.py index 2d2c089..0933206 100644 --- a/lacre/config.py +++ b/lacre/config.py @@ -16,72 +16,86 @@ CONFIG_PATH_ENV = "GPG_MAILGATE_CONFIG" # List of mandatory configuration parameters. Each item on this list should be # a pair: a section name and a parameter name. MANDATORY_CONFIG_ITEMS = [("relay", "host"), - ("relay", "port")] + ("relay", "port")] # Global dict to keep configuration parameters. It's hidden behind several # utility functions to make it easy to replace it with ConfigParser object in # the future. cfg = dict() + def load_config() -> dict: - """Parses configuration file. + """Parse configuration file. - If environment variable identified by CONFIG_PATH_ENV - variable is set, its value is taken as a configuration file - path. Otherwise, the default is taken - ('/etc/gpg-mailgate.conf'). - """ - configFile = os.getenv(CONFIG_PATH_ENV, '/etc/gpg-mailgate.conf') + If environment variable identified by CONFIG_PATH_ENV + variable is set, its value is taken as a configuration file + path. Otherwise, the default is taken + ('/etc/gpg-mailgate.conf'). + """ + configFile = os.getenv(CONFIG_PATH_ENV, '/etc/gpg-mailgate.conf') - parser = read_config(configFile) + parser = _read_config(configFile) - global cfg - cfg = copy_to_dict(parser) - return cfg + global cfg + cfg = _copy_to_dict(parser) + return cfg -def read_config(fileName) -> RawConfigParser: - cp = RawConfigParser() - cp.read(fileName) - return cp +def _read_config(fileName) -> RawConfigParser: + cp = RawConfigParser() + cp.read(fileName) -def copy_to_dict(confParser) -> dict: - config = dict() + return cp - for sect in confParser.sections(): - config[sect] = dict() - for (name, value) in confParser.items(sect): - config[sect][name] = value - return config +def _copy_to_dict(confParser) -> dict: + config = dict() + + for sect in confParser.sections(): + config[sect] = dict() + for (name, value) in confParser.items(sect): + config[sect][name] = value + + return config + + +def get_item(section, key, empty_value=None): + global cfg + if config_item_set(section, key): + return cfg[section][key] + else: + return empty_value -def get_item(section, key, empty_value = None): - global cfg - if config_item_set(section, key): - return cfg[section][key] - else: - return empty_value def has_section(section) -> bool: - global cfg - return section in cfg + return section in cfg + def config_item_set(section, key) -> bool: - global cfg - return section in cfg and (key in cfg[section]) and not (cfg[section][key] is None) + return section in cfg and (key in cfg[section]) and not (cfg[section][key] is None) + def config_item_equals(section, key, value) -> bool: - global cfg - return section in cfg and key in cfg[section] and cfg[section][key] == value + return section in cfg and key in cfg[section] and cfg[section][key] == value + def validate_config(): - """Checks whether the configuration is complete. + """Check if configuration is complete. - Returns a list of missing parameters, so an empty list means - configuration is complete. - """ - missing = [] - for (section, param) in MANDATORY_CONFIG_ITEMS: - if not config_item_set(section, param): - missing.append((section, param)) - return missing + Returns a list of missing parameters, so an empty list means + configuration is complete. + """ + missing = [] + for (section, param) in MANDATORY_CONFIG_ITEMS: + if not config_item_set(section, param): + missing.append((section, param)) + return missing + + +# +# High level access to configuration. +# + +def relay_params(): + """Return a (HOST, PORT) tuple identifying the mail relay.""" + return (cfg["relay"]["host"], int(cfg["relay"]["port"])) diff --git a/lacre/daemon.py b/lacre/daemon.py new file mode 100644 index 0000000..dc1df28 --- /dev/null +++ b/lacre/daemon.py @@ -0,0 +1,34 @@ +"""Lacre Daemon, the Advanced Mail Filter message dispatcher.""" + +from aiosmtpd.controller import Controller + +import lacre.config as conf +# import lacre.mailgate as gate + + +RESULT_OK = '250 OK' +RESULT_ERROR = '500 Could not process your message' +RESULT_NOT_IMPLEMENTED = '500 Not implemented yet' + + +class MailEncryptionProxy: + """A mail handler dispatching to appropriate mail operation.""" + + async def handle_DATA(self, server, session, envelope): + """Accept a message and either encrypt it or forward as-is.""" + # for now, just return an error because we're not ready to handle mail + return RESULT_NOT_IMPLEMENTED + + +if __name__ == '__main__': + proxy = MailEncryptionProxy() + host, port = conf.relay_params() + controller = Controller(proxy, hostname=host, port=port) + + # starts the controller in a new thread + controller.start() + + # _this_ thread now continues operation, so it may be used to control key + # and certificate cache + + controller.stop() diff --git a/lacre/mailgate.py b/lacre/mailgate.py index 55547f7..110f034 100644 --- a/lacre/mailgate.py +++ b/lacre/mailgate.py @@ -378,7 +378,7 @@ def send_msg( message, recipients ): recipients = [_f for _f in recipients if _f] if recipients: LOG.info(f"Sending email to: {recipients!r}") - relay = (conf.get_item('relay', 'host'), int(conf.get_item('relay', 'port'))) + relay = conf.relay_params() smtp = smtplib.SMTP(relay[0], relay[1]) if conf.config_item_equals('relay', 'starttls', 'yes'): smtp.starttls()