From 3d96f0fdfa6650abc2d598ca78925d0ed54b48ac Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sat, 6 Jul 2024 03:19:57 +0200 Subject: [PATCH] Support iterating over all users with doveadm commands (#344) --- CHANGELOG.md | 3 +++ chatmaild/src/chatmaild/doveauth.py | 18 +++++++++++++++ .../src/chatmaild/tests/test_doveauth.py | 23 +++++++++++++++++++ cmdeploy/src/cmdeploy/dovecot/auth.conf | 4 +++- 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90378ad4..4191e3a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## untagged +- Support iterating over all users with doveadm commands + ([#344](https://github.com/deltachat/chatmail/pull/344)) + - Test and fix for attempts to create inadmissible accounts ([#333](https://github.com/deltachat/chatmail/pull/321)) diff --git a/chatmaild/src/chatmaild/doveauth.py b/chatmaild/src/chatmaild/doveauth.py index 52246beb..700fa73e 100644 --- a/chatmaild/src/chatmaild/doveauth.py +++ b/chatmaild/src/chatmaild/doveauth.py @@ -129,6 +129,15 @@ def lookup_passdb(db, config: Config, user, cleartext_password): ) +def iter_userdb(db, config: Config) -> list: + """Get a list of all user addresses.""" + with db.read_connection() as conn: + rows = conn.execute( + "SELECT addr from users", + ).fetchall() + return [x[0] for x in rows] + + def split_and_unescape(s): """Split strings using double quote as a separator and backslash as escape character into parts.""" @@ -192,6 +201,15 @@ def handle_dovecot_request(msg, db, config: Config): reply_command = "N" json_res = json.dumps(res) if res else "" return f"{reply_command}{json_res}\n" + elif short_command == "I": # ITERATE + # example: I0\t0\tshared/userdb/ + parts = msg[1:].split("\t") + if parts[2] == "shared/userdb/": + result = "".join( + f"Oshared/userdb/{user}\t\n" for user in iter_userdb(db, config) + ) + return f"{result}\n" + raise UnknownCommand(msg) diff --git a/chatmaild/src/chatmaild/tests/test_doveauth.py b/chatmaild/src/chatmaild/tests/test_doveauth.py index 99bf9575..2b27942e 100644 --- a/chatmaild/src/chatmaild/tests/test_doveauth.py +++ b/chatmaild/src/chatmaild/tests/test_doveauth.py @@ -12,6 +12,7 @@ from chatmaild.doveauth import ( handle_dovecot_protocol, handle_dovecot_request, is_allowed_to_create, + iter_userdb, lookup_passdb, ) from chatmaild.newemail import create_newemail_dict @@ -27,6 +28,16 @@ def test_basic(db, example_config): assert data == data2 +def test_iterate_addresses(db, example_config): + addresses = [] + + for i in range(10): + addresses.append(f"asdf1234{i}@chat.example.org") + lookup_passdb(db, example_config, addresses[-1], "q9mr3faue") + res = iter_userdb(db, example_config) + assert res == addresses + + def test_invalid_username_length(example_config): config = example_config config.username_min_length = 6 @@ -108,6 +119,18 @@ def test_handle_dovecot_protocol(db, example_config): assert wfile.getvalue() == b"N\n" +def test_handle_dovecot_protocol_iterate(db, gencreds, example_config): + lookup_passdb(db, example_config, "asdf00000@chat.example.org", "q9mr3faue") + lookup_passdb(db, example_config, "asdf11111@chat.example.org", "q9mr3faue") + rfile = io.BytesIO(b"H3\t2\t0\t\tauth\nI0\t0\tshared/userdb/") + wfile = io.BytesIO() + handle_dovecot_protocol(rfile, wfile, db, example_config) + lines = wfile.getvalue().decode("ascii").split("\n") + assert lines[0] == "Oshared/userdb/asdf00000@chat.example.org\t" + assert lines[1] == "Oshared/userdb/asdf11111@chat.example.org\t" + assert not lines[2] + + def test_50_concurrent_lookups_different_accounts(db, gencreds, example_config): num_threads = 50 req_per_thread = 5 diff --git a/cmdeploy/src/cmdeploy/dovecot/auth.conf b/cmdeploy/src/cmdeploy/dovecot/auth.conf index 4abcdfb9..cc9758d4 100644 --- a/cmdeploy/src/cmdeploy/dovecot/auth.conf +++ b/cmdeploy/src/cmdeploy/dovecot/auth.conf @@ -1,5 +1,7 @@ uri = proxy:/run/doveauth/doveauth.socket:auth -iterate_disable = yes +iterate_disable = no +iterate_prefix = userdb/ + default_pass_scheme = plain # %E escapes characters " (double quote), ' (single quote) and \ (backslash) with \ (backslash). # See