merge lastlogin and doveauth logic to use the "password" file for both states

This commit is contained in:
holger krekel
2024-07-23 11:11:36 +02:00
parent e3ff82544a
commit 68a62537e1
7 changed files with 41 additions and 44 deletions

View File

@@ -4,6 +4,8 @@ from pathlib import Path
import iniconfig
echobot_password_path = Path("/run/echobot/password")
def read_config(inipath):
assert Path(inipath).exists(), inipath
@@ -45,21 +47,18 @@ class Config:
return res
raise ValueError(f"invalid address {addr!r}")
def get_user_dict(self, addr, enc_password=None):
def get_user_dict(self, addr, enc_password):
home = self.get_user_maildir(addr)
res = dict(home=str(home), uid="vmail", gid="vmail")
if enc_password is not None:
res["password"] = enc_password
return res
return dict(
addr=addr, home=str(home), uid="vmail", gid="vmail", password=enc_password
)
def set_user_password(self, addr, enc_password):
# reading and writing user data needs to be atomic
# to allow concurrent logins to succeed.
assert not addr.startswith("echo@"), addr
userdir = self.get_user_maildir(addr)
try:
userdir.mkdir()
except FileExistsError:
pass
userdir.mkdir(exist_ok=True)
password_path = userdir.joinpath("password")
password_path_tmp = userdir.joinpath("password.tmp")
password_path_tmp.write_text(enc_password)

View File

@@ -4,7 +4,7 @@ import logging
import os
import sys
from .config import Config, read_config
from .config import Config, echobot_password_path, read_config
from .dictproxy import DictProxy
NOCREATE_FILE = "/etc/chatmail-nocreate"
@@ -131,12 +131,17 @@ class AuthDictProxy(DictProxy):
def lookup_userdb(self, user):
userdir = self.config.get_user_maildir(user)
password_path = userdir.joinpath("password")
result = {}
if password_path.exists():
result = dict(addr=user, password=password_path.read_text())
result.update(self.config.get_user_dict(user))
return result
if user.startswith("echo@"):
password_path = echobot_password_path
else:
password_path = userdir.joinpath("password")
try:
enc_password = password_path.read_text()
except FileNotFoundError:
return {}
else:
return self.config.get_user_dict(user, enc_password=enc_password)
def lookup_passdb(self, user, cleartext_password):
userdata = self.lookup_userdb(user)

View File

@@ -6,11 +6,13 @@ it will echo back any message that has non-empty text and also supports the /hel
import logging
import os
import subprocess
import sys
from deltachat_rpc_client import Bot, DeltaChat, EventType, Rpc, events
from chatmaild.config import read_config
from chatmaild.config import echobot_password_path, read_config
from chatmaild.doveauth import encrypt_password
from chatmaild.newemail import create_newemail_dict
hooks = events.HookCollection()
@@ -85,10 +87,16 @@ def main():
password = bot.account.get_config("mail_pw")
else:
password = create_newemail_dict(config)["password"]
config.set_user_password(addr, password)
echobot_password_path.write_text(encrypt_password(password))
# Give the user which doveauth runs as access to the password file.
subprocess.check_call(
["/usr/bin/setfacl", "-m", "user:vmail:r", echobot_password_path],
)
if not bot.is_configured():
bot.configure(addr, password)
bot.run_forever()

View File

@@ -6,7 +6,7 @@ from .config import read_config
from .dictproxy import DictProxy
# this file's mtime reflects the last login-time for a user
LAST_LOGIN = "last-login"
LAST_LOGIN = "password"
def get_daytimestamp(timestamp) -> int:
@@ -16,31 +16,16 @@ def get_daytimestamp(timestamp) -> int:
def write_last_login_to_userdir(userdir, timestamp):
target = userdir.joinpath(LAST_LOGIN)
timestamp = get_daytimestamp(timestamp)
try:
st = target.stat()
except FileNotFoundError:
# only happens on initial login
userdir.mkdir(exist_ok=True)
target.touch()
st = target.stat()
if int(st.st_mtime) != timestamp:
os.utime(target, (timestamp, timestamp))
else:
if st.st_mtime < timestamp:
os.utime(target, (timestamp, timestamp))
def get_last_login_from_userdir(userdir) -> int:
if "@" not in userdir.name:
return get_daytimestamp(time.time())
target = userdir.joinpath(LAST_LOGIN)
try:
return int(target.stat().st_mtime)
except FileNotFoundError:
# during migration many directories will not have last-login file
# so we write it here to the current time
target.touch()
timestamp = get_daytimestamp(time.time())
os.utime(target, (timestamp, timestamp))
return timestamp
return int(target.stat().st_mtime)
class LastLoginDictProxy(DictProxy):

View File

@@ -63,12 +63,6 @@ def test_config_userstate_paths(make_config, tmp_path):
def test_config_get_user_dict(make_config, tmp_path):
config = make_config("something.testrun.org")
data = config.get_user_dict("user1@something.org")
assert data["home"]
assert data["uid"] == "vmail"
assert data["gid"] == "vmail"
assert "password" not in data
addr = "user1@something.org"
enc_password = "l1k2j31lk2j3l1k23j123"
data = config.get_user_dict(addr, enc_password=enc_password)

View File

@@ -8,6 +8,7 @@ from chatmaild.lastlogin import get_last_login_from_userdir, write_last_login_to
def test_login_timestamps(tmp_path):
userdir = tmp_path.joinpath("someuser@chat.example.org")
userdir.mkdir()
userdir.joinpath("password").touch()
write_last_login_to_userdir(userdir, timestamp=100000)
assert get_last_login_from_userdir(userdir) == 86400

View File

@@ -1,6 +1,7 @@
import time
import pytest
from chatmaild.doveauth import AuthDictProxy
from chatmaild.lastlogin import (
LastLoginDictProxy,
get_last_login_from_userdir,
@@ -16,6 +17,9 @@ def testaddr():
def test_handle_dovecot_request_last_login(testaddr, example_config):
dictproxy = LastLoginDictProxy(config=example_config)
authproxy = AuthDictProxy(config=example_config)
authproxy.lookup_passdb(testaddr, "1l2k3j1l2k3jl123")
userdir = dictproxy.config.get_user_maildir(testaddr)
# set last-login info for user
@@ -41,8 +45,9 @@ def test_handle_dovecot_request_last_login(testaddr, example_config):
def test_login_timestamp(testaddr, example_config):
dictproxy = LastLoginDictProxy(config=example_config)
authproxy = AuthDictProxy(config=example_config)
authproxy.lookup_passdb(testaddr, "1l2k3j1l2k3jl123")
userdir = dictproxy.config.get_user_maildir(testaddr)
userdir.mkdir()
write_last_login_to_userdir(userdir, timestamp=100000)
assert get_last_login_from_userdir(userdir) == 86400