#!/usr/bin/env python3.6 import asyncio import functools import io import json import operator import os import reprlib import signal import socket import sys import types import qubes import qubes.libvirtaio import qubes.utils import qubes.vm.qubesvm QUBESD_SOCK = '/var/run/qubesd.sock' class ProtocolRepr(reprlib.Repr): def repr1(self, x, level): if isinstance(x, qubes.vm.qubesvm.QubesVM): x = x.name return super().repr1(x, level) def repr_str(self, x, level): '''Warning: this is incompatible with python 3 wrt to b'' ''' return "'{}'".format(''.join( chr(c) if 0x20 < c < 0x7f and c not in (ord("'"), ord('\\')) else '\\x{:02x}'.format(c) for c in x.encode())) def repr_Label(self, x, level): return self.repr1(x.name, level) class ProtocolError(AssertionError): '''Raised when something is wrong with data received''' pass class PermissionDenied(Exception): '''Raised deliberately by handlers when we decide not to cooperate''' pass def not_in_api(func): func.not_in_api = True return func class QubesMgmt(object): def __init__(self, app, src, method, dest, arg): self.app = app self.src = self.app.domains[src.decode('ascii')] self.dest = self.app.domains[dest.decode('ascii')] self.arg = arg.decode('ascii') self.prepr = ProtocolRepr() self.method = method.decode('ascii') untrusted_func_name = self.method if untrusted_func_name.startswith('mgmt.'): untrusted_func_name = untrusted_func_name[5:] untrusted_func_name = untrusted_func_name.lower().replace('.', '_') if untrusted_func_name.startswith('_') \ or not '_' in untrusted_func_name: raise ProtocolError( 'possibly malicious function name: {!r}'.format( untrusted_func_name)) try: untrusted_func = getattr(self, untrusted_func_name) except AttributeError: raise ProtocolError( 'no such attribute: {!r}'.format( untrusted_function_name)) if not isinstance(untrusted_func, types.MethodType): raise ProtocolError( 'no such method: {!r}'.format( untrusted_function_name)) if getattr(untrusted_func, 'not_in_api', False): raise ProtocolError( 'attempt to call private method: {!r}'.format( untrusted_function_name)) self.execute = untrusted_func del untrusted_func_name del untrusted_func # # PRIVATE METHODS, not to be called via RPC # @not_in_api def fire_event_for_permission(self, *args, **kwargs): return self.src.fire_event_pre('mgmt-permission:{}'.format(self.method), self.dest, self.arg, *args, **kwargs) @not_in_api def repr(self, *args, **kwargs): return self.prepr.repr(*args, **kwargs) # # ACTUAL RPC CALLS # def vm_list(self, untrusted_payload): assert self.dest.name == 'dom0' assert not self.arg assert not untrusted_payload del untrusted_payload domains = self.app.domains for selector in self.fire_event_for_permission(): domains = filter(selector, domains) return ''.join('{} class={} state={}\n'.format( self.repr(vm), vm.__class__.__name__, vm.get_power_state()) for vm in sorted(domains)) def vm_property_get(self, untrusted_payload): assert self.arg in self.dest.property_list() assert not untrusted_payload del untrusted_payload self.fire_event_for_permission() try: value = getattr(self.dest, self.arg) except AttributeError: return 'default=True ' else: return 'default={} {}'.format( str(dest.property_is_default(self.arg)), self.repr(self.value)) class QubesDaemonProtocol(asyncio.Protocol): buffer_size = 65536 def __init__(self, *args, app, **kwargs): super().__init__() self.app = app self.untrusted_buffer = io.BytesIO() self.untrusted_buffer_trusted_len = 0 def connection_made(self, transport): print('connection_made()') self.transport = transport def connection_lost(self, exc): print('connection_lost(exc={!r})'.format(exc)) self.untrusted_buffer.close() def data_received(self, untrusted_data): print('data_received(untrusted_data={!r})'.format(untrusted_data)) if self.untrusted_buffer_trusted_len + len(untrusted_data) \ > self.buffer_size: print(' request too long') self.transport.close() return self.untrusted_buffer_trusted_len += \ self.untrusted_buffer.write(untrusted_data) def eof_received(self): print('eof_received()') try: src, method, dest, arg, untrusted_payload = \ self.untrusted_buffer.getvalue().split(b'\0', 4) except ValueError: # TODO logging return try: mgmt = QubesMgmt(self.app, src, method, dest, arg) response = mgmt.execute(untrusted_payload=untrusted_payload) except PermissionDenied as err: # TODO logging return except ProtocolError as err: # TODO logging print(repr(err)) return except AssertionError: # TODO logging print(repr(err)) return self.transport.write(response.encode('ascii')) try: self.transport.write_eof() except NotImplementedError: pass def sighandler(loop, signame, server): print('caught {}, exiting'.format(signame)) server.close() loop.stop() parser = qubes.tools.QubesArgumentParser(description='Qubes OS daemon') def main(args=None): args = parser.parse_args(args) loop = asyncio.get_event_loop() qubes.libvirtaio.LibvirtAsyncIOEventImpl(loop).register() try: os.unlink(QUBESD_SOCK) except FileNotFoundError: pass old_umask = os.umask(0o007) server = loop.run_until_complete(loop.create_unix_server( functools.partial(QubesDaemonProtocol, app=args.app), QUBESD_SOCK)) os.umask(old_umask) del old_umask for signame in ('SIGINT', 'SIGTERM'): loop.add_signal_handler(getattr(signal, signame), sighandler, loop, signame, server) qubes.utils.systemd_notify() try: loop.run_forever() loop.run_until_complete(server.wait_closed()) finally: loop.close() if __name__ == '__main__': main()