diff --git a/qubes/config.py b/qubes/config.py index d4a15117..b3c979d1 100644 --- a/qubes/config.py +++ b/qubes/config.py @@ -33,6 +33,7 @@ system_path = { 'qubes_guid_path': '/usr/bin/qubes-guid', 'qrexec_daemon_path': '/usr/sbin/qrexec-daemon', 'qrexec_client_path': '/usr/bin/qrexec-client', + 'qrexec_rpc_multiplexer': '/usr/lib/qubes/qubes-rpc-multiplexer', 'qubesdb_daemon_path': '/usr/sbin/qubesdb-daemon', # Relative to qubes_base_dir diff --git a/qubes/vm/adminvm.py b/qubes/vm/adminvm.py index a7c949c5..46871b7b 100644 --- a/qubes/vm/adminvm.py +++ b/qubes/vm/adminvm.py @@ -21,7 +21,8 @@ # ''' This module contains the AdminVM implementation ''' - +import asyncio +import subprocess import libvirt import qubes @@ -212,6 +213,81 @@ class AdminVM(qubes.vm.BaseVM): self._qdb_connection = qubesdb.QubesDB(self.name) return self._qdb_connection + @asyncio.coroutine + def run_service(self, service, source=None, user=None, + filter_esc=False, autostart=False, gui=False, **kwargs): + '''Run service on this VM + + :param str service: service name + :param qubes.vm.qubesvm.QubesVM source: source domain as presented to + this VM + :param str user: username to run service as + :param bool filter_esc: filter escape sequences to protect terminal \ + emulator + :param bool autostart: if :py:obj:`True`, machine will be started if \ + it is not running + :param bool gui: when autostarting, also start gui daemon + :rtype: asyncio.subprocess.Process + + .. note:: + User ``root`` is redefined to ``SYSTEM`` in the Windows agent code + ''' + # pylint: disable=unused-argument + + source = 'dom0' if source is None else self.app.domains[source].name + + if filter_esc: + raise NotImplementedError( + 'filter_esc=True not supported on calls to dom0') + + if user is None: + user = 'root' + + yield from self.fire_event_async('domain-cmd-pre-run', pre_event=True, + start_guid=gui) + + if user != 'root': + cmd = ['runuser', '-u', user, '--'] + else: + cmd = [] + cmd.extend([ + qubes.config.system_path['qrexec_rpc_multiplexer'], + service, + source, + 'name', + self.name, + ]) + return (yield from asyncio.create_subprocess_exec( + *cmd, + **kwargs)) + + @asyncio.coroutine + def run_service_for_stdio(self, *args, input=None, **kwargs): + '''Run a service, pass an optional input and return (stdout, stderr). + + Raises an exception if return code != 0. + + *args* and *kwargs* are passed verbatim to :py:meth:`run_service`. + + .. warning:: + There are some combinations if stdio-related *kwargs*, which are + not filtered for problems originating between the keyboard and the + chair. + ''' # pylint: disable=redefined-builtin + + kwargs.setdefault('stdin', subprocess.PIPE) + kwargs.setdefault('stdout', subprocess.PIPE) + kwargs.setdefault('stderr', subprocess.PIPE) + p = yield from self.run_service(*args, **kwargs) + + # this one is actually a tuple, but there is no need to unpack it + stdouterr = yield from p.communicate(input=input) + + if p.returncode: + raise subprocess.CalledProcessError(p.returncode, + args[0], *stdouterr) + + return stdouterr # def __init__(self, **kwargs): # super(QubesAdminVm, self).__init__(qid=0, name="dom0",