diff --git a/mail4one/config.py b/mail4one/config.py index 7dbc34f..47d3f0c 100644 --- a/mail4one/config.py +++ b/mail4one/config.py @@ -33,6 +33,7 @@ class Config(Jata): smtp_port_tls = 465 smtp_port_submission = 587 pop_port = 995 + pop_timeout_seconds = 60 smtputf8 = True rules: list[Rule] boxes: list[Mbox] diff --git a/mail4one/pop3.py b/mail4one/pop3.py index 165b4cf..20a5092 100644 --- a/mail4one/pop3.py +++ b/mail4one/pop3.py @@ -8,44 +8,13 @@ from hashlib import sha256 from pathlib import Path from typing import ClassVar, List, Set from .config import User -from .pwhash import parse_hash, check_pass +from .pwhash import parse_hash, check_pass, PWInfo from asyncio import StreamReader, StreamWriter from .poputils import InvalidCommand, parse_command, err, Command, ClientQuit, ClientError, AuthError, ok, msg, end, \ Request, MailEntry, get_mail, get_mails_list, MailList -def add_season(content: bytes, season: bytes): - return sha256(season + content).digest() - - -# noinspection PyProtectedMember -@dataclass -class Session: - _reader: StreamReader - _writer: asyncio.StreamWriter - username: str - mbox: str - - # common state - all_sessions: ClassVar[Set] = set() - mails_path: ClassVar[Path] = Path("") - users: ClassVar[list[User]] = list() - current_session: ClassVar = ContextVar("session") - - @classmethod - def get(cls): - return cls.current_session.get() - - @classmethod - def reader(cls): - return cls.get()._reader - - @classmethod - def writer(cls): - return cls.get()._writer - - async def next_req(): for _ in range(InvalidCommand.RETRIES): line = await state().reader.readline() @@ -254,12 +223,13 @@ async def start_session(): assert username is not None config().loggedin_users.add(username) _, mbox = config().users[username] - deleted_items_path = config().mails_path/ mbox / username + deleted_items_path = config().mails_path / mbox / username logging.info(f"User:{username} logged in successfully") existing_deleted_items: Set = get_deleted_items(deleted_items_path) - new_deleted_items: Set = await transaction_stage(existing_deleted_items) + new_deleted_items: Set = await transaction_stage(existing_deleted_items + ) logging.info( f"{username=} completed transactions. Deleted:{len(new_deleted_items)}" ) @@ -300,11 +270,12 @@ class State: mbox: str = "" -@dataclass class Config: - mails_path: Path - users: dict[str, tuple[pwhash.PWInfo, str]] - loggedin_users: set[str] = set() + + def __init__(self, mails_path: Path, users: dict[str, tuple[PWInfo, str]]): + self.mails_path = mails_path + self.users = users + self.loggedin_users: set[str] = set() c_config = contextvars.ContextVar('config') @@ -336,11 +307,11 @@ def make_pop_server_callback(dirpath: Path, users: list[User], return session_cb -async def create_pop_server(dirpath: Path, +async def create_pop_server(host: str, port: int, + mails_path: Path, users: list[User], - host="", - context: ssl.SSLContext = None, + ssl_context: ssl.SSLContext = None, timeout_seconds: int = 60): logging.info( f"Starting POP3 server {dirpath=}, {host=}, {port=}, {timeout_seconds=}, ssl={context != None}" diff --git a/mail4one/server.py b/mail4one/server.py index 4dbe5a7..ac3c1ef 100644 --- a/mail4one/server.py +++ b/mail4one/server.py @@ -1,8 +1,4 @@ import asyncio -# Though we don't use requests, without the below import, we crash https://stackoverflow.com/a/13057751 -# When running on privilege port after dropping privileges. -# noinspection PyUnresolvedReferences -import encodings.idna import logging import os import ssl @@ -22,27 +18,6 @@ def create_tls_context(certfile, keyfile): return context -def parse_args(): - parser = ArgumentParser() - parser.add_argument('--certfile') - parser.add_argument('--keyfile') - parser.add_argument('--password_hash') - parser.add_argument("mail_dir_path") - - args = parser.parse_args() - args.mail_dir_path = Path(args.mail_dir_path) - - # Hardcoded args - args.host = '0.0.0.0' - args.smtp_port = 25 - args.smtp_port_tls = 465 - args.smtp_port_submission = 587 - args.pop_port = 995 - args.smtputf8 = True - args.debug = True - return args - - def setup_logging(args): if args.debug: logging.basicConfig(level=logging.DEBUG) @@ -51,11 +26,13 @@ def setup_logging(args): async def a_main(config, tls_context): - pop_server = await create_pop_server(config.mails_path, - port=config.pop_port, - host=config.host, - context=tls_context, - users=config.users) + pop_server = await create_pop_server( + host=config.host, + port=config.pop_port, + mails_path=config.mails_path, + users=config.users, + ssl_context=tls_context, + timeout_seconds=config.pop_timeout_seconds) smtp_server_starttls = await create_smtp_server_starttls( config.mail_dir_path, @@ -74,7 +51,6 @@ async def a_main(config, tls_context): def main(): - config_path = sys.argv[1] parser = ArgumentParser() parser.add_argument("config_path") args = parser.parse_args() diff --git a/mail4one/smtp.py b/mail4one/smtp.py index c2acb8b..c05986f 100644 --- a/mail4one/smtp.py +++ b/mail4one/smtp.py @@ -23,6 +23,7 @@ class MaildirCRLF(mailbox.Maildir): class MailboxCRLF(Mailbox): + def __init__(self, mail_dir: Path): super().__init__(mail_dir) for sub in ('new', 'tmp', 'cur'): @@ -50,23 +51,37 @@ def protocol_factory(dirpath: Path): logging.info("Got smtp client cb") try: handler = MailboxCRLF(dirpath) - smtp = SMTP(handler=handler, data_size_limit=DATA_SIZE_DEFAULT, enable_SMTPUTF8=True) + smtp = SMTP(handler=handler, + data_size_limit=DATA_SIZE_DEFAULT, + enable_SMTPUTF8=True) except Exception as e: logging.error("Something went wrong", e) raise return smtp -async def create_smtp_server_starttls(dirpath: Path, port: int, host="", context: ssl.SSLContext = None): +async def create_smtp_server_starttls(dirpath: Path, + port: int, + host="", + context: ssl.SSLContext = None): loop = asyncio.get_event_loop() - return await loop.create_server(partial(protocol_factory_starttls, dirpath, context), - host=host, port=port, start_serving=False) + return await loop.create_server(partial(protocol_factory_starttls, dirpath, + context), + host=host, + port=port, + start_serving=False) -async def create_smtp_server_tls(dirpath: Path, port: int, host="", context: ssl.SSLContext = None): +async def create_smtp_server_tls(dirpath: Path, + port: int, + host="", + context: ssl.SSLContext = None): loop = asyncio.get_event_loop() return await loop.create_server(partial(protocol_factory, dirpath), - host=host, port=port, ssl=context, start_serving=False) + host=host, + port=port, + ssl=context, + start_serving=False) async def a_main(*args, **kwargs):