From eaff92cebcadf32bae2a334862428ce18ccf8c78 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 25 Jul 2024 14:19:51 +0200 Subject: [PATCH] don't use filelocks for writing password because there only is a single doveauth process anyway --- chatmaild/src/chatmaild/doveauth.py | 8 +++++++- chatmaild/src/chatmaild/user.py | 21 +++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/chatmaild/src/chatmaild/doveauth.py b/chatmaild/src/chatmaild/doveauth.py index fadd5fdd..1e44a354 100644 --- a/chatmaild/src/chatmaild/doveauth.py +++ b/chatmaild/src/chatmaild/doveauth.py @@ -3,6 +3,7 @@ import json import logging import os import sys +from threading import RLock from .config import Config, read_config from .dictproxy import DictProxy @@ -85,6 +86,10 @@ class AuthDictProxy(DictProxy): def __init__(self, config): super().__init__() self.config = config + # We serialize all password-writes in the single doveauth process + # so that threads can not mangle the password when writing. + # Setting a password is a quite rare event anyway. + self._password_write_lock = RLock() def handle_lookup(self, parts): # Dovecot <2.3.17 has only one part, @@ -140,7 +145,8 @@ class AuthDictProxy(DictProxy): if not is_allowed_to_create(self.config, addr, cleartext_password): return - user.set_password(encrypt_password(cleartext_password)) + with self._password_write_lock: + user.set_password(encrypt_password(cleartext_password)) print(f"Created address: {user}", file=sys.stderr) return user.get_userdb_dict() diff --git a/chatmaild/src/chatmaild/user.py b/chatmaild/src/chatmaild/user.py index 25bc2898..7b468310 100644 --- a/chatmaild/src/chatmaild/user.py +++ b/chatmaild/src/chatmaild/user.py @@ -1,8 +1,6 @@ import logging import os -import filelock - def get_daytimestamp(timestamp) -> int: return int(timestamp) // 86400 * 86400 @@ -39,20 +37,19 @@ class User: def set_password(self, enc_password): """Set the specified password for this user. - If called concurrently from multiple threads - the last password set call will be persisted. + NOTE that this method is not multi-thread/process safe. + The caller has to ensure only a single thread writes to the same + user's password file. """ self.maildir.mkdir(exist_ok=True) - lock_path = self.maildir.joinpath("password.lock") password = enc_password.encode("ascii") - with filelock.FileLock(lock_path): - try: - self.password_path.write_bytes(password) - except PermissionError: - if not self.addr.startswith("echo@"): - logging.error(f"could not write password for: {self.addr}") - raise + try: + self.password_path.write_bytes(password) + except PermissionError: + if not self.addr.startswith("echo@"): + logging.error(f"could not write password for: {self.addr}") + raise def set_last_login_timestamp(self, timestamp): """Track login time with daily granularity