diff --git a/qubes/mgmtinternal.py b/qubes/mgmtinternal.py new file mode 100644 index 00000000..519df410 --- /dev/null +++ b/qubes/mgmtinternal.py @@ -0,0 +1,84 @@ +# -*- encoding: utf8 -*- +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2017 Marek Marczykowski-Górecki +# +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, see . + +''' Internal interface for dom0 components to communicate with qubesd. ''' + +import asyncio +import json + +import qubes.mgmt +import qubes.vm.dispvm + +api = qubes.mgmt.api + + +class QubesInternalMgmt(qubes.mgmt.AbstractQubesMgmt): + ''' Communication interface for dom0 components, + by design the input here is trusted.''' + # + # PRIVATE METHODS, not to be called via RPC + # + + # + # ACTUAL RPC CALLS + # + + @api('mgmtinternal.GetSystemInfo', no_payload=True) + @asyncio.coroutine + def getsysteminfo(self): + assert self.dest.name == 'dom0' + assert not self.arg + + system_info = {'domains': { + domain.name: { + 'tags': list(domain.tags), + 'type': domain.__class__.__name__, + 'dispvm_allowed': getattr(domain, 'dispvm_allowed', False), + 'default_dispvm': (str(domain.default_dispvm) if + domain.default_dispvm else None), + 'icon': str(domain.label.icon), + } for domain in self.app.domains + }} + + return json.dumps(system_info) + + @api('mgmtinternal.vm.Start', no_payload=True) + @asyncio.coroutine + def start(self): + assert not self.arg + + yield from self.dest.start() + + @api('mgmtinternal.vm.Create.DispVM', no_payload=True) + @asyncio.coroutine + def create_dispvm(self): + assert not self.arg + + # TODO convert to coroutine + dispvm = qubes.vm.dispvm.DispVM.from_appvm(self.dest) + return dispvm.name + + @api('mgmtinternal.vm.CleanupDispVM', no_payload=True) + @asyncio.coroutine + def cleanup_dispvm(self): + assert not self.arg + + # TODO convert to coroutine + self.dest.cleanup() diff --git a/qubes/tools/qubesd.py b/qubes/tools/qubesd.py index a624fcc9..e72a7dd7 100644 --- a/qubes/tools/qubesd.py +++ b/qubes/tools/qubesd.py @@ -13,17 +13,20 @@ import libvirtaio import qubes import qubes.mgmt +import qubes.mgmtinternal import qubes.utils import qubes.vm.qubesvm QUBESD_SOCK = '/var/run/qubesd.sock' +QUBESD_INTERNAL_SOCK = '/var/run/qubesd.internal.sock' class QubesDaemonProtocol(asyncio.Protocol): buffer_size = 65536 header = struct.Struct('Bx') - def __init__(self, *args, app, debug=False, **kwargs): + def __init__(self, handler, *args, app, debug=False, **kwargs): super().__init__(*args, **kwargs) + self.handler = handler self.app = app self.untrusted_buffer = io.BytesIO() self.len_untrusted_buffer = 0 @@ -69,7 +72,7 @@ class QubesDaemonProtocol(asyncio.Protocol): @asyncio.coroutine def respond(self, src, method, dest, arg, *, untrusted_payload): try: - mgmt = qubes.mgmt.QubesMgmt(self.app, src, method, dest, arg) + mgmt = self.handler(self.app, src, method, dest, arg) response = yield from mgmt.execute( untrusted_payload=untrusted_payload) @@ -147,9 +150,10 @@ class QubesDaemonProtocol(asyncio.Protocol): self.transport.write(str(exc).encode('utf-8') + b'\0') -def sighandler(loop, signame, server): +def sighandler(loop, signame, server, server_internal): print('caught {}, exiting'.format(signame)) server.close() + server_internal.close() loop.stop() parser = qubes.tools.QubesArgumentParser(description='Qubes OS daemon') @@ -166,20 +170,35 @@ def main(args=None): pass old_umask = os.umask(0o007) server = loop.run_until_complete(loop.create_unix_server( - functools.partial(QubesDaemonProtocol, app=args.app), QUBESD_SOCK)) + functools.partial(QubesDaemonProtocol, qubes.mgmt.QubesMgmt, + app=args.app), QUBESD_SOCK)) shutil.chown(QUBESD_SOCK, group='qubes') + + try: + os.unlink(QUBESD_INTERNAL_SOCK) + except FileNotFoundError: + pass + server_internal = loop.run_until_complete(loop.create_unix_server( + functools.partial(QubesDaemonProtocol, + qubes.mgmtinternal.QubesInternalMgmt, + app=args.app), QUBESD_INTERNAL_SOCK)) + shutil.chown(QUBESD_INTERNAL_SOCK, group='qubes') + os.umask(old_umask) del old_umask for signame in ('SIGINT', 'SIGTERM'): loop.add_signal_handler(getattr(signal, signame), - sighandler, loop, signame, server) + sighandler, loop, signame, server, server_internal) qubes.utils.systemd_notify() try: loop.run_forever() - loop.run_until_complete(server.wait_closed()) + loop.run_until_complete(asyncio.wait([ + server.wait_closed(), + server_internal.wait_closed(), + ])) finally: loop.close() diff --git a/qubespolicy/__init__.py b/qubespolicy/__init__.py index df9f9b60..6f3e879f 100755 --- a/qubespolicy/__init__.py +++ b/qubespolicy/__init__.py @@ -643,4 +643,4 @@ def get_system_info(): ''' system_info = qubesd_call('dom0', 'mgmtinternal.GetSystemInfo') - return json.loads(system_info) + return json.loads(system_info.decode('utf-8')) diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec index 9fc8278b..d9298ef8 100644 --- a/rpm_spec/core-dom0.spec +++ b/rpm_spec/core-dom0.spec @@ -234,6 +234,7 @@ fi %{python3_sitelib}/qubes/firewall.py %{python3_sitelib}/qubes/log.py %{python3_sitelib}/qubes/mgmt.py +%{python3_sitelib}/qubes/mgmtinternal.py %{python3_sitelib}/qubes/rngdoc.py %{python3_sitelib}/qubes/tarwriter.py %{python3_sitelib}/qubes/utils.py