Compare commits

..

4 Commits

Author SHA1 Message Date
holger krekel
bcd9d615d4 remove superflous check in tests 2024-03-20 17:58:59 +01:00
holger krekel
d820644f66 fixes 2024-03-20 17:38:39 +01:00
holger krekel
4e97e4dc3d implement persistence via marshal 2024-03-20 17:02:04 +01:00
holger krekel
92a27983be make notifier take a directory 2024-03-20 16:41:41 +01:00
9 changed files with 23 additions and 71 deletions

View File

@@ -1,22 +0,0 @@
# Changelog for chatmail deployment
## unreleased
### Changes since March 15th, 2024
- Fix various tests to pass again with "cmdeploy test".
([#245](https://github.com/deltachat/chatmail/pull/245),
[#242](https://github.com/deltachat/chatmail/pull/242)
- Ensure lets-encrypt certificates are reloaded after renewal
([#244]) https://github.com/deltachat/chatmail/pull/244
- Persist tokens to avoid iOS users loosing push-notifications when the
chatmail metadata service is restarted (happens regularly during deploys)
([#238](https://github.com/deltachat/chatmail/pull/239)
- Fix failing sieve-script compile errors on incoming messages
([#237](https://github.com/deltachat/chatmail/pull/239)
- Fix quota reporting after expunging of old mails
([#233](https://github.com/deltachat/chatmail/pull/239)

View File

@@ -17,10 +17,6 @@ from .config import read_config, Config
NOCREATE_FILE = "/etc/chatmail-nocreate" NOCREATE_FILE = "/etc/chatmail-nocreate"
class UnknownCommand(ValueError):
"""dictproxy handler received an unkown command"""
def encrypt_password(password: str): def encrypt_password(password: str):
# https://doc.dovecot.org/configuration_manual/authentication/password_schemes/ # https://doc.dovecot.org/configuration_manual/authentication/password_schemes/
passhash = crypt.crypt(password, crypt.METHOD_SHA512) passhash = crypt.crypt(password, crypt.METHOD_SHA512)
@@ -131,12 +127,8 @@ def split_and_unescape(s):
def handle_dovecot_request(msg, db, config: Config): def handle_dovecot_request(msg, db, config: Config):
# see https://doc.dovecot.org/3.0/developer_manual/design/dict_protocol/
short_command = msg[0] short_command = msg[0]
if short_command == "H": # HELLO if short_command == "L": # LOOKUP
# we don't do any checking on versions and just return
return
elif short_command == "L": # LOOKUP
parts = msg[1:].split("\t") parts = msg[1:].split("\t")
# Dovecot <2.3.17 has only one part, # Dovecot <2.3.17 has only one part,
@@ -167,7 +159,7 @@ def handle_dovecot_request(msg, db, config: Config):
reply_command = "N" reply_command = "N"
json_res = json.dumps(res) if res else "" json_res = json.dumps(res) if res else ""
return f"{reply_command}{json_res}\n" return f"{reply_command}{json_res}\n"
raise UnknownCommand(msg) return None
def handle_dovecot_protocol(rfile, wfile, db: Database, config: Config): def handle_dovecot_protocol(rfile, wfile, db: Database, config: Config):
@@ -175,14 +167,12 @@ def handle_dovecot_protocol(rfile, wfile, db: Database, config: Config):
msg = rfile.readline().strip().decode() msg = rfile.readline().strip().decode()
if not msg: if not msg:
break break
try: res = handle_dovecot_request(msg, db, config)
res = handle_dovecot_request(msg, db, config) if res:
except UnknownCommand: wfile.write(res.encode("ascii"))
logging.warning("unknown command: %r", msg) wfile.flush()
else: else:
if res: logging.warning("request had no answer: %r", msg)
wfile.write(res.encode("ascii"))
wfile.flush()
class ThreadedUnixStreamServer(ThreadingMixIn, UnixStreamServer): class ThreadedUnixStreamServer(ThreadingMixIn, UnixStreamServer):

View File

@@ -18,14 +18,14 @@ hooks = events.HookCollection()
@hooks.on(events.RawEvent) @hooks.on(events.RawEvent)
def log_event(event): def log_event(event):
if event.kind == EventType.INFO: if event.kind == EventType.INFO:
logging.info("%s", event.msg) logging.info(event.msg)
elif event.kind == EventType.WARNING: elif event.kind == EventType.WARNING:
logging.warning("%s", event.msg) logging.warning(event.msg)
@hooks.on(events.RawEvent(EventType.ERROR)) @hooks.on(events.RawEvent(EventType.ERROR))
def log_error(event): def log_error(event):
logging.error("%s", event.msg) logging.error(event.msg)
@hooks.on(events.MemberListChanged) @hooks.on(events.MemberListChanged)
@@ -48,9 +48,6 @@ def on_group_name_changed(event):
@hooks.on(events.NewMessage(func=lambda e: not e.command)) @hooks.on(events.NewMessage(func=lambda e: not e.command))
def echo(event): def echo(event):
snapshot = event.message_snapshot snapshot = event.message_snapshot
if snapshot.is_info:
# Ignore info messages
return
if snapshot.text or snapshot.file: if snapshot.text or snapshot.file:
snapshot.chat.send_message(text=snapshot.text, file=snapshot.file) snapshot.chat.send_message(text=snapshot.text, file=snapshot.file)
@@ -62,7 +59,6 @@ def help_command(event):
def main(): def main():
logging.basicConfig(level=logging.INFO)
path = os.environ.get("PATH") path = os.environ.get("PATH")
venv_path = sys.argv[0].strip("echobot") venv_path = sys.argv[0].strip("echobot")
os.environ["PATH"] = path + ":" + venv_path os.environ["PATH"] = path + ":" + venv_path
@@ -84,4 +80,5 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
main() main()

View File

@@ -75,14 +75,6 @@ def test_handle_dovecot_request(db, example_config):
assert userdata["password"].startswith("{SHA512-CRYPT}") assert userdata["password"].startswith("{SHA512-CRYPT}")
def test_handle_dovecot_protocol_hello_is_skipped(db, example_config, caplog):
rfile = io.BytesIO(b"H3\t2\t0\t\tauth\n")
wfile = io.BytesIO()
handle_dovecot_protocol(rfile, wfile, db, example_config)
assert wfile.getvalue() == b""
assert not caplog.messages
def test_handle_dovecot_protocol(db, example_config): def test_handle_dovecot_protocol(db, example_config):
rfile = io.BytesIO( rfile = io.BytesIO(
b"H3\t2\t0\t\tauth\nLshared/userdb/foobar@chat.example.org\tfoobar@chat.example.org\n" b"H3\t2\t0\t\tauth\nLshared/userdb/foobar@chat.example.org\tfoobar@chat.example.org\n"

View File

@@ -350,18 +350,15 @@ def _configure_dovecot(config: Config, debug: bool = False) -> bool:
need_restart |= lua_push_notification_script.changed need_restart |= lua_push_notification_script.changed
sieve_script = files.put( sieve_script = files.put(
src=importlib.resources.files(__package__).joinpath("dovecot/default.sieve"), src=importlib.resources.files(__package__).joinpath(
"dovecot/default.sieve"
),
dest="/etc/dovecot/default.sieve", dest="/etc/dovecot/default.sieve",
user="root", user="root",
group="root", group="root",
mode="644", mode="644",
) )
need_restart |= sieve_script.changed need_restart |= sieve_script.changed
if sieve_script.changed:
server.shell(
name="compile sieve script",
commands=["/usr/bin/sievec /etc/dovecot/default.sieve"],
)
files.template( files.template(
src=importlib.resources.files(__package__).joinpath("dovecot/expunge.cron.j2"), src=importlib.resources.files(__package__).joinpath("dovecot/expunge.cron.j2"),
@@ -453,9 +450,7 @@ def check_config(config):
blocked_words = "merlinux schmieder testrun.org".split() blocked_words = "merlinux schmieder testrun.org".split()
for key in config.__dict__: for key in config.__dict__:
value = config.__dict__[key] value = config.__dict__[key]
if key.startswith("privacy") and any( if key.startswith("privacy") and any(x in str(value) for x in blocked_words):
x in str(value) for x in blocked_words
):
raise ValueError( raise ValueError(
f"please set your own privacy contacts/addresses in {config._inipath}" f"please set your own privacy contacts/addresses in {config._inipath}"
) )

View File

@@ -1,4 +1,4 @@
SHELL=/bin/sh SHELL=/bin/sh
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
MAILTO=root MAILTO=root
20 16 * * * root /usr/bin/acmetool --batch reconcile && systemctl reload dovecot && systemctl reload postfix 20 16 * * * root /usr/bin/acmetool --batch reconcile

View File

@@ -5,6 +5,8 @@ import importlib
import subprocess import subprocess
import datetime import datetime
from typing import Optional
class DNS: class DNS:
def __init__(self, out, mail_domain): def __init__(self, out, mail_domain):

View File

@@ -1,7 +1,5 @@
require ["imap4flags"]; require ["imap4flags"];
# flag the message so it doesn't cause a push notification
if header :is ["Auto-Submitted"] ["auto-replied", "auto-generated"] { if header :is ["Auto-Submitted"] ["auto-replied", "auto-generated"] {
addflag "$Auto"; addflag "$Auto";
} }

View File

@@ -63,7 +63,7 @@ class TestEndToEndDeltaChat:
addr = ac2.get_config("addr").lower() addr = ac2.get_config("addr").lower()
saved_ok = 0 saved_ok = 0
for line in remote.iter_output("journalctl -n0 -f -u dovecot"): for line in remote.iter_output("journalctl -f -u dovecot"):
if addr not in line: if addr not in line:
# print(line) # print(line)
continue continue
@@ -75,7 +75,7 @@ class TestEndToEndDeltaChat:
) )
lp.indent("good, message sending failed because quota was exceeded") lp.indent("good, message sending failed because quota was exceeded")
return return
if "stored mail into mailbox 'inbox'" in line or "saved mail to inbox" in line: if "saved mail to inbox" in line:
saved_ok += 1 saved_ok += 1
print(f"{saved_ok}: {line}") print(f"{saved_ok}: {line}")
if saved_ok >= num_to_send: if saved_ok >= num_to_send:
@@ -112,7 +112,7 @@ class TestEndToEndDeltaChat:
lp.sec("ac1 sends a message and ac2 marks it as seen") lp.sec("ac1 sends a message and ac2 marks it as seen")
chat = ac1.create_chat(ac2) chat = ac1.create_chat(ac2)
msg = chat.send_text("hi") msg = chat.send_text("hi")
m = ac2._evtracker.wait_next_incoming_message() m = ac2.wait_next_incoming_message()
m.mark_seen() m.mark_seen()
# we can only indirectly wait for mark-seen to cause an smtp-error # we can only indirectly wait for mark-seen to cause an smtp-error
lp.sec("try to wait for markseen to complete and check error states") lp.sec("try to wait for markseen to complete and check error states")
@@ -132,7 +132,7 @@ def test_hide_senders_ip_address(cmfactory):
chat = cmfactory.get_accepted_chat(user1, user2) chat = cmfactory.get_accepted_chat(user1, user2)
chat.send_text("testing submission header cleanup") chat.send_text("testing submission header cleanup")
user2._evtracker.wait_next_incoming_message() user2.wait_next_incoming_message()
user2.direct_imap.select_folder("Inbox") user2.direct_imap.select_folder("Inbox")
msg = user2.direct_imap.get_all_messages()[0] msg = user2.direct_imap.get_all_messages()[0]
assert public_ip not in msg.obj.as_string() assert public_ip not in msg.obj.as_string()
@@ -146,5 +146,5 @@ def test_echobot(cmfactory, chatmail_config, lp):
text = "hi, I hope you text me back" text = "hi, I hope you text me back"
chat.send_text(text) chat.send_text(text)
lp.sec("Wait for reply from echobot") lp.sec("Wait for reply from echobot")
reply = ac._evtracker.wait_next_incoming_message() reply = ac.wait_next_incoming_message()
assert reply.text == text assert reply.text == text