Compare commits
7 Commits
v1.1
...
64dcc74b8d
Author | SHA1 | Date | |
---|---|---|---|
64dcc74b8d | |||
1d644a9bcc | |||
e853cfe62d | |||
cb15ec15e4 | |||
b8858085ab | |||
2c6d98ce2d | |||
520dfd7b14 |
26
Makefile
26
Makefile
@ -1,10 +1,10 @@
|
||||
# Needs python3 >= 3.9, sed, git for build, docker for tests
|
||||
build: clean
|
||||
# Needs python3 >= 3.9, sed, git for build
|
||||
mail4one.pyz: requirements.txt mail4one/*py
|
||||
python3 -m pip install -r requirements.txt --no-compile --target build
|
||||
cp -r mail4one/ build/
|
||||
sed -i "s/DEVELOMENT/$(shell scripts/get_version.sh)/" build/mail4one/version.py
|
||||
find build -name "*.pyi" -o -name "py.typed" | xargs -I typefile rm typefile
|
||||
rm -rf build/bin
|
||||
rm -rf build/bin build/aiosmtpd/{docs,tests,qa}
|
||||
rm -rf build/mail4one/__pycache__
|
||||
rm -rf build/*.dist-info
|
||||
python3 -m zipapp \
|
||||
@ -13,10 +13,19 @@ build: clean
|
||||
--main mail4one.server:main \
|
||||
--compress build
|
||||
|
||||
.PHONY: build
|
||||
build: clean mail4one.pyz
|
||||
|
||||
.PHONY: test
|
||||
test: mail4one.pyz
|
||||
PYTHONPATH=mail4one.pyz python3 -m unittest discover
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf build
|
||||
rm -rf mail4one.pyz
|
||||
|
||||
.PHONY: docker-tests
|
||||
docker-tests:
|
||||
docker run --pull=always -v `pwd`:/app -w /app --rm python:3.11-alpine sh scripts/runtests.sh
|
||||
docker run --pull=always -v `pwd`:/app -w /app --rm python:3.10-alpine sh scripts/runtests.sh
|
||||
@ -31,24 +40,31 @@ docker-tests:
|
||||
requirements.txt: Pipfile.lock
|
||||
pipenv requirements > requirements.txt
|
||||
|
||||
.PHONY: format
|
||||
format:
|
||||
black mail4one/*py tests/*py
|
||||
|
||||
.PHONY: build-dev
|
||||
build-dev: requirements.txt build
|
||||
|
||||
.PHONY: setup
|
||||
setup:
|
||||
pipenv install
|
||||
|
||||
.PHONY: cleanup
|
||||
cleanup:
|
||||
pipenv --rm
|
||||
|
||||
.PHONY: update
|
||||
update:
|
||||
rm requirements.txt Pipfile.lock
|
||||
pipenv update
|
||||
pipenv requirements > requirements.txt
|
||||
|
||||
.PHONY: shell
|
||||
shell:
|
||||
MYPYPATH=`pipenv --venv`/lib/python3.11/site-packages pipenv shell
|
||||
MYPYPATH=$(shell ls -d `pipenv --venv`/lib/python3*/site-packages) pipenv shell
|
||||
|
||||
test:
|
||||
.PHONY: dev-test
|
||||
dev-test:
|
||||
pipenv run python -m unittest discover
|
||||
|
@ -1,4 +1,5 @@
|
||||
import json
|
||||
"""Module for parsing mail4one config.json"""
|
||||
|
||||
import re
|
||||
import logging
|
||||
from typing import Callable, Union, Optional
|
||||
@ -56,13 +57,14 @@ class PopCfg(ServerCfg):
|
||||
|
||||
class SmtpStartTLSCfg(ServerCfg):
|
||||
server_type = "smtp_starttls"
|
||||
smtputf8 = True # Not used yet
|
||||
require_starttls = True
|
||||
smtputf8 = True
|
||||
port = 25
|
||||
|
||||
|
||||
class SmtpCfg(ServerCfg):
|
||||
server_type = "smtp_starttls"
|
||||
smtputf8 = True # Not used yet
|
||||
server_type = "smtp"
|
||||
smtputf8 = True
|
||||
port = 465
|
||||
|
||||
|
||||
|
@ -2,18 +2,15 @@ import asyncio
|
||||
import contextlib
|
||||
import contextvars
|
||||
import logging
|
||||
import os
|
||||
import ssl
|
||||
import uuid
|
||||
import random
|
||||
from typing import Optional
|
||||
from asyncio import StreamReader, StreamWriter
|
||||
from dataclasses import dataclass
|
||||
from hashlib import sha256
|
||||
from pathlib import Path
|
||||
from .config import User
|
||||
from .pwhash import parse_hash, check_pass, PWInfo
|
||||
from asyncio import StreamReader, StreamWriter
|
||||
import random
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from .poputils import (
|
||||
InvalidCommand,
|
||||
@ -316,12 +313,10 @@ async def start_session() -> None:
|
||||
assert state().mbox
|
||||
await transaction_stage()
|
||||
logger.info(f"User:{state().username} done")
|
||||
except ClientDisconnected as c:
|
||||
except ClientDisconnected:
|
||||
logger.info("Client disconnected")
|
||||
pass
|
||||
except ClientQuit:
|
||||
logger.info("Client QUIT")
|
||||
pass
|
||||
except ClientError as c:
|
||||
write(err("Something went wrong"))
|
||||
logger.error(f"Unexpected client error: {c}")
|
||||
|
@ -1,11 +1,10 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import ssl
|
||||
import sys
|
||||
from argparse import ArgumentParser
|
||||
from pathlib import Path
|
||||
from getpass import getpass
|
||||
from typing import Optional, Union
|
||||
|
||||
from .smtp import create_smtp_server_starttls, create_smtp_server
|
||||
from .pop3 import create_pop_server
|
||||
@ -13,7 +12,6 @@ from .version import VERSION
|
||||
|
||||
from . import config
|
||||
from . import pwhash
|
||||
from typing import Optional, Union
|
||||
|
||||
|
||||
def create_tls_context(certfile, keyfile) -> ssl.SSLContext:
|
||||
@ -86,6 +84,8 @@ async def a_main(cfg: config.Config) -> None:
|
||||
mails_path=Path(cfg.mails_path),
|
||||
mbox_finder=mbox_finder,
|
||||
ssl_context=stls_context,
|
||||
require_starttls=stls.require_starttls,
|
||||
smtputf8=stls.smtputf8,
|
||||
)
|
||||
servers.append(smtp_server_starttls)
|
||||
elif scfg.server_type == "smtp":
|
||||
@ -96,6 +96,7 @@ async def a_main(cfg: config.Config) -> None:
|
||||
mails_path=Path(cfg.mails_path),
|
||||
mbox_finder=mbox_finder,
|
||||
ssl_context=get_tls_context(smtp.tls),
|
||||
smtputf8=smtp.smtputf8,
|
||||
)
|
||||
servers.append(smtp_server)
|
||||
else:
|
||||
|
@ -1,23 +1,18 @@
|
||||
import asyncio
|
||||
import io
|
||||
import logging
|
||||
import mailbox
|
||||
import ssl
|
||||
import uuid
|
||||
import shutil
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from typing import Callable, Optional
|
||||
from . import config
|
||||
from email.message import Message
|
||||
import email.policy
|
||||
from email.generator import BytesGenerator
|
||||
import tempfile
|
||||
import random
|
||||
|
||||
from aiosmtpd.handlers import Mailbox, AsyncMessage
|
||||
from aiosmtpd.smtp import SMTP, DATA_SIZE_DEFAULT
|
||||
from aiosmtpd.smtp import SMTP as SMTPServer
|
||||
from aiosmtpd.handlers import AsyncMessage
|
||||
from aiosmtpd.smtp import SMTP
|
||||
from aiosmtpd.smtp import Envelope as SMTPEnvelope
|
||||
from aiosmtpd.smtp import Session as SMTPSession
|
||||
|
||||
@ -31,7 +26,7 @@ class MyHandler(AsyncMessage):
|
||||
self.mbox_finder = mbox_finder
|
||||
|
||||
async def handle_DATA(
|
||||
self, server: SMTPServer, session: SMTPSession, envelope: SMTPEnvelope
|
||||
self, server: SMTP, session: SMTPSession, envelope: SMTPEnvelope
|
||||
) -> str:
|
||||
self.rcpt_tos = envelope.rcpt_tos
|
||||
self.peer = session.peer
|
||||
@ -63,16 +58,20 @@ class MyHandler(AsyncMessage):
|
||||
|
||||
|
||||
def protocol_factory_starttls(
|
||||
mails_path: Path, mbox_finder: Callable[[str], list[str]], context: ssl.SSLContext
|
||||
mails_path: Path,
|
||||
mbox_finder: Callable[[str], list[str]],
|
||||
context: ssl.SSLContext,
|
||||
require_starttls: bool,
|
||||
smtputf8: bool,
|
||||
):
|
||||
logger.info("Got smtp client cb starttls")
|
||||
try:
|
||||
handler = MyHandler(mails_path, mbox_finder)
|
||||
smtp = SMTP(
|
||||
handler=handler,
|
||||
require_starttls=True,
|
||||
require_starttls=require_starttls,
|
||||
tls_context=context,
|
||||
enable_SMTPUTF8=True,
|
||||
enable_SMTPUTF8=smtputf8,
|
||||
)
|
||||
except:
|
||||
logger.exception("Something went wrong")
|
||||
@ -80,11 +79,13 @@ def protocol_factory_starttls(
|
||||
return smtp
|
||||
|
||||
|
||||
def protocol_factory(mails_path: Path, mbox_finder: Callable[[str], list[str]]):
|
||||
def protocol_factory(
|
||||
mails_path: Path, mbox_finder: Callable[[str], list[str]], smtputf8: bool
|
||||
):
|
||||
logger.info("Got smtp client cb")
|
||||
try:
|
||||
handler = MyHandler(mails_path, mbox_finder)
|
||||
smtp = SMTP(handler=handler, enable_SMTPUTF8=True)
|
||||
smtp = SMTP(handler=handler, enable_SMTPUTF8=smtputf8)
|
||||
except:
|
||||
logger.exception("Something went wrong")
|
||||
raise
|
||||
@ -97,13 +98,22 @@ async def create_smtp_server_starttls(
|
||||
mails_path: Path,
|
||||
mbox_finder: Callable[[str], list[str]],
|
||||
ssl_context: ssl.SSLContext,
|
||||
require_starttls: bool,
|
||||
smtputf8: bool,
|
||||
) -> asyncio.Server:
|
||||
logging.info(
|
||||
f"Starting SMTP STARTTLS server {host=}, {port=}, {mails_path=!s}, {ssl_context != None=}"
|
||||
)
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.create_server(
|
||||
partial(protocol_factory_starttls, mails_path, mbox_finder, ssl_context),
|
||||
partial(
|
||||
protocol_factory_starttls,
|
||||
mails_path,
|
||||
mbox_finder,
|
||||
ssl_context,
|
||||
require_starttls,
|
||||
smtputf8,
|
||||
),
|
||||
host=host,
|
||||
port=port,
|
||||
start_serving=False,
|
||||
@ -115,14 +125,15 @@ async def create_smtp_server(
|
||||
port: int,
|
||||
mails_path: Path,
|
||||
mbox_finder: Callable[[str], list[str]],
|
||||
ssl_context: Optional[ssl.SSLContext] = None,
|
||||
ssl_context: Optional[ssl.SSLContext],
|
||||
smtputf8: bool,
|
||||
) -> asyncio.Server:
|
||||
logging.info(
|
||||
f"Starting SMTP server {host=}, {port=}, {mails_path=!s}, {ssl_context != None=}"
|
||||
)
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.create_server(
|
||||
partial(protocol_factory, mails_path, mbox_finder),
|
||||
partial(protocol_factory, mails_path, mbox_finder, smtputf8),
|
||||
host=host,
|
||||
port=port,
|
||||
ssl=ssl_context,
|
||||
|
@ -89,6 +89,9 @@ class TestPop3(unittest.IsolatedAsyncioTestCase):
|
||||
self.task = asyncio.create_task(pop_server.serve_forever())
|
||||
self.reader, self.writer = await asyncio.open_connection("127.0.0.1", 7995)
|
||||
|
||||
# Additional writers to close
|
||||
self.ws: list[asyncio.StreamWriter] = []
|
||||
|
||||
async def test_QUIT(self) -> None:
|
||||
dialog = """
|
||||
S: +OK Server Ready
|
||||
@ -133,6 +136,7 @@ class TestPop3(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_dupe_AUTH(self) -> None:
|
||||
r1, w1 = await asyncio.open_connection("127.0.0.1", 7995)
|
||||
r2, w2 = await asyncio.open_connection("127.0.0.1", 7995)
|
||||
self.ws += w1, w2
|
||||
dialog = """
|
||||
S: +OK Server Ready
|
||||
C: USER foobar
|
||||
@ -231,8 +235,10 @@ class TestPop3(unittest.IsolatedAsyncioTestCase):
|
||||
|
||||
async def asyncTearDown(self) -> None:
|
||||
logging.debug("at teardown")
|
||||
self.writer.close()
|
||||
await self.writer.wait_closed()
|
||||
for w in self.ws + [self.writer]:
|
||||
w.close()
|
||||
await w.wait_closed()
|
||||
self.ws.clear()
|
||||
self.task.cancel("test done")
|
||||
|
||||
async def dialog_checker(self, dialog: str) -> None:
|
||||
|
@ -33,6 +33,8 @@ class TestSMTP(unittest.IsolatedAsyncioTestCase):
|
||||
port=7996,
|
||||
mails_path=MAILS_PATH,
|
||||
mbox_finder=lambda addr: [TEST_MBOX],
|
||||
ssl_context=None,
|
||||
smtputf8=True,
|
||||
)
|
||||
self.task = asyncio.create_task(smtp_server.serve_forever())
|
||||
|
||||
|
Reference in New Issue
Block a user