Compare commits

..

4 Commits

Author SHA1 Message Date
link2xt
5b8b41a0d0 Sandbox chatmail-metadata 2024-03-30 14:21:07 +00:00
link2xt
aa55cf3439 Run chatmail-metadata and doveauth as vmail 2024-03-30 14:21:05 +00:00
link2xt
221f4a2b0c Apply systemd restrictions to echobot
These options are suggested by
`systemd-analyze security echobot.service`
2024-03-30 14:17:48 +00:00
link2xt
080ae058d8 Remove non-existent file pattern from MANIFEST.in 2024-03-30 09:14:01 +00:00
8 changed files with 110 additions and 33 deletions

View File

@@ -2,8 +2,11 @@
## untagged ## untagged
- better preserve notification order - Run chatmail-metadata and doveauth as vmail
([#263](https://github.com/deltachat/chatmail/pull/263)) ([#261](https://github.com/deltachat/chatmail/pull/261))
- Apply systemd restrictions to echobot
([#259](https://github.com/deltachat/chatmail/pull/259))
- re-enable running the CI in pull requests, but not concurrently - re-enable running the CI in pull requests, but not concurrently
([#258](https://github.com/deltachat/chatmail/pull/258)) ([#258](https://github.com/deltachat/chatmail/pull/258))

View File

@@ -1,4 +1,3 @@
include src/chatmaild/*.f
include src/chatmaild/ini/*.ini.f include src/chatmaild/ini/*.ini.f
include src/chatmaild/ini/*.ini include src/chatmaild/ini/*.ini
include src/chatmaild/tests/mail-data/* include src/chatmaild/tests/mail-data/*

View File

@@ -1,8 +1,7 @@
import pwd import pwd
from pathlib import Path from pathlib import Path
from threading import Thread from threading import Thread, Event
from queue import Queue
from socketserver import ( from socketserver import (
UnixStreamServer, UnixStreamServer,
StreamRequestHandler, StreamRequestHandler,
@@ -35,7 +34,7 @@ class Notifier:
self.notification_dir = vmail_dir / "pending_notifications" self.notification_dir = vmail_dir / "pending_notifications"
if not self.notification_dir.exists(): if not self.notification_dir.exists():
self.notification_dir.mkdir() self.notification_dir.mkdir()
self.notification_queue = Queue() self.message_arrived_event = Event()
def get_metadata_dict(self, addr): def get_metadata_dict(self, addr):
return FileDict(self.vmail_dir / addr / "metadata.json") return FileDict(self.vmail_dir / addr / "metadata.json")
@@ -61,37 +60,31 @@ class Notifier:
def new_message_for_addr(self, addr): def new_message_for_addr(self, addr):
self.notification_dir.joinpath(addr).touch() self.notification_dir.joinpath(addr).touch()
self.notification_queue.put(addr) self.message_arrived_event.set()
def thread_run_loop(self): def thread_run_loop(self):
requests_session = requests.Session() requests_session = requests.Session()
# on startup deliver all persisted notifications from last process run
self.notification_queue.put(None)
while 1: while 1:
self.message_arrived_event.wait()
self.message_arrived_event.clear()
self.thread_run_one(requests_session) self.thread_run_one(requests_session)
def thread_run_one(self, requests_session): def thread_run_one(self, requests_session):
addr = self.notification_queue.get() for addr_path in self.notification_dir.iterdir():
if addr is None: addr = addr_path.name
# startup, notify any "pending" notifications from last run if "@" not in addr:
for addr_path in self.notification_dir.iterdir(): continue
if "@" in addr_path.name: for token in self.get_tokens(addr):
self.notify_tokens_for(requests_session, addr_path.name) response = requests_session.post(
else: "https://notifications.delta.chat/notify",
self.notify_tokens_for(requests_session, addr) data=token,
timeout=60,
def notify_tokens_for(self, requests_session, addr): )
for token in self.get_tokens(addr): if response.status_code == 410:
response = requests_session.post( # 410 Gone status code
"https://notifications.delta.chat/notify", # means the token is no longer valid.
data=token, self.remove_token(addr, token)
timeout=60, addr_path.unlink()
)
if response.status_code == 410:
# 410 Gone status code
# means the token is no longer valid.
self.remove_token(addr, token)
self.notification_dir.joinpath(addr).unlink(missing_ok=True)
def handle_dovecot_protocol(rfile, wfile, notifier): def handle_dovecot_protocol(rfile, wfile, notifier):

View File

@@ -84,7 +84,7 @@ def test_handle_dovecot_request_happy_path(notifier, testaddr):
assert handle_dovecot_request(f"B{tx2}\t{testaddr}", transactions, notifier) is None assert handle_dovecot_request(f"B{tx2}\t{testaddr}", transactions, notifier) is None
msg = f"S{tx2}\tpriv/guid00/messagenew" msg = f"S{tx2}\tpriv/guid00/messagenew"
assert handle_dovecot_request(msg, transactions, notifier) is None assert handle_dovecot_request(msg, transactions, notifier) is None
assert notifier.notification_queue.get() == testaddr assert notifier.message_arrived_event.is_set()
assert handle_dovecot_request(f"C{tx2}", transactions, notifier) == "O\n" assert handle_dovecot_request(f"C{tx2}", transactions, notifier) == "O\n"
assert not transactions assert not transactions
assert notifier.notification_dir.joinpath(testaddr).exists() assert notifier.notification_dir.joinpath(testaddr).exists()
@@ -159,8 +159,8 @@ def test_handle_dovecot_protocol_messagenew(notifier):
wfile = io.BytesIO() wfile = io.BytesIO()
handle_dovecot_protocol(rfile, wfile, notifier) handle_dovecot_protocol(rfile, wfile, notifier)
assert wfile.getvalue() == b"O\n" assert wfile.getvalue() == b"O\n"
addr = notifier.notification_queue.get() assert notifier.message_arrived_event.is_set()
assert notifier.notification_dir.joinpath(addr).exists() assert notifier.notification_dir.joinpath("user@example.org").exists()
def test_notifier_thread_run(notifier, testaddr): def test_notifier_thread_run(notifier, testaddr):

View File

@@ -478,6 +478,11 @@ def deploy_chatmail(config_path: Path) -> None:
system=True, system=True,
) )
server.shell(
name="Fix file owner in /home/vmail",
commands=["test -d /home/vmail && chown -R vmail:vmail /home/vmail"],
)
apt.update(name="apt update", cache_time=24 * 3600) apt.update(name="apt update", cache_time=24 * 3600)
apt.packages( apt.packages(

View File

@@ -5,6 +5,43 @@ Description=Chatmail dict proxy for IMAP METADATA
ExecStart={execpath} /run/dovecot/metadata.socket vmail /home/vmail/mail/{mail_domain} ExecStart={execpath} /run/dovecot/metadata.socket vmail /home/vmail/mail/{mail_domain}
Restart=always Restart=always
RestartSec=30 RestartSec=30
User=vmail
# Make `systemd-analyze security` happy.
CapabilityBoundingSet=
LockPersonality=true
MemoryDenyWriteExecute=true
NoNewPrivileges=true
PrivateDevices=true
PrivateMounts=true
PrivateTmp=true
PrivateUsers=true
ProtectClock=true
ProtectControlGroups=true
ProtectHostname=true
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectProc=noaccess
ProtectSystem=strict
RemoveIPC=true
RestrictAddressFamilies=AF_UNIX
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
SystemCallArchitectures=native
SystemCallFilter=~@clock
SystemCallFilter=~@cpu-emulation
SystemCallFilter=~@debug
SystemCallFilter=~@module
SystemCallFilter=~@mount
SystemCallFilter=~@obsolete
SystemCallFilter=~@privileged
SystemCallFilter=~@raw-io
SystemCallFilter=~@reboot
SystemCallFilter=~@resources
SystemCallFilter=~@swap
UMask=0077
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@@ -5,6 +5,7 @@ Description=Chatmail dict authentication proxy for dovecot
ExecStart={execpath} /run/dovecot/doveauth.socket vmail /home/vmail/passdb.sqlite {config_path} ExecStart={execpath} /run/dovecot/doveauth.socket vmail /home/vmail/passdb.sqlite {config_path}
Restart=always Restart=always
RestartSec=30 RestartSec=30
User=vmail
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@@ -7,5 +7,44 @@ Environment="PATH={remote_venv_dir}:$PATH"
Restart=always Restart=always
RestartSec=30 RestartSec=30
# Apply security restrictions suggested by
# systemd-analyze security echobot.service
CapabilityBoundingSet=
LockPersonality=true
MemoryDenyWriteExecute=true
NoNewPrivileges=true
PrivateDevices=true
PrivateMounts=true
PrivateTmp=true
PrivateUsers=true
ProtectClock=true
ProtectControlGroups=true
ProtectHostname=true
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectProc=noaccess
# Should be "strict", but we currently write /accounts folder in a protected path
ProtectSystem=full
RemoveIPC=true
RestrictAddressFamilies=AF_INET AF_INET6
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
SystemCallArchitectures=native
SystemCallFilter=~@clock
SystemCallFilter=~@cpu-emulation
SystemCallFilter=~@debug
SystemCallFilter=~@module
SystemCallFilter=~@mount
SystemCallFilter=~@obsolete
SystemCallFilter=~@raw-io
SystemCallFilter=~@reboot
SystemCallFilter=~@resources
SystemCallFilter=~@swap
UMask=0077
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target