core-admin/qubes/tools/qubesd.py

244 lines
6.8 KiB
Python
Raw Normal View History

2017-02-02 13:03:08 +01:00
#!/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()