pantalaimon: Add a configuration file for the daemon.

This commit is contained in:
Damir Jelić 2019-05-08 12:33:42 +02:00
parent 65d1401b76
commit 2987589854
3 changed files with 202 additions and 58 deletions

View File

@ -10,4 +10,4 @@ typecheck:
mypy --ignore-missing-imports pantalaimon
run-local:
python -m pantalaimon.main https://localhost:8448 --proxy http://localhost:8080 -k --log-level debug
python -m pantalaimon.main --log-level debug --config ./contrib/pantalaimon.conf

150
pantalaimon/config.py Normal file
View File

@ -0,0 +1,150 @@
import configparser
import os
from typing import Union
from ipaddress import ip_address, IPv4Address, IPv6Address
from urllib.parse import urlparse, ParseResult
import logbook
import attr
class PanConfigParser(configparser.ConfigParser):
def __init__(self):
super().__init__(
default_section="Default",
defaults={
"SSL": "True",
"ListenAddress": "localhost",
"ListenPort": "8009",
"LogLevel": "warnig",
},
converters={
"address": parse_address,
"url": parse_url,
"loglevel": parse_log_level,
}
)
def parse_address(value):
# type: (str) -> Union[IPv4Address, IPv6Address]
if value == "localhost":
return ip_address("127.0.0.1")
return ip_address(value)
def parse_url(value):
# type: (str) -> ParseResult
value = urlparse(value)
if value.scheme not in ('http', 'https'):
raise ValueError(f"Invalid URL scheme {value.scheme}. "
f"Only HTTP(s) URLs are allowed")
value.port
return value
def parse_log_level(value):
# type: (str) -> logbook
value = value.lower()
if value == "info":
return logbook.INFO
elif value == "warning":
return logbook.WARNING
elif value == "error":
return logbook.ERROR
elif value == "debug":
return logbook.DEBUG
return logbook.WARNING
class PanConfigError(Exception):
"""Pantalaimon configuration error."""
pass
@attr.s
class ServerConfig:
"""Server configuration.
Args:
homeserver (ParseResult): The URL of the Matrix homeserver that we want
to forward requests to.
listen_address (str): The local address where pantalaimon will listen
for connections.
listen_port (int): The port where pantalaimon will listen for
connections.
proxy (ParseResult):
A proxy that the daemon should use when making connections to the
homeserver.
ssl (bool): Enable or disable SSL for the connection between
pantalaimon and the homeserver.
"""
homeserver = attr.ib()
listen_address = attr.ib(type=Union[IPv4Address, IPv6Address])
listen_port = attr.ib(type=int)
proxy = attr.ib(type=str)
ssl = attr.ib(type=bool, default=True)
@attr.s
class PanConfig:
"""Pantalaimon configuration.
Args:
config_path (str): The path where we should search for a configuration
file.
filename (str): The name of the file that we should read.
"""
config_file = attr.ib()
log_level = attr.ib(default=None)
servers = attr.ib(init=False, default=attr.Factory(dict))
def read(self):
"""Read the configuration file.
Raises OSError if the file can't be read or PanConfigError if there is
a syntax error with the config file.
"""
config = PanConfigParser()
try:
config.read(os.path.abspath(self.config_file))
except configparser.Error as e:
raise PanConfigError(e)
if self.log_level is None:
self.log_level = config["Default"].getloglevel("LogLevel")
try:
for section_name, section in config.items():
if section_name == "Default":
continue
homeserver = section.geturl("Homeserver")
listen_address = section.getaddress("ListenAddress")
listen_port = section.getint("ListenPort")
ssl = section.getboolean("SSL")
proxy = section.geturl("Proxy")
server_conf = ServerConfig(
homeserver,
listen_address,
listen_port,
proxy,
ssl
)
self.servers[section_name] = server_conf
except ValueError as e:
raise PanConfigError(e)

View File

@ -7,16 +7,28 @@ from urllib.parse import urlparse
import click
import janus
import logbook
from appdirs import user_data_dir
from appdirs import user_data_dir, user_config_dir
from logbook import StderrHandler
from aiohttp import web
from pantalaimon.ui import GlibT
from pantalaimon.log import logger
from pantalaimon.daemon import ProxyDaemon
from pantalaimon.config import PanConfig, PanConfigError, parse_log_level
from pantalaimon.log import logger
def create_dirs(data_dir, conf_dir):
try:
os.makedirs(data_dir)
except OSError:
pass
try:
os.makedirs(conf_dir)
except OSError:
pass
async def init(homeserver, http_proxy, ssl, send_queue, recv_queue):
@ -84,77 +96,55 @@ class ipaddress(click.ParamType):
@click.command(
help=("pantalaimon is a reverse proxy for matrix homeservers that "
"transparently encrypts and decrypts messages for clients that "
"connect to pantalaimon.\n\n"
"HOMESERVER - the homeserver that the daemon should connect to.")
"connect to pantalaimon.")
)
@click.option(
"--proxy",
type=URL(),
default=None,
help="A proxy that will be used to connect to the homeserver."
)
@click.option(
"-k",
"--ssl-insecure/--no-ssl-insecure",
default=False,
help="Disable SSL verification for the homeserver connection."
)
@click.option(
"-l",
"--listen-address",
type=ipaddress(),
default=ip_address("127.0.0.1"),
help=("The listening address for incoming client connections "
"(default: 127.0.0.1)")
)
@click.option(
"-p",
"--listen-port",
type=int,
default=8009,
help="The listening port for incoming client connections (default: 8009)"
)
@click.option("--log-level", type=click.Choice([
"error",
"warning",
"info",
"debug"
]), default="error")
@click.argument(
"homeserver",
type=URL(),
)
]), default=None)
@click.option("-c", "--config", type=click.Path(exists=True))
@click.pass_context
def main(
proxy,
ssl_insecure,
listen_address,
listen_port,
context,
log_level,
homeserver
config
):
ssl = None if ssl_insecure is False else False
conf_dir = user_config_dir("pantalaimon", "")
data_dir = user_data_dir("pantalaimon", "")
create_dirs(data_dir, conf_dir)
StderrHandler(level=log_level.upper()).push_application()
config = config or os.path.join(conf_dir, "pantalaimon.conf")
if log_level == "info":
logger.level = logbook.INFO
elif log_level == "warning":
logger.level = logbook.WARNING
elif log_level == "error":
logger.level = logbook.ERROR
elif log_level == "debug":
logger.level = logbook.DEBUG
if log_level:
log_level = parse_log_level(log_level)
pan_conf = PanConfig(config, log_level)
try:
pan_conf.read()
except (OSError, PanConfigError) as e:
context.fail(e)
if not pan_conf.servers:
context.fail("Homeserver is not configured.")
logger.level = pan_conf.log_level
StderrHandler().push_application()
loop = asyncio.get_event_loop()
pan_queue = janus.Queue(loop=loop)
ui_queue = janus.Queue(loop=loop)
# TODO start the other servers as well
server_conf = list(pan_conf.servers.values())[0]
proxy, app = loop.run_until_complete(init(
homeserver,
proxy.geturl() if proxy else None,
ssl,
server_conf.homeserver,
server_conf.proxy.geturl() if server_conf.proxy else None,
server_conf.ssl,
pan_queue.async_q,
ui_queue.async_q
))
@ -179,7 +169,11 @@ def main(
home = os.path.expanduser("~")
os.chdir(home)
web.run_app(app, host=str(listen_address), port=listen_port)
web.run_app(
app,
host=str(server_conf.listen_address),
port=server_conf.listen_port
)
if __name__ == "__main__":