From 65924f96569a16167d72915fe235492d386714f0 Mon Sep 17 00:00:00 2001 From: Florian Brandes Date: Fri, 5 Jul 2024 11:29:19 +0200 Subject: [PATCH] format Signed-off-by: Florian Brandes --- devenv.nix | 4 +- smtprd.py | 124 ++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 87 insertions(+), 41 deletions(-) diff --git a/devenv.nix b/devenv.nix index 0ed8dd0..547b5e8 100644 --- a/devenv.nix +++ b/devenv.nix @@ -48,8 +48,10 @@ black.enable = true; # sort imports isort.enable = true; + # line length the same for black and isort + isort.settings.flags = "-l 88"; # look for code smell - pylint.enable = true; + # pylint.enable = true; detect-private-keys.enable = true; diff --git a/smtprd.py b/smtprd.py index c168fd1..a6fc778 100644 --- a/smtprd.py +++ b/smtprd.py @@ -13,19 +13,18 @@ import sys import threading import time import uuid - -from locale import setlocale, LC_ALL - +from dataclasses import dataclass from email import message_from_bytes, message_from_string from email.message import Message from email.policy import default as EmailMessagePolicy +from locale import LC_ALL, setlocale +from typing import List, Optional, Set, Tuple, Union -from aiosmtpd.controller import Controller, SMTP as Server -from aiosmtpd.smtp import Session, Envelope -from aiosmtplib import SMTP, SMTPException, SMTPRecipientsRefused, SMTPRecipientRefused - -from dataclasses import dataclass -from typing import Optional, Union, List, Tuple, Set +import envelope +from aiosmtpd.controller import SMTP as Server +from aiosmtpd.controller import Controller +from aiosmtpd.smtp import Envelope, Session +from aiosmtplib import SMTP, SMTPException, SMTPRecipientRefused, SMTPRecipientsRefused @dataclass(frozen=True) @@ -53,7 +52,7 @@ class Config: client: ClientConfig @classmethod - def _from_config(cls, config: configparser.ConfigParser) -> 'Config': + def _from_config(cls, config: configparser.ConfigParser) -> "Config": return cls( server=ServerConfig( hostname=config.get("server", "hostname", fallback="localhost"), @@ -63,17 +62,21 @@ class Config: hostname=config.get("client", "hostname"), port=config.getint("client", "port"), sender=config.get("client", "sender"), - recipients=[_.strip() for _ in config.get("client", "recipients").split(",")], + recipients=[ + _.strip() for _ in config.get("client", "recipients").split(",") + ], username=config.get("client", "username"), password=config.get("client", "password"), - set_reply_to=config.getboolean("client", "set_reply_to", fallback=False), + set_reply_to=config.getboolean( + "client", "set_reply_to", fallback=False + ), use_tls=config.getboolean("client", "use_tls", fallback=True), start_tls=config.getboolean("client", "start_tls", fallback=False), ), ) @classmethod - def from_ini(cls, filename: str) -> 'Config': + def from_ini(cls, filename: str) -> "Config": try: config_parser: configparser.ConfigParser = configparser.ConfigParser() with open(filename, "r") as fp: @@ -88,19 +91,30 @@ class SMTPClient(SMTP): self._config: ClientConfig = config self._lock: asyncio.Lock = asyncio.Lock() super().__init__( - hostname=self._config.hostname, port=self._config.port, - username=self._config.username, password=self._config.password, - use_tls=self._config.use_tls, start_tls=self._config.start_tls, + hostname=self._config.hostname, + port=self._config.port, + username=self._config.username, + password=self._config.password, + use_tls=self._config.use_tls, + start_tls=self._config.start_tls, ) - async def _send_message(self, message: Message, sender: str, recipients: List[str]) -> None: - failed_recipients: Set[str] = set((await self.send_message( - message, sender=sender, recipients=recipients - ))[0].keys()) + async def _send_message( + self, message: Message, sender: str, recipients: List[str] + ) -> None: + failed_recipients: Set[str] = set( + (await self.send_message(message, sender=sender, recipients=recipients))[ + 0 + ].keys() + ) if len(failed_recipients): # raise also when not all have been refused - raise SMTPRecipientsRefused([SMTPRecipientRefused(0, "", _) for _ in failed_recipients]) + raise SMTPRecipientsRefused( + [SMTPRecipientRefused(0, "", _) for _ in failed_recipients] + ) - async def forward_message(self, message: Message, sender: Optional[str], recipients: List[str]) -> None: + async def forward_message( + self, message: Message, sender: Optional[str], recipients: List[str] + ) -> None: del message["From"] del message["To"] del message["Reply-To"] @@ -108,19 +122,29 @@ class SMTPClient(SMTP): message["Original-Recipient"] = ", ".join(recipients) message["From"] = self._config.sender message["To"] = ", ".join(self._config.recipients) - if self._config.set_reply_to and sender is not None and sender != self._config.sender: + if ( + self._config.set_reply_to + and sender is not None + and sender != self._config.sender + ): message["Reply-To"] = sender - print(f"{message['From']} ({message['Original-Sender']}) -> " - f"{message['To']} ({message['Original-Recipient']}): " - f"'{message.get('Subject', '')}'") + print( + f"{message['From']} ({message['Original-Sender']}) -> " + f"{message['To']} ({message['Original-Recipient']}): " + f"'{message.get('Subject', '')}'" + ) async with self._lock: # TODO: consumer task from spool queue, reusing connections try: await self.connect() - await self._send_message(message, self._config.sender, self._config.recipients) + await self._send_message( + message, self._config.sender, self._config.recipients + ) except SMTPRecipientsRefused as e: - raise RuntimeError(f"Recipients refused: {', '.join(_.recipient for _ in e.recipients)}") from e + raise RuntimeError( + f"Recipients refused: {', '.join(_.recipient for _ in e.recipients)}" + ) from e except (SMTPException, OSError) as e: raise RuntimeError(str(e)) from e finally: @@ -131,11 +155,15 @@ class Handler: def __init__(self, client: SMTPClient) -> None: self._client: SMTPClient = client - async def handle_DATA(self, server: Server, session: Session, envelope: Envelope) -> str: + async def handle_DATA( + self, server: Server, session: Session, envelope: Envelope + ) -> str: try: - await self._client.forward_message(self._prepare_message(server, envelope), - sender=envelope.mail_from, - recipients=envelope.rcpt_tos) + await self._client.forward_message( + self._prepare_message(server, envelope), + sender=envelope.mail_from, + recipients=envelope.rcpt_tos, + ) except (RuntimeError, ValueError) as e: print(f"Cannot forward: {str(e)}", file=sys.stderr) return f"550 {e.__cause__.__class__.__name__ if e.__cause__ is not None else e.__class__.__name__}" @@ -161,7 +189,9 @@ class Handler: host: str = server.hostname # getfqdn now: str = time.strftime("%a, %d %b %Y %H:%M:%S %z") - message["Received"] = f"from {peer[0]} ([{peer[0]}]) by {host} ([{sock[0]}]); {now}" + message["Received"] = ( + f"from {peer[0]} ([{peer[0]}]) by {host} ([{sock[0]}]); {now}" + ) message["X-Peer"] = f"[{peer[0]}]:{peer[1]}" if "Date" not in message: message["Date"] = now @@ -173,7 +203,12 @@ class Handler: class SMTPServer(Controller): def __init__(self, config: ServerConfig, handler: Handler) -> None: - super().__init__(handler=handler, hostname=config.hostname, port=config.port, ready_timeout=10.0) + super().__init__( + handler=handler, + hostname=config.hostname, + port=config.port, + ready_timeout=10.0, + ) def run(self) -> bool: shutdown_requested: threading.Event = threading.Event() @@ -193,10 +228,17 @@ class SMTPServer(Controller): def main() -> int: - parser = argparse.ArgumentParser(description=__doc__.strip(), - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument("--config", metavar="CONFIG.INI", type=str, default="./config.ini", - help="configuration file") + parser = argparse.ArgumentParser( + description=__doc__.strip(), + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( + "--config", + metavar="CONFIG.INI", + type=str, + default="./config.ini", + help="configuration file", + ) args = parser.parse_args() setlocale(LC_ALL, "C") # for strftime @@ -206,9 +248,11 @@ def main() -> int: print(str(e), file=sys.stderr) return 1 else: - controller = SMTPServer(config=config.server, handler=Handler(SMTPClient(config.client))) + controller = SMTPServer( + config=config.server, handler=Handler(SMTPClient(config.client)) + ) return 0 if controller.run() else 1 if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file + sys.exit(main())