Compare commits

..

7 Commits

Author SHA1 Message Date
holger krekel
ca763960e5 streamline account creation and add tests
also incorporates nine.testrun.org user policies
2023-11-01 20:30:12 +01:00
holger krekel
3a9db729f8 simplify sysctl call 2023-10-31 22:03:03 +01:00
holger krekel
7eb86cba34 increase inotify limits for dovecot 2023-10-31 22:03:03 +01:00
link2xt
5633c0612e dovecot: increase number of simultaneous connections handled by imap-login
Otherwise deltachat core CI running fails with "Connection queue full"
error on IMAP connections.
2023-10-29 19:09:33 +00:00
holger krekel
d5912b909c fix benchmark script 2023-10-28 16:50:24 +02:00
link2xt
f75eb0658c Require that passwords are at least 10 characters long 2023-10-28 13:38:15 +00:00
link2xt
7c5ec1e0df Add scripts/generate-dns-zone.sh 2023-10-24 21:23:20 +00:00
7 changed files with 98 additions and 25 deletions

View File

@@ -21,15 +21,40 @@ def encrypt_password(password: str):
return "{SHA512-CRYPT}" + passhash
def create_user(db, user, password):
def is_allowed_to_create(user, cleartext_password) -> bool:
"""Return True if user and password are admissable."""
if os.path.exists(NOCREATE_FILE):
logging.warning(
f"Didn't create account: {NOCREATE_FILE} exists. Delete the file to enable account creation."
)
return
logging.warning(f"blocked account creation because {NOCREATE_FILE!r} exists.")
return False
if len(cleartext_password) < 10:
logging.warning("Password needs to be at least 10 characters long")
return False
parts = user.split("@")
if len(parts) != 2:
logging.warning(f"user {user!r} is not a proper e-mail address")
return False
localpart, domain = parts
if domain == "nine.testrun.org":
# nine.testrun.org policy, username has to be exactly nine chars
if len(localpart) != 9:
logging.warning(f"localpart {localpart!r} has not exactly nine chars")
return False
return True
def create_user(db, user, encrypted_password):
with db.write_transaction() as conn:
conn.create_user(user, password)
return dict(home=f"/home/vmail/{user}", uid="vmail", gid="vmail", password=password)
conn.create_user(user, encrypted_password)
return dict(
home=f"/home/vmail/{user}",
uid="vmail",
gid="vmail",
password=encrypted_password,
)
def get_user_data(db, user):
@@ -45,10 +70,13 @@ def lookup_userdb(db, user):
return get_user_data(db, user)
def lookup_passdb(db, user, password):
def lookup_passdb(db, user, cleartext_password):
userdata = get_user_data(db, user)
if not userdata:
return create_user(db, user, encrypt_password(password))
if not is_allowed_to_create(user, cleartext_password):
return
encrypted_password = encrypt_password(cleartext_password)
userdata = create_user(db=db, user=user, encrypted_password=encrypted_password)
userdata["password"] = userdata["password"].strip()
return userdata
@@ -72,7 +100,7 @@ def handle_dovecot_request(msg, db, mail_domain):
reply_command = "N"
elif type == "passdb":
if user.endswith(f"@{mail_domain}"):
res = lookup_passdb(db, user, password=args[0])
res = lookup_passdb(db, user, cleartext_password=args[0])
if res:
reply_command = "O"
else:

View File

@@ -183,6 +183,17 @@ def _configure_dovecot(mail_server: str, debug: bool = False) -> bool:
mode="644",
)
# as per https://doc.dovecot.org/configuration_manual/os/
# it is recommended to set the following inotify limits
for name in ("max_user_instances", "max_user_watches"):
key = f"fs.inotify.{name}"
server.sysctl(
name=f"Change {key}",
key=key,
value=65535,
persist=True,
)
return need_restart

View File

@@ -118,6 +118,24 @@ service auth-worker {
user = vmail
}
service imap-login {
# High-security mode.
# Each process serves a single connection and exits afterwards.
# This is the default, but we set it explicitly to be sure.
# See <https://doc.dovecot.org/admin_manual/login_processes/#high-security-mode> for details.
service_count = 1
# Inrease the number of simultaneous connections.
#
# As of Dovecot 2.3.19.1 the default is 100 processes.
# Combined with `service_count = 1` it means only 100 connections
# can be handled simultaneously.
process_limit = 10000
# Avoid startup latency for new connections.
process_min_avail = 10
}
ssl = required
ssl_cert = </var/lib/acme/live/{{ config.hostname }}/fullchain
ssl_key = </var/lib/acme/live/{{ config.hostname }}/privkey

View File

@@ -1,4 +1,4 @@
#!/bin/bash
set -e
online-tests/venv/bin/pytest online-tests/benchmark.py -vrx
venv/bin/pytest online-tests/benchmark.py -vrx

View File

@@ -1,9 +1,10 @@
import os
import json
import pytest
import chatmaild.dictproxy
from chatmaild.dictproxy import get_user_data, lookup_passdb
from chatmaild.dictproxy import get_user_data, lookup_passdb, handle_dovecot_request
from chatmaild.database import Database, DBError
@@ -14,13 +15,13 @@ def db(tmpdir):
return Database(db_path)
def test_basic(db):
chatmaild.dictproxy.NOCREATE_FILE = "/tmp/nocreate"
if os.path.exists(chatmaild.dictproxy.NOCREATE_FILE):
os.remove(chatmaild.dictproxy.NOCREATE_FILE)
lookup_passdb(db, "link2xt@c1.testrun.org", "asdf")
lookup_passdb(db, "link2xt@c1.testrun.org", "Pieg9aeToe3eghuthe5u")
data = get_user_data(db, "link2xt@c1.testrun.org")
assert data
data2 = lookup_passdb(db, "link2xt@c1.testrun.org", "Pieg9aeToe3eghuthe5u")
assert data == data2
def test_dont_overwrite_password_on_wrong_login(db):
@@ -32,14 +33,12 @@ def test_dont_overwrite_password_on_wrong_login(db):
assert res["password"] == res2["password"]
def test_nocreate_file(db):
chatmaild.dictproxy.NOCREATE_FILE = "/tmp/nocreate"
with open(chatmaild.dictproxy.NOCREATE_FILE, "w+") as f:
f.write("")
assert os.path.exists(chatmaild.dictproxy.NOCREATE_FILE)
lookup_passdb(db, "newuser1@something.org", "kajdlqweqwe")
def test_nocreate_file(db, monkeypatch, tmpdir):
p = tmpdir.join("nocreate")
p.write("")
monkeypatch.setattr(chatmaild.dictproxy, "NOCREATE_FILE", str(p))
lookup_passdb(db, "newuser1@something.org", "zequ0Aimuchoodaechik")
assert not get_user_data(db, "newuser1@something.org")
os.remove(chatmaild.dictproxy.NOCREATE_FILE)
def test_db_version(db):
@@ -51,3 +50,15 @@ def test_too_high_db_version(db):
conn.execute("PRAGMA user_version=%s;" % (999,))
with pytest.raises(DBError):
db.ensure_tables()
def test_handle_dovecot_request(db):
msg = ('Lshared/passdb/laksjdlaksjdlaksjdlk12j3l1k2j3123/'
'some42@c3.testrun.org\tsome42@c3.testrun.org')
res = handle_dovecot_request(msg, db, "c3.testrun.org")
assert res
assert res[0] == "O" and res.endswith("\n")
userdata = json.loads(res[1:].strip())
assert userdata["home"] == "/home/vmail/some42@c3.testrun.org"
assert userdata["uid"] == userdata["gid"] == "vmail"
assert userdata["password"].startswith("{SHA512-CRYPT}")

View File

@@ -195,8 +195,8 @@ def gencreds(maildomain):
num = next(count)
alphanumeric = "abcdefghijklmnopqrstuvwxyz1234567890"
user = "".join(random.choices(alphanumeric, k=10))
user = f"ac{num}_{user}"
password = "".join(random.choices(alphanumeric, k=10))
user = f"ac{num}_{user}"[:9]
password = "".join(random.choices(alphanumeric, k=12))
yield f"{user}@{domain}", f"{password}"
return lambda domain=None: next(gen(domain))

View File

@@ -23,6 +23,11 @@ def test_login_basic_functioning(imap_or_smtp, gencreds, lp):
with pytest.raises(imap_or_smtp.AuthError):
imap_or_smtp.login(user, password + "wrong")
lp.sec(f"creating users with a short password is not allowed")
user, _password = gencreds()
with pytest.raises(imap_or_smtp.AuthError):
imap_or_smtp.login(user, "admin")
def test_login_same_password(imap_or_smtp, gencreds):
"""Test two different users logging in with the same password