Compare commits

..

1 Commits

Author SHA1 Message Date
link2xt
501351cfe5 Prepare for pyinfra 3 2024-02-13 21:20:25 +00:00
12 changed files with 88 additions and 225 deletions

View File

@@ -17,8 +17,7 @@ def check_encrypted(message):
"""Check that the message is an OpenPGP-encrypted message."""
if not message.is_multipart():
return False
subject = message.get("subject")
if subject not in ("...", "Encrypted Message"):
if message.get("subject") != "...":
return False
if message.get_content_type() != "multipart/encrypted":
return False

View File

@@ -1,92 +0,0 @@
Date: Mon, 05 Feb 2024 12:22:17 +0100
From: Jus <tqwertyjd@nine.testrun.org>
To: REDACTED
User-Agent: K-9 Mail for Android
Message-ID: <A238D610-1471-4A32-B387-B07811665F6D@nine.testrun.org>
Autocrypt: addr=tqwertyjd@nine.testrun.org; keydata=
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
PP5Qy9N6C2QONUAb4aFBPe0cYXQ6AUnOqBmd3w==
Subject: Encrypted Message
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Type: multipart/encrypted; boundary="----GAXGEBIG1Q6UA7H3D91DE8MM3Q54OF";
protocol="application/pgp-encrypted"
------GAXGEBIG1Q6UA7H3D91DE8MM3Q54OF
Content-Type: application/pgp-encrypted
Content-Transfer-Encoding: quoted-printable
Version: 1
------GAXGEBIG1Q6UA7H3D91DE8MM3Q54OF
Content-Type: application/octet-stream; name="encrypted.asc"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline; filename="encrypted.asc"
-----BEGIN PGP MESSAGE-----
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
MrxVY64iHOwasTdMu9F+fv8+akI=
=Dj6B
-----END PGP MESSAGE-----
------GAXGEBIG1Q6UA7H3D91DE8MM3Q54OF--

View File

@@ -1,6 +1,4 @@
import random
from pathlib import Path
import os
import importlib.resources
import itertools
from email.parser import BytesParser
@@ -59,12 +57,7 @@ def db(tmpdir):
@pytest.fixture
def maildata(request):
try:
datadir = importlib.resources.files(__package__).joinpath("mail-data")
except TypeError:
# in python3.9 or lower, the above doesn't work, so we get datadir this way:
datadir = Path(os.getcwd()).joinpath("chatmaild/src/chatmaild/tests/mail-data")
datadir = importlib.resources.files(__package__).joinpath("mail-data")
assert datadir.exists(), datadir
def maildata(name, from_addr, to_addr):

View File

@@ -63,17 +63,6 @@ def test_filtermail_encryption_detection(maildata):
assert not check_encrypted(msg)
def test_filtermail_encryption_detection_k9subject(maildata):
msg = maildata(
"encrypted-k9.eml", from_addr="1@example.org", to_addr="2@example.org"
)
assert check_encrypted(msg)
# if the subject is not "..." it is not considered ac-encrypted
msg.replace_header("Subject", "Click this link")
assert not check_encrypted(msg)
def test_filtermail_is_mdn(maildata, gencreds, handler):
from_addr = gencreds()[0]
to_addr = gencreds()[0] + ".other"

View File

@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
name = "cmdeploy"
version = "0.2"
dependencies = [
"pyinfra",
"pyinfra==3.0b0",
"pillow",
"qrcode",
"markdown",

View File

@@ -10,13 +10,15 @@ import io
from pathlib import Path
from pyinfra import host
from pyinfra.operations import apt, files, server, systemd, pip
from pyinfra.operations import apt, files, server, systemd, pip, util
from pyinfra.facts.files import File
from pyinfra.facts.systemd import SystemdEnabled
from .acmetool import deploy_acmetool
from chatmaild.config import read_config, Config
from typing import Callable
def _build_chatmaild(dist_dir) -> None:
dist_dir = Path(dist_dir).resolve()
@@ -127,10 +129,8 @@ def _install_remote_venv_with_chatmaild(config) -> None:
)
def _configure_opendkim(domain: str, dkim_selector: str = "dkim") -> bool:
def _configure_opendkim(domain: str, dkim_selector: str = "dkim") -> Callable[[], bool]:
"""Configures OpenDKIM"""
need_restart = False
server.group(name="Create opendkim group", group="opendkim", system=True)
server.user(
name="Create opendkim user",
@@ -153,7 +153,6 @@ def _configure_opendkim(domain: str, dkim_selector: str = "dkim") -> bool:
mode="644",
config={"domain_name": domain, "opendkim_selector": dkim_selector},
)
need_restart |= main_config.changed
screen_script = files.put(
src=importlib.resources.files(__package__).joinpath("opendkim/screen.lua"),
@@ -162,7 +161,6 @@ def _configure_opendkim(domain: str, dkim_selector: str = "dkim") -> bool:
group="root",
mode="644",
)
need_restart |= screen_script.changed
final_script = files.put(
src=importlib.resources.files(__package__).joinpath("opendkim/final.lua"),
@@ -171,7 +169,6 @@ def _configure_opendkim(domain: str, dkim_selector: str = "dkim") -> bool:
group="root",
mode="644",
)
need_restart |= final_script.changed
files.directory(
name="Add opendkim directory to /etc",
@@ -190,7 +187,6 @@ def _configure_opendkim(domain: str, dkim_selector: str = "dkim") -> bool:
mode="644",
config={"domain_name": domain, "opendkim_selector": dkim_selector},
)
need_restart |= keytable.changed
signing_table = files.template(
src=importlib.resources.files(__package__).joinpath("opendkim/SigningTable"),
@@ -200,7 +196,7 @@ def _configure_opendkim(domain: str, dkim_selector: str = "dkim") -> bool:
mode="644",
config={"domain_name": domain, "opendkim_selector": dkim_selector},
)
need_restart |= signing_table.changed
files.directory(
name="Add opendkim socket directory to /var/spool/postfix",
path="/var/spool/postfix/opendkim",
@@ -225,10 +221,12 @@ def _configure_opendkim(domain: str, dkim_selector: str = "dkim") -> bool:
_sudo_user="opendkim",
)
return need_restart
return util.any_changed(
main_config, screen_script, final_script, keytable, signing_table
)
def _install_mta_sts_daemon() -> bool:
def _install_mta_sts_daemon() -> Callable[[], bool]:
need_restart = False
config = files.put(
@@ -241,7 +239,6 @@ def _install_mta_sts_daemon() -> bool:
group="root",
mode="644",
)
need_restart |= config.changed
server.shell(
name="install postfix-mta-sts-resolver with pip",
@@ -261,15 +258,12 @@ def _install_mta_sts_daemon() -> bool:
group="root",
mode="644",
)
need_restart |= systemd_unit.changed
return need_restart
return util.any_changed(config, systemd_unit)
def _configure_postfix(config: Config, debug: bool = False) -> bool:
def _configure_postfix(config: Config, debug: bool = False) -> Callable[[], bool]:
"""Configures Postfix SMTP server."""
need_restart = False
main_config = files.template(
src=importlib.resources.files(__package__).joinpath("postfix/main.cf.j2"),
dest="/etc/postfix/main.cf",
@@ -278,7 +272,6 @@ def _configure_postfix(config: Config, debug: bool = False) -> bool:
mode="644",
config=config,
)
need_restart |= main_config.changed
master_config = files.template(
src=importlib.resources.files(__package__).joinpath("postfix/master.cf.j2"),
@@ -289,7 +282,6 @@ def _configure_postfix(config: Config, debug: bool = False) -> bool:
debug=debug,
config=config,
)
need_restart |= master_config.changed
header_cleanup = files.put(
src=importlib.resources.files(__package__).joinpath(
@@ -300,7 +292,6 @@ def _configure_postfix(config: Config, debug: bool = False) -> bool:
group="root",
mode="644",
)
need_restart |= header_cleanup.changed
# Login map that 1:1 maps email address to login.
login_map = files.put(
@@ -310,15 +301,12 @@ def _configure_postfix(config: Config, debug: bool = False) -> bool:
group="root",
mode="644",
)
need_restart |= login_map.changed
return need_restart
return util.any_changed(main_config, master_config, header_cleanup, login_map)
def _configure_dovecot(config: Config, debug: bool = False) -> bool:
def _configure_dovecot(config: Config, debug: bool = False) -> Callable[[], bool]:
"""Configures Dovecot IMAP server."""
need_restart = False
main_config = files.template(
src=importlib.resources.files(__package__).joinpath("dovecot/dovecot.conf.j2"),
dest="/etc/dovecot/dovecot.conf",
@@ -328,7 +316,6 @@ def _configure_dovecot(config: Config, debug: bool = False) -> bool:
config=config,
debug=debug,
)
need_restart |= main_config.changed
auth_config = files.put(
src=importlib.resources.files(__package__).joinpath("dovecot/auth.conf"),
dest="/etc/dovecot/auth.conf",
@@ -336,7 +323,6 @@ def _configure_dovecot(config: Config, debug: bool = False) -> bool:
group="root",
mode="644",
)
need_restart |= auth_config.changed
files.template(
src=importlib.resources.files(__package__).joinpath("dovecot/expunge.cron.j2"),
@@ -358,13 +344,11 @@ def _configure_dovecot(config: Config, debug: bool = False) -> bool:
persist=True,
)
return need_restart
return util.any_changed(main_config, auth_config)
def _configure_nginx(domain: str, debug: bool = False) -> bool:
def _configure_nginx(domain: str, debug: bool = False) -> Callable[[], bool]:
"""Configures nginx HTTP server."""
need_restart = False
main_config = files.template(
src=importlib.resources.files(__package__).joinpath("nginx/nginx.conf.j2"),
dest="/etc/nginx/nginx.conf",
@@ -373,7 +357,6 @@ def _configure_nginx(domain: str, debug: bool = False) -> bool:
mode="644",
config={"domain_name": domain},
)
need_restart |= main_config.changed
autoconfig = files.template(
src=importlib.resources.files(__package__).joinpath("nginx/autoconfig.xml.j2"),
@@ -383,7 +366,6 @@ def _configure_nginx(domain: str, debug: bool = False) -> bool:
mode="644",
config={"domain_name": domain},
)
need_restart |= autoconfig.changed
mta_sts_config = files.template(
src=importlib.resources.files(__package__).joinpath("nginx/mta-sts.txt.j2"),
@@ -393,7 +375,6 @@ def _configure_nginx(domain: str, debug: bool = False) -> bool:
mode="644",
config={"domain_name": domain},
)
need_restart |= mta_sts_config.changed
# install CGI newemail script
#
@@ -414,7 +395,7 @@ def _configure_nginx(domain: str, debug: bool = False) -> bool:
mode="755",
)
return need_restart
return util.any_changed(main_config, autoconfig, mta_sts_config)
def _remove_rspamd() -> None:
@@ -518,7 +499,12 @@ def deploy_chatmail(config_path: Path) -> None:
service="opendkim.service",
running=True,
enabled=True,
restarted=opendkim_need_restart,
)
systemd.service(
name="Restart OpenDKIM",
service="opendkim.service",
restarted=True,
_if=opendkim_need_restart,
)
systemd.service(
@@ -527,7 +513,12 @@ def deploy_chatmail(config_path: Path) -> None:
daemon_reload=True,
running=True,
enabled=True,
restarted=mta_sts_need_restart,
)
systemd.service(
name="Restart MTA-STS daemon",
service="mta-sts-daemon.service",
restarted=True,
_if=mta_sts_need_restart,
)
systemd.service(
@@ -535,7 +526,12 @@ def deploy_chatmail(config_path: Path) -> None:
service="postfix.service",
running=True,
enabled=True,
restarted=postfix_need_restart,
)
systemd.service(
name="Restart Postfix",
service="postfix.service",
restarted=True,
_if=postfix_need_restart,
)
systemd.service(
@@ -543,7 +539,12 @@ def deploy_chatmail(config_path: Path) -> None:
service="dovecot.service",
running=True,
enabled=True,
restarted=dovecot_need_restart,
)
systemd.service(
name="Restart Dovecot",
service="dovecot.service",
restarted=True,
_if=dovecot_need_restart,
)
systemd.service(
@@ -551,7 +552,12 @@ def deploy_chatmail(config_path: Path) -> None:
service="nginx.service",
running=True,
enabled=True,
restarted=nginx_need_restart,
)
systemd.service(
name="Restart nginx",
service="nginx.service",
restarted=True,
_if=nginx_need_restart,
)
# This file is used by auth proxy.

View File

@@ -69,7 +69,12 @@ def deploy_acmetool(nginx_hook=False, email="", domains=[]):
service="acmetool-redirector.service",
running=True,
enabled=True,
restarted=service_file.changed,
)
systemd.service(
name="Restart acmetool-redirector service",
service="acmetool-redirector.service",
restarted=True,
_if=service_file.did_change,
)
server.shell(

View File

@@ -2,7 +2,6 @@
Provides the `cmdeploy` entry point function,
along with command line option and subcommand parsing.
"""
import argparse
import shutil
import subprocess

View File

@@ -5,8 +5,6 @@ import importlib
import subprocess
import datetime
from typing import Optional
class DNS:
def __init__(self, out, mail_domain):
@@ -36,7 +34,7 @@ class DNS:
cmd = "ip a | grep inet6 | grep 'scope global' | sed -e 's#/64 scope global##' | sed -e 's#inet6##'"
return self.shell(cmd).strip()
def get(self, typ: str, domain: str) -> Optional[str]:
def get(self, typ: str, domain: str) -> str | None:
"""Get a DNS entry"""
dig_result = self.shell(f"dig -r -q {domain} -t {typ} +short")
line = dig_result.partition("\n")[0]
@@ -56,25 +54,22 @@ def show_dns(args, out) -> int:
ssh = f"ssh root@{mail_domain}"
dns = DNS(out, mail_domain)
def read_dkim_entries(entry):
lines = []
for line in entry.split("\n"):
if line.startswith(";") or not line.strip():
continue
line = line.replace("\t", " ")
lines.append(line)
return "\n".join(lines)
print("Checking your DKIM keys and DNS entries...")
try:
acme_account_url = out.shell_output(f"{ssh} -- acmetool account-url")
except subprocess.CalledProcessError:
print("Please run `cmdeploy run` first.")
return 1
dkim_selector = "opendkim"
dkim_pubkey = out.shell_output(
ssh + f" -- openssl rsa -in /etc/dkimkeys/{dkim_selector}.private"
" -pubout 2>/dev/null | awk '/-/{next}{printf(\"%s\",$0)}'"
)
dkim_entry_value = f"v=DKIM1;k=rsa;p={dkim_pubkey};s=email;t=s"
dkim_entry_str = ""
while len(dkim_entry_value) >= 255:
dkim_entry_str += '"' + dkim_entry_value[:255] + '" '
dkim_entry_value = dkim_entry_value[255:]
dkim_entry_str += '"' + dkim_entry_value + '"'
dkim_entry = f"{dkim_selector}._domainkey.{mail_domain}. TXT {dkim_entry_str}"
dkim_entry = read_dkim_entries(out.shell_output(f"{ssh} -- opendkim-genzone -F"))
ipv6 = dns.get_ipv6()
reverse_ipv6 = dns.check_ptr_record(ipv6, mail_domain)
@@ -102,6 +97,7 @@ def show_dns(args, out) -> int:
return 0
except TypeError:
pass
started_dkim_parsing = False
for line in zonefile.splitlines():
line = line.format(
acme_account_url=acme_account_url,
@@ -128,23 +124,28 @@ def show_dns(args, out) -> int:
current = dns.get("SRV", domain[:-1])
if current != f"{prio} {weight} {port} {value}":
to_print.append(line)
if " TXT " in line:
if " TXT " in line:
domain, value = line.split(" TXT ")
current = dns.get("TXT", domain.strip()[:-1])
if domain.startswith("_mta-sts."):
if current:
if current.split("id=")[0] == value.split("id=")[0]:
continue
# TXT records longer than 255 bytes
# are split into multiple <character-string>s.
# This typically happens with DKIM record
# which contains long RSA key.
#
# Removing `" "` before comparison
# to get back a single string.
if current.replace('" "', "") != value.replace('" "', ""):
if current != value:
to_print.append(line)
if "IN TXT ( " in line:
started_dkim_parsing = True
dkim_lines = [line]
if started_dkim_parsing and line.startswith('"'):
dkim_lines.append(" " + line)
domain, data = "\n".join(dkim_lines).split(" IN TXT ")
current = dns.get("TXT", domain.strip()[:-1])
if current:
current = "( %s )" % (current.replace('" "', '"\n "'))
if current.replace(";", "\\;") != data:
to_print.append(dkim_entry)
else:
to_print.append(dkim_entry)
exit_code = 0
if to_print:

View File

@@ -1,10 +1,10 @@
# delete all mails after {{ config.delete_mails_after }} days, in the Inbox
2 0 * * * vmail find /home/vmail/mail/{{ config.mail_domain }} -path '*/cur/*' -mtime +{{ config.delete_mails_after }} -type f -delete
2 0 * * * dovecot find /home/vmail/mail/{{ config.mail_domain }} -path '*/cur/*' -mtime +{{ config.delete_mails_after }} -type f -delete
# or in any IMAP subfolder
2 0 * * * vmail find /home/vmail/mail/{{ config.mail_domain }} -path '*/.*/cur/*' -mtime +{{ config.delete_mails_after }} -type f -delete
2 0 * * * dovecot find /home/vmail/mail/{{ config.mail_domain }} -path '*/.*/cur/*' -mtime +{{ config.delete_mails_after }} -type f -delete
# even if they are unseen
2 0 * * * vmail find /home/vmail/mail/{{ config.mail_domain }} -path '*/new/*' -mtime +{{ config.delete_mails_after }} -type f -delete
2 0 * * * vmail find /home/vmail/mail/{{ config.mail_domain }} -path '*/.*/new/*' -mtime +{{ config.delete_mails_after }} -type f -delete
2 0 * * * dovecot find /home/vmail/mail/{{ config.mail_domain }} -path '*/new/*' -mtime +{{ config.delete_mails_after }} -type f -delete
2 0 * * * dovecot find /home/vmail/mail/{{ config.mail_domain }} -path '*/.*/new/*' -mtime +{{ config.delete_mails_after }} -type f -delete
# or only temporary (but then they shouldn't be around after {{ config.delete_mails_after }} days anyway).
2 0 * * * vmail find /home/vmail/mail/{{ config.mail_domain }} -path '*/tmp/*' -mtime +{{ config.delete_mails_after }} -type f -delete
2 0 * * * vmail find /home/vmail/mail/{{ config.mail_domain }} -path '*/.*/tmp/*' -mtime +{{ config.delete_mails_after }} -type f -delete
2 0 * * * dovecot find /home/vmail/mail/{{ config.mail_domain }} -path '*/tmp/*' -mtime +{{ config.delete_mails_after }} -type f -delete
2 0 * * * dovecot find /home/vmail/mail/{{ config.mail_domain }} -path '*/.*/tmp/*' -mtime +{{ config.delete_mails_after }} -type f -delete

View File

@@ -1,7 +1,6 @@
import pytest
import threading
import queue
import socket
from chatmaild.config import read_config
from cmdeploy.cmdeploy import main
@@ -79,24 +78,3 @@ def test_concurrent_logins_same_account(
for _ in conns:
assert login_results.get()
def test_no_vrfy(chatmail_config):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((chatmail_config.mail_domain, 25))
banner = sock.recv(1024)
print(banner)
sock.send(b"VRFY wrongaddress@%s\r\n" % (chatmail_config.mail_domain.encode(),))
result = sock.recv(1024)
print(result)
sock.send(b"VRFY echo@%s\r\n" % (chatmail_config.mail_domain.encode(),))
result2 = sock.recv(1024)
print(result2)
assert result[0:10] == result2[0:10]
sock.send(b"VRFY wrongaddress\r\n")
result = sock.recv(1024)
print(result)
sock.send(b"VRFY echo\r\n")
result2 = sock.recv(1024)
print(result2)
assert result[0:10] == result2[0:10] == b"252 2.0.0 "

View File

@@ -83,18 +83,3 @@ def test_exceed_rate_limit(cmsetup, gencreds, maildata, chatmail_config):
assert b"4.7.1: Too much mail from" in outcome[1]
return
pytest.fail("Rate limit was not exceeded")
def test_expunged(remote, chatmail_config):
outdated_days = int(chatmail_config.delete_mails_after) + 1
find_cmds = [
f"find /home/vmail/mail/{chatmail_config.mail_domain} -path '*/cur/*' -mtime +{outdated_days} -type f",
f"find /home/vmail/mail/{chatmail_config.mail_domain} -path '*/.*/cur/*' -mtime +{outdated_days} -type f",
f"find /home/vmail/mail/{chatmail_config.mail_domain} -path '*/new/*' -mtime +{outdated_days} -type f",
f"find /home/vmail/mail/{chatmail_config.mail_domain} -path '*/.*/new/*' -mtime +{outdated_days} -type f",
f"find /home/vmail/mail/{chatmail_config.mail_domain} -path '*/tmp/*' -mtime +{outdated_days} -type f",
f"find /home/vmail/mail/{chatmail_config.mail_domain} -path '*/.*/tmp/*' -mtime +{outdated_days} -type f",
]
for cmd in find_cmds:
for line in remote.iter_output(cmd):
assert not line