diff --git a/client.py b/client.py deleted file mode 100755 index 40d54a3b2f28c81f0e8a73ae32e1a40f375fe882..0000000000000000000000000000000000000000 --- a/client.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/python3 - -from dbus_next.aio import MessageBus, ProxyInterface -import dbus_next.introspection as intr -from dbus_next.message import Message -from dbus_next.service import ServiceInterface, method, dbus_property, signal -from dbus_next import Variant, DBusError, BusType, MessageType, RequestNameReply - -import asyncio -import os -from typing import Union, Optional, Callable, Dict, List, Set - - -async def main(): - bus = await MessageBus(bus_type=BusType.SYSTEM, negotiate_unix_fd=True).connect() - - ins = await bus.introspect('cz.ucw.shipcat', '/cz/ucw/ShipCat') - - pxy = bus.get_proxy_object('cz.ucw.shipcat', '/cz/ucw/ShipCat', ins) - pif = pxy.get_interface('cz.ucw.ShipCat') - - # fd = os.open('/dev/null', os.O_RDWR) - # xx = await pif.call_shell(0, 1, 2) - - xx = await pif.call_check('test') - - print(xx) - - -asyncio.get_event_loop().run_until_complete(main()) diff --git a/server.py b/server.py deleted file mode 100755 index 3e066257ee0bc2bf0d0034e85dfa531ea0e7405e..0000000000000000000000000000000000000000 --- a/server.py +++ /dev/null @@ -1,257 +0,0 @@ -#!/usr/bin/python3 - -import asyncio -from dbus_next import Message, Variant, DBusError, BusType, MessageType, RequestNameReply -from dbus_next.aio import MessageBus, ProxyInterface -import dbus_next.introspection as intr -import logging -import os -import sys -from typing import Union, Optional, Dict, List, Set, Any - -from shipcat.config import GlobalConfig, ContainerConfig, ConfigError - - -class BusLogic: - config: GlobalConfig - bus: MessageBus - msg_tasks: Set[asyncio.Task] - dbus_iface: ProxyInterface - object_tree: Any - method_list: List[intr.Method] - method_dict: Dict[str, intr.Method] - interface_intro: intr.Interface - - def __init__(self, config: GlobalConfig) -> None: - self.config = config - - self.logger = logging.getLogger('shipscat') - if config.verbosity > 0: - self.logger.setLevel(logging.DEBUG) - else: - self.logger.setLevel(logging.INFO) - self.logger.propagate = True - - self.msg_tasks = set() - - self.object_tree = { - 'cz': { - 'ucw': { - 'ShipCat': True, - } - } - } - - self.method_list = [ - intr.Method( - name='Check', - in_args=[ - intr.Arg('s', intr.ArgDirection.IN, 'container'), - ], - ), - intr.Method( - name='Shell', - in_args=[ - intr.Arg('h', intr.ArgDirection.IN, 'stdin_fd'), - intr.Arg('h', intr.ArgDirection.IN, 'stdout_fd'), - intr.Arg('h', intr.ArgDirection.IN, 'stderr_fd'), - ], - out_args=[ - intr.Arg('i', intr.ArgDirection.OUT, 'exit_code'), - ], - ), - ] - - self.method_dict = {m.name: m for m in self.method_list} - - self.interface_intro = intr.Interface( - name='cz.ucw.ShipCat', - methods=self.method_list, - ) - - async def loop(self) -> None: - self.bus = await MessageBus(bus_type=BusType.SYSTEM, negotiate_unix_fd=True).connect() - - # Obtain proxy interface for org.freedesktop.DBus - dbus_intro = await self.bus.introspect(bus_name='org.freedesktop.DBus', path='/org/freedesktop/DBus') - dbus_proxy = self.bus.get_proxy_object(bus_name='org.freedesktop.DBus', path='/org/freedesktop/DBus', introspection=dbus_intro) - self.dbus_iface = dbus_proxy.get_interface('org.freedesktop.DBus') - - self.bus.add_message_handler(self.handle_message) - - rn = await self.bus.request_name('cz.ucw.shipcat') - assert rn == RequestNameReply.PRIMARY_OWNER - - await self.bus.wait_for_disconnect() - - def handle_message(self, msg: Message) -> Optional[Union[Message, bool]]: - self.logger.debug(f'Got message: type={msg.message_type.name} dest={msg.destination} path={msg.path} iface={msg.interface} member={msg.member} sender={msg.sender} fds={msg.unix_fds}') - - if (msg.destination == 'cz.ucw.shipcat' - and msg.interface == 'org.freedesktop.DBus.Introspectable' - and msg.member == 'Introspect'): - self.close_fds(msg) - result = self.handle_introspect(msg) - - elif (msg.destination == 'cz.ucw.shipcat' - and msg.interface == 'cz.ucw.ShipCat' - and msg.message_type == MessageType.METHOD_CALL - and msg.member in self.method_dict - and msg.signature == self.method_dict[msg.member].in_signature): - result = self.handle_method_call(msg) - - else: - self.close_fds(msg) - result = None - - return result - - def close_fds(self, msg: Message) -> None: - # XXX: The dbus-next library leaks file descriptors. Work around it. - # (see https://github.com/altdesktop/python-dbus-next/issues/162) - for fd in msg.unix_fds: - os.close(fd) - msg.unix_fds = [] - - def handle_method_call(self, msg: Message) -> bool: - async def async_method_call(msg): - self.logger.debug(f'Calling method {msg.member}') - try: - reply = await getattr(self, 'handle_' + msg.member)(msg) - except DBusError as e: - self.logger.debug(f'DBusError: {e}') - reply = Message.new_error(msg, e.type, e.text) - except Exception as e: - self.logger.error(f'Internal error: {e}') - reply = Message.new_error(msg, 'cz.ucw.ShipCat.Error.Internal', 'Internal error') - self.bus.send(reply) - self.close_fds(msg) - - task = asyncio.create_task(async_method_call(msg)) - self.msg_tasks.add(task) - task.add_done_callback(self.msg_tasks.discard) - return True - - def handle_introspect(self, msg: Message) -> Message: - self.logger.debug(f'Introspecting {msg.path}') - intro = intr.Node.default(msg.path) - # We do not want to speak ObjectManager-ish - intro.interfaces = [i for i in intro.interfaces if i.name != 'org.freedesktop.DBus.ObjectManager'] - - if msg.path == '/': - path = [""] - else: - path = msg.path.split('/') - assert path[0] == "" - - i = 1 - node = self.object_tree - while i < len(path) and isinstance(node, dict) and path[i] in node: - node = node[path[i]] - i += 1 - - if i == len(path): - if isinstance(node, dict): - intro.nodes = [intr.Node(n) for n in sorted(node.keys())] - else: - intro.interfaces.append(self.interface_intro) - return Message.new_method_return(msg, signature='s', body=[intro.tostring()]) - - async def handle_Check(self, msg: Message) -> Message: - [container] = msg.body - await self.setup_container(container, msg) - - ins = await self.bus.introspect('org.freedesktop.systemd1', '/org/freedesktop/systemd1') - pxy = self.bus.get_proxy_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1', ins) - pif = pxy.get_interface('org.freedesktop.systemd1.Manager') - p = await pif.call_get_unit('inetd.service') - print(p) - - uns = await self.bus.introspect('org.freedesktop.systemd1', p) - uxy = self.bus.get_proxy_object('org.freedesktop.systemd1', p, uns) - uif = uxy.get_interface('org.freedesktop.systemd1.Unit') - active = await uif.get_active_state() - sub_state = await uif.get_sub_state() - print(active, sub_state) - - await pif.call_subscribe() - - def cb(a, b, c): - self.logger.debug(f'PROP: {a} {b} {c}') - - prif = uxy.get_interface('org.freedesktop.DBus.Properties') - prif.on_properties_changed(cb) - - await asyncio.sleep(60) - - await pif.call_unsubscribe() - - return Message.new_method_return(msg) - - async def handle_Shell(self, msg: Message) -> Message: - stdin_fd = msg.unix_fds[msg.body[0]] - stdout_fd = msg.unix_fds[msg.body[1]] - stderr_fd = msg.unix_fds[msg.body[2]] - - cred = await self.dbus_iface.call_get_connection_credentials(msg.sender) - - proc = await asyncio.create_subprocess_exec('/bin/cat', '/etc/profile', stdin=stdin_fd, stdout=stdout_fd, stderr=stderr_fd) - await proc.wait() - - return Message.new_method_return(msg, signature='i', body=[proc.returncode]) - - async def setup_container(self, name: str, msg: Message) -> ContainerConfig: - cred = await self.dbus_iface.call_get_connection_credentials(msg.sender) - assert isinstance(cred, dict) - - unix_user_id = cred.get('UnixUserID', None) - if isinstance(unix_user_id, Variant) and unix_user_id.signature == 'u': - uid = unix_user_id.value - assert isinstance(uid, int) - else: - uid = -1 - - unix_group_ids = cred.get('UnixGroupIDs', None) - if isinstance(unix_group_ids, Variant) and unix_group_ids.signature == 'a(u)': - gids = unix_user_id.value - else: - gids = [] - - try: - cc = ContainerConfig.load(self.config, name) - except ConfigError as e: - self.logger.error(f'{msg.member}({name}) by uid {uid}: {e}') - raise DBusError('cz.ucw.ShipCat.Error.ContainerConfig', 'Cannot load container configuration') - - if not self.check_rights(cc, uid, gids): - self.logger.error(f'{msg.member}({name}) by uid {uid}: Permission denied') - raise DBusError('cz.ucw.ShipCat.Error.PermissionDenied', 'You do not have permission to handle this container') - - self.logger.info(f'{msg.member}({name}) by uid {uid}') - - return cc - - def check_rights(self, cc: ContainerConfig, uid: int, gids: List[int]) -> bool: - if uid == 0: - return True - - if uid in cc.allowed_users: - return True - - for gid in gids: - assert isinstance(gid, int) - if gid in cc.allowed_groups: - return True - - return False - - -try: - config = GlobalConfig.load('shipcat.toml') -except ConfigError as e: - print(e, file=sys.stderr) - sys.exit(1) - -logging.basicConfig(format='%(asctime)-15s %(levelname)-5.5s %(message)s') -bl = BusLogic(config) -asyncio.get_event_loop().run_until_complete(bl.loop())