mirror of
https://github.com/chatmail/relay.git
synced 2026-06-11 06:01:07 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 221f5ed10e |
@@ -20,9 +20,9 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
no-dns:
|
no-dns:
|
||||||
name: LXC deploy and test
|
name: LXC deploy and test
|
||||||
uses: chatmail/cmlxc/.github/workflows/lxc-test.yml@main
|
uses: chatmail/cmlxc/.github/workflows/lxc-test.yml@v0.14.6
|
||||||
with:
|
with:
|
||||||
cmlxc_version: main
|
cmlxc_version: v0.14.6
|
||||||
cmlxc_commands: |
|
cmlxc_commands: |
|
||||||
cmlxc init
|
cmlxc init
|
||||||
# single cmdeploy relay test
|
# single cmdeploy relay test
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: download filtermail
|
- name: download filtermail
|
||||||
run: curl -L https://github.com/chatmail/filtermail/releases/download/v0.7.0/filtermail-x86_64 -o /usr/local/bin/filtermail && chmod +x /usr/local/bin/filtermail
|
run: curl -L https://github.com/chatmail/filtermail/releases/download/v0.6.6/filtermail-x86_64 -o /usr/local/bin/filtermail && chmod +x /usr/local/bin/filtermail
|
||||||
- name: run chatmaild tests
|
- name: run chatmaild tests
|
||||||
working-directory: chatmaild
|
working-directory: chatmaild
|
||||||
run: pipx run tox
|
run: pipx run tox
|
||||||
@@ -57,9 +57,9 @@ jobs:
|
|||||||
|
|
||||||
lxc-test:
|
lxc-test:
|
||||||
name: LXC deploy and test
|
name: LXC deploy and test
|
||||||
uses: chatmail/cmlxc/.github/workflows/lxc-test.yml@main
|
uses: chatmail/cmlxc/.github/workflows/lxc-test.yml@v0.14.6
|
||||||
with:
|
with:
|
||||||
cmlxc_version: main
|
cmlxc_version: v0.14.6
|
||||||
cmlxc_commands: |
|
cmlxc_commands: |
|
||||||
cmlxc init
|
cmlxc init
|
||||||
# single cmdeploy relay test
|
# single cmdeploy relay test
|
||||||
|
|||||||
@@ -168,16 +168,6 @@ class Expiry:
|
|||||||
if mbox.last_login and mbox.last_login < cutoff_without_login:
|
if mbox.last_login and mbox.last_login < cutoff_without_login:
|
||||||
self.remove_mailbox(mbox.basedir)
|
self.remove_mailbox(mbox.basedir)
|
||||||
return
|
return
|
||||||
elif mbox.last_login is None:
|
|
||||||
try:
|
|
||||||
if not self.dry:
|
|
||||||
os.rmdir(mbox.basedir)
|
|
||||||
self.del_mboxes += 1
|
|
||||||
except OSError:
|
|
||||||
print_info(
|
|
||||||
f"Skipped deleting {mbox.basedir}, doesn't have last_login but isn't empty"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
mboxname = os.path.basename(mbox.basedir)
|
mboxname = os.path.basename(mbox.basedir)
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import shutil
|
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from fnmatch import fnmatch
|
from fnmatch import fnmatch
|
||||||
@@ -10,7 +9,6 @@ from pathlib import Path
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from chatmaild.expire import (
|
from chatmaild.expire import (
|
||||||
Expiry,
|
|
||||||
FileEntry,
|
FileEntry,
|
||||||
MailboxStat,
|
MailboxStat,
|
||||||
expire_to_target,
|
expire_to_target,
|
||||||
@@ -106,32 +104,6 @@ def test_stats_mailbox(mbox1):
|
|||||||
assert mbox3.last_login is None
|
assert mbox3.last_login is None
|
||||||
|
|
||||||
|
|
||||||
def test_mbox_without_password(mbox1, example_config, capsys):
|
|
||||||
password = Path(mbox1.basedir).joinpath("password")
|
|
||||||
os.remove(password)
|
|
||||||
mbox_rescan = MailboxStat(mbox1.basedir)
|
|
||||||
assert mbox_rescan.last_login is None
|
|
||||||
|
|
||||||
exp = Expiry(
|
|
||||||
example_config, dry=False, now=datetime.now().timestamp(), verbose=False
|
|
||||||
)
|
|
||||||
exp.process_mailbox_stat(mbox_rescan)
|
|
||||||
out, err = capsys.readouterr()
|
|
||||||
assert "doesn't have last_login but isn't empty" in err
|
|
||||||
assert os.path.isdir(mbox_rescan.basedir)
|
|
||||||
|
|
||||||
for entry in os.scandir(mbox_rescan.basedir):
|
|
||||||
if os.path.isdir(entry):
|
|
||||||
shutil.rmtree(entry)
|
|
||||||
else:
|
|
||||||
os.remove(entry)
|
|
||||||
|
|
||||||
exp.process_mailbox_stat(mbox_rescan)
|
|
||||||
out, err = capsys.readouterr()
|
|
||||||
assert "doesn't have last_login but isn't empty" not in err
|
|
||||||
assert not os.path.isdir(mbox_rescan.basedir)
|
|
||||||
|
|
||||||
|
|
||||||
def test_report_no_mailboxes(example_config):
|
def test_report_no_mailboxes(example_config):
|
||||||
args = (str(example_config._inipath),)
|
args = (str(example_config._inipath),)
|
||||||
report_main(args)
|
report_main(args)
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ dependencies = [
|
|||||||
"pytest-xdist",
|
"pytest-xdist",
|
||||||
"execnet",
|
"execnet",
|
||||||
"imap_tools",
|
"imap_tools",
|
||||||
"deltachat-rpc-client",
|
"deltachat-rpc-client==2.49.0",
|
||||||
"deltachat-rpc-server",
|
"deltachat-rpc-server==2.49.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|||||||
@@ -171,14 +171,16 @@ class UnboundDeployer(Deployer):
|
|||||||
"unbound-anchor -a /var/lib/unbound/root.key || true",
|
"unbound-anchor -a /var/lib/unbound/root.key || true",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
self.ensure_directory(
|
if self.config.disable_ipv6:
|
||||||
path="/etc/unbound/unbound.conf.d",
|
self.ensure_directory(
|
||||||
)
|
path="/etc/unbound/unbound.conf.d",
|
||||||
self.put_template(
|
)
|
||||||
"unbound/unbound.conf.j2",
|
self.put_template(
|
||||||
"/etc/unbound/unbound.conf.d/chatmail.conf",
|
"unbound/unbound.conf.j2",
|
||||||
disable_ipv6=self.config.disable_ipv6,
|
"/etc/unbound/unbound.conf.d/chatmail.conf",
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
self.remove_file("/etc/unbound/unbound.conf.d/chatmail.conf")
|
||||||
|
|
||||||
def activate(self):
|
def activate(self):
|
||||||
server.shell(
|
server.shell(
|
||||||
@@ -512,8 +514,6 @@ def deploy_chatmail(config_path: Path, disable_mail: bool, website_only: bool) -
|
|||||||
(["master", "smtpd"], config.postfix_reinject_port_incoming),
|
(["master", "smtpd"], config.postfix_reinject_port_incoming),
|
||||||
("filtermail", config.filtermail_smtp_port),
|
("filtermail", config.filtermail_smtp_port),
|
||||||
("filtermail", config.filtermail_smtp_port_incoming),
|
("filtermail", config.filtermail_smtp_port_incoming),
|
||||||
("filtermail", config.filtermail_http_port_incoming),
|
|
||||||
("filtermail", config.filtermail_lmtp_port_transport),
|
|
||||||
]
|
]
|
||||||
for service, port in port_services:
|
for service, port in port_services:
|
||||||
print(f"Checking if port {port} is available for {service}...")
|
print(f"Checking if port {port} is available for {service}...")
|
||||||
|
|||||||
@@ -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"): "b24dcb26eee8fe2f769290fe4cdb665df3a017811eae972840c54dbc74936aad",
|
("core", "amd64"): "dd060706f52a306fa863d874717210b9fe10536c824afe1790eec247ded5b27d",
|
||||||
("core", "arm64"): "42afe5e00e136c8f8513a2c321a29566811a0c8805aeca4302252604f00e3433",
|
("core", "arm64"): "e7548e8a82929722e973629ecc40fcfa886894cef3db88f23535149e7f730dc9",
|
||||||
("imapd", "amd64"): "d13c486e7e19c68f316bae4836d9e94db54892a6eb56260d0d2914aa4babe1b6",
|
("imapd", "amd64"): "8d8dc6fc00bbb6cdb25d345844f41ce2f1c53f764b79a838eb2a03103eebfa86",
|
||||||
("imapd", "arm64"): "537840c4a3d7cdd529ce611481dd38c7ee41246fb8a678d9abeb4fd843f0f88d",
|
("imapd", "arm64"): "178fa877ddd5df9930e8308b518f4b07df10e759050725f8217a0c1fb3fd707f",
|
||||||
("lmtpd", "amd64"): "2dd43f8860ee2152b0dffe5e29104add8d6134d9bccf10a21ab1b369b8aa0490",
|
("lmtpd", "amd64"): "2f69ba5e35363de50962d42cccbfe4ed8495265044e244007d7ccddad77513ab",
|
||||||
("lmtpd", "arm64"): "ed26f31fe52eb8cb2104aa877c919443670a0ae42aa832eccc3f70bc497291ba",
|
("lmtpd", "arm64"): "89f52fb36524f5877a177dff4a713ba771fd3f91f22ed0af7238d495e143b38f",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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/staging-matrix-trixie-trixie/{deb_base}"
|
primary_url = f"https://download.delta.chat/dovecot/{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}"
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ class FiltermailDeployer(Deployer):
|
|||||||
return
|
return
|
||||||
|
|
||||||
arch = host.get_fact(facts.server.Arch)
|
arch = host.get_fact(facts.server.Arch)
|
||||||
url = f"https://github.com/chatmail/filtermail/releases/download/v0.7.0/filtermail-{arch}"
|
url = f"https://github.com/chatmail/filtermail/releases/download/v0.6.6/filtermail-{arch}"
|
||||||
sha256sum = {
|
sha256sum = {
|
||||||
"x86_64": "451f295a85b3b12dbb0f89e18ec319f742ee46dec218f20f7923bfb017a248bd",
|
"x86_64": "05c7e7ac244606c2eeb275f2d282ffdbc2403e0169f1cdd3110ffcebdb994a92",
|
||||||
"aarch64": "6833061b2a2028264fdeb32f0a6123e1ff73de57dace125364016300b748452e",
|
"aarch64": "8cf8bbda4d907beca547b365cc7e6753532a74b1712492d0d2f3d2d8a553fb3d",
|
||||||
}[arch]
|
}[arch]
|
||||||
self.download_executable(url, self.bin_path, sha256sum)
|
self.download_executable(url, self.bin_path, sha256sum)
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,3 @@
|
|||||||
# List of headers for incoming messages
|
/^DKIM-Signature:/ IGNORE
|
||||||
# that must be retained for functionality and compatibility reasons
|
/^Authentication-Results:/ IGNORE
|
||||||
/^From:/ DUNNO
|
/^Received:/ IGNORE
|
||||||
/^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
|
|
||||||
|
|||||||
@@ -349,9 +349,9 @@ class ChatmailACFactory:
|
|||||||
qr = (
|
qr = (
|
||||||
f"dclogin:{addr}"
|
f"dclogin:{addr}"
|
||||||
f"?p={password}&v=1"
|
f"?p={password}&v=1"
|
||||||
f"&ih={domain}&ip=993&is=ssl"
|
f"&ih={domain}&ip=993"
|
||||||
f"&sh={domain}&sp=465&ss=ssl"
|
f"&sh={domain}&sp=465"
|
||||||
f"&ic=3"
|
f"&ic=3&ss=default"
|
||||||
)
|
)
|
||||||
future = account.add_transport_from_qr.future(qr)
|
future = account.add_transport_from_qr.future(qr)
|
||||||
else:
|
else:
|
||||||
@@ -362,7 +362,7 @@ class ChatmailACFactory:
|
|||||||
|
|
||||||
# ensure messages stay in INBOX so that they can be
|
# ensure messages stay in INBOX so that they can be
|
||||||
# concurrently fetched via extra IMAP connections during tests
|
# concurrently fetched via extra IMAP connections during tests
|
||||||
account.set_config("bcc_self", "1")
|
account.set_config("delete_server_after", "10")
|
||||||
accounts.append(account)
|
accounts.append(account)
|
||||||
|
|
||||||
for future in futures:
|
for future in futures:
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
# Managed by cmdeploy
|
# Managed by cmdeploy: disable IPv6 in unbound.
|
||||||
server:
|
server:
|
||||||
{% if disable_ipv6 %}
|
|
||||||
interface: 127.0.0.1
|
interface: 127.0.0.1
|
||||||
do-ip6: no
|
do-ip6: no
|
||||||
{% endif %}
|
|
||||||
cache-max-negative-ttl: 0
|
|
||||||
|
|||||||
+15
-56
@@ -156,7 +156,6 @@ Chatmail relay dependency diagram
|
|||||||
postfix --- |10083|filtermail-transport;
|
postfix --- |10083|filtermail-transport;
|
||||||
filtermail-outgoing --- |10025 reinject|postfix;
|
filtermail-outgoing --- |10025 reinject|postfix;
|
||||||
filtermail-incoming --- |10026 reinject|postfix;
|
filtermail-incoming --- |10026 reinject|postfix;
|
||||||
postfix --- |milter opendkim.sock|OpenDKIM
|
|
||||||
dovecot --- |doveauth.socket|doveauth;
|
dovecot --- |doveauth.socket|doveauth;
|
||||||
dovecot --- |message delivery|maildir["maildir
|
dovecot --- |message delivery|maildir["maildir
|
||||||
/home/vmail/.../user"];
|
/home/vmail/.../user"];
|
||||||
@@ -180,66 +179,26 @@ Chatmail relay dependency diagram
|
|||||||
style nginx-right fill:#f66;
|
style nginx-right fill:#f66;
|
||||||
style postfix fill:#f66;
|
style postfix fill:#f66;
|
||||||
style dovecot fill:#f66;
|
style dovecot fill:#f66;
|
||||||
style OpenDKIM fill:#f66;
|
|
||||||
style notification-proxy fill:#f66;
|
style notification-proxy fill:#f66;
|
||||||
|
|
||||||
Accepting and delivering mail
|
Message between users on the same relay
|
||||||
-----------------------------
|
---------------------------------------
|
||||||
|
|
||||||
.. mermaid::
|
.. mermaid::
|
||||||
:caption: This diagram shows all the paths a message can take.
|
:caption: This diagram shows the path a non-federated message takes.
|
||||||
|
|
||||||
flowchart LR
|
graph LR;
|
||||||
subgraph chatmail relay
|
sender --> |465|smtps/smtpd;
|
||||||
subgraph postfix
|
sender --> |587|submission/smtpd;
|
||||||
qmgr .-> lmtp-filtermail["lmtp/lmtp-filtermail (default_transport)"]
|
smtps/smtpd --> |10080|filtermail;
|
||||||
qmgr .-> lmtp["lmtp (local_transport)"]
|
submission/smtpd --> |10080|filtermail;
|
||||||
lmtp --> cleanup["cleanup (lmtp_header_cleanup)"]
|
filtermail --> |10025|smtpd_reinject;
|
||||||
bounce
|
smtpd_reinject --> cleanup;
|
||||||
smtpd-submission["smtpd/submission"]
|
cleanup --> qmgr;
|
||||||
smtpd-smtps["smtpd/smtps"]
|
qmgr --> smtpd_accepts_message;
|
||||||
smtpd-reinject-outgoing["smtpd/reinject-outgoing"] --> authclean["cleanup/authclean (submission_header_cleanup)"]
|
qmgr --> |lmtp|dovecot;
|
||||||
authclean --> qmgr
|
dovecot --> recipient;
|
||||||
smtpd-smtp["smtpd/smtp"]
|
dovecot --> sender's_other_devices;
|
||||||
smtpd-reinject-incoming["smtpd/reinject-incoming"] --> qmgr
|
|
||||||
end
|
|
||||||
lmtp-filtermail --LMTP inet:10083--> filtermail-transport
|
|
||||||
cleanup --LMTP unix:private/dovecot-lmtp --> dovecot
|
|
||||||
dovecot --> maildir
|
|
||||||
smtpd-submission --SMTP inet:10080--> filtermail-outgoing
|
|
||||||
smtpd-smtps --SMTP inet:10080--> filtermail-outgoing
|
|
||||||
filtermail-outgoing --SMTP inet:10025--> smtpd-reinject-outgoing
|
|
||||||
open-dkim["OpenDKIM (signing only)"] <--milter unix:opendkim/opendkim.sock--> smtpd-reinject-outgoing
|
|
||||||
bounce <--milter unix:opendkim/opendkim.sock--> open-dkim
|
|
||||||
bounce --> qmgr
|
|
||||||
nginx
|
|
||||||
smtpd-smtp -.SMTP inet:10081.-> filtermail-incoming
|
|
||||||
nginx -.HTTP inet:10082.-> filtermail-incoming
|
|
||||||
filtermail-incoming --SMTP inet:10026--> smtpd-reinject-incoming
|
|
||||||
end
|
|
||||||
filtermail-transport -.SMTP inet:25.-> mta1[Remote relay]
|
|
||||||
filtermail-transport -.HTTPS /mxdeliv.-> mta1
|
|
||||||
client[Client] -.SMTP inet:587.-> smtpd-submission
|
|
||||||
client -.SMTP inet:465.-> smtpd-smtps
|
|
||||||
client -.SMTP inet:443.-> nginx
|
|
||||||
nginx -.SMTP inet:465.-> smtpd-smtps
|
|
||||||
mta2[Remote relay] -.SMTP inet:25.-> smtpd-smtp
|
|
||||||
mta2 -.HTTPS /mxdeliv.-> nginx
|
|
||||||
style postfix fill:#363
|
|
||||||
style qmgr fill:#252
|
|
||||||
style authclean fill:#252
|
|
||||||
style cleanup fill:#252
|
|
||||||
style lmtp-filtermail fill:#252
|
|
||||||
style lmtp fill:#252
|
|
||||||
style bounce fill:#252
|
|
||||||
style smtpd-submission fill:#252
|
|
||||||
style smtpd-smtps fill:#252
|
|
||||||
style smtpd-reinject-outgoing fill:#252
|
|
||||||
style smtpd-reinject-incoming fill:#252
|
|
||||||
style smtpd-smtp fill:#252
|
|
||||||
style filtermail-outgoing fill:#225
|
|
||||||
style filtermail-incoming fill:#225
|
|
||||||
style filtermail-transport fill:#225
|
|
||||||
|
|
||||||
Operational details of a chatmail relay
|
Operational details of a chatmail relay
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user