mirror of
https://github.com/chatmail/relay.git
synced 2026-05-19 04:18:09 +00:00
move passwords to file in user maildir
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import iniconfig
|
import iniconfig
|
||||||
@@ -50,6 +52,21 @@ class Config:
|
|||||||
res["password"] = enc_password
|
res["password"] = enc_password
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def set_user_password(self, addr, enc_password):
|
||||||
|
# reading and writing user data needs to be atomic
|
||||||
|
# to allow concurrent logins to succeed.
|
||||||
|
userdir = self.get_user_maildir(addr)
|
||||||
|
try:
|
||||||
|
userdir.mkdir()
|
||||||
|
except FileExistsError:
|
||||||
|
pass
|
||||||
|
password_path = userdir.joinpath("password")
|
||||||
|
password_path_tmp = userdir.joinpath("password.tmp")
|
||||||
|
password_path_tmp.write_text(enc_password)
|
||||||
|
os.rename(password_path_tmp, password_path)
|
||||||
|
print(f"Created address: {addr}", file=sys.stderr)
|
||||||
|
return self.get_user_dict(addr=addr, enc_password=enc_password)
|
||||||
|
|
||||||
|
|
||||||
def write_initial_config(inipath, mail_domain, overrides):
|
def write_initial_config(inipath, mail_domain, overrides):
|
||||||
"""Write out default config file, using the specified config value overrides."""
|
"""Write out default config file, using the specified config value overrides."""
|
||||||
|
|||||||
@@ -7,35 +7,17 @@ import sys
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from .config import read_config
|
from .config import read_config
|
||||||
from .database import Database
|
|
||||||
from .lastlogin import get_last_login_from_userdir
|
from .lastlogin import get_last_login_from_userdir
|
||||||
|
|
||||||
|
|
||||||
def delete_inactive_users(db, config, chunksize=100):
|
def delete_inactive_users(config):
|
||||||
cutoff_date = time.time() - config.delete_inactive_users_after * 86400
|
cutoff_date = time.time() - config.delete_inactive_users_after * 86400
|
||||||
pending = []
|
|
||||||
|
|
||||||
def remove(userdir):
|
|
||||||
shutil.rmtree(userdir, ignore_errors=True)
|
|
||||||
pending.append(userdir.name)
|
|
||||||
if len(pending) > chunksize:
|
|
||||||
clear_pending()
|
|
||||||
|
|
||||||
def clear_pending():
|
|
||||||
with db.write_transaction() as conn:
|
|
||||||
for user in pending:
|
|
||||||
conn.execute("DELETE FROM users WHERE addr = ?", (user,))
|
|
||||||
pending.clear()
|
|
||||||
|
|
||||||
for userdir in config.mailboxes_dir.iterdir():
|
for userdir in config.mailboxes_dir.iterdir():
|
||||||
if get_last_login_from_userdir(userdir) < cutoff_date:
|
if get_last_login_from_userdir(userdir) < cutoff_date:
|
||||||
remove(userdir)
|
shutil.rmtree(userdir, ignore_errors=True)
|
||||||
|
|
||||||
clear_pending()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
(cfgpath,) = sys.argv[1:]
|
(cfgpath,) = sys.argv[1:]
|
||||||
config = read_config(cfgpath)
|
config = read_config(cfgpath)
|
||||||
db = Database(config.passdb_path)
|
delete_inactive_users(config)
|
||||||
delete_inactive_users(db, config)
|
|
||||||
|
|||||||
@@ -3,19 +3,13 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from .config import Config, read_config
|
from .config import Config, read_config
|
||||||
from .database import Database
|
|
||||||
from .dictproxy import DictProxy
|
from .dictproxy import DictProxy
|
||||||
|
|
||||||
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)
|
||||||
@@ -60,61 +54,26 @@ def is_allowed_to_create(config: Config, user, cleartext_password) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def get_user_data(db, config: Config, user, conn=None):
|
def lookup_userdb(config: Config, user):
|
||||||
if user == f"echo@{config.mail_domain}":
|
userdir = config.get_user_maildir(user)
|
||||||
return config.get_user_dict(user)
|
password_path = userdir.joinpath("password")
|
||||||
|
result = {}
|
||||||
if conn is None:
|
if password_path.exists():
|
||||||
with db.read_connection() as conn:
|
result = dict(addr=user, password=password_path.read_text())
|
||||||
result = conn.get_user(user)
|
|
||||||
else:
|
|
||||||
result = conn.get_user(user)
|
|
||||||
|
|
||||||
if result:
|
|
||||||
result.update(config.get_user_dict(user))
|
result.update(config.get_user_dict(user))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def lookup_userdb(db, config: Config, user):
|
def lookup_passdb(config: Config, user, cleartext_password):
|
||||||
return get_user_data(db, config, user)
|
userdata = lookup_userdb(config, user)
|
||||||
|
|
||||||
|
|
||||||
def lookup_passdb(db, config: Config, user, cleartext_password):
|
|
||||||
if user == f"echo@{config.mail_domain}":
|
|
||||||
# Echobot writes password it wants to log in with into /run/echobot/password
|
|
||||||
try:
|
|
||||||
password = Path("/run/echobot/password").read_text()
|
|
||||||
except Exception:
|
|
||||||
logging.exception("Exception when trying to read /run/echobot/password")
|
|
||||||
return None
|
|
||||||
|
|
||||||
return config.get_user_dict(user, enc_password=encrypt_password(password))
|
|
||||||
|
|
||||||
userdata = get_user_data(db, config, user)
|
|
||||||
if userdata:
|
if userdata:
|
||||||
return userdata
|
return userdata
|
||||||
if not is_allowed_to_create(config, user, cleartext_password):
|
if not is_allowed_to_create(config, user, cleartext_password):
|
||||||
return
|
return
|
||||||
|
|
||||||
# reading and writing user data needs to be atomic
|
enc_password = encrypt_password(cleartext_password)
|
||||||
# to allow concurrent logins to succeed.
|
config.set_user_password(user, enc_password=enc_password)
|
||||||
with db.write_transaction() as conn:
|
return config.get_user_dict(user, enc_password=enc_password)
|
||||||
userdata = get_user_data(db, config, user, conn=conn)
|
|
||||||
if userdata:
|
|
||||||
return userdata
|
|
||||||
|
|
||||||
enc_password = encrypt_password(cleartext_password)
|
|
||||||
q = "INSERT INTO users (addr, password) VALUES (?, ?)"
|
|
||||||
conn.execute(q, (user, enc_password))
|
|
||||||
print(f"Created address: {user}", file=sys.stderr)
|
|
||||||
return config.get_user_dict(user, enc_password=enc_password)
|
|
||||||
|
|
||||||
|
|
||||||
def iter_userdb(db) -> 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):
|
def split_and_unescape(s):
|
||||||
@@ -144,9 +103,8 @@ def split_and_unescape(s):
|
|||||||
|
|
||||||
|
|
||||||
class AuthDictProxy(DictProxy):
|
class AuthDictProxy(DictProxy):
|
||||||
def __init__(self, db, config):
|
def __init__(self, config):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.db = db
|
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
def handle_lookup(self, parts):
|
def handle_lookup(self, parts):
|
||||||
@@ -158,14 +116,13 @@ class AuthDictProxy(DictProxy):
|
|||||||
args = list(split_and_unescape(args))
|
args = list(split_and_unescape(args))
|
||||||
|
|
||||||
config = self.config
|
config = self.config
|
||||||
db = self.db
|
|
||||||
reply_command = "F"
|
reply_command = "F"
|
||||||
res = ""
|
res = ""
|
||||||
if namespace == "shared":
|
if namespace == "shared":
|
||||||
if type == "userdb":
|
if type == "userdb":
|
||||||
user = args[0]
|
user = args[0]
|
||||||
if user.endswith(f"@{config.mail_domain}"):
|
if user.endswith(f"@{config.mail_domain}"):
|
||||||
res = lookup_userdb(db, config, user)
|
res = lookup_userdb(config, user)
|
||||||
if res:
|
if res:
|
||||||
reply_command = "O"
|
reply_command = "O"
|
||||||
else:
|
else:
|
||||||
@@ -173,7 +130,7 @@ class AuthDictProxy(DictProxy):
|
|||||||
elif type == "passdb":
|
elif type == "passdb":
|
||||||
user = args[1]
|
user = args[1]
|
||||||
if user.endswith(f"@{config.mail_domain}"):
|
if user.endswith(f"@{config.mail_domain}"):
|
||||||
res = lookup_passdb(db, config, user, cleartext_password=args[0])
|
res = lookup_passdb(config, user, cleartext_password=args[0])
|
||||||
if res:
|
if res:
|
||||||
reply_command = "O"
|
reply_command = "O"
|
||||||
else:
|
else:
|
||||||
@@ -184,16 +141,21 @@ class AuthDictProxy(DictProxy):
|
|||||||
def handle_iterate(self, parts):
|
def handle_iterate(self, parts):
|
||||||
# example: I0\t0\tshared/userdb/
|
# example: I0\t0\tshared/userdb/
|
||||||
if parts[2] == "shared/userdb/":
|
if parts[2] == "shared/userdb/":
|
||||||
db = self.db
|
result = "".join(
|
||||||
result = "".join(f"Oshared/userdb/{user}\t\n" for user in iter_userdb(db))
|
f"Oshared/userdb/{user}\t\n" for user in self.iter_userdb()
|
||||||
|
)
|
||||||
return f"{result}\n"
|
return f"{result}\n"
|
||||||
|
|
||||||
|
def iter_userdb(self) -> list:
|
||||||
|
"""Get a list of all user addresses."""
|
||||||
|
getuserpaths = self.config.mailboxes_dir.iterdir()
|
||||||
|
return [x.name for x in getuserpaths if "@" in x.name]
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
socket, cfgpath = sys.argv[1:]
|
socket, cfgpath = sys.argv[1:]
|
||||||
config = read_config(cfgpath)
|
config = read_config(cfgpath)
|
||||||
db = Database(config.passdb_path)
|
|
||||||
|
|
||||||
dictproxy = AuthDictProxy(db=db, config=config)
|
dictproxy = AuthDictProxy(config=config)
|
||||||
|
|
||||||
dictproxy.serve_forever_from_socket(socket)
|
dictproxy.serve_forever_from_socket(socket)
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ it will echo back any message that has non-empty text and also supports the /hel
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from deltachat_rpc_client import Bot, DeltaChat, EventType, Rpc, events
|
from deltachat_rpc_client import Bot, DeltaChat, EventType, Rpc, events
|
||||||
|
|
||||||
@@ -80,23 +78,17 @@ def main():
|
|||||||
bot = Bot(account, hooks)
|
bot = Bot(account, hooks)
|
||||||
|
|
||||||
config = read_config(sys.argv[1])
|
config = read_config(sys.argv[1])
|
||||||
|
addr = "echo@" + config.mail_domain
|
||||||
|
|
||||||
# Create password file
|
# Create password file
|
||||||
if bot.is_configured():
|
if bot.is_configured():
|
||||||
password = bot.account.get_config("mail_pw")
|
password = bot.account.get_config("mail_pw")
|
||||||
else:
|
else:
|
||||||
password = create_newemail_dict(config)["password"]
|
password = create_newemail_dict(config)["password"]
|
||||||
Path("/run/echobot/password").write_text(password)
|
config.set_user_password(addr, password)
|
||||||
|
|
||||||
# Give the user which doveauth runs as access to the password file.
|
|
||||||
subprocess.run(
|
|
||||||
["/usr/bin/setfacl", "-m", "user:vmail:r", "/run/echobot/password"],
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not bot.is_configured():
|
if not bot.is_configured():
|
||||||
email = "echo@" + config.mail_domain
|
bot.configure(addr, password)
|
||||||
bot.configure(email, password)
|
|
||||||
bot.run_forever()
|
bot.run_forever()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ from pathlib import Path
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from chatmaild.config import read_config, write_initial_config
|
from chatmaild.config import read_config, write_initial_config
|
||||||
from chatmaild.database import Database
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -54,13 +53,6 @@ def gencreds(maildomain):
|
|||||||
return lambda domain=None: next(gen(domain))
|
return lambda domain=None: next(gen(domain))
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def db(tmpdir):
|
|
||||||
db_path = tmpdir / "passdb.sqlite"
|
|
||||||
print("database path:", db_path)
|
|
||||||
return Database(db_path)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def maildata(request):
|
def maildata(request):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -25,14 +25,13 @@ def test_delete_skips_non_email_dir(db, example_config):
|
|||||||
assert not list(userdir.iterdir())
|
assert not list(userdir.iterdir())
|
||||||
|
|
||||||
|
|
||||||
def test_delete_inactive_users(db, example_config):
|
def test_delete_inactive_users(example_config):
|
||||||
new = time.time()
|
new = time.time()
|
||||||
old = new - (example_config.delete_inactive_users_after * 86400) - 1
|
old = new - (example_config.delete_inactive_users_after * 86400) - 1
|
||||||
|
|
||||||
def create_user(addr, last_login):
|
def create_user(addr, last_login):
|
||||||
lookup_passdb(db, example_config, addr, "q9mr3faue")
|
lookup_passdb(example_config, addr, "q9mr3faue")
|
||||||
md = example_config.get_user_maildir(addr)
|
md = example_config.get_user_maildir(addr)
|
||||||
md.mkdir(parents=True)
|
|
||||||
md.joinpath("cur").mkdir()
|
md.joinpath("cur").mkdir()
|
||||||
md.joinpath("cur", "something").mkdir()
|
md.joinpath("cur", "something").mkdir()
|
||||||
write_last_login_to_userdir(md, timestamp=last_login)
|
write_last_login_to_userdir(md, timestamp=last_login)
|
||||||
@@ -42,8 +41,6 @@ def test_delete_inactive_users(db, example_config):
|
|||||||
for i in range(150):
|
for i in range(150):
|
||||||
addr = f"oldold{i:03}@chat.example.org"
|
addr = f"oldold{i:03}@chat.example.org"
|
||||||
create_user(addr, last_login=old)
|
create_user(addr, last_login=old)
|
||||||
with db.read_connection() as conn:
|
|
||||||
assert conn.get_user(addr)
|
|
||||||
to_remove.append(addr)
|
to_remove.append(addr)
|
||||||
|
|
||||||
remain = []
|
remain = []
|
||||||
@@ -57,17 +54,15 @@ def test_delete_inactive_users(db, example_config):
|
|||||||
for addr in to_remove:
|
for addr in to_remove:
|
||||||
assert example_config.get_user_maildir(addr).exists()
|
assert example_config.get_user_maildir(addr).exists()
|
||||||
|
|
||||||
delete_inactive_users(db, example_config)
|
delete_inactive_users(example_config)
|
||||||
|
|
||||||
for p in example_config.mailboxes_dir.iterdir():
|
for p in example_config.mailboxes_dir.iterdir():
|
||||||
assert not p.name.startswith("old")
|
assert not p.name.startswith("old")
|
||||||
|
|
||||||
for addr in to_remove:
|
for addr in to_remove:
|
||||||
assert not example_config.get_user_maildir(addr).exists()
|
assert not example_config.get_user_maildir(addr).exists()
|
||||||
with db.read_connection() as conn:
|
|
||||||
assert not conn.get_user(addr)
|
|
||||||
|
|
||||||
for addr in remain:
|
for addr in remain:
|
||||||
assert example_config.get_user_maildir(addr).exists()
|
userdir = example_config.get_user_maildir(addr)
|
||||||
with db.read_connection() as conn:
|
assert userdir.exists()
|
||||||
assert conn.get_user(addr)
|
assert userdir.joinpath("password").read_text()
|
||||||
|
|||||||
@@ -6,35 +6,35 @@ import traceback
|
|||||||
|
|
||||||
import chatmaild.doveauth
|
import chatmaild.doveauth
|
||||||
import pytest
|
import pytest
|
||||||
from chatmaild.database import DBError
|
|
||||||
from chatmaild.doveauth import (
|
from chatmaild.doveauth import (
|
||||||
AuthDictProxy,
|
AuthDictProxy,
|
||||||
get_user_data,
|
|
||||||
is_allowed_to_create,
|
is_allowed_to_create,
|
||||||
iter_userdb,
|
|
||||||
lookup_passdb,
|
lookup_passdb,
|
||||||
|
lookup_userdb,
|
||||||
)
|
)
|
||||||
from chatmaild.newemail import create_newemail_dict
|
from chatmaild.newemail import create_newemail_dict
|
||||||
|
|
||||||
|
|
||||||
def test_basic(db, example_config):
|
def test_basic(example_config):
|
||||||
lookup_passdb(db, example_config, "asdf12345@chat.example.org", "q9mr3faue")
|
lookup_passdb(example_config, "asdf12345@chat.example.org", "q9mr3faue")
|
||||||
data = get_user_data(db, example_config, "asdf12345@chat.example.org")
|
data = lookup_userdb(example_config, "asdf12345@chat.example.org")
|
||||||
assert data
|
assert data
|
||||||
data2 = lookup_passdb(
|
data2 = lookup_passdb(
|
||||||
db, example_config, "asdf12345@chat.example.org", "q9mr3jewvadsfaue"
|
example_config, "asdf12345@chat.example.org", "q9mr3jewvadsfaue"
|
||||||
)
|
)
|
||||||
assert data == data2
|
assert data == data2
|
||||||
|
|
||||||
|
|
||||||
def test_iterate_addresses(db, example_config):
|
def test_iterate_addresses(example_config):
|
||||||
addresses = []
|
addresses = []
|
||||||
|
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
addresses.append(f"asdf1234{i}@chat.example.org")
|
addresses.append(f"asdf1234{i}@chat.example.org")
|
||||||
lookup_passdb(db, example_config, addresses[-1], "q9mr3faue")
|
lookup_passdb(example_config, addresses[-1], "q9mr3faue")
|
||||||
res = iter_userdb(db)
|
|
||||||
assert res == addresses
|
dictproxy = AuthDictProxy(config=example_config)
|
||||||
|
res = dictproxy.iter_userdb()
|
||||||
|
assert set(res) == set(addresses)
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_username_length(example_config):
|
def test_invalid_username_length(example_config):
|
||||||
@@ -51,40 +51,27 @@ def test_invalid_username_length(example_config):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_dont_overwrite_password_on_wrong_login(db, example_config):
|
def test_dont_overwrite_password_on_wrong_login(example_config):
|
||||||
"""Test that logging in with a different password doesn't create a new user"""
|
"""Test that logging in with a different password doesn't create a new user"""
|
||||||
res = lookup_passdb(
|
res = lookup_passdb(
|
||||||
db, example_config, "newuser12@chat.example.org", "kajdlkajsldk12l3kj1983"
|
example_config, "newuser12@chat.example.org", "kajdlkajsldk12l3kj1983"
|
||||||
)
|
)
|
||||||
assert res["password"]
|
assert res["password"]
|
||||||
res2 = lookup_passdb(db, example_config, "newuser12@chat.example.org", "kajdslqwe")
|
res2 = lookup_passdb(example_config, "newuser12@chat.example.org", "kajdslqwe")
|
||||||
# this function always returns a password hash, which is actually compared by dovecot.
|
# this function always returns a password hash, which is actually compared by dovecot.
|
||||||
assert res["password"] == res2["password"]
|
assert res["password"] == res2["password"]
|
||||||
|
|
||||||
|
|
||||||
def test_nocreate_file(db, monkeypatch, tmpdir, example_config):
|
def test_nocreate_file(monkeypatch, tmpdir, example_config):
|
||||||
p = tmpdir.join("nocreate")
|
p = tmpdir.join("nocreate")
|
||||||
p.write("")
|
p.write("")
|
||||||
monkeypatch.setattr(chatmaild.doveauth, "NOCREATE_FILE", str(p))
|
monkeypatch.setattr(chatmaild.doveauth, "NOCREATE_FILE", str(p))
|
||||||
lookup_passdb(
|
lookup_passdb(example_config, "newuser12@chat.example.org", "zequ0Aimuchoodaechik")
|
||||||
db, example_config, "newuser12@chat.example.org", "zequ0Aimuchoodaechik"
|
assert not lookup_userdb(example_config, "newuser12@chat.example.org")
|
||||||
)
|
|
||||||
assert not get_user_data(db, example_config, "newuser12@chat.example.org")
|
|
||||||
|
|
||||||
|
|
||||||
def test_db_version(db):
|
def test_handle_dovecot_request(example_config):
|
||||||
assert db.get_schema_version() == 1
|
dictproxy = AuthDictProxy(config=example_config)
|
||||||
|
|
||||||
|
|
||||||
def test_too_high_db_version(db):
|
|
||||||
with db.write_transaction() as conn:
|
|
||||||
conn.execute("PRAGMA user_version=%s;" % (999,))
|
|
||||||
with pytest.raises(DBError):
|
|
||||||
db.ensure_tables()
|
|
||||||
|
|
||||||
|
|
||||||
def test_handle_dovecot_request(db, example_config):
|
|
||||||
dictproxy = AuthDictProxy(db=db, config=example_config)
|
|
||||||
|
|
||||||
# Test that password can contain ", ', \ and /
|
# Test that password can contain ", ', \ and /
|
||||||
msg = (
|
msg = (
|
||||||
@@ -100,8 +87,8 @@ 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):
|
def test_handle_dovecot_protocol_hello_is_skipped(example_config, caplog):
|
||||||
dictproxy = AuthDictProxy(db=db, config=example_config)
|
dictproxy = AuthDictProxy(config=example_config)
|
||||||
rfile = io.BytesIO(b"H3\t2\t0\t\tauth\n")
|
rfile = io.BytesIO(b"H3\t2\t0\t\tauth\n")
|
||||||
wfile = io.BytesIO()
|
wfile = io.BytesIO()
|
||||||
dictproxy.loop_forever(rfile, wfile)
|
dictproxy.loop_forever(rfile, wfile)
|
||||||
@@ -109,8 +96,8 @@ def test_handle_dovecot_protocol_hello_is_skipped(db, example_config, caplog):
|
|||||||
assert not caplog.messages
|
assert not caplog.messages
|
||||||
|
|
||||||
|
|
||||||
def test_handle_dovecot_protocol_user_not_exists(db, example_config):
|
def test_handle_dovecot_protocol_user_not_exists(example_config):
|
||||||
dictproxy = AuthDictProxy(db=db, config=example_config)
|
dictproxy = AuthDictProxy(config=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"
|
||||||
)
|
)
|
||||||
@@ -119,29 +106,29 @@ def test_handle_dovecot_protocol_user_not_exists(db, example_config):
|
|||||||
assert wfile.getvalue() == b"N\n"
|
assert wfile.getvalue() == b"N\n"
|
||||||
|
|
||||||
|
|
||||||
def test_handle_dovecot_protocol_iterate(db, gencreds, example_config):
|
def test_handle_dovecot_protocol_iterate(gencreds, example_config):
|
||||||
dictproxy = AuthDictProxy(db=db, config=example_config)
|
dictproxy = AuthDictProxy(config=example_config)
|
||||||
lookup_passdb(db, example_config, "asdf00000@chat.example.org", "q9mr3faue")
|
lookup_passdb(example_config, "asdf00000@chat.example.org", "q9mr3faue")
|
||||||
lookup_passdb(db, example_config, "asdf11111@chat.example.org", "q9mr3faue")
|
lookup_passdb(example_config, "asdf11111@chat.example.org", "q9mr3faue")
|
||||||
rfile = io.BytesIO(b"H3\t2\t0\t\tauth\nI0\t0\tshared/userdb/")
|
rfile = io.BytesIO(b"H3\t2\t0\t\tauth\nI0\t0\tshared/userdb/")
|
||||||
wfile = io.BytesIO()
|
wfile = io.BytesIO()
|
||||||
dictproxy.loop_forever(rfile, wfile)
|
dictproxy.loop_forever(rfile, wfile)
|
||||||
lines = wfile.getvalue().decode("ascii").split("\n")
|
lines = wfile.getvalue().decode("ascii").split("\n")
|
||||||
assert lines[0] == "Oshared/userdb/asdf00000@chat.example.org\t"
|
assert "Oshared/userdb/asdf00000@chat.example.org\t" in lines
|
||||||
assert lines[1] == "Oshared/userdb/asdf11111@chat.example.org\t"
|
assert "Oshared/userdb/asdf11111@chat.example.org\t" in lines
|
||||||
assert not lines[2]
|
assert not lines[2]
|
||||||
|
|
||||||
|
|
||||||
def test_50_concurrent_lookups_different_accounts(db, gencreds, example_config):
|
def test_50_concurrent_lookups_different_accounts(gencreds, example_config):
|
||||||
num_threads = 50
|
num_threads = 50
|
||||||
req_per_thread = 5
|
req_per_thread = 5
|
||||||
results = queue.Queue()
|
results = queue.Queue()
|
||||||
|
|
||||||
def lookup(db):
|
def lookup():
|
||||||
for i in range(req_per_thread):
|
for i in range(req_per_thread):
|
||||||
addr, password = gencreds()
|
addr, password = gencreds()
|
||||||
try:
|
try:
|
||||||
lookup_passdb(db, example_config, addr, password)
|
lookup_passdb(example_config, addr, password)
|
||||||
except Exception:
|
except Exception:
|
||||||
results.put(traceback.format_exc())
|
results.put(traceback.format_exc())
|
||||||
else:
|
else:
|
||||||
@@ -149,7 +136,7 @@ def test_50_concurrent_lookups_different_accounts(db, gencreds, example_config):
|
|||||||
|
|
||||||
threads = []
|
threads = []
|
||||||
for i in range(num_threads):
|
for i in range(num_threads):
|
||||||
thread = threading.Thread(target=lookup, args=(db,), daemon=True)
|
thread = threading.Thread(target=lookup, daemon=True)
|
||||||
threads.append(thread)
|
threads.append(thread)
|
||||||
|
|
||||||
print(f"created {num_threads} threads, starting them and waiting for results")
|
print(f"created {num_threads} threads, starting them and waiting for results")
|
||||||
|
|||||||
Reference in New Issue
Block a user