gpg-lacre/lacre/daemon.py

122 lines
3.7 KiB
Python
Raw Normal View History

"""Lacre Daemon, the Advanced Mail Filter message dispatcher."""
2022-06-30 22:55:04 +02:00
import logging
import lacre
import lacre.config as conf
import sys
2022-06-30 22:55:04 +02:00
from aiosmtpd.controller import Controller
from aiosmtpd.smtp import Envelope
import asyncio
import email
import time
from watchdog.observers import Observer
2022-06-30 22:55:04 +02:00
# Mail status constants.
#
# These are the only values that our mail handler is allowed to return.
RESULT_OK = '250 OK'
RESULT_ERROR = '500 Could not process your message'
2022-06-30 22:55:04 +02:00
# Load configuration and init logging, in this order. Only then can we load
# the last Lacre module, i.e. lacre.mailgate.
conf.load_config()
lacre.init_logging(conf.get_item("logging", "config"))
LOG = logging.getLogger('lacre.daemon')
2022-06-30 22:55:04 +02:00
2022-10-22 10:13:54 +02:00
import lacre.core as gate
import lacre.keyring as kcache
2022-06-30 22:55:04 +02:00
class MailEncryptionProxy:
"""A mail handler dispatching to appropriate mail operation."""
def __init__(self, keyring: kcache.KeyRing):
2022-09-30 22:40:42 +02:00
"""Initialise the mail proxy with a reference to the key cache."""
self._keyring = keyring
2022-09-30 22:40:42 +02:00
async def handle_DATA(self, server, session, envelope: Envelope):
"""Accept a message and either encrypt it or forward as-is."""
start = time.process_time()
try:
keys = await self._keyring.freeze_identities()
message = email.message_from_bytes(envelope.content)
for operation in gate.delivery_plan(envelope.rcpt_tos, message, keys):
LOG.debug(f"Sending mail via {operation!r}")
new_message = operation.perform(message)
gate.send_msg(new_message, operation.recipients(), envelope.mail_from)
except TypeError as te:
LOG.exception("Got exception while processing", exc_info=te)
return RESULT_ERROR
except:
LOG.exception('Unexpected exception caught, bouncing message')
return RESULT_ERROR
2022-06-30 22:55:04 +02:00
ellapsed = (time.process_time() - start) * 1000
LOG.info(f'Message delivered in {ellapsed:.2f} ms')
return RESULT_OK
def _init_controller(keys: kcache.KeyRing, tout: float = 5):
2022-09-30 22:40:42 +02:00
proxy = MailEncryptionProxy(keys)
host, port = conf.daemon_params()
LOG.info(f"Initialising a mail Controller at {host}:{port}")
return Controller(proxy, hostname=host, port=port, ready_timeout=tout)
2022-06-30 22:55:04 +02:00
def _init_reloader(keyring_dir: str, reloader) -> kcache.KeyringModificationListener:
listener = kcache.KeyringModificationListener(reloader)
observer = Observer()
observer.schedule(listener, keyring_dir, recursive=False)
return observer
def _validate_config():
missing = conf.validate_config()
if missing:
params = ", ".join([_full_param_name(tup) for tup in missing])
LOG.error(f"Following mandatory parameters are missing: {params}")
sys.exit(lacre.EX_CONFIG)
def _full_param_name(tup):
return f"[{tup[0]}]{tup[1]}"
async def _sleep():
while True:
await asyncio.sleep(360)
2022-06-30 22:55:04 +02:00
def _main():
_validate_config()
keyring_path = conf.get_item('gpg', 'keyhome')
loop = asyncio.get_event_loop()
keyring = kcache.KeyRing(keyring_path, loop)
controller = _init_controller(keyring)
reloader = _init_reloader(keyring_path, keyring)
LOG.info(f'Watching keyring directory {keyring_path}...')
reloader.start()
LOG.info('Starting the daemon...')
controller.start()
try:
loop.run_until_complete(_sleep())
except KeyboardInterrupt:
LOG.info("Finishing...")
except:
LOG.exception('Unexpected exception caught, your system may be unstable')
finally:
LOG.info('Shutting down keyring watcher and the daemon...')
reloader.stop()
reloader.join()
controller.stop()
2022-06-30 22:55:04 +02:00
LOG.info("Done")
2022-06-30 22:55:04 +02:00
if __name__ == '__main__':
_main()