core-admin/qubes/tools/qubesd.py

242 lines
6.7 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 os
import reprlib
import signal
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)
2017-02-08 15:20:15 +01:00
# pylint: disable=invalid-name
2017-02-02 13:03:08 +01:00
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(
2017-02-08 15:20:15 +01:00
untrusted_func_name))
2017-02-02 13:03:08 +01:00
if not isinstance(untrusted_func, types.MethodType):
raise ProtocolError(
'no such method: {!r}'.format(
2017-02-08 15:20:15 +01:00
untrusted_func_name))
2017-02-02 13:03:08 +01:00
if getattr(untrusted_func, 'not_in_api', False):
raise ProtocolError(
'attempt to call private method: {!r}'.format(
2017-02-08 15:20:15 +01:00
untrusted_func_name))
2017-02-02 13:03:08 +01:00
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(
2017-02-08 15:20:15 +01:00
str(self.dest.property_is_default(self.arg)),
self.repr(value))
2017-02-02 13:03:08 +01:00
class QubesDaemonProtocol(asyncio.Protocol):
buffer_size = 65536
def __init__(self, *args, app, **kwargs):
2017-02-08 15:20:15 +01:00
super().__init__(*args, **kwargs)
2017-02-02 13:03:08 +01:00
self.app = app
self.untrusted_buffer = io.BytesIO()
2017-02-08 15:20:15 +01:00
self.len_untrusted_buffer = 0
self.transport = None
2017-02-02 13:03:08 +01:00
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))
2017-02-08 15:20:15 +01:00
if self.len_untrusted_buffer + len(untrusted_data) > self.buffer_size:
2017-02-02 13:03:08 +01:00
print(' request too long')
self.transport.close()
return
2017-02-08 15:20:15 +01:00
self.len_untrusted_buffer += \
2017-02-02 13:03:08 +01:00
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()