Compare commits

..

1 Commits

Author SHA1 Message Date
missytake 1e352d0f1c chore(update): use dovecot 2.3 built for Debian 13 2026-06-05 13:14:16 +02:00
9 changed files with 46 additions and 54 deletions
+1
View File
@@ -10,6 +10,7 @@ dependencies = [
"filelock", "filelock",
"requests", "requests",
"crypt-r >= 3.13.1 ; python_version >= '3.11'", "crypt-r >= 3.13.1 ; python_version >= '3.11'",
"domain-validator",
] ]
[tool.setuptools] [tool.setuptools]
+4 -15
View File
@@ -1,8 +1,8 @@
import ipaddress import ipaddress
from pathlib import Path from pathlib import Path
from random import randint
import iniconfig import iniconfig
from domain_validator import DomainValidator
from chatmaild.user import User from chatmaild.user import User
@@ -25,6 +25,7 @@ class Config:
self.mail_domain = f"[{raw_domain}]" self.mail_domain = f"[{raw_domain}]"
self.postfix_myhostname = ipaddress.IPv4Address(raw_domain).reverse_pointer self.postfix_myhostname = ipaddress.IPv4Address(raw_domain).reverse_pointer
else: else:
DomainValidator().validate_domain_re(raw_domain)
self.ipv4_relay = None self.ipv4_relay = None
self.mail_domain = raw_domain self.mail_domain = raw_domain
self.postfix_myhostname = raw_domain self.postfix_myhostname = raw_domain
@@ -42,11 +43,6 @@ class Config:
self.username_max_length = int(params.pop("username_max_length", 9)) self.username_max_length = int(params.pop("username_max_length", 9))
self.password_min_length = int(params.pop("password_min_length", 9)) self.password_min_length = int(params.pop("password_min_length", 9))
self.www_folder = params.pop("www_folder", "") self.www_folder = params.pop("www_folder", "")
self.imap_port = int(params.pop("imap_port", 143))
self.imaps_port = int(params.pop("imaps_port", 993))
self.smtp_port = int(params.pop("smtp_port", 587))
self.smtps_port = int(params.pop("smtps_port", 465))
self.filtermail_smtp_port = int(params.pop("filtermail_smtp_port", "10080")) self.filtermail_smtp_port = int(params.pop("filtermail_smtp_port", "10080"))
self.filtermail_smtp_port_incoming = int( self.filtermail_smtp_port_incoming = int(
params.pop("filtermail_smtp_port_incoming", "10081") params.pop("filtermail_smtp_port_incoming", "10081")
@@ -144,15 +140,8 @@ def parse_size_mb(limit):
def write_initial_config(inipath, mail_domain, overrides): def write_initial_config(inipath, mail_domain, overrides):
"""Write out default config file, using the specified config value overrides.""" """Write out default config file, using the specified config value overrides."""
content = get_default_config_content(mail_domain, **overrides).splitlines() content = get_default_config_content(mail_domain, **overrides)
used_ports = [25, 53, 80, 143, 402, 443, 465, 587, 993, 3340, 3903, 3904, 8443, 10080, 10081, 10082, 10083, 10025, 10026] inipath.write_text(content)
for config_key in ["smtp_port", "imap_port", "smtps_port", "imaps_port"]:
value = randint(1, 65536)
while value in used_ports:
value = randint(65535)
used_ports.append(value)
content.append(f"{config_key} = {value}")
inipath.write_text("\n".join(content))
def get_default_config_content(mail_domain, **overrides): def get_default_config_content(mail_domain, **overrides):
@@ -1,2 +1,2 @@
"acme-enter-email": "{{ email }}" "acme-enter-email": "{{ email }}"
"acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.7-June-04-2026.pdf": true "acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.6-August-18-2025.pdf": true
+4 -4
View File
@@ -495,15 +495,15 @@ def deploy_chatmail(config_path: Path, disable_mail: bool, website_only: bool) -
if config.tls_cert_mode == "acme": if config.tls_cert_mode == "acme":
port_services.append(("acmetool", 402)) port_services.append(("acmetool", 402))
port_services += [ port_services += [
(["imap-login", "dovecot"], config.imap_port), (["imap-login", "dovecot"], 143),
# acmetool previously listened on port 80, # acmetool previously listened on port 80,
# so don't complain during upgrade that moved it to port 402 # so don't complain during upgrade that moved it to port 402
# and gave the port to nginx. # and gave the port to nginx.
(["acmetool", "nginx"], 80), (["acmetool", "nginx"], 80),
("nginx", 443), ("nginx", 443),
(["master", "smtpd"], config.smtp_port), (["master", "smtpd"], 465),
(["master", "smtpd"], config.smtps_port), (["master", "smtpd"], 587),
(["imap-login", "dovecot"], config.imaps_port), (["imap-login", "dovecot"], 993),
("iroh-relay", 3340), ("iroh-relay", 3340),
("mtail", 3903), ("mtail", 3903),
("stats", 3904), ("stats", 3904),
+7 -7
View File
@@ -19,12 +19,12 @@ DOVECOT_ARCHIVE_VERSION = "2.3.21+dfsg1-3"
DOVECOT_PACKAGE_VERSION = f"1:{DOVECOT_ARCHIVE_VERSION}" DOVECOT_PACKAGE_VERSION = f"1:{DOVECOT_ARCHIVE_VERSION}"
DOVECOT_SHA256 = { DOVECOT_SHA256 = {
("core", "amd64"): "dd060706f52a306fa863d874717210b9fe10536c824afe1790eec247ded5b27d", ("core", "amd64"): "b24dcb26eee8fe2f769290fe4cdb665df3a017811eae972840c54dbc74936aad",
("core", "arm64"): "e7548e8a82929722e973629ecc40fcfa886894cef3db88f23535149e7f730dc9", ("core", "arm64"): "42afe5e00e136c8f8513a2c321a29566811a0c8805aeca4302252604f00e3433",
("imapd", "amd64"): "8d8dc6fc00bbb6cdb25d345844f41ce2f1c53f764b79a838eb2a03103eebfa86", ("imapd", "amd64"): "d13c486e7e19c68f316bae4836d9e94db54892a6eb56260d0d2914aa4babe1b6",
("imapd", "arm64"): "178fa877ddd5df9930e8308b518f4b07df10e759050725f8217a0c1fb3fd707f", ("imapd", "arm64"): "537840c4a3d7cdd529ce611481dd38c7ee41246fb8a678d9abeb4fd843f0f88d",
("lmtpd", "amd64"): "2f69ba5e35363de50962d42cccbfe4ed8495265044e244007d7ccddad77513ab", ("lmtpd", "amd64"): "2dd43f8860ee2152b0dffe5e29104add8d6134d9bccf10a21ab1b369b8aa0490",
("lmtpd", "arm64"): "89f52fb36524f5877a177dff4a713ba771fd3f91f22ed0af7238d495e143b38f", ("lmtpd", "arm64"): "ed26f31fe52eb8cb2104aa877c919443670a0ae42aa832eccc3f70bc497291ba",
} }
@@ -119,7 +119,7 @@ def _download_dovecot_package(package: str, arch: str) -> tuple[str | None, bool
url_version = DOVECOT_ARCHIVE_VERSION.replace("+", "%2B") url_version = DOVECOT_ARCHIVE_VERSION.replace("+", "%2B")
deb_base = f"{pkg_name}_{url_version}_{arch}.deb" deb_base = f"{pkg_name}_{url_version}_{arch}.deb"
primary_url = f"https://download.delta.chat/dovecot/{deb_base}" primary_url = f"https://download.delta.chat/dovecot/staging-matrix-trixie-trixie/{deb_base}"
fallback_url = f"https://github.com/chatmail/dovecot/releases/download/upstream%2F{url_version}/{deb_base}" fallback_url = f"https://github.com/chatmail/dovecot/releases/download/upstream%2F{url_version}/{deb_base}"
url = _pick_url(primary_url, fallback_url) url = _pick_url(primary_url, fallback_url)
deb_filename = f"/root/{deb_base}" deb_filename = f"/root/{deb_base}"
@@ -7,14 +7,14 @@
<displayShortName>{{ config.mail_domain }}</displayShortName> <displayShortName>{{ config.mail_domain }}</displayShortName>
<incomingServer type="imap"> <incomingServer type="imap">
<hostname>{{ config.mail_domain }}</hostname> <hostname>{{ config.mail_domain }}</hostname>
<port>{{ config.imaps_port }}</port> <port>993</port>
<socketType>SSL</socketType> <socketType>SSL</socketType>
<authentication>password-cleartext</authentication> <authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username> <username>%EMAILADDRESS%</username>
</incomingServer> </incomingServer>
<incomingServer type="imap"> <incomingServer type="imap">
<hostname>{{ config.mail_domain }}</hostname> <hostname>{{ config.mail_domain }}</hostname>
<port>{{ config.imap_port }}</port> <port>143</port>
<socketType>STARTTLS</socketType> <socketType>STARTTLS</socketType>
<authentication>password-cleartext</authentication> <authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username> <username>%EMAILADDRESS%</username>
@@ -28,14 +28,14 @@
</incomingServer> </incomingServer>
<outgoingServer type="smtp"> <outgoingServer type="smtp">
<hostname>{{ config.mail_domain }}</hostname> <hostname>{{ config.mail_domain }}</hostname>
<port>{{ config.smtps_port }}</port> <port>465</port>
<socketType>SSL</socketType> <socketType>SSL</socketType>
<authentication>password-cleartext</authentication> <authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username> <username>%EMAILADDRESS%</username>
</outgoingServer> </outgoingServer>
<outgoingServer type="smtp"> <outgoingServer type="smtp">
<hostname>{{ config.mail_domain }}</hostname> <hostname>{{ config.mail_domain }}</hostname>
<port>{{ config.smtp_port }}</port> <port>587</port>
<socketType>STARTTLS</socketType> <socketType>STARTTLS</socketType>
<authentication>password-cleartext</authentication> <authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username> <username>%EMAILADDRESS%</username>
-20
View File
@@ -31,26 +31,6 @@ stream {
~\bimap\b 127.0.0.1:993; ~\bimap\b 127.0.0.1:993;
} }
server {
listen {{ config.smtp_port }};
proxy_pass 127.0.0.1:587;
}
server {
listen {{ config.imap_port }};
proxy_pass 127.0.0.1:143;
}
server {
listen {{ config.smtps_port }};
proxy_pass 127.0.0.1:465;
}
server {
listen {{ config.imaps_port }};
proxy_pass 127.0.0.1:993;
}
server { server {
listen 443; listen 443;
{% if not disable_ipv6 %} {% if not disable_ipv6 %}
@@ -1,3 +1,23 @@
/^DKIM-Signature:/ IGNORE # List of headers for incoming messages
/^Authentication-Results:/ IGNORE # that must be retained for functionality and compatibility reasons
/^Received:/ IGNORE /^From:/ DUNNO
/^Message-Id:/ DUNNO
/^Chat-/ DUNNO
/^Content-Type:/ DUNNO
# For receiving clear-text messages (still supported in May 2026)
/^Subject:/ DUNNO
/^Date:/ DUNNO
/^To:/ DUNNO
/^CC:/ DUNNO
/^References:/ DUNNO
/^In-Reply-To:/ DUNNO
# Senders might support Autocrypt 1 but not RFC9788 (Header Protection)
/^Autocrypt:/ DUNNO
# SecureJoin V2 protocol headers (for backward compatibility)
/^Secure-Join/ DUNNO
# Ignore all other headers
/.*/ IGNORE
+2
View File
@@ -10,11 +10,13 @@ from pathlib import Path
import pytest import pytest
from chatmaild.config import is_valid_ipv4, read_config from chatmaild.config import is_valid_ipv4, read_config
from domain_validator import DomainValidator
def format_mail_domain(raw_domain: str) -> str: def format_mail_domain(raw_domain: str) -> str:
if is_valid_ipv4(raw_domain): if is_valid_ipv4(raw_domain):
return f"[{raw_domain}]" return f"[{raw_domain}]"
DomainValidator().validate_domain_re(raw_domain)
return raw_domain return raw_domain