Add filtermail

This commit is contained in:
link2xt
2023-10-13 21:50:14 +00:00
parent e061d98cfc
commit c9dc32bd10
9 changed files with 201 additions and 13 deletions

View File

@@ -23,10 +23,6 @@ def _install_doveauth() -> None:
src=doveauth_path.open("rb"),
dest=remote_path,
)
apt.packages(
name="apt install python3-pip",
packages="python3-pip",
)
# Maybe if we introduce dependencies to the doveauth package at some point, we should not install doveauth
# system-wide anymore. For now it's fine though.
server.shell(
@@ -35,6 +31,48 @@ def _install_doveauth() -> None:
)
def _install_filtermail() -> None:
"""Setup filtermail."""
filtermail_filename = "filtermail-0.1.tar.gz"
filtermail_path = importlib.resources.files(__package__).joinpath(
f"../../../filtermail/dist/{filtermail_filename}"
)
remote_path = f"/tmp/{filtermail_filename}"
if Path(str(filtermail_path)).exists():
files.put(
name="upload local filtermail build",
src=filtermail_path.open("rb"),
dest=remote_path,
)
apt.packages(
name="apt install python3-aiosmtpd",
packages="python3-aiosmtpd",
)
# --no-deps because aiosmtplib is installed with `apt`.
server.shell(
name="install local doveauth build with pip",
commands=[f"pip install --break-system-packages --no-deps {remote_path}"],
)
files.put(
src=importlib.resources.files(__package__)
.joinpath("../../../filtermail/filtermail.service")
.open("rb"),
dest="/etc/systemd/system/filtermail.service",
user="root",
group="root",
mode="644",
)
systemd.service(
name="Setup filtermail service",
service="filtermail.service",
running=True,
enabled=True,
restarted=True,
)
def _configure_opendkim(domain: str, dkim_selector: str) -> bool:
"""Configures OpenDKIM"""
need_restart = False
@@ -172,7 +210,12 @@ def deploy_chatmail(mail_domain: str, mail_server: str, dkim_selector: str) -> N
],
)
apt.packages(
name="apt install python3-pip",
packages="python3-pip",
)
_install_doveauth()
_install_filtermail()
dovecot_need_restart = _configure_dovecot(mail_server)
postfix_need_restart = _configure_postfix(mail_domain)
opendkim_need_restart = _configure_opendkim(mail_domain, dkim_selector)

View File

@@ -28,6 +28,7 @@ submission inet n - y - - smtpd
-o smtpd_recipient_restrictions=
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
-o content_filter=filter:unix:private/filtemail
smtps inet n - y - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
@@ -42,6 +43,7 @@ smtps inet n - y - - smtpd
-o smtpd_recipient_restrictions=
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
-o content_filter=filter:unix:private/filtermail
#628 inet n - y - - qmqpd
pickup unix n - y 60 1 pickup
cleanup unix n - y - 0 cleanup
@@ -70,3 +72,7 @@ lmtp unix - - y - - lmtp
anvil unix - - y - 1 anvil
scache unix - - y - 1 scache
postlog unix-dgram n - n - 1 postlogd
filter unix - n n - - lmtp
# Local SMTP server for reinjecting filered mail.
localhost:10026 inet n - n - 10 smtpd
-o content_filter=

View File

@@ -0,0 +1,10 @@
[Unit]
Description=Email filter for chatmail servers
[Service]
ExecStart=/usr/local/bin/filtermail
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target

33
filtermail/pyproject.toml Normal file
View File

@@ -0,0 +1,33 @@
[build-system]
requires = ["setuptools>=45"]
build-backend = "setuptools.build_meta"
[project]
name = "filtermail"
version = "0.1"
dependencies = [
"aiosmtpd"
]
[project.scripts]
filtermail = "filtermail.filtermail:main"
[tool.pytest.ini_options]
addopts = "-v -ra --strict-markers"
[tool.tox]
legacy_tox_ini = """
[tox]
isolated_build = true
envlist = lint
[testenv:lint]
skipdist = True
skip_install = True
deps =
ruff
black
commands =
black --quiet --check --diff src/
ruff src/
"""

View File

View File

@@ -0,0 +1,85 @@
#!/usr/bin/env python3
import asyncio
import logging
from aiosmtpd.lmtp import LMTP
from aiosmtpd.controller import UnixSocketController
from smtplib import SMTP as SMTPClient
def check_encrypted(envelope):
"""https://xkcd.com/1181/"""
return "-----BEGIN PGP MESSAGE-----" in envelope.content.decode(
"utf8", errors="replace"
)
class ExampleController(UnixSocketController):
def factory(self):
return LMTP(self.handler, **self.SMTP_kwargs)
class ExampleHandler:
async def handle_RCPT(self, server, session, envelope, address, rcpt_options):
envelope.rcpt_tos.append(address)
return "250 OK"
async def handle_DATA(self, server, session, envelope):
logging.info("Processing DATA message from %s", envelope.mail_from)
valid_recipients = []
mail_encrypted = check_encrypted(envelope)
res = []
for recipient in envelope.rcpt_tos:
my_local_domain = envelope.mail_from.split("@")
if len(my_local_domain) != 2:
res += [f"500 Invalid from address <{envelope.mail_from}>"]
continue
if envelope.mail_from == recipient:
# Always allow sending emails to self.
valid_recipients += [recipient]
res += ["250 OK"]
continue
recipient_local_domain = recipient.split("@")
if len(recipient_local_domain) != 2:
res += [f"500 Invalid address <{recipient}>"]
continue
is_outgoing = recipient_local_domain[1] != my_local_domain[1]
if is_outgoing and not mail_encrypted:
res += ["500 Outgoing mail must be encrypted"]
continue
valid_recipients += [recipient]
res += ["250 OK"]
# Reinject the mail back into Postfix.
if valid_recipients:
logging.info("Reinjecting the mail")
client = SMTPClient("localhost", "10026")
client.sendmail(envelope.mail_from, valid_recipients, envelope.content)
return "\r\n".join(res)
async def asyncmain(loop):
controller = ExampleController(
ExampleHandler(), unix_socket="/var/spool/postfix/private/filtermail"
)
controller.start()
def main():
logging.basicConfig(level=logging.INFO)
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.create_task(asyncmain(loop=loop))
loop.run_forever()
if __name__ == "__main__":
main()

View File

@@ -11,14 +11,12 @@
4. automatic expiry of users that haven't logged in for N days
## Postfix goals/steps
## Filtermail
1. block all outgoing mails with our own LMTP program
1. Only allow (outgoing) mails if secure-join or autocrypt-pgp-encrypted format.
Currently only checks for "-----BEGIN PGP MESSAGE-----".
2. only allow (outgoing) mails if secure-join or autocrypt-pgp-encrypted format
(probably via an lmtp service)
3. basic outgoing send rate/limits (depending on "account-rating")
2. basic outgoing send rate/limits (depending on "account-rating")
## online tests (first with plain python/pytest)

View File

@@ -1,7 +1,16 @@
#!/usr/bin/env bash
: ${CHATMAIL_DOMAIN:=c1.testrun.org}
export CHATMAIL_DOMAIN
cd doveauth
pushd doveauth
venv/bin/python3 -m build
../chatmail-pyinfra/venv/bin/pyinfra --ssh-user root "$CHATMAIL_DOMAIN" ../deploy.py
rm -r dist/
popd
pushd filtermail
venv/bin/python3 -m build
popd
chatmail-pyinfra/venv/bin/pyinfra --ssh-user root "$CHATMAIL_DOMAIN" deploy.py
#rm -r doveauth/dist/
#rm -r filtermail/dist/

View File

@@ -10,3 +10,7 @@ doveauth/venv/bin/pip install -e doveauth
python3 -m venv online-tests/venv
online-tests/venv/bin/pip install pytest pytest-timeout pdbpp deltachat
python3 -m venv filtermail/venv
filtermail/venv/bin/pip install build
filtermail/venv/bin/pip install -e filtermail