From 0136565ed8bc2d931d09b532d0c699f13915bb8c Mon Sep 17 00:00:00 2001 From: saces Date: Wed, 29 Apr 2026 00:37:36 +0200 Subject: [PATCH] smal: add e2ee setup tool --- mxsmal/pyproject.toml | 3 +- mxsmal/src/mxsmal/smalsetup/__init__.py | 1 - mxsmal/src/mxsmal/smalsetup/__main__.py | 6 -- mxsmal/src/mxsmal/smalsetup/e2eebot.py | 36 +++++++++ mxsmal/src/mxsmal/smalsetup/e2eesetup.py | 94 ++++++++++++++++++++++++ 5 files changed, 132 insertions(+), 8 deletions(-) delete mode 100644 mxsmal/src/mxsmal/smalsetup/__init__.py delete mode 100644 mxsmal/src/mxsmal/smalsetup/__main__.py create mode 100644 mxsmal/src/mxsmal/smalsetup/e2eebot.py create mode 100644 mxsmal/src/mxsmal/smalsetup/e2eesetup.py diff --git a/mxsmal/pyproject.toml b/mxsmal/pyproject.toml index 835d0eb..b46cea4 100644 --- a/mxsmal/pyproject.toml +++ b/mxsmal/pyproject.toml @@ -41,6 +41,7 @@ mxlogout = "pymxutils.mxutils:logout" mxclearaccount = "pymxutils.mxutils:clearaccount" mxserverinfo = "pymxutils.mxutils:serverinfo" mxpassitem = "pymxutils.mxutils:passitem" -smalsetup = "mxsmal.smalsetup:smalsetup" +smalsetup = "mxsmal.smalsetup.smalsetup:smalsetup" +e2eesetup = "mxsmal.smalsetup.e2eesetup:e2eesetup" demobot = "demobot:main" simplebot = "demobot.simple:main" diff --git a/mxsmal/src/mxsmal/smalsetup/__init__.py b/mxsmal/src/mxsmal/smalsetup/__init__.py deleted file mode 100644 index 8d6fb75..0000000 --- a/mxsmal/src/mxsmal/smalsetup/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .smalsetup import smalsetup diff --git a/mxsmal/src/mxsmal/smalsetup/__main__.py b/mxsmal/src/mxsmal/smalsetup/__main__.py deleted file mode 100644 index 0829d78..0000000 --- a/mxsmal/src/mxsmal/smalsetup/__main__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (C) 2026 saces@c-base.org -# SPDX-License-Identifier: AGPL-3.0-only -import sys -from .smalsetup import smalsetup - -sys.exit(smalsetup()) diff --git a/mxsmal/src/mxsmal/smalsetup/e2eebot.py b/mxsmal/src/mxsmal/smalsetup/e2eebot.py new file mode 100644 index 0000000..c54a227 --- /dev/null +++ b/mxsmal/src/mxsmal/smalsetup/e2eebot.py @@ -0,0 +1,36 @@ +# Copyright (C) 2026 saces@c-base.org +# SPDX-License-Identifier: AGPL-3.0-only +import logging +from mxsmal.bot import SMALBot +from pygomx.apiv0 import ApiV0Api + +# setup logging, we want timestamps +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s.%(msecs)03d %(levelname)s %(name)s - %(funcName)s: %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", +) +logger = logging.getLogger(__name__) +logger.setLevel(level=logging.INFO) + + +# TODO this should be an App, not a Bot +class E2eeBot(SMALBot): + + def __init__(self): + super().__init__("¿") + + async def on_startup(self): + print("e2eeBot started.") + res = ApiV0Api.self_sign(self.client_id) + print(res) + print("e2eeBot done?.") + + async def on_sys(self, ntf): + print("Got a system notification: ", ntf) + + async def on_event(self, evt): + print("Got an event: ", evt) + + async def on_message(self, msg): + print("Got a mesage: ", msg) diff --git a/mxsmal/src/mxsmal/smalsetup/e2eesetup.py b/mxsmal/src/mxsmal/smalsetup/e2eesetup.py new file mode 100644 index 0000000..06d90ed --- /dev/null +++ b/mxsmal/src/mxsmal/smalsetup/e2eesetup.py @@ -0,0 +1,94 @@ +# Copyright (C) 2026 saces@c-base.org +# SPDX-License-Identifier: AGPL-3.0-only +from datetime import datetime +import getpass +import os +import json +from functools import partial, wraps + +import click +from pygomx.errors import PygomxAPIError + +from pygomx.apiv0 import ApiV0 +from pygomx.cliv0 import CliV0 + +from .e2eebot import E2eeBot + + +def catch_exception(func=None, *, handle): + if not func: + return partial(catch_exception, handle=handle) + + @wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except handle as e: + raise click.ClickException(e) + + return wrapper + + +@click.command() +@click.option( + "--mxpass", + "mxpassfile", + metavar="filepath", + default=".mxpass", + help="mxpass file name", +) +@catch_exception(handle=(PygomxAPIError)) +def e2eesetup(mxpassfile): + """Utility for smalbot e2ee setup""" + + cli = CliV0.from_mxpass(mxpassfile, "*", "*", "*") + + # we need to know who we are and the device id needs to be known too + whoami = cli.Whoami() + + # check for other devices + devices = cli.Generic("GET", ["_matrix", "client", "v3", "devices"]) + + if len(devices["devices"]) > 1: + click.echo(f"Other devices found for '{whoami['user_id']}'") + click.echo(f"This device: {whoami['device_id']}") + click.echo("Other devices:") + other_devices_list = [] + for device in devices["devices"]: + # print(device) + if device["device_id"] != whoami["device_id"]: + other_devices_list.append(device["device_id"]) + click.echo( + f" {device['device_id']} ({device['display_name']}) - {datetime.fromtimestamp(device['last_seen_ts']/1000)} from {device['last_seen_ip']}" + ) + click.echo() + if click.confirm("Do you want to log them out?"): + + cli.Generic( + "POST", + ["_matrix", "client", "v3", "delete_devices"], + { + "devices": other_devices_list, + "auth": { + "type": "m.login.password", + "password": getpass.getpass(prompt="Password: "), + "identifier": { + "type": "m.id.user", + "user": whoami["user_id"], + }, + }, + }, + ) + + click.echo("Well done!") + # TODO maybe check for master key and reset it too. + + # TODO the config thingy comes here, for now a lot of stuff is hard coded to make it work + + # Start the bot to setup e2ee, the real bot will not be startd + + e2eeBot = E2eeBot() + + e2eeBot.run(sync=False) + + print("Huhu Bämm!")