mirror of
https://github.com/chatmail/relay.git
synced 2026-05-18 15:18:58 +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
|
import iniconfig
|
||||||
|
|
||||||
|
echobot_password_path = Path("/run/echobot/password")
|
||||||
|
|
||||||
|
|
||||||
def read_config(inipath):
|
def read_config(inipath):
|
||||||
assert Path(inipath).exists(), inipath
|
assert Path(inipath).exists(), inipath
|
||||||
@@ -45,21 +47,18 @@ class Config:
|
|||||||
return res
|
return res
|
||||||
raise ValueError(f"invalid address {addr!r}")
|
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)
|
home = self.get_user_maildir(addr)
|
||||||
res = dict(home=str(home), uid="vmail", gid="vmail")
|
return dict(
|
||||||
if enc_password is not None:
|
addr=addr, home=str(home), uid="vmail", gid="vmail", password=enc_password
|
||||||
res["password"] = enc_password
|
)
|
||||||
return res
|
|
||||||
|
|
||||||
def set_user_password(self, addr, enc_password):
|
def set_user_password(self, addr, enc_password):
|
||||||
# reading and writing user data needs to be atomic
|
# reading and writing user data needs to be atomic
|
||||||
# to allow concurrent logins to succeed.
|
# to allow concurrent logins to succeed.
|
||||||
|
assert not addr.startswith("echo@"), addr
|
||||||
userdir = self.get_user_maildir(addr)
|
userdir = self.get_user_maildir(addr)
|
||||||
try:
|
userdir.mkdir(exist_ok=True)
|
||||||
userdir.mkdir()
|
|
||||||
except FileExistsError:
|
|
||||||
pass
|
|
||||||
password_path = userdir.joinpath("password")
|
password_path = userdir.joinpath("password")
|
||||||
password_path_tmp = userdir.joinpath("password.tmp")
|
password_path_tmp = userdir.joinpath("password.tmp")
|
||||||
password_path_tmp.write_text(enc_password)
|
password_path_tmp.write_text(enc_password)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from .config import Config, read_config
|
from .config import Config, echobot_password_path, read_config
|
||||||
from .dictproxy import DictProxy
|
from .dictproxy import DictProxy
|
||||||
|
|
||||||
NOCREATE_FILE = "/etc/chatmail-nocreate"
|
NOCREATE_FILE = "/etc/chatmail-nocreate"
|
||||||
@@ -131,12 +131,17 @@ class AuthDictProxy(DictProxy):
|
|||||||
|
|
||||||
def lookup_userdb(self, user):
|
def lookup_userdb(self, user):
|
||||||
userdir = self.config.get_user_maildir(user)
|
userdir = self.config.get_user_maildir(user)
|
||||||
password_path = userdir.joinpath("password")
|
if user.startswith("echo@"):
|
||||||
result = {}
|
password_path = echobot_password_path
|
||||||
if password_path.exists():
|
else:
|
||||||
result = dict(addr=user, password=password_path.read_text())
|
password_path = userdir.joinpath("password")
|
||||||
result.update(self.config.get_user_dict(user))
|
|
||||||
return result
|
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):
|
def lookup_passdb(self, user, cleartext_password):
|
||||||
userdata = self.lookup_userdb(user)
|
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 logging
|
||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from deltachat_rpc_client import Bot, DeltaChat, EventType, Rpc, events
|
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
|
from chatmaild.newemail import create_newemail_dict
|
||||||
|
|
||||||
hooks = events.HookCollection()
|
hooks = events.HookCollection()
|
||||||
@@ -85,10 +87,16 @@ def main():
|
|||||||
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"]
|
||||||
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():
|
if not bot.is_configured():
|
||||||
bot.configure(addr, password)
|
bot.configure(addr, password)
|
||||||
|
|
||||||
bot.run_forever()
|
bot.run_forever()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from .config import read_config
|
|||||||
from .dictproxy import DictProxy
|
from .dictproxy import DictProxy
|
||||||
|
|
||||||
# this file's mtime reflects the last login-time for a user
|
# this file's mtime reflects the last login-time for a user
|
||||||
LAST_LOGIN = "last-login"
|
LAST_LOGIN = "password"
|
||||||
|
|
||||||
|
|
||||||
def get_daytimestamp(timestamp) -> int:
|
def get_daytimestamp(timestamp) -> int:
|
||||||
@@ -16,31 +16,16 @@ def get_daytimestamp(timestamp) -> int:
|
|||||||
def write_last_login_to_userdir(userdir, timestamp):
|
def write_last_login_to_userdir(userdir, timestamp):
|
||||||
target = userdir.joinpath(LAST_LOGIN)
|
target = userdir.joinpath(LAST_LOGIN)
|
||||||
timestamp = get_daytimestamp(timestamp)
|
timestamp = get_daytimestamp(timestamp)
|
||||||
try:
|
st = target.stat()
|
||||||
st = target.stat()
|
if int(st.st_mtime) != timestamp:
|
||||||
except FileNotFoundError:
|
|
||||||
# only happens on initial login
|
|
||||||
userdir.mkdir(exist_ok=True)
|
|
||||||
target.touch()
|
|
||||||
os.utime(target, (timestamp, 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:
|
def get_last_login_from_userdir(userdir) -> int:
|
||||||
if "@" not in userdir.name:
|
if "@" not in userdir.name:
|
||||||
return get_daytimestamp(time.time())
|
return get_daytimestamp(time.time())
|
||||||
target = userdir.joinpath(LAST_LOGIN)
|
target = userdir.joinpath(LAST_LOGIN)
|
||||||
try:
|
return int(target.stat().st_mtime)
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class LastLoginDictProxy(DictProxy):
|
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):
|
def test_config_get_user_dict(make_config, tmp_path):
|
||||||
config = make_config("something.testrun.org")
|
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"
|
addr = "user1@something.org"
|
||||||
enc_password = "l1k2j31lk2j3l1k23j123"
|
enc_password = "l1k2j31lk2j3l1k23j123"
|
||||||
data = config.get_user_dict(addr, enc_password=enc_password)
|
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):
|
def test_login_timestamps(tmp_path):
|
||||||
userdir = tmp_path.joinpath("someuser@chat.example.org")
|
userdir = tmp_path.joinpath("someuser@chat.example.org")
|
||||||
userdir.mkdir()
|
userdir.mkdir()
|
||||||
|
userdir.joinpath("password").touch()
|
||||||
write_last_login_to_userdir(userdir, timestamp=100000)
|
write_last_login_to_userdir(userdir, timestamp=100000)
|
||||||
assert get_last_login_from_userdir(userdir) == 86400
|
assert get_last_login_from_userdir(userdir) == 86400
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from chatmaild.doveauth import AuthDictProxy
|
||||||
from chatmaild.lastlogin import (
|
from chatmaild.lastlogin import (
|
||||||
LastLoginDictProxy,
|
LastLoginDictProxy,
|
||||||
get_last_login_from_userdir,
|
get_last_login_from_userdir,
|
||||||
@@ -16,6 +17,9 @@ def testaddr():
|
|||||||
def test_handle_dovecot_request_last_login(testaddr, example_config):
|
def test_handle_dovecot_request_last_login(testaddr, example_config):
|
||||||
dictproxy = LastLoginDictProxy(config=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 = dictproxy.config.get_user_maildir(testaddr)
|
||||||
|
|
||||||
# set last-login info for user
|
# 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):
|
def test_login_timestamp(testaddr, example_config):
|
||||||
dictproxy = LastLoginDictProxy(config=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 = dictproxy.config.get_user_maildir(testaddr)
|
||||||
userdir.mkdir()
|
|
||||||
write_last_login_to_userdir(userdir, timestamp=100000)
|
write_last_login_to_userdir(userdir, timestamp=100000)
|
||||||
assert get_last_login_from_userdir(userdir) == 86400
|
assert get_last_login_from_userdir(userdir) == 86400
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user