remove Controller
This commit is contained in:
parent
dd321ab8b7
commit
bcfaeb1569
@ -82,7 +82,6 @@ async def auth_stage():
|
|||||||
|
|
||||||
|
|
||||||
MAILS_PATH = ""
|
MAILS_PATH = ""
|
||||||
WAIT_FOR_PRIVILEGES_TO_DROP = None
|
|
||||||
|
|
||||||
|
|
||||||
async def transaction_stage(user: User):
|
async def transaction_stage(user: User):
|
||||||
@ -138,9 +137,6 @@ def delete_messages(delete_ids):
|
|||||||
|
|
||||||
|
|
||||||
async def new_session(stream_reader: asyncio.StreamReader, stream_writer: asyncio.StreamWriter):
|
async def new_session(stream_reader: asyncio.StreamReader, stream_writer: asyncio.StreamWriter):
|
||||||
if WAIT_FOR_PRIVILEGES_TO_DROP:
|
|
||||||
logging.warning("Waiting for privileges to drop")
|
|
||||||
await WAIT_FOR_PRIVILEGES_TO_DROP
|
|
||||||
reader.set(stream_reader)
|
reader.set(stream_reader)
|
||||||
writer.set(stream_writer)
|
writer.set(stream_writer)
|
||||||
logging.info(f"New session started with {stream_reader} and {stream_writer}")
|
logging.info(f"New session started with {stream_reader} and {stream_writer}")
|
||||||
@ -153,23 +149,25 @@ async def new_session(stream_reader: asyncio.StreamReader, stream_writer: asynci
|
|||||||
except ClientError as c:
|
except ClientError as c:
|
||||||
write(err("Something went wrong"))
|
write(err("Something went wrong"))
|
||||||
logging.error(f"Unexpected client error", c)
|
logging.error(f"Unexpected client error", c)
|
||||||
except:
|
except Exception as e:
|
||||||
logging.error(f"Serious client error")
|
logging.error(f"Serious client error", e)
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
stream_writer.close()
|
stream_writer.close()
|
||||||
|
|
||||||
|
|
||||||
async def a_main(dirpath: Path, port: int, host="", context: ssl.SSLContext = None, waiter=None):
|
async def create_pop_server(dirpath: Path, port: int, host="", context: ssl.SSLContext = None):
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Starting POP3 server Maildir={dirpath}, host={host}, port={port}, context={context}, waiter={waiter}")
|
f"Starting POP3 server Maildir={dirpath}, host={host}, port={port}, context={context}")
|
||||||
global MAILS_PATH, WAIT_FOR_PRIVILEGES_TO_DROP
|
global MAILS_PATH
|
||||||
MAILS_PATH = dirpath / 'new'
|
MAILS_PATH = dirpath / 'new'
|
||||||
WAIT_FOR_PRIVILEGES_TO_DROP = waiter
|
return await asyncio.start_server(new_session, host=host, port=port, ssl=context)
|
||||||
server = await asyncio.start_server(new_session, host=host, port=port, ssl=context)
|
|
||||||
|
|
||||||
|
async def a_main(*args, **kwargs):
|
||||||
|
server = await create_pop_server(*args, **kwargs)
|
||||||
await server.serve_forever()
|
await server.serve_forever()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# noinspection PyTypeChecker
|
|
||||||
asyncio.run(a_main(Path("/tmp/mails"), 9995))
|
asyncio.run(a_main(Path("/tmp/mails"), 9995))
|
||||||
|
@ -3,22 +3,15 @@ import asyncio
|
|||||||
# When running on privilege port after dropping privileges.
|
# When running on privilege port after dropping privileges.
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
import encodings.idna
|
import encodings.idna
|
||||||
import io
|
|
||||||
import logging
|
import logging
|
||||||
import mailbox
|
|
||||||
import os
|
import os
|
||||||
import ssl
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from functools import partial
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from aiosmtpd.controller import Controller
|
from .smtp import create_smtp_server
|
||||||
from aiosmtpd.handlers import Mailbox
|
from .pop3 import create_pop_server
|
||||||
from aiosmtpd.main import DATA_SIZE_DEFAULT
|
|
||||||
from aiosmtpd.smtp import SMTP
|
|
||||||
|
|
||||||
from .pop3 import a_main as pop3_main
|
|
||||||
|
|
||||||
|
|
||||||
def create_tls_context(certfile, keyfile):
|
def create_tls_context(certfile, keyfile):
|
||||||
@ -27,49 +20,6 @@ def create_tls_context(certfile, keyfile):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class STARTTLSController(Controller):
|
|
||||||
def __init__(self, *args, tls_context, smtp_args=None, **kwargs):
|
|
||||||
self.tls_context = tls_context
|
|
||||||
self.smtp_args = smtp_args or {}
|
|
||||||
self.has_privileges_dropped: asyncio.Future = None
|
|
||||||
if 'ssl_context' in kwargs:
|
|
||||||
raise Exception("ssl_context not allowed when using STARTTLS, set tls_context instead")
|
|
||||||
Controller.__init__(self, *args, **kwargs)
|
|
||||||
|
|
||||||
async def create_future(self):
|
|
||||||
self.has_privileges_dropped = asyncio.get_event_loop().create_future()
|
|
||||||
|
|
||||||
async def wait_for_privileges_to_drop(self):
|
|
||||||
await self.has_privileges_dropped
|
|
||||||
|
|
||||||
def factory(self):
|
|
||||||
if not self.has_privileges_dropped.done():
|
|
||||||
# Ideally we should await here. But this is callback and not a coroutine
|
|
||||||
raise Exception("Client connected too fast before we could drop root privileges")
|
|
||||||
return SMTP(self.handler, require_starttls=True, tls_context=self.tls_context, **self.smtp_args)
|
|
||||||
|
|
||||||
|
|
||||||
class MaildirCRLF(mailbox.Maildir):
|
|
||||||
_append_newline = True
|
|
||||||
|
|
||||||
def _dump_message(self, message, target, mangle_from_=False):
|
|
||||||
temp_buffer = io.BytesIO()
|
|
||||||
super()._dump_message(message, temp_buffer, mangle_from_=mangle_from_)
|
|
||||||
temp_buffer.seek(0)
|
|
||||||
data = temp_buffer.read()
|
|
||||||
data = data.replace(b'\n', b'\r\n')
|
|
||||||
target.write(data)
|
|
||||||
|
|
||||||
|
|
||||||
class MailboxCRLF(Mailbox):
|
|
||||||
def __init__(self, mail_dir: Path):
|
|
||||||
super().__init__(mail_dir)
|
|
||||||
for sub in ('new', 'tmp', 'cur'):
|
|
||||||
sub_path = mail_dir / sub
|
|
||||||
sub_path.mkdir(mode=0o755, exist_ok=True, parents=True)
|
|
||||||
self.mailbox = MaildirCRLF(mail_dir)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
parser = ArgumentParser()
|
parser = ArgumentParser()
|
||||||
parser.add_argument('--certfile')
|
parser.add_argument('--certfile')
|
||||||
@ -83,8 +33,6 @@ def parse_args():
|
|||||||
args.host = '0.0.0.0'
|
args.host = '0.0.0.0'
|
||||||
args.smtp_port = 25
|
args.smtp_port = 25
|
||||||
args.pop_port = 995
|
args.pop_port = 995
|
||||||
args.size = DATA_SIZE_DEFAULT
|
|
||||||
args.classpath = MailboxCRLF
|
|
||||||
args.smtputf8 = True
|
args.smtputf8 = True
|
||||||
args.debug = True
|
args.debug = True
|
||||||
return args
|
return args
|
||||||
@ -97,7 +45,7 @@ def setup_logging(args):
|
|||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
def drop_privileges(future_cb):
|
def drop_privileges():
|
||||||
try:
|
try:
|
||||||
import pwd
|
import pwd
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -111,32 +59,24 @@ def drop_privileges(future_cb):
|
|||||||
logging.error("Cannot setuid nobody; run as root")
|
logging.error("Cannot setuid nobody; run as root")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
logging.info("Dropped privileges")
|
logging.info("Dropped privileges")
|
||||||
future_cb().set_result("Go!")
|
|
||||||
logging.debug("Signalled! Clients can come in")
|
logging.debug("Signalled! Clients can come in")
|
||||||
|
|
||||||
|
|
||||||
|
async def a_main(args, tls_context):
|
||||||
|
# pop_server = await create_pop_server(args.mail_dir_path, port=args.pop_port, host=args.host, context=tls_context)
|
||||||
|
smtp_server = await create_smtp_server(args.mail_dir_path, port=args.smtp_port, host=args.host, context=tls_context)
|
||||||
|
drop_privileges()
|
||||||
|
# await pop_server.start_serving()
|
||||||
|
await smtp_server.serve_forever()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
tls_context = create_tls_context(args.certfile, args.keyfile)
|
tls_context = create_tls_context(args.certfile, args.keyfile)
|
||||||
smtp_args = dict(data_size_limit=args.size, enable_SMTPUTF8=args.smtputf8)
|
|
||||||
setup_logging(args)
|
setup_logging(args)
|
||||||
handler = args.classpath(args.mail_dir_path)
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
loop.set_debug(args.debug)
|
loop.set_debug(args.debug)
|
||||||
controller = STARTTLSController(
|
asyncio.run(a_main(args, tls_context))
|
||||||
handler, tls_context=tls_context, smtp_args=smtp_args, hostname=args.host, port=args.smtp_port, loop=loop)
|
|
||||||
|
|
||||||
loop.create_task(controller.create_future())
|
|
||||||
loop.create_task(pop3_main(args.mail_dir_path, args.pop_port,
|
|
||||||
host=args.host, context=tls_context, waiter=controller.wait_for_privileges_to_drop()))
|
|
||||||
|
|
||||||
controller.start()
|
|
||||||
loop.call_soon_threadsafe(partial(drop_privileges, lambda: controller.has_privileges_dropped))
|
|
||||||
logging.info("Server started. Press [ENTER] to stop")
|
|
||||||
input()
|
|
||||||
controller.stop()
|
|
||||||
# loop.create_task(a_main(controller))
|
|
||||||
# loop.run_forever()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
62
mail4one/smtp.py
Normal file
62
mail4one/smtp.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import asyncio
|
||||||
|
import io
|
||||||
|
import logging
|
||||||
|
import mailbox
|
||||||
|
import ssl
|
||||||
|
from functools import partial
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from aiosmtpd.handlers import Mailbox
|
||||||
|
from aiosmtpd.smtp import SMTP, DATA_SIZE_DEFAULT
|
||||||
|
|
||||||
|
|
||||||
|
class MaildirCRLF(mailbox.Maildir):
|
||||||
|
_append_newline = True
|
||||||
|
|
||||||
|
def _dump_message(self, message, target, mangle_from_=False):
|
||||||
|
temp_buffer = io.BytesIO()
|
||||||
|
super()._dump_message(message, temp_buffer, mangle_from_=mangle_from_)
|
||||||
|
temp_buffer.seek(0)
|
||||||
|
data = temp_buffer.read()
|
||||||
|
data = data.replace(b'\n', b'\r\n')
|
||||||
|
target.write(data)
|
||||||
|
|
||||||
|
|
||||||
|
class MailboxCRLF(Mailbox):
|
||||||
|
def __init__(self, mail_dir: Path):
|
||||||
|
super().__init__(mail_dir)
|
||||||
|
for sub in ('new', 'tmp', 'cur'):
|
||||||
|
sub_path = mail_dir / sub
|
||||||
|
sub_path.mkdir(mode=0o755, exist_ok=True, parents=True)
|
||||||
|
self.mailbox = MaildirCRLF(mail_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def protocol_factory(dirpath: Path, context: ssl.SSLContext = None):
|
||||||
|
logging.info("Got smtp client cb")
|
||||||
|
try:
|
||||||
|
handler = MailboxCRLF(dirpath)
|
||||||
|
smtp = SMTP(handler=handler,
|
||||||
|
require_starttls=True,
|
||||||
|
tls_context=context,
|
||||||
|
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(dirpath: Path, port: int, host="", context: ssl.SSLContext = None):
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
return await loop.create_server(partial(protocol_factory, dirpath, context),
|
||||||
|
host=host, port=port, start_serving=False)
|
||||||
|
|
||||||
|
|
||||||
|
async def a_main(*args, **kwargs):
|
||||||
|
server = await create_smtp_server(*args, **kwargs)
|
||||||
|
await server.serve_forever()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
asyncio.run(a_main(Path("/tmp/mails"), 9995))
|
Loading…
Reference in New Issue
Block a user