mirror of
https://github.com/chatmail/relay.git
synced 2026-05-10 16:04:37 +00:00
merge lastlogin and doveauth logic to use the "password" file for both states
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user