c534b68665
This is migration of core2 commits: commitd0ba43f253
Author: Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com> Date: Mon Jun 6 02:21:08 2016 +0200 core: start guid as normal user even when VM started by root Another attempt to avoid permissions-related problems... QubesOS/qubes-issues#1768 commit89d002a031
Author: Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com> Date: Mon Jun 6 02:19:51 2016 +0200 core: use runuser instead of sudo for switching root->user There are problems with using sudo in early system startup (systemd-logind not running yet, pam_systemd timeouts). Since we don't need full session here, runuser is good enough (even better: faster). commit2265fd3d52
Author: Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com> Date: Sat Jun 4 17:42:24 2016 +0200 core: start qubesdb as normal user, even when VM is started by root On VM start, old qubesdb-daemon is terminated (if still running). In practice it happen only at VM startart (shutdown and quickly start again). But in that case, if the VM was started by root, such operation would fail. So when VM is started by root, make sure that qubesdb-daemon will be running as normal user (the first user in group 'qubes' - there should be only one). Fixes QubesOS/qubes-issues#1745
295 lines
9.9 KiB
Python
295 lines
9.9 KiB
Python
#!/usr/bin/env python
|
|
# vim: fileencoding=utf-8
|
|
|
|
#
|
|
# The Qubes OS Project, https://www.qubes-os.org/
|
|
#
|
|
# Copyright (C) 2010-2016 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
|
# Copyright (C) 2013-2016 Marek Marczykowski-Górecki
|
|
# <marmarek@invisiblethingslab.com>
|
|
# Copyright (C) 2014-2016 Wojtek Porczyk <woju@invisiblethingslab.com>
|
|
#
|
|
# 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, write to the Free Software Foundation, Inc.,
|
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
#
|
|
|
|
import os
|
|
import re
|
|
import subprocess
|
|
|
|
import qubes.config
|
|
import qubes.ext
|
|
|
|
|
|
# "LVDS connected 1024x768+0+0 (normal left inverted right) 304mm x 228mm"
|
|
REGEX_OUTPUT = re.compile(r'''
|
|
(?x) # ignore whitespace
|
|
^ # start of string
|
|
(?P<output>[A-Za-z0-9\-]*)[ ] # LVDS VGA etc
|
|
(?P<connect>(dis)?connected)[ ]# dis/connected
|
|
(?P<primary>(primary)?)[ ]?
|
|
(( # a group
|
|
(?P<width>\d+)x # either 1024x768+0+0
|
|
(?P<height>\d+)[+]
|
|
(?P<x>\d+)[+]
|
|
(?P<y>\d+)
|
|
)|[\D]) # or not a digit
|
|
([ ]\(.*\))?[ ]? # ignore options
|
|
( # 304mm x 228mm
|
|
(?P<width_mm>\d+)mm[ ]x[ ]
|
|
(?P<height_mm>\d+)mm
|
|
)?
|
|
.* # ignore rest of line
|
|
''')
|
|
|
|
|
|
def get_monitor_layout():
|
|
outputs = []
|
|
|
|
for line in subprocess.Popen(
|
|
['xrandr', '-q'], stdout=subprocess.PIPE).stdout:
|
|
if not line.startswith("Screen") and not line.startswith(" "):
|
|
output_params = REGEX_OUTPUT.match(line).groupdict()
|
|
if output_params['width']:
|
|
phys_size = ""
|
|
if output_params['width_mm']:
|
|
# don't provide real values for privacy reasons - see
|
|
# #1951 for details
|
|
dpi = (int(output_params['width']) * 254 /
|
|
int(output_params['width_mm']) / 10)
|
|
if dpi > 300:
|
|
dpi = 300
|
|
elif dpi > 200:
|
|
dpi = 200
|
|
elif dpi > 150:
|
|
dpi = 150
|
|
else:
|
|
# if lower, don't provide this info to the VM at all
|
|
dpi = 0
|
|
if dpi:
|
|
# now calculate dimensions based on approximate DPI
|
|
phys_size = " {} {}".format(
|
|
int(output_params['width']) * 254 / dpi / 10,
|
|
int(output_params['height']) * 254 / dpi / 10,
|
|
)
|
|
outputs.append("%s %s %s %s%s\n" % (
|
|
output_params['width'],
|
|
output_params['height'],
|
|
output_params['x'],
|
|
output_params['y'],
|
|
phys_size,
|
|
))
|
|
return outputs
|
|
|
|
|
|
class GUI(qubes.ext.Extension):
|
|
@staticmethod
|
|
def kde_guid_args(vm):
|
|
'''Return KDE-specific arguments for guid, if applicable'''
|
|
|
|
guid_cmd = []
|
|
# Avoid using environment variables for checking the current session,
|
|
# because this script may be called with cleared env (like with sudo).
|
|
if subprocess.check_output(
|
|
['xprop', '-root', '-notype', 'KDE_SESSION_VERSION']) == \
|
|
'KDE_SESSION_VERSION = 5\n':
|
|
# native decoration plugins is used, so adjust window properties
|
|
# accordingly
|
|
guid_cmd += ['-T'] # prefix window titles with VM name
|
|
# get owner of X11 session
|
|
session_owner = None
|
|
for line in subprocess.check_output(['xhost']).splitlines():
|
|
if line == 'SI:localuser:root':
|
|
pass
|
|
elif line.startswith('SI:localuser:'):
|
|
session_owner = line.split(":")[2]
|
|
if session_owner is not None:
|
|
data_dir = os.path.expanduser(
|
|
'~{}/.local/share'.format(session_owner))
|
|
else:
|
|
# fallback to current user
|
|
data_dir = os.path.expanduser('~/.local/share')
|
|
|
|
guid_cmd += ['-p',
|
|
'_KDE_NET_WM_COLOR_SCHEME=s:{}'.format(
|
|
os.path.join(data_dir,
|
|
'qubes-kde', vm.label.name + '.colors'))]
|
|
return guid_cmd
|
|
|
|
|
|
@qubes.ext.handler('domain-start', 'domain-cmd-pre-run')
|
|
def start_guid(self, vm, event, preparing_dvm=False, start_guid=True,
|
|
extra_guid_args=None, **kwargs):
|
|
'''Launch gui daemon.
|
|
|
|
GUI daemon securely displays windows from domain.
|
|
''' # pylint: disable=no-self-use,unused-argument
|
|
|
|
if not start_guid or preparing_dvm \
|
|
or not os.path.exists('/var/run/shm.id'):
|
|
return
|
|
|
|
if self.is_guid_running(vm):
|
|
return
|
|
|
|
if not vm.features.check_with_template('gui', not vm.hvm):
|
|
vm.log.debug('Not starting gui daemon, disabled by features')
|
|
return
|
|
|
|
if not os.getenv('DISPLAY'):
|
|
vm.log.error('Not starting gui daemon, no DISPLAY set')
|
|
return
|
|
|
|
vm.log.info('Starting gui daemon')
|
|
|
|
guid_cmd = [qubes.config.system_path['qubes_guid_path'],
|
|
'-d', str(vm.xid), '-N', vm.name,
|
|
'-c', vm.label.color,
|
|
'-i', vm.label.icon_path,
|
|
'-l', str(vm.label.index)]
|
|
|
|
if extra_guid_args is not None:
|
|
guid_cmd += extra_guid_args
|
|
|
|
if vm.debug:
|
|
guid_cmd += ['-v', '-v']
|
|
|
|
# elif not verbose:
|
|
else:
|
|
guid_cmd += ['-q']
|
|
|
|
if vm.hvm:
|
|
guid_cmd += ['-Q', '-n']
|
|
|
|
stubdom_guid_pidfile = '/var/run/qubes/guid-running.{}'.format(
|
|
self.get_stubdom_xid(vm))
|
|
if not vm.debug and os.path.exists(stubdom_guid_pidfile):
|
|
# Terminate stubdom guid once "real" gui agent connects
|
|
stubdom_guid_pid = \
|
|
open(stubdom_guid_pidfile, 'r').read().strip()
|
|
guid_cmd += ['-K', stubdom_guid_pid]
|
|
|
|
guid_cmd += self.kde_guid_args(vm)
|
|
|
|
try:
|
|
vm.start_daemon(guid_cmd)
|
|
except subprocess.CalledProcessError:
|
|
raise qubes.exc.QubesVMError(vm,
|
|
'Cannot start qubes-guid for domain {!r}'.format(vm.name))
|
|
|
|
vm.fire_event('monitor-layout-change')
|
|
vm.wait_for_session()
|
|
|
|
|
|
@staticmethod
|
|
def get_stubdom_xid(vm):
|
|
if vm.xid < 0:
|
|
return -1
|
|
|
|
if vm.app.vmm.xs is None:
|
|
return -1
|
|
|
|
stubdom_xid_str = vm.app.vmm.xs.read('',
|
|
'/local/domain/{}/image/device-model-domid'.format(vm.xid))
|
|
if stubdom_xid_str is None or not stubdom_xid_str.isdigit():
|
|
return -1
|
|
|
|
return int(stubdom_xid_str)
|
|
|
|
|
|
@staticmethod
|
|
def send_gui_mode(vm):
|
|
vm.run_service('qubes.SetGuiMode',
|
|
input=('SEAMLESS'
|
|
if vm.features.get('gui-seamless', False)
|
|
else 'FULLSCREEN'))
|
|
|
|
|
|
@qubes.ext.handler('domain-spawn')
|
|
def on_domain_spawn(self, vm, event, start_guid=True, **kwargs):
|
|
# pylint: disable=unused-argument
|
|
if not start_guid:
|
|
return
|
|
|
|
if not vm.hvm:
|
|
return
|
|
|
|
if not os.getenv('DISPLAY'):
|
|
vm.log.error('Not starting gui daemon, no DISPLAY set')
|
|
return
|
|
|
|
guid_cmd = [qubes.config.system_path['qubes_guid_path'],
|
|
'-d', str(self.get_stubdom_xid(vm)),
|
|
'-t', str(vm.xid),
|
|
'-N', vm.name,
|
|
'-c', vm.label.color,
|
|
'-i', vm.label.icon_path,
|
|
'-l', str(vm.label.index),
|
|
]
|
|
|
|
if vm.debug:
|
|
guid_cmd += ['-v', '-v']
|
|
else:
|
|
guid_cmd += ['-q']
|
|
|
|
guid_cmd += self.kde_guid_args(vm)
|
|
|
|
try:
|
|
vm.start_daemon(guid_cmd)
|
|
except subprocess.CalledProcessError:
|
|
raise qubes.exc.QubesVMError(vm, 'Cannot start gui daemon')
|
|
|
|
|
|
@qubes.ext.handler('monitor-layout-change')
|
|
def on_monitor_layout_change(self, vm, event, monitor_layout=None):
|
|
# pylint: disable=no-self-use,unused-argument
|
|
if vm.features.check_with_template('no-monitor-layout', False) \
|
|
or not vm.is_running():
|
|
return
|
|
|
|
if monitor_layout is None:
|
|
monitor_layout = get_monitor_layout()
|
|
if not monitor_layout:
|
|
return
|
|
|
|
pipe = vm.run('QUBESRPC qubes.SetMonitorLayout dom0',
|
|
passio_popen=True, wait=True)
|
|
|
|
pipe.stdin.write(''.join(monitor_layout))
|
|
pipe.stdin.close()
|
|
pipe.wait()
|
|
|
|
|
|
@staticmethod
|
|
def is_guid_running(vm):
|
|
'''Check whether gui daemon for this domain is available.
|
|
|
|
:returns: :py:obj:`True` if guid is running, \
|
|
:py:obj:`False` otherwise.
|
|
:rtype: bool
|
|
'''
|
|
xid = vm.xid
|
|
if xid < 0:
|
|
return False
|
|
if not os.path.exists('/var/run/qubes/guid-running.{}'.format(xid)):
|
|
return False
|
|
return True
|
|
|
|
|
|
@qubes.ext.handler('domain-is-fully-usable')
|
|
def on_domain_is_fully_usable(self, vm, event):
|
|
# pylint: disable=unused-argument
|
|
if not self.is_guid_running(vm):
|
|
yield False
|