2019-05-21 16:46:22 +02:00
|
|
|
# Copyright 2019 The Matrix.org Foundation CIC
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
|
2019-05-07 10:42:40 +02:00
|
|
|
import asyncio
|
|
|
|
import os
|
2019-05-08 16:11:07 +02:00
|
|
|
import signal
|
2019-05-14 21:50:30 +02:00
|
|
|
from typing import Optional
|
2019-05-07 10:42:40 +02:00
|
|
|
|
|
|
|
import click
|
|
|
|
import janus
|
2019-05-23 13:50:02 +02:00
|
|
|
import keyring
|
2019-05-07 10:42:40 +02:00
|
|
|
from aiohttp import web
|
2019-05-14 21:50:30 +02:00
|
|
|
from appdirs import user_config_dir, user_data_dir
|
|
|
|
from logbook import StderrHandler
|
2019-05-07 10:42:40 +02:00
|
|
|
|
2019-05-08 12:33:42 +02:00
|
|
|
from pantalaimon.config import PanConfig, PanConfigError, parse_log_level
|
2019-05-14 21:50:30 +02:00
|
|
|
from pantalaimon.daemon import ProxyDaemon
|
2019-05-08 12:33:42 +02:00
|
|
|
from pantalaimon.log import logger
|
2019-05-14 21:50:30 +02:00
|
|
|
from pantalaimon.thread_messages import DaemonResponse
|
|
|
|
from pantalaimon.ui import GlibT
|
2019-05-08 12:33:42 +02:00
|
|
|
|
|
|
|
|
|
|
|
def create_dirs(data_dir, conf_dir):
|
|
|
|
try:
|
|
|
|
os.makedirs(data_dir)
|
|
|
|
except OSError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
try:
|
|
|
|
os.makedirs(conf_dir)
|
|
|
|
except OSError:
|
|
|
|
pass
|
2019-05-07 10:42:40 +02:00
|
|
|
|
|
|
|
|
2019-05-08 14:21:38 +02:00
|
|
|
async def init(data_dir, server_conf, send_queue, recv_queue):
|
2019-05-07 10:42:40 +02:00
|
|
|
"""Initialize the proxy and the http server."""
|
|
|
|
proxy = ProxyDaemon(
|
2019-05-08 14:21:38 +02:00
|
|
|
server_conf.name,
|
|
|
|
server_conf.homeserver,
|
2019-05-21 12:35:55 +02:00
|
|
|
server_conf,
|
2019-05-07 10:42:40 +02:00
|
|
|
data_dir,
|
|
|
|
send_queue=send_queue,
|
|
|
|
recv_queue=recv_queue,
|
2019-05-08 14:21:38 +02:00
|
|
|
proxy=server_conf.proxy.geturl() if server_conf.proxy else None,
|
2019-06-19 12:37:44 +02:00
|
|
|
ssl=None if server_conf.ssl is True else False,
|
2019-05-07 10:42:40 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
app = web.Application()
|
2019-05-08 14:21:38 +02:00
|
|
|
|
2019-06-19 12:37:44 +02:00
|
|
|
app.add_routes(
|
|
|
|
[
|
|
|
|
web.post("/_matrix/client/r0/login", proxy.login),
|
|
|
|
web.get("/_matrix/client/r0/sync", proxy.sync),
|
|
|
|
web.get("/_matrix/client/r0/rooms/{room_id}/messages", proxy.messages),
|
|
|
|
web.put(
|
|
|
|
r"/_matrix/client/r0/rooms/{room_id}/send/{event_type}/{txnid}",
|
|
|
|
proxy.send_message,
|
|
|
|
),
|
|
|
|
web.post("/_matrix/client/r0/user/{user_id}/filter", proxy.filter),
|
|
|
|
web.post("/_matrix/client/r0/search", proxy.search),
|
|
|
|
web.options("/_matrix/client/r0/search", proxy.search_opts),
|
|
|
|
]
|
|
|
|
)
|
2019-05-07 10:42:40 +02:00
|
|
|
app.router.add_route("*", "/" + "{proxyPath:.*}", proxy.router)
|
2019-05-08 14:21:38 +02:00
|
|
|
app.on_shutdown.append(proxy.shutdown)
|
2019-05-07 10:42:40 +02:00
|
|
|
|
2019-05-08 14:21:38 +02:00
|
|
|
runner = web.AppRunner(app)
|
|
|
|
await runner.setup()
|
2019-05-07 10:42:40 +02:00
|
|
|
|
2019-06-19 12:37:44 +02:00
|
|
|
site = web.TCPSite(runner, str(server_conf.listen_address), server_conf.listen_port)
|
2019-05-07 10:42:40 +02:00
|
|
|
|
2019-05-08 14:21:38 +02:00
|
|
|
return proxy, runner, site
|
2019-05-07 10:42:40 +02:00
|
|
|
|
|
|
|
|
2019-05-08 15:47:16 +02:00
|
|
|
async def message_router(receive_queue, send_queue, proxies):
|
|
|
|
"""Find the recipient of a message and forward it to the right proxy."""
|
2019-06-19 12:37:44 +02:00
|
|
|
|
2019-05-08 15:47:16 +02:00
|
|
|
def find_proxy_by_user(user):
|
|
|
|
# type: (str) -> Optional[ProxyDaemon]
|
|
|
|
for proxy in proxies:
|
|
|
|
if user in proxy.pan_clients:
|
|
|
|
return proxy
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
2019-05-13 16:29:59 +02:00
|
|
|
async def send_info(message_id, pan_user, code, string):
|
|
|
|
message = DaemonResponse(message_id, pan_user, code, string)
|
2019-05-08 15:47:16 +02:00
|
|
|
await send_queue.put(message)
|
|
|
|
|
|
|
|
while True:
|
|
|
|
message = await receive_queue.get()
|
|
|
|
logger.debug(f"Router got message {message}")
|
|
|
|
|
|
|
|
proxy = find_proxy_by_user(message.pan_user)
|
|
|
|
|
|
|
|
if not proxy:
|
|
|
|
msg = f"No pan client found for {message.pan_user}."
|
|
|
|
logger.warn(msg)
|
2019-05-13 16:29:59 +02:00
|
|
|
await send_info(
|
2019-06-19 12:37:44 +02:00
|
|
|
message.message_id, message.pan_user, "m.unknown_client", msg
|
2019-05-13 16:29:59 +02:00
|
|
|
)
|
2019-05-08 15:47:16 +02:00
|
|
|
|
|
|
|
await proxy.receive_message(message)
|
|
|
|
|
|
|
|
|
2019-05-07 15:34:29 +02:00
|
|
|
@click.command(
|
2019-06-19 12:37:44 +02:00
|
|
|
help=(
|
|
|
|
"pantalaimon is a reverse proxy for matrix homeservers that "
|
|
|
|
"transparently encrypts and decrypts messages for clients that "
|
|
|
|
"connect to pantalaimon."
|
|
|
|
)
|
2019-05-07 10:42:40 +02:00
|
|
|
)
|
2019-06-11 11:24:37 +02:00
|
|
|
@click.version_option(version="0.1", prog_name="pantalaimon")
|
2019-06-19 12:37:44 +02:00
|
|
|
@click.option(
|
|
|
|
"--log-level",
|
|
|
|
type=click.Choice(["error", "warning", "info", "debug"]),
|
|
|
|
default=None,
|
|
|
|
)
|
2019-05-08 12:33:42 +02:00
|
|
|
@click.option("-c", "--config", type=click.Path(exists=True))
|
|
|
|
@click.pass_context
|
2019-06-19 12:37:44 +02:00
|
|
|
def main(context, log_level, config):
|
2019-05-23 13:50:02 +02:00
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
|
2019-05-08 12:33:42 +02:00
|
|
|
conf_dir = user_config_dir("pantalaimon", "")
|
|
|
|
data_dir = user_data_dir("pantalaimon", "")
|
|
|
|
create_dirs(data_dir, conf_dir)
|
2019-05-07 10:42:40 +02:00
|
|
|
|
2019-05-08 12:33:42 +02:00
|
|
|
config = config or os.path.join(conf_dir, "pantalaimon.conf")
|
2019-05-07 10:42:40 +02:00
|
|
|
|
2019-05-08 12:33:42 +02:00
|
|
|
if log_level:
|
|
|
|
log_level = parse_log_level(log_level)
|
2019-05-07 10:42:40 +02:00
|
|
|
|
2019-05-08 12:33:42 +02:00
|
|
|
pan_conf = PanConfig(config, log_level)
|
|
|
|
|
|
|
|
try:
|
|
|
|
pan_conf.read()
|
|
|
|
except (OSError, PanConfigError) as e:
|
|
|
|
context.fail(e)
|
2019-05-07 10:42:40 +02:00
|
|
|
|
2019-05-08 12:33:42 +02:00
|
|
|
if not pan_conf.servers:
|
|
|
|
context.fail("Homeserver is not configured.")
|
|
|
|
|
|
|
|
logger.level = pan_conf.log_level
|
|
|
|
StderrHandler().push_application()
|
|
|
|
|
2019-05-07 10:42:40 +02:00
|
|
|
pan_queue = janus.Queue(loop=loop)
|
|
|
|
ui_queue = janus.Queue(loop=loop)
|
|
|
|
|
2019-05-08 14:21:38 +02:00
|
|
|
servers = []
|
2019-05-08 15:47:16 +02:00
|
|
|
proxies = []
|
2019-05-08 12:33:42 +02:00
|
|
|
|
2019-05-23 13:50:02 +02:00
|
|
|
try:
|
|
|
|
|
|
|
|
for server_conf in pan_conf.servers.values():
|
|
|
|
proxy, runner, site = loop.run_until_complete(
|
2019-06-19 12:37:44 +02:00
|
|
|
init(data_dir, server_conf, pan_queue.async_q, ui_queue.async_q)
|
2019-05-08 14:21:38 +02:00
|
|
|
)
|
2019-05-23 13:50:02 +02:00
|
|
|
servers.append((proxy, runner, site))
|
|
|
|
proxies.append(proxy)
|
|
|
|
|
|
|
|
except keyring.errors.KeyringError as e:
|
|
|
|
context.fail(f"Error initializing keyring: {e}")
|
2019-05-07 10:42:40 +02:00
|
|
|
|
2019-06-19 12:37:44 +02:00
|
|
|
glib_thread = GlibT(
|
|
|
|
pan_queue.sync_q, ui_queue.sync_q, data_dir, pan_conf.servers.values(), pan_conf
|
2019-05-07 10:42:40 +02:00
|
|
|
)
|
|
|
|
|
2019-06-19 12:37:44 +02:00
|
|
|
glib_fut = loop.run_in_executor(None, glib_thread.run)
|
|
|
|
|
2019-05-08 14:21:38 +02:00
|
|
|
async def wait_for_glib(glib_thread, fut):
|
2019-05-07 10:42:40 +02:00
|
|
|
glib_thread.stop()
|
|
|
|
await fut
|
|
|
|
|
2019-05-08 15:47:16 +02:00
|
|
|
message_router_task = loop.create_task(
|
|
|
|
message_router(ui_queue.async_q, pan_queue.async_q, proxies)
|
|
|
|
)
|
|
|
|
|
2019-05-07 10:42:40 +02:00
|
|
|
home = os.path.expanduser("~")
|
|
|
|
os.chdir(home)
|
|
|
|
|
2019-05-08 16:11:07 +02:00
|
|
|
def handler(signum, frame):
|
|
|
|
raise KeyboardInterrupt
|
|
|
|
|
|
|
|
signal.signal(signal.SIGTERM, handler)
|
|
|
|
|
2019-05-08 14:21:38 +02:00
|
|
|
try:
|
|
|
|
for proxy, _, site in servers:
|
2019-06-19 12:37:44 +02:00
|
|
|
click.echo(
|
|
|
|
f"======== Starting daemon for homeserver "
|
|
|
|
f"{proxy.name} on {site.name} ========"
|
|
|
|
)
|
2019-05-08 14:21:38 +02:00
|
|
|
loop.run_until_complete(site.start())
|
|
|
|
|
|
|
|
click.echo("(Press CTRL+C to quit)")
|
|
|
|
loop.run_forever()
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
for _, runner, _ in servers:
|
|
|
|
loop.run_until_complete(runner.cleanup())
|
|
|
|
|
|
|
|
loop.run_until_complete(wait_for_glib(glib_thread, glib_fut))
|
2019-05-08 15:47:16 +02:00
|
|
|
message_router_task.cancel()
|
|
|
|
loop.run_until_complete(asyncio.wait({message_router_task}))
|
2019-05-08 14:21:38 +02:00
|
|
|
loop.close()
|
2019-05-07 10:42:40 +02:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2019-05-07 15:34:29 +02:00
|
|
|
main()
|