Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
95423ebf63 | |||
2bcc807b91 | |||
bcd250d2b0 | |||
9107474d31 | |||
2bf809c454 | |||
1e6655a715 |
7
Makefile
7
Makefile
@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
shell:
|
||||
MYPYPATH=`pipenv --venv`/lib/python3.11/site-packages pipenv shell
|
||||
|
||||
@ -16,9 +14,10 @@ requirements.txt: Pipfile.lock
|
||||
pipenv requirements > requirements.txt
|
||||
|
||||
build: clean requirements.txt
|
||||
python3 -m pip install -r requirements.txt --target build
|
||||
python3 -m pip install -r requirements.txt --no-compile --target build
|
||||
cp -r mail4one/ build/
|
||||
python3 -m compileall build/mail4one -f
|
||||
sed -i "s/DEVELOMENT/$(shell scripts/get_version.sh)/" build/mail4one/version.py
|
||||
rm -rf build/mail4one/__pycache__
|
||||
rm -rf build/*.dist-info
|
||||
python3 -m zipapp \
|
||||
--output mail4one.pyz \
|
||||
|
@ -9,14 +9,19 @@ Requires=network-online.target
|
||||
[Service]
|
||||
User=mail4one
|
||||
ExecStart=/usr/local/bin/mail4one --config /etc/mail4one/config.json
|
||||
PrivateTmp=true
|
||||
ProtectSystem=full
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
|
||||
StateDirectory=mail4one
|
||||
StateDirectory=mail4one/certs mail4one/mails
|
||||
StateDirectoryMode=0750
|
||||
UMask=
|
||||
LogsDirectory=mail4one
|
||||
WorkingDirectory=/var/lib/mail4one
|
||||
|
||||
ProtectSystem=strict
|
||||
PrivateTmp=true
|
||||
PrivateUsers=true
|
||||
ProtectHome=yes
|
||||
NoNewPrivileges=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
@ -10,7 +10,7 @@ set -x
|
||||
if [ "$RENEWED_DOMAINS" = "mail.mydomain.com" ]
|
||||
then
|
||||
mkdir -p /var/lib/mail4one/certs
|
||||
chmod 500 /var/lib/mail4one/certs
|
||||
chmod 750 /var/lib/mail4one/certs
|
||||
chown mail4one:mail4one /var/lib/mail4one/certs
|
||||
cp "$RENEWED_LINEAGE/fullchain.pem" /var/lib/mail4one/certs/
|
||||
cp "$RENEWED_LINEAGE/privkey.pem" /var/lib/mail4one/certs/
|
||||
|
@ -41,6 +41,7 @@ class TLSCfg(Jata):
|
||||
|
||||
|
||||
class ServerCfg(Jata):
|
||||
server_type: str
|
||||
host: str = "default"
|
||||
port: int
|
||||
# disabled: bool = False
|
||||
@ -48,22 +49,25 @@ class ServerCfg(Jata):
|
||||
|
||||
|
||||
class PopCfg(ServerCfg):
|
||||
server_type = "pop"
|
||||
port = 995
|
||||
timeout_seconds = 60
|
||||
|
||||
|
||||
class SmtpStartTLSCfg(ServerCfg):
|
||||
smtputf8 = True
|
||||
server_type = "smtp_starttls"
|
||||
smtputf8 = True # Not used yet
|
||||
port = 25
|
||||
|
||||
|
||||
class SmtpCfg(ServerCfg):
|
||||
smtputf8 = True
|
||||
server_type = "smtp_starttls"
|
||||
smtputf8 = True # Not used yet
|
||||
port = 465
|
||||
|
||||
|
||||
class LogCfg(Jata):
|
||||
logfile = "STDOUT"
|
||||
logfile = "CONSOLE"
|
||||
level = "INFO"
|
||||
|
||||
|
||||
@ -77,10 +81,7 @@ class Config(Jata):
|
||||
boxes: list[Mbox]
|
||||
users: list[User]
|
||||
|
||||
pop: PopCfg | None = None
|
||||
smtp_starttls: SmtpStartTLSCfg | None = None
|
||||
smtp: SmtpCfg | None = None
|
||||
# smtp_port_submission = 587
|
||||
servers: list[ServerCfg]
|
||||
|
||||
|
||||
CheckerFn = Callable[[str], bool]
|
||||
|
@ -321,8 +321,8 @@ async def start_session() -> None:
|
||||
except ClientError as c:
|
||||
write(err("Something went wrong"))
|
||||
logger.error(f"Unexpected client error: {c}")
|
||||
except Exception as e:
|
||||
logger.error(f"Serious client error: {e}")
|
||||
except:
|
||||
logger.exception("Serious client error")
|
||||
raise
|
||||
finally:
|
||||
with contextlib.suppress(KeyError):
|
||||
@ -351,10 +351,13 @@ def make_pop_server_callback(mails_path: Path, users: list[User],
|
||||
State(reader=reader, writer=writer, ip=ip, req_id=scfg.next_id()))
|
||||
logger.info(f"Got pop server callback")
|
||||
try:
|
||||
return await asyncio.wait_for(start_session(), timeout_seconds)
|
||||
finally:
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
try:
|
||||
return await asyncio.wait_for(start_session(), timeout_seconds)
|
||||
finally:
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
except:
|
||||
logger.exception("unexpected exception")
|
||||
|
||||
return session_cb
|
||||
|
||||
@ -368,7 +371,7 @@ async def create_pop_server(
|
||||
timeout_seconds: int = 60,
|
||||
) -> asyncio.Server:
|
||||
logging.info(
|
||||
f"Starting POP3 server {host=}, {port=}, {mails_path=}, {len(users)=}, {ssl_context != None=}, {timeout_seconds=}"
|
||||
f"Starting POP3 server {host=}, {port=}, {mails_path=!s}, {len(users)=}, {ssl_context != None=}, {timeout_seconds=}"
|
||||
)
|
||||
return await asyncio.start_server(
|
||||
make_pop_server_callback(mails_path, users, timeout_seconds),
|
||||
|
@ -9,6 +9,7 @@ from getpass import getpass
|
||||
|
||||
from .smtp import create_smtp_server_starttls, create_smtp_server
|
||||
from .pop3 import create_pop_server
|
||||
from .version import VERSION
|
||||
|
||||
from . import config
|
||||
from . import pwhash
|
||||
@ -22,18 +23,20 @@ def create_tls_context(certfile, keyfile) -> ssl.SSLContext:
|
||||
|
||||
def setup_logging(cfg: config.LogCfg):
|
||||
logging_format = "%(asctime)s %(name)s %(levelname)s %(message)s @ %(filename)s:%(lineno)d"
|
||||
if cfg.logfile == "STDOUT":
|
||||
if cfg.logfile == "CONSOLE":
|
||||
logging.basicConfig(level=cfg.level, format=logging_format)
|
||||
else:
|
||||
logging.basicConfig(filename=cfg.logfile, level=cfg.level, format=logging_format)
|
||||
|
||||
logging.basicConfig(filename=cfg.logfile,
|
||||
level=cfg.level,
|
||||
format=logging_format)
|
||||
|
||||
|
||||
async def a_main(cfg: config.Config) -> None:
|
||||
default_tls_context: ssl.SSLContext | None = None
|
||||
|
||||
if tls := cfg.default_tls:
|
||||
logging.info(f"Initializing default tls {tls.certfile=}, {tls.keyfile=}")
|
||||
logging.info(
|
||||
f"Initializing default tls {tls.certfile=}, {tls.keyfile=}")
|
||||
default_tls_context = create_tls_context(tls.certfile, tls.keyfile)
|
||||
|
||||
def get_tls_context(tls: config.TLSCfg | str):
|
||||
@ -42,7 +45,7 @@ async def a_main(cfg: config.Config) -> None:
|
||||
elif tls == "disable":
|
||||
return None
|
||||
else:
|
||||
tls_cfg = config.TLSCfg(pop.tls)
|
||||
tls_cfg = config.TLSCfg(tls)
|
||||
return create_tls_context(tls_cfg.certfile, tls_cfg.keyfile)
|
||||
|
||||
def get_host(host):
|
||||
@ -54,47 +57,52 @@ async def a_main(cfg: config.Config) -> None:
|
||||
mbox_finder = config.gen_addr_to_mboxes(cfg)
|
||||
servers: list[asyncio.Server] = []
|
||||
|
||||
if cfg.pop:
|
||||
pop = config.PopCfg(cfg.pop)
|
||||
pop_server = await create_pop_server(
|
||||
host=get_host(pop.host),
|
||||
port=pop.port,
|
||||
mails_path=Path(cfg.mails_path),
|
||||
users=cfg.users,
|
||||
ssl_context=get_tls_context(pop.tls),
|
||||
timeout_seconds=pop.timeout_seconds,
|
||||
)
|
||||
servers.append(pop_server)
|
||||
if not cfg.servers:
|
||||
logging.warning("Nothing to do!")
|
||||
return
|
||||
|
||||
if cfg.smtp_starttls:
|
||||
stls = config.SmtpStartTLSCfg(cfg.smtp_starttls)
|
||||
stls_context = get_tls_context(stls.tls)
|
||||
if not stls_context:
|
||||
raise Exception("starttls requires ssl_context")
|
||||
smtp_server_starttls = await create_smtp_server_starttls(
|
||||
host=get_host(stls.host),
|
||||
port=stls.port,
|
||||
mails_path=Path(cfg.mails_path),
|
||||
mbox_finder=mbox_finder,
|
||||
ssl_context=stls_context,
|
||||
)
|
||||
servers.append(smtp_server_starttls)
|
||||
|
||||
if cfg.smtp:
|
||||
smtp = config.SmtpCfg(cfg.smtp)
|
||||
smtp_server = await create_smtp_server(
|
||||
host=get_host(smtp.host),
|
||||
port=smtp.port,
|
||||
mails_path=Path(cfg.mails_path),
|
||||
mbox_finder=mbox_finder,
|
||||
ssl_context=get_tls_context(smtp.tls),
|
||||
)
|
||||
servers.append(smtp_server)
|
||||
for scfg in cfg.servers:
|
||||
if scfg.server_type == "pop":
|
||||
pop = config.PopCfg(scfg)
|
||||
pop_server = await create_pop_server(
|
||||
host=get_host(pop.host),
|
||||
port=pop.port,
|
||||
mails_path=Path(cfg.mails_path),
|
||||
users=cfg.users,
|
||||
ssl_context=get_tls_context(pop.tls),
|
||||
timeout_seconds=pop.timeout_seconds,
|
||||
)
|
||||
servers.append(pop_server)
|
||||
elif scfg.server_type == "smtp_starttls":
|
||||
stls = config.SmtpStartTLSCfg(scfg)
|
||||
stls_context = get_tls_context(stls.tls)
|
||||
if not stls_context:
|
||||
raise Exception("starttls requires ssl_context")
|
||||
smtp_server_starttls = await create_smtp_server_starttls(
|
||||
host=get_host(stls.host),
|
||||
port=stls.port,
|
||||
mails_path=Path(cfg.mails_path),
|
||||
mbox_finder=mbox_finder,
|
||||
ssl_context=stls_context,
|
||||
)
|
||||
servers.append(smtp_server_starttls)
|
||||
elif scfg.server_type == "smtp":
|
||||
smtp = config.SmtpCfg(scfg)
|
||||
smtp_server = await create_smtp_server(
|
||||
host=get_host(smtp.host),
|
||||
port=smtp.port,
|
||||
mails_path=Path(cfg.mails_path),
|
||||
mbox_finder=mbox_finder,
|
||||
ssl_context=get_tls_context(smtp.tls),
|
||||
)
|
||||
servers.append(smtp_server)
|
||||
else:
|
||||
logging.error(f"Unknown server {scfg.server_type=}")
|
||||
|
||||
if servers:
|
||||
await asyncio.gather(*[server.serve_forever() for server in servers])
|
||||
else:
|
||||
logging.warn("Nothing to do!")
|
||||
logging.warning("Nothing to do!")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
@ -102,6 +110,7 @@ def main() -> None:
|
||||
description="Personal Mail Server",
|
||||
epilog="See https://gitea.balki.me/balki/mail4one for more info",
|
||||
)
|
||||
parser.add_argument("-v", "--version", action="version", version=VERSION)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--echo_password",
|
||||
@ -150,7 +159,7 @@ def main() -> None:
|
||||
else:
|
||||
cfg = config.Config(args.config.read_text())
|
||||
setup_logging(config.LogCfg(cfg.logging))
|
||||
logging.info(f"Starting mail4one {args.config=}")
|
||||
logging.info(f"Starting mail4one {VERSION} {args.config=!s}")
|
||||
asyncio.run(a_main(cfg))
|
||||
|
||||
|
||||
|
@ -75,8 +75,8 @@ def protocol_factory_starttls(mails_path: Path,
|
||||
tls_context=context,
|
||||
enable_SMTPUTF8=True,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Something went wrong", e)
|
||||
except:
|
||||
logger.exception("Something went wrong")
|
||||
raise
|
||||
return smtp
|
||||
|
||||
@ -87,8 +87,8 @@ def protocol_factory(mails_path: Path, mbox_finder: Callable[[str],
|
||||
try:
|
||||
handler = MyHandler(mails_path, mbox_finder)
|
||||
smtp = SMTP(handler=handler, enable_SMTPUTF8=True)
|
||||
except Exception as e:
|
||||
logger.error("Something went wrong", e)
|
||||
except:
|
||||
logger.exception("Something went wrong")
|
||||
raise
|
||||
return smtp
|
||||
|
||||
@ -101,7 +101,7 @@ async def create_smtp_server_starttls(
|
||||
ssl_context: ssl.SSLContext,
|
||||
) -> asyncio.Server:
|
||||
logging.info(
|
||||
f"Starting SMTP STARTTLS server {host=}, {port=}, {mails_path=}, {ssl_context != None=}"
|
||||
f"Starting SMTP STARTTLS server {host=}, {port=}, {mails_path=!s}, {ssl_context != None=}"
|
||||
)
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.create_server(
|
||||
@ -121,7 +121,7 @@ async def create_smtp_server(
|
||||
ssl_context: ssl.SSLContext | None = None,
|
||||
) -> asyncio.Server:
|
||||
logging.info(
|
||||
f"Starting SMTP server {host=}, {port=}, {mails_path=}, {ssl_context != None=}"
|
||||
f"Starting SMTP server {host=}, {port=}, {mails_path=!s}, {ssl_context != None=}"
|
||||
)
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.create_server(
|
||||
|
2
mail4one/version.py
Normal file
2
mail4one/version.py
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
VERSION = "DEVELOMENT"
|
30
scripts/get_version.sh
Executable file
30
scripts/get_version.sh
Executable file
@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
|
||||
commit=$(git rev-parse --short HEAD)
|
||||
|
||||
# This is true if there is a tag on current HEAD
|
||||
if git describe --exact-match > /dev/null 2>&1
|
||||
then
|
||||
tag_val=$(git describe --dirty=DIRTY --exact-match)
|
||||
case "$tag_val" in
|
||||
*DIRTY)
|
||||
echo "git=$commit-changes"
|
||||
exit
|
||||
;;
|
||||
v*) # Only consider tags starting with v
|
||||
echo "$tag_val"
|
||||
;;
|
||||
*)
|
||||
echo "git-$commit"
|
||||
esac
|
||||
else
|
||||
tag_val=$(git describe --dirty=DIRTY)
|
||||
case "$tag_val" in
|
||||
*DIRTY)
|
||||
echo "git-$commit-changes"
|
||||
;;
|
||||
*)
|
||||
echo "git-$commit"
|
||||
esac
|
||||
fi
|
||||
|
Reference in New Issue
Block a user