Compare commits

..

27 Commits

Author SHA1 Message Date
Marek Marczykowski-Górecki 473985c94d
version 3.2.18
6 years ago
Marek Marczykowski-Górecki ce2193d85b
kernel-install: fix initramfs copying scripts
6 years ago
Marek Marczykowski-Górecki cc8bffb664
kernel-install: remove EFI variant of BLS dirs too
6 years ago
Marek Marczykowski-Górecki 06e260e92d
kernel-install: cleanup old kernel binary on remove
6 years ago
Marek Marczykowski-Górecki 306330e042
kernel-install: use up to date initramfs
6 years ago
Marek Marczykowski-Górecki 72c2a30eab
kernel-install: avoid creating initramfs multiple times
6 years ago
Marek Marczykowski-Górecki 58f0af9d80
rpm: add missing BR: python
6 years ago
Marek Marczykowski-Górecki 9f18e6d742
Drop salsa20-x86_64 module from dracut image
6 years ago
Marek Marczykowski-Górecki 61f83e888f
Merge remote-tracking branch 'qubesos/pr/39' into release3.2
6 years ago
donoban e2ff40c188
Added -p to mkdir for skip error if dir exists
6 years ago
donoban facf84a4e6
Make dir dom0-updates if not exists on UpdateVM
6 years ago
Marek Marczykowski-Górecki 86efd7e287
version 3.2.17
6 years ago
Marek Marczykowski-Górecki a29580572e
rpm: adjust dependencies
6 years ago
Marek Marczykowski-Górecki 1a01417994
qubespolicy: unify calling qrexec service
6 years ago
Marek Marczykowski-Górecki fe271c5672
qubespolicy: use '@' instead of '$' for policy keywords
6 years ago
Marek Marczykowski-Górecki 6b0fef6dba
qrexec: automatically translate '$' to '@' in target name specification
6 years ago
Marek Marczykowski-Górecki f481671782
qrexec: forbid '$' in target and service name
6 years ago
Marek Marczykowski-Górecki e6d8b52197
qrexec: use exec_qubes_rpc_if_requested() from qubes-utils
6 years ago
Simon Gaiser e7f4fa9a5d pm-utils: Fix handling of HVM domains
6 years ago
Marek Marczykowski-Górecki d67af4bfd9
version 3.2.16
7 years ago
Marek Marczykowski-Górecki e4469c7fde
qrexec: fix domain autostart on qrexec call with 'target=' in policy
7 years ago
Marek Marczykowski-Górecki 012ca712d2
version 3.2.15
7 years ago
Christopher Laprise 3bd5c13996
Fix root.img handling bug
7 years ago
Marek Marczykowski-Górecki 63e023883b
version 3.2.14
7 years ago
Marek Marczykowski-Górecki bb88d74689
qrexec: fix pending requests cleanup code (cont)
7 years ago
Marek Marczykowski-Górecki a94102c940
Really disable lesspipe
7 years ago
Marek Marczykowski-Górecki 5e79b88adc
version 3.2.13
7 years ago

@ -1,13 +1,7 @@
sudo: required
dist: bionic
dist: trusty
language: generic
install: git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder
script: ~/qubes-builder/scripts/travis-build
env:
- DIST_DOM0=fc31 USE_QUBES_REPO_VERSION=4.1 USE_QUBES_REPO_TESTING=1
# don't build tags which are meant for code signing only
branches:
except:
- /.*_.*/
- build
- DIST_DOM0=fc23 USE_QUBES_REPO_VERSION=3.2 USE_QUBES_REPO_TESTING=1

@ -0,0 +1,8 @@
[Desktop Entry]
Version=1.0
Type=Application
Terminal=false
Name=Command Prompt
Comment=Use the command line
Categories=GNOME;GTK;Utility;TerminalEmulator;System;
Exec=cmd /c start cmd

@ -0,0 +1,8 @@
[Desktop Entry]
Version=1.0
Type=Application
Terminal=false
Name=Explorer
Comment=Browse files
Categories=Utility;Core;
Exec=explorer

@ -0,0 +1,8 @@
[Desktop Entry]
Version=1.0
Type=Application
Terminal=false
Name=Internet Explorer
Comment=Browse the Web
Categories=Network;WebBrowser;
Exec=C:\\Program Files\\Internet Explorer\\iexplore.exe

@ -0,0 +1,10 @@
[Desktop Entry]
Version=1.0
Type=Application
Exec=qubes-vm-settings %VMNAME% applications
Icon=qubes-appmenu-select
Terminal=false
Name=%VMNAME%: Add more shortcuts...
GenericName=%VMNAME%: Add more shortcuts...
StartupNotify=false
Categories=System;X-Qubes-VM;

@ -0,0 +1,10 @@
[Desktop Entry]
Version=1.0
Type=Application
Exec=sh -c 'echo firefox | /usr/lib/qubes/qfile-daemon-dvm qubes.VMShell dom0 DEFAULT red'
Icon=dispvm-red
Terminal=false
Name=DispVM: Firefox web browser
GenericName=DispVM: Web browser
StartupNotify=false
Categories=Network;X-Qubes-VM;

@ -0,0 +1,10 @@
[Desktop Entry]
Version=1.0
Type=Application
Exec=sh -c 'echo xterm | /usr/lib/qubes/qfile-daemon-dvm qubes.VMShell dom0 DEFAULT red'
Icon=dispvm-red
Terminal=false
Name=DispVM: xterm
GenericName=DispVM: Terminal
StartupNotify=false
Categories=Network;X-Qubes-VM;

@ -0,0 +1,5 @@
[Desktop Entry]
Encoding=UTF-8
Type=Directory
Name=DisposableVM
Icon=dispvm-red

@ -0,0 +1,5 @@
[Desktop Entry]
Encoding=UTF-8
Type=Directory
Name=ServiceVM: %VMNAME%
Icon=%XDGICON%

@ -0,0 +1,10 @@
[Desktop Entry]
Version=1.0
Type=Application
Exec=qvm-start --quiet --tray %VMNAME%
Icon=%XDGICON%
Terminal=false
Name=%VMNAME%: Start
GenericName=%VMNAME%: Start
StartupNotify=false
Categories=System;X-Qubes-VM;

@ -0,0 +1,5 @@
[Desktop Entry]
Encoding=UTF-8
Type=Directory
Name=Template: %VMNAME%
Icon=%XDGICON%

@ -0,0 +1,5 @@
[Desktop Entry]
Encoding=UTF-8
Type=Directory
Name=Domain: %VMNAME%
Icon=%XDGICON%

@ -0,0 +1,20 @@
#!/bin/sh
SRC=$1
DSTDIR=$2
VMNAME=$3
VMDIR=$4
XDGICON=$5
DST=$DSTDIR/$VMNAME-$(basename $SRC)
if ! [ -r "$SRC" ]; then
exit 0
fi
sed \
-e "s/%VMNAME%/$VMNAME/" \
-e "s %VMDIR% $VMDIR " \
-e "s/%XDGICON%/$XDGICON/" \
<$SRC >$DST

@ -0,0 +1,14 @@
#!/bin/sh
SRC=$1
DST=$2
VMNAME=$3
VMDIR=$4
XDGICON=$5
sed \
-e "s/%VMNAME%/$VMNAME/" \
-e "s %VMDIR% $VMDIR " \
-e "s/%XDGICON%/$XDGICON/" \
<$SRC >$DST

@ -0,0 +1,64 @@
#!/bin/sh
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@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.
#
#
SRCDIR=$1
VMNAME=$2
VMTYPE=$3
if [ -z "$VMTYPE" ]; then
VMTYPE=appvms
fi
XDGICON=$4
VMDIR=/var/lib/qubes/$VMTYPE/$VMNAME
APPSDIR=$VMDIR/apps
if [ $# -lt 2 ]; then
echo "usage: $0 <apps_templates_dir> <vmname> [appvms|vm-templates|servicevms]"
exit
fi
mkdir -p $APPSDIR
if [ "$SRCDIR" != "none" ]; then
echo "--> Converting Appmenu Templates..."
if [ -r "$VMDIR/whitelisted-appmenus.list" ]; then
cat $VMDIR/whitelisted-appmenus.list | xargs -I{} /usr/libexec/qubes-appmenus/convert-apptemplate2vm.sh $SRCDIR/{} $APPSDIR $VMNAME $VMDIR $XDGICON
else
find $SRCDIR -name "*.desktop" $CHECK_WHITELISTED -exec /usr/libexec/qubes-appmenus/convert-apptemplate2vm.sh {} $APPSDIR $VMNAME $VMDIR $XDGICON \;
fi
/usr/libexec/qubes-appmenus/convert-apptemplate2vm.sh /usr/share/qubes-appmenus/qubes-appmenu-select.desktop $APPSDIR $VMNAME $VMDIR $XDGICON
if [ "$VMTYPE" = "vm-templates" ]; then
DIR_TEMPLATE=/usr/share/qubes-appmenus/qubes-templatevm.directory.template
elif [ "$VMTYPE" = "servicevms" ]; then
DIR_TEMPLATE=/usr/share/qubes-appmenus/qubes-servicevm.directory.template
else
DIR_TEMPLATE=/usr/share/qubes-appmenus/qubes-vm.directory.template
fi
/usr/libexec/qubes-appmenus/convert-dirtemplate2vm.sh $DIR_TEMPLATE $APPSDIR/$VMNAME-vm.directory $VMNAME $VMDIR $XDGICON
fi
echo "--> Adding Apps to the Menu..."
LC_COLLATE=C xdg-desktop-menu install --noupdate $APPSDIR/*.directory $APPSDIR/*.desktop
if [ -n "$KDE_SESSION_UID" -a -z "$SKIP_CACHE_REBUILD" ]; then
xdg-desktop-menu forceupdate
kbuildsycoca$KDE_SESSION_VERSION
fi

@ -0,0 +1,394 @@
#!/usr/bin/python2
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2013 Marek Marczykowski <marmarek@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 subprocess
import sys
import os
import os.path
import shutil
import dbus
from qubes.qubes import QubesVm, QubesHVm
from qubes.qubes import vm_files, system_path
import qubes.imgconverter
vm_files['appmenus_templates_subdir'] = 'apps.templates'
vm_files['appmenus_template_icons_subdir'] = 'apps.tempicons'
vm_files['appmenus_subdir'] = 'apps'
vm_files['appmenus_icons_subdir'] = 'apps.icons'
vm_files['appmenus_template_templates_subdir'] = 'apps-template.templates'
vm_files['appmenus_whitelist'] = 'whitelisted-appmenus.list'
system_path['appmenu_start_hvm_template'] = \
'/usr/share/qubes-appmenus/qubes-start.desktop'
system_path['appmenu_create_cmd'] = \
'/usr/libexec/qubes-appmenus/create-apps-for-appvm.sh'
system_path['appmenu_remove_cmd'] = \
'/usr/libexec/qubes-appmenus/remove-appvm-appmenus.sh'
def QubesVm_get_appmenus_templates_dir(self):
if self.updateable:
return self.absolute_path(vm_files["appmenus_templates_subdir"], None)
elif self.template is not None:
return self.template.appmenus_templates_dir
else:
return None
QubesVm.appmenus_templates_dir = property(QubesVm_get_appmenus_templates_dir)
def QubesVm_get_appmenus_template_icons_dir(self):
if self.updateable:
return self.absolute_path(vm_files["appmenus_template_icons_subdir"],
None)
elif self.template:
return self.template.appmenus_template_icons_dir
else:
return None
QubesVm.appmenus_template_icons_dir = \
property(QubesVm_get_appmenus_template_icons_dir)
def QubesVm_get_appmenus_dir(self):
return self.absolute_path(vm_files["appmenus_subdir"], None)
QubesVm.appmenus_dir = property(QubesVm_get_appmenus_dir)
def QubesVm_get_appmenus_icons_dir(self):
return self.absolute_path(vm_files["appmenus_icons_subdir"], None)
QubesVm.appmenus_icons_dir = property(QubesVm_get_appmenus_icons_dir)
def get_whitelist_names(vm):
return (whitelist for whitelist in (
vm_files["appmenus_whitelist"],
'vm-' + vm_files["appmenus_whitelist"],
'netvm-' + vm_files["appmenus_whitelist"])
if os.path.exists(os.path.join(vm.dir_path, whitelist)))
def QubesVm_appmenus_create(self, verbose=False, source_template=None):
if source_template is None:
source_template = self.template
if self.internal:
return
if self.is_disposablevm():
return
if self.is_netvm():
vmtype = 'servicevms'
elif self.is_template():
vmtype = 'vm-templates'
else:
vmtype = 'appvms'
try:
msgoutput = None if verbose else open(os.devnull, 'w')
if source_template is not None:
subprocess.check_call([system_path["appmenu_create_cmd"],
source_template.appmenus_templates_dir,
self.name, vmtype, self.label.icon],
stdout=msgoutput, stderr=msgoutput)
elif self.appmenus_templates_dir is not None:
subprocess.check_call([system_path["appmenu_create_cmd"],
self.appmenus_templates_dir, self.name,
vmtype, self.label.icon],
stdout=msgoutput, stderr=msgoutput)
else:
# Only add apps to menu
subprocess.check_call([system_path["appmenu_create_cmd"],
"none", self.name, vmtype,
self.label.icon],
stdout=msgoutput, stderr=msgoutput)
except subprocess.CalledProcessError:
print >> sys.stderr, "Ooops, there was a problem creating appmenus " \
"for {0} VM!".format(self.name)
def QubesVm_appmenus_remove(self):
if self.is_netvm():
vmtype = 'servicevms'
elif self.is_template():
vmtype = 'vm-templates'
else:
vmtype = 'appvms'
subprocess.check_call([system_path["appmenu_remove_cmd"], self.name,
vmtype], stderr=open(os.devnull, 'w'))
def QubesVm_appmenus_cleanup(self):
srcdir = self.appmenus_templates_dir
if srcdir is None:
return
if not os.path.exists(srcdir):
return
if not os.path.exists(self.appmenus_dir):
return
for appmenu in os.listdir(self.appmenus_dir):
if not os.path.exists(os.path.join(srcdir, appmenu)):
os.unlink(os.path.join(self.appmenus_dir, appmenu))
def QubesVm_appmenus_replace_entry(self, old_name, new_name):
for whitelist in get_whitelist_names(self):
whitelist_path = os.path.join(self.dir_path, whitelist)
with open(whitelist_path) as f:
old_lines = f.readlines()
new_lines = [
(new_name + '\n' if l == old_name + '\n' else l)
for l in old_lines]
if new_lines != old_lines:
with open(whitelist_path, 'w') as f:
f.write(''.join(new_lines))
def QubesVm_appicons_create(self, srcdir=None, force=False):
if srcdir is None:
srcdir = self.appmenus_template_icons_dir
if srcdir is None:
return
if not os.path.exists(srcdir):
return
if self.internal:
return
if self.is_disposablevm():
return
whitelist = os.path.join(self.dir_path, vm_files['appmenus_whitelist'])
if os.path.exists(whitelist):
whitelist = [line.strip() for line in open(whitelist)]
else:
whitelist = None
if not os.path.exists(self.appmenus_icons_dir):
os.mkdir(self.appmenus_icons_dir)
elif not os.path.isdir(self.appmenus_icons_dir):
os.unlink(self.appmenus_icons_dir)
os.mkdir(self.appmenus_icons_dir)
for icon in os.listdir(srcdir):
desktop = os.path.splitext(icon)[0] + '.desktop'
if whitelist and desktop not in whitelist:
continue
src_icon = os.path.join(srcdir, icon)
dst_icon = os.path.join(self.appmenus_icons_dir, icon)
if not os.path.exists(dst_icon) or force or \
os.path.getmtime(src_icon) > os.path.getmtime(dst_icon):
qubes.imgconverter.tint(src_icon, dst_icon, self.label.color)
def QubesVm_appicons_remove(self):
if not os.path.exists(self.appmenus_icons_dir):
return
for icon in os.listdir(self.appmenus_icons_dir):
os.unlink(os.path.join(self.appmenus_icons_dir, icon))
def QubesVm_appicons_cleanup(self):
srcdir = self.appmenus_template_icons_dir
if srcdir is None:
return
if not os.path.exists(srcdir):
return
if not os.path.exists(self.appmenus_icons_dir):
return
for icon in os.listdir(self.appmenus_icons_dir):
if not os.path.exists(os.path.join(srcdir, icon)):
os.unlink(os.path.join(self.appmenus_icons_dir, icon))
def QubesVm_pre_rename(self, new_name):
self.appmenus_remove()
def QubesVm_post_rename(self, old_name):
self.appmenus_create()
def QubesVm_create_on_disk(self, verbose, source_template):
if isinstance(self, QubesHVm) and source_template is None:
if verbose:
print >> sys.stderr, "--> Creating appmenus directory: {0}".format(
self.appmenus_templates_dir)
os.mkdir(self.appmenus_templates_dir)
shutil.copy(system_path["appmenu_start_hvm_template"],
self.appmenus_templates_dir)
source_whitelist_filename = 'vm-' + vm_files["appmenus_whitelist"]
if self.is_netvm():
source_whitelist_filename = 'netvm-' + vm_files["appmenus_whitelist"]
if source_template and os.path.exists(
os.path.join(source_template.dir_path, source_whitelist_filename)):
if verbose:
print >> sys.stderr, "--> Creating default whitelisted apps list: {0}". \
format(self.dir_path + '/' + vm_files["whitelisted_appmenus"])
shutil.copy(
os.path.join(source_template.dir_path, source_whitelist_filename),
os.path.join(self.dir_path, vm_files["whitelisted_appmenus"]))
if source_template and self.updateable:
if verbose:
print >> sys.stderr, "--> Copying the template's appmenus templates dir:\n{0} ==>\n{1}". \
format(source_template.appmenus_templates_dir,
self.appmenus_templates_dir)
if os.path.isdir(source_template.appmenus_templates_dir):
shutil.copytree(source_template.appmenus_templates_dir,
self.appmenus_templates_dir)
else:
os.mkdir(self.appmenus_templates_dir)
if os.path.isdir(source_template.appmenus_template_icons_dir):
shutil.copytree(source_template.appmenus_template_icons_dir,
self.appmenus_template_icons_dir)
else:
os.mkdir(self.appmenus_template_icons_dir)
# Create appmenus
self.appicons_create()
self.appmenus_create(verbose=verbose)
def QubesVm_clone_disk_files(self, src_vm, verbose):
if src_vm.updateable and src_vm.appmenus_templates_dir is not None and \
self.appmenus_templates_dir is not None:
if verbose:
print >> sys.stderr, "--> Copying the template's appmenus templates dir:\n{0} ==>\n{1}". \
format(src_vm.appmenus_templates_dir,
self.appmenus_templates_dir)
shutil.copytree(src_vm.appmenus_templates_dir,
self.appmenus_templates_dir)
if src_vm.updateable and src_vm.appmenus_template_icons_dir is not None \
and self.appmenus_template_icons_dir is not None and \
os.path.isdir(src_vm.appmenus_template_icons_dir):
if verbose:
print >> sys.stderr, "--> Copying the template's appmenus " \
"template icons dir:\n{0} ==>\n{1}". \
format(src_vm.appmenus_template_icons_dir,
self.appmenus_template_icons_dir)
shutil.copytree(src_vm.appmenus_template_icons_dir,
self.appmenus_template_icons_dir)
for whitelist in get_whitelist_names(src_vm):
if verbose:
print >> sys.stderr, "--> Copying whitelisted apps list: {0}". \
format(whitelist)
shutil.copy(os.path.join(src_vm.dir_path, whitelist),
os.path.join(self.dir_path, whitelist))
# Create appmenus
self.appicons_create()
self.appmenus_create(verbose=verbose)
def QubesVm_remove_from_disk(self):
self.appmenus_remove()
def QubesVm_label_setter(self, _):
self.appicons_create(force=True)
# Apparently desktop environments heavily caches the icons,
# see #751 for details
if "plasma" in os.environ.get("DESKTOP_SESSION", ""):
try:
os.unlink(os.path.expandvars(
"$HOME/.kde/cache-$HOSTNAME/icon-cache.kcache"))
except:
pass
try:
notify_object = dbus.SessionBus().get_object(
"org.freedesktop.Notifications",
"/org/freedesktop/Notifications")
notify_object.Notify(
"Qubes", 0, self.label.icon, "Qubes",
"You will need to log off and log in again for the VM icons "
"to update in the KDE launcher menu",
[], [], 10000,
dbus_interface="org.freedesktop.Notifications")
except:
pass
elif "xfce" in os.environ.get("DESKTOP_SESSION", ""):
self.appmenus_remove()
self.appmenus_create()
def QubesVm_appmenus_recreate(self):
"""
Force recreation of all appmenus and icons. For example when VM label
color was changed
"""
self.appmenus_remove()
self.appmenus_cleanup()
self.appicons_remove()
self.appicons_create()
self.appmenus_create()
def QubesVm_appmenus_update(self):
"""
Similar to appmenus_recreate, but do not touch unchanged files
"""
self.appmenus_remove()
self.appmenus_cleanup()
self.appicons_create()
self.appicons_cleanup()
self.appmenus_create()
def QubesVm_set_attr(self, name, newvalue, oldvalue):
if name == 'internal':
if newvalue and not oldvalue:
self.appmenus_remove()
elif not newvalue and oldvalue:
self.appmenus_create()
# new methods
QubesVm.appmenus_create = QubesVm_appmenus_create
QubesVm.appmenus_remove = QubesVm_appmenus_remove
QubesVm.appmenus_cleanup = QubesVm_appmenus_cleanup
QubesVm.appmenus_recreate = QubesVm_appmenus_recreate
QubesVm.appmenus_update = QubesVm_appmenus_update
QubesVm.appmenus_replace_entry = QubesVm_appmenus_replace_entry
QubesVm.appicons_create = QubesVm_appicons_create
QubesVm.appicons_cleanup = QubesVm_appicons_cleanup
QubesVm.appicons_remove = QubesVm_appicons_remove
# hooks for existing methods
QubesVm.hooks_pre_rename.append(QubesVm_pre_rename)
QubesVm.hooks_post_rename.append(QubesVm_post_rename)
QubesVm.hooks_create_on_disk.append(QubesVm_create_on_disk)
QubesVm.hooks_clone_disk_files.append(QubesVm_clone_disk_files)
QubesVm.hooks_remove_from_disk.append(QubesVm_remove_from_disk)
QubesVm.hooks_label_setter.append(QubesVm_label_setter)
QubesVm.hooks_set_attr.append(QubesVm_set_attr)

@ -0,0 +1,392 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2011 Marek Marczykowski <marmarek@mimuw.edu.pl>
#
# 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 optparse
import subprocess
import re
import os
import sys
import shutil
import pipes
from optparse import OptionParser
from qubes.qubes import QubesVmCollection, QubesException, system_path
from qubes.qubes import QubesHVm
from qubes.qubes import vm_files
import qubes.imgconverter
from qubes.qubes import vmm
# fields required to be present (and verified) in retrieved desktop file
required_fields = ["Name", "Exec"]
# limits
appmenus_line_size = 1024
appmenus_line_count = 100000
# regexps for sanitization of retrieved values
std_re = re.compile(r"^[/a-zA-Z0-9.,:&()_ -]*$")
fields_regexp = {
"Name": std_re,
"GenericName": std_re,
"Comment": std_re,
"Categories": re.compile(r"^[a-zA-Z0-9/.;:'() -]*$"),
"Exec": re.compile(r"^[a-zA-Z0-9()_%&>/{}\"'\\:.= -]*$"),
"Icon": re.compile(r"^[a-zA-Z0-9/_.-]*$"),
}
CATEGORIES_WHITELIST = {
# Main Categories
# http://standards.freedesktop.org/menu-spec/1.1/apa.html 20140507
'AudioVideo', 'Audio', 'Video', 'Development', 'Education', 'Game',
'Graphics', 'Network', 'Office', 'Science', 'Settings', 'System',
'Utility',
# Additional Categories
# http://standards.freedesktop.org/menu-spec/1.1/apas02.html
'Building', 'Debugger', 'IDE', 'GUIDesigner', 'Profiling',
'RevisionControl', 'Translation', 'Calendar', 'ContactManagement',
'Database', 'Dictionary', 'Chart', 'Email', 'Finance', 'FlowChart', 'PDA',
'ProjectManagement', 'Presentation', 'Spreadsheet', 'WordProcessor',
'2DGraphics', 'VectorGraphics', 'RasterGraphics', '3DGraphics', 'Scanning',
'OCR', 'Photography', 'Publishing', 'Viewer', 'TextTools',
'DesktopSettings', 'HardwareSettings', 'Printing', 'PackageManager',
'Dialup', 'InstantMessaging', 'Chat', 'IRCClient', 'Feed', 'FileTransfer',
'HamRadio', 'News', 'P2P', 'RemoteAccess', 'Telephony', 'TelephonyTools',
'VideoConference', 'WebBrowser', 'WebDevelopment', 'Midi', 'Mixer',
'Sequencer', 'Tuner', 'TV', 'AudioVideoEditing', 'Player', 'Recorder',
'DiscBurning', 'ActionGame', 'AdventureGame', 'ArcadeGame', 'BoardGame',
'BlocksGame', 'CardGame', 'KidsGame', 'LogicGame', 'RolePlaying',
'Shooter', 'Simulation', 'SportsGame', 'StrategyGame', 'Art',
'Construction', 'Music', 'Languages', 'ArtificialIntelligence',
'Astronomy', 'Biology', 'Chemistry', 'ComputerScience',
'DataVisualization', 'Economy', 'Electricity', 'Geography', 'Geology',
'Geoscience', 'History', 'Humanities', 'ImageProcessing', 'Literature',
'Maps', 'Math', 'NumericalAnalysis', 'MedicalSoftware', 'Physics',
'Robotics', 'Spirituality', 'Sports', 'ParallelComputing', 'Amusement',
'Archiving', 'Compression', 'Electronics', 'Emulator', 'Engineering',
'FileTools', 'FileManager', 'TerminalEmulator', 'Filesystem', 'Monitor',
'Security', 'Accessibility', 'Calculator', 'Clock', 'TextEditor',
'Documentation', 'Adult', 'Core', 'KDE', 'GNOME', 'XFCE', 'GTK', 'Qt',
'Motif', 'Java', 'ConsoleOnly',
# Reserved Categories (not whitelisted)
# http://standards.freedesktop.org/menu-spec/1.1/apas03.html
# 'Screensaver', 'TrayIcon', 'Applet', 'Shell',
}
def sanitise_categories(untrusted_value):
untrusted_categories = (c.strip() for c in untrusted_value.split(';') if c)
categories = (c for c in untrusted_categories if c in CATEGORIES_WHITELIST)
return ';'.join(categories) + ';'
def fallback_hvm_appmenulist():
p = subprocess.Popen(["grep", "-rH", "=", "/usr/share/qubes-appmenus/hvm"],
stdout=subprocess.PIPE)
(stdout, stderr) = p.communicate()
return stdout.splitlines()
def get_appmenus(vm):
global appmenus_line_count
global appmenus_line_size
untrusted_appmenulist = []
if vm is None:
while appmenus_line_count > 0:
untrusted_line = sys.stdin.readline(appmenus_line_size)
if untrusted_line == "":
break
untrusted_appmenulist.append(untrusted_line.strip())
appmenus_line_count -= 1
if appmenus_line_count == 0:
raise QubesException("Line count limit exceeded")
else:
p = vm.run('QUBESRPC qubes.GetAppmenus dom0', passio_popen=True,
gui=False)
while appmenus_line_count > 0:
untrusted_line = p.stdout.readline(appmenus_line_size)
if untrusted_line == "":
break
untrusted_appmenulist.append(untrusted_line.strip())
appmenus_line_count -= 1
p.wait()
if p.returncode != 0:
if isinstance(vm, QubesHVm):
untrusted_appmenulist = fallback_hvm_appmenulist()
else:
raise QubesException("Error getting application list")
if appmenus_line_count == 0:
raise QubesException("Line count limit exceeded")
appmenus = {}
line_rx = re.compile(
r"([a-zA-Z0-9.()_-]+.desktop):([a-zA-Z0-9-]+(?:\[[a-zA-Z@_]+\])?)=(.*)")
ignore_rx = re.compile(r".*([a-zA-Z0-9._-]+.desktop):(#.*|\s+)$")
for untrusted_line in untrusted_appmenulist:
# Ignore blank lines and comments
if len(untrusted_line) == 0 or ignore_rx.match(untrusted_line):
continue
# use search instead of match to skip file path
untrusted_m = line_rx.search(untrusted_line)
if untrusted_m:
filename = untrusted_m.group(1)
assert '/' not in filename
assert '\0' not in filename
untrusted_key = untrusted_m.group(2)
assert '\0' not in untrusted_key
assert '\x1b' not in untrusted_key
assert '=' not in untrusted_key
untrusted_value = untrusted_m.group(3)
# TODO add key-dependent asserts
# Look only at predefined keys
if untrusted_key in fields_regexp:
if fields_regexp[untrusted_key].match(untrusted_value):
# now values are sanitized
key = untrusted_key
if key == 'Categories':
value = sanitise_categories(untrusted_value)
else:
value = untrusted_value
if filename not in appmenus:
appmenus[filename] = {}
appmenus[filename][key] = value
else:
print >> sys.stderr, \
"Warning: ignoring key %r of %s" % \
(untrusted_key, filename)
# else: ignore this key
return appmenus
def create_template(path, values):
# check if all required fields are present
for key in required_fields:
if key not in values:
print >> sys.stderr, "Warning: not creating/updating '%s' " \
"because of missing '%s' key" % (
path, key)
return
desktop_entry = ""
desktop_entry += "[Desktop Entry]\n"
desktop_entry += "Version=1.0\n"
desktop_entry += "Type=Application\n"
desktop_entry += "Terminal=false\n"
desktop_entry += "X-Qubes-VmName=%VMNAME%\n"
if 'Icon' in values:
icon_file = os.path.splitext(os.path.split(path)[1])[0] + '.png'
desktop_entry += "Icon={0}\n".format(os.path.join(
'%VMDIR%', vm_files['appmenus_icons_subdir'], icon_file))
else:
desktop_entry += "Icon=%XDGICON%\n"
for key in ["Name", "GenericName"]:
if key in values:
desktop_entry += "{0}=%VMNAME%: {1}\n".format(key, values[key])
# force category X-Qubes-VM
values["Categories"] = values.get("Categories", "") + "X-Qubes-VM;"
for key in ["Comment", "Categories"]:
if key in values:
desktop_entry += "{0}={1}\n".format(key, values[key])
desktop_entry += "Exec=qvm-run -q --tray -a %VMNAME% -- {0}\n".format(
pipes.quote(values['Exec']))
if not os.path.exists(path) or desktop_entry != open(path, "r").read():
desktop_file = open(path, "w")
desktop_file.write(desktop_entry)
desktop_file.close()
def main():
env_vmname = os.environ.get("QREXEC_REMOTE_DOMAIN")
usage = "usage: %prog [options] <vm-name>\n" \
"Update desktop file templates for given StandaloneVM or TemplateVM"
parser = OptionParser(usage)
parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
default=False)
parser.add_option("--force-root", action="store_true", dest="force_root",
default=False,
help="Force to run, even with root privileges")
parser.add_option("--force-rpc", action="store_true", dest="force_rpc",
default=False,
help="Force to start a new RPC call, "
"even if called from existing one")
# Do not use any RPC call, expects data on stdin (in qubes.GetAppmenus
# format)
parser.add_option("--offline-mode", dest="offline_mode",
action="store_true", default=False,
help=optparse.SUPPRESS_HELP)
(options, args) = parser.parse_args()
if (len(args) != 1) and env_vmname is None:
parser.error("You must specify at least the VM name!")
if env_vmname:
vmname = env_vmname
else:
vmname = args[0]
if os.geteuid() == 0:
if not options.force_root:
print >> sys.stderr, "*** Running this tool as root is strongly " \
"discouraged, this will lead you into " \
"permissions problems."
print >> sys.stderr, "Retry as unprivileged user."
print >> sys.stderr, "... or use --force-root to continue anyway."
exit(1)
if options.offline_mode:
vmm.offline_mode = True
qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_reading()
qvm_collection.load()
qvm_collection.unlock_db()
vm = qvm_collection.get_vm_by_name(vmname)
if vm is None:
print >> sys.stderr, "ERROR: A VM with the name '{0}' " \
"does not exist in the system.".format(
vmname)
exit(1)
if vm.template is not None:
print >> sys.stderr, "ERROR: To sync appmenus for template based VM, " \
"do it on template instead"
exit(1)
if not options.offline_mode and not vm.is_running():
print >> sys.stderr, "ERROR: Appmenus can be retrieved only from " \
"running VM - start it first"
exit(1)
if not options.offline_mode and env_vmname is None or options.force_rpc:
new_appmenus = get_appmenus(vm)
else:
options.verbose = False
new_appmenus = get_appmenus(None)
if len(new_appmenus) == 0:
print >> sys.stderr, "ERROR: No appmenus received, terminating"
exit(1)
os.umask(002)
if not os.path.exists(vm.appmenus_templates_dir):
os.mkdir(vm.appmenus_templates_dir)
if not os.path.exists(vm.appmenus_template_icons_dir):
os.mkdir(vm.appmenus_template_icons_dir)
# Create new/update existing templates
if options.verbose:
print >> sys.stderr, "--> Got {0} appmenus, storing to disk".format(
str(len(new_appmenus)))
for appmenu_file in new_appmenus.keys():
if options.verbose:
if os.path.exists(
os.path.join(vm.appmenus_templates_dir, appmenu_file)):
print >> sys.stderr, "---> Updating {0}".format(appmenu_file)
else:
print >> sys.stderr, "---> Creating {0}".format(appmenu_file)
# TODO: icons support in offline mode
if options.offline_mode:
new_appmenus[appmenu_file].pop('Icon', None)
if 'Icon' in new_appmenus[appmenu_file]:
# the following line is used for time comparison
icondest = os.path.join(vm.appmenus_template_icons_dir,
os.path.splitext(appmenu_file)[0] + '.png')
try:
icon = qubes.imgconverter.Image. \
get_xdg_icon_from_vm(vm,
new_appmenus[
appmenu_file][
'Icon'])
if os.path.exists(icondest):
old_icon = qubes.imgconverter.Image.load_from_file(icondest)
else:
old_icon = None
if old_icon is None or icon != old_icon:
icon.save(icondest)
except Exception, e:
print >> sys.stderr, '----> Failed to get icon for {0}: {1!s}'.\
format(appmenu_file, e)
if os.path.exists(icondest):
print >> sys.stderr, '-----> Found old icon, ' \
'using it instead'
else:
del new_appmenus[appmenu_file]['Icon']
create_template(os.path.join(vm.appmenus_templates_dir, appmenu_file),
new_appmenus[appmenu_file])
# Delete appmenus of removed applications
if options.verbose:
print >> sys.stderr, "--> Cleaning old files"
for appmenu_file in os.listdir(vm.appmenus_templates_dir):
if not appmenu_file.endswith('.desktop'):
continue
if appmenu_file not in new_appmenus:
if options.verbose:
print >> sys.stderr, "---> Removing {0}".format(appmenu_file)
os.unlink(os.path.join(vm.appmenus_templates_dir, appmenu_file))
if isinstance(vm, QubesHVm):
if not os.path.exists(os.path.join(vm.appmenus_templates_dir,
os.path.basename(system_path[
'appmenu_start_hvm_template']))):
shutil.copy(system_path['appmenu_start_hvm_template'],
vm.appmenus_templates_dir)
vm.appmenus_update()
if hasattr(vm, 'appvms'):
os.putenv('SKIP_CACHE_REBUILD', '1')
for child_vm in vm.appvms.values():
try:
child_vm.appmenus_update()
except Exception, e:
print >> sys.stderr, "---> Failed to recreate appmenus for " \
"'{0}': {1}".format(child_vm.name, str(e))
subprocess.call(['xdg-desktop-menu', 'forceupdate'])
if 'KDE_SESSION_UID' in os.environ:
subprocess.call(['kbuildsycoca' + os.environ.get('KDE_SESSION_VERSION', '4')])
os.unsetenv('SKIP_CACHE_REBUILD')
main()

@ -0,0 +1 @@
/usr/libexec/qubes-appmenus/qubes-receive-appmenus

@ -0,0 +1,56 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# 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 sys
from qubes.qubes import QubesVmCollection
def main():
if len(sys.argv) != 4:
print >> sys.stderr, \
'Usage: qvm-appmenu-replace VM_NAME OLD_NAME.desktop NEW_NAME.desktop'
sys.exit(1)
vm_name = sys.argv[1]
old_name = sys.argv[2]
new_name = sys.argv[3]
qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_reading()
qvm_collection.load()
qvm_collection.unlock_db()
vm = qvm_collection.get_vm_by_name(vm_name)
if vm is None:
print >> sys.stderr, "ERROR: A VM with the name '{0}' " \
"does not exist in the system.".format(
vm_name)
exit(1)
if vm.template is not None:
print >> sys.stderr, "ERROR: To replace appmenu for template based VM, " \
"do it on template instead"
exit(1)
vm.appmenus_replace_entry(old_name, new_name)
if hasattr(vm, 'appvms'):
for child_vm in vm.appvms.values():
child_vm.appmenus_replace_entry(old_name, new_name)
main()

@ -0,0 +1,2 @@
#!/bin/sh
exec /usr/libexec/qubes-appmenus/qubes-receive-appmenus $@

@ -0,0 +1,23 @@
#!/bin/sh
VMNAME=$1
VMTYPE=$2
if [ -z "$VMTYPE" ]; then
VMTYPE=appvms
fi
VMDIR=/var/lib/qubes/$VMTYPE/$VMNAME
APPSDIR=$VMDIR/apps
if [ $# -lt 1 ]; then
echo "usage: $0 <vmname> [appvms|vm-templates|servicevms]"
exit
fi
if ls $APPSDIR/*.directory $APPSDIR/*.desktop > /dev/null 2>&1; then
LC_COLLATE=C xdg-desktop-menu uninstall $APPSDIR/*.directory $APPSDIR/*.desktop
rm -f $APPSDIR/*.desktop $APPSDIR/*.directory
rm -f $HOME/.config/menus/applications-merged/user-$VMNAME-vm.menu
fi
if [ -n "$KDE_SESSION_UID" -a -z "$SKIP_CACHE_REBUILD" ]; then
kbuildsycoca$KDE_SESSION_VERSION
fi

@ -73,7 +73,8 @@ EOF
if [ -e /etc/dnf/dnf.conf ]; then
update_conf /etc/dnf/dnf.conf "
reposdir=/etc/yum.real.repos.d
installonlypkgs = kernel, kernel-qubes-vm"
installonlypkgs = kernel, kernel-qubes-vm
distroverpkg = qubes-release"
fi
if [ -e /etc/yum.conf ]; then

@ -1,29 +1,6 @@
#!/bin/bash
escape_args() {
local eargs=""
for arg in "$@"; do
printf -v eargs '%s%q ' "$eargs" "$arg"
done
echo "${eargs%?}"
}
find_regex_in_args() {
local regex="${1}"
shift 1
for arg in "${@}"; do
if echo "${arg}" | grep -q -e "${regex}"; then
return 0
fi
done
return 1
}
UPDATEVM=`qubes-prefs --force-root updatevm`
UPDATEVM=`qubes-prefs --get updatevm`
UPDATES_STAT_FILE=/var/lib/qubes/updates/dom0-updates-available
if [ -z "$UPDATEVM" ]; then
@ -36,26 +13,24 @@ if [ "$1" = "--help" ]; then
echo "it checks for updates for installed packages"
echo ""
echo "Usage: $0 [--clean] [--check-only] [--gui] [<pkg list>]"
echo " --clean clean dnf cache before doing anything"
echo " --clean clean yum cache before doing anything"
echo " --check-only only check for updates (no install)"
echo " --gui use gpk-update-viewer for update selection"
echo " --action=... use specific dnf action, instead of automatic install/update"
echo " --action=... use specific yum action, instead of automatic install/update"
echo " <pkg list> download (and install if run by root) new packages"
echo " in dom0 instead of updating"
exit
fi
PKGS=()
YUM_OPTS=()
PKGS=
YUM_OPTS=
GUI=
CHECK_ONLY=
ALL_OPTS=( "${@}" )
ALL_OPTS="$*"
YUM_ACTION=
QVMRUN_OPTS=
CLEAN=
TEMPLATE=
TEMPLATE_BACKUP=
# Filter out some dnf options and collect packages list
# Filter out some yum options and collect packages list
while [ $# -gt 0 ]; do
case "$1" in
--enablerepo=*|\
@ -74,10 +49,10 @@ while [ $# -gt 0 ]; do
YUM_ACTION=${1#--action=}
;;
-*)
YUM_OPTS+=( "${1}" )
YUM_OPTS="$YUM_OPTS $1"
;;
*)
PKGS+=( "${1}" )
PKGS="$PKGS $1"
if [ -z "$YUM_ACTION" ]; then
YUM_ACTION=install
fi
@ -89,46 +64,36 @@ done
# Prevent implicit update of template - this would override user changes -
# but do allow explicit template upgrade, downgrade, reinstall
if [ "$YUM_ACTION" == "reinstall" ] || [ "$YUM_ACTION" == "upgrade" ] || [ "$YUM_ACTION" == "upgrade-to" ] \
|| [ "$YUM_ACTION" == "downgrade" ] && find_regex_in_args '^qubes-template-' "${PKGS[@]}"; then
TEMPLATE_EXCLUDE_OPTS=()
|| [ "$YUM_ACTION" == "downgrade" ] && [[ "$PKGS" == *"qubes-template-"* ]]; then
TEMPLATE_EXCLUDE_OPTS=""
echo "WARNING: Replacing a template will erase all files in template's /home and /rw !"
# At least one package name matches the regex '^qubes-template-',
# so if there is only one package name in the array, then the
# code can safely assume that the array includes only a template
# package name.
if [[ ${#PKGS[@]} -eq 1 ]]; then
ONEPKG="$(echo "${PKGS[0]}" | sed -r 's/-[0-9]+(\.[0-9-]+)+(\.noarch)*$//')" # Remove version suffix
ONEPKG=`cut -f 1 -d ' ' <<<$PKGS`
if [[ "$ONEPKG" == "qubes-template-"* ]] && [[ "$ONEPKG" == "${PKGS#\ }" ]]; then # test "$PKGS" minus space
# Prepare to backup template root.img in case reinstall doesn't complete.
ONEPKG=`sed -r 's/-[0-9]+(\.[0-9-]+)+(\.noarch)*$//' <<<$ONEPKG` # Remove version suffix
TEMPLATE=${ONEPKG#qubes-template-} # Remove prefix
if qvm-shutdown --wait $TEMPLATE ; then
echo "Template VM halted"
fi
# Try to avoid unrecoverable failures when operating on the template of
# the UpdateVM by making a backup first.
UPDATEVM_TEMPLATE=$(qvm-prefs -- "$UPDATEVM" template 2>/dev/null)
if [ X"$UPDATEVM_TEMPLATE" = X"$TEMPLATE" ]; then
TEMPLATE_BACKUP="${TEMPLATE}-backup-$(date +%Y%m%d)-$(mktemp -u XXXX)"
TEMPLATE_BACKUP=${TEMPLATE_BACKUP:0:31}
echo "Attempting to operate on template of UpdateVM... backing up $TEMPLATE to $TEMPLATE_BACKUP"
if ! qvm-clone -- "$TEMPLATE" "$TEMPLATE_BACKUP"; then
echo "ERROR: Unable to make backup of UpdateVM template!" >&2
exit 1
fi
if ! TEMPLATE_NETVM=`qvm-prefs --force-root $TEMPLATE netvm` \
|| ! BAK_TEMPLATE_ROOT=`qvm-prefs --force-root $TEMPLATE root_img` \
|| ! BAK_TEMPLATE_PRIVATE=`qvm-prefs --force-root $TEMPLATE private_img` ; then
exit 1
fi
if [[ "$TEMPLATE_NETVM" == *"(default)" ]] ; then
TEMPLATE_NETVM="default"
fi
else
echo "ERROR: Specify only one package to reinstall template"
exit 1
fi
elif [ "$YUM_ACTION" == "search" ] || [ "$YUM_ACTION" == "info" ]; then # No need to shutdown for search/info
TEMPLATE_EXCLUDE_OPTS=()
else
TEMPLATE_EXCLUDE_OPTS=( "--exclude=$(rpm -qa --qf '%{NAME},' qubes-template-\*|head -c -1)" )
TEMPLATE_EXCLUDE_OPTS="--exclude=`rpm -qa --qf '%{NAME},' qubes-template-\*`"
fi
YUM_OPTS=( "${TEMPLATE_EXCLUDE_OPTS[@]}" "${YUM_OPTS[@]}" )
ALL_OPTS=( "${TEMPLATE_EXCLUDE_OPTS[@]}" "${ALL_OPTS[@]}" )
YUM_OPTS="$TEMPLATE_EXCLUDE_OPTS $YUM_OPTS"
ALL_OPTS="$TEMPLATE_EXCLUDE_OPTS $ALL_OPTS"
ID=$(id -ur)
if [ $ID != 0 -a -z "$GUI" -a -z "$CHECK_ONLY" ] ; then
@ -136,16 +101,16 @@ if [ $ID != 0 -a -z "$GUI" -a -z "$CHECK_ONLY" ] ; then
exit 1
fi
if [ "$GUI" == "1" -a ${#PKGS[@]} -ne 0 ]; then
if [ "$GUI" == "1" -a -n "$PKGS" ]; then
echo "ERROR: GUI mode can be used only for updates" >&2
exit 1
fi
if [ "$GUI" == "1" ]; then
apps="xterm konsole yumex apper gpk-update-viewer"
apps="yumex apper gpk-update-viewer"
if [ -n "$KDE_FULL_SESSION" ]; then
apps="konsole xterm apper yumex gpk-update-viewer"
apps="apper yumex gpk-update-viewer"
fi
guiapp=
@ -154,8 +119,6 @@ if [ "$GUI" == "1" ]; then
guiapp=$app
case $guiapp in
apper) guiapp="apper --updates --nofork" ;;
xterm) guiapp="xterm -e sudo dnf update" ;;
konsole) guiapp="konsole --hold -e sudo dnf update" ;;
*) guiapp=$app ;;
esac
break;
@ -163,7 +126,7 @@ if [ "$GUI" == "1" ]; then
done
if [ -z "$guiapp" ]; then
message1="You don't have any supported dnf frontend installed."
message1="You don't have installed any supported yum frontend."
message2="Install (using qubes-dom0-update) one of: $apps"
if [ "$KDE_FULL_SESSION" ]; then
@ -180,8 +143,8 @@ if [ "$GUI" != "1" ]; then
QVMRUN_OPTS=--nogui
fi
# Do not start VM automatically when running from cron (only checking for updates)
if [ "$CHECK_ONLY" == "1" ] && ! qvm-check -q --running $UPDATEVM > /dev/null 2>&1; then
# Do not start VM automaticaly when running from cron (only checking for updates)
if [ "$CHECK_ONLY" == "1" ] && ! xl domid $UPDATEVM > /dev/null 2>&1; then
echo "ERROR: UpdateVM not running, not starting it in non-interactive mode" >&2
exit 1
fi
@ -192,16 +155,21 @@ if [ -n "$CLEAN" ]; then
fi
rm -f /var/lib/qubes/updates/errors
# We should ensure the clocks in Dom0 and UpdateVM are in sync
# becuase otherwise yum might complain about future timestamps
qvm-sync-clock
echo "Using $UPDATEVM as UpdateVM to download updates for Dom0; this may take some time..." >&2
# qvm-run by default auto-starts the VM if not running
qvm-run --nogui -q -u root $UPDATEVM 'mkdir -m 775 -p /var/lib/qubes/dom0-updates/' || exit 1
qvm-run --nogui -q -u root $UPDATEVM 'chown user:user /var/lib/qubes/dom0-updates/' || exit 1
qvm-run --nogui -q $UPDATEVM 'rm -rf /var/lib/qubes/dom0-updates/etc' || exit 1
tar c /var/lib/rpm /etc/yum.repos.d /etc/yum.conf /etc/dnf/dnf.conf 2>/dev/null | \
qvm-run --nogui -q --pass-io "$UPDATEVM" 'LC_MESSAGES=C tar x -C /var/lib/qubes/dom0-updates 2>&1 | grep -v -E "s in the future"'
# Start VM if not running already
qvm-run $QVMRUN_OPTS -a $UPDATEVM true || exit 1
qvm-run -u root $UPDATEVM 'mkdir -m 775 -p /var/lib/qubes/dom0-updates/' || exit 1
qvm-run -u root $UPDATEVM 'chown user:user /var/lib/qubes/dom0-updates/' || exit 1
qvm-run $UPDATEVM 'rm -rf /var/lib/qubes/dom0-updates/etc' || exit 1
tar c /var/lib/rpm /etc/yum.repos.d /etc/yum.conf 2>/dev/null | \
qvm-run -p "$UPDATEVM" 'LC_MESSAGES=C tar x -C /var/lib/qubes/dom0-updates 2>&1 | grep -v -E "s in the future"'
qvm-run $QVMRUN_OPTS --pass-io $UPDATEVM "script --quiet --return --command '/usr/lib/qubes/qubes-download-dom0-updates.sh --doit --nogui $(escape_args "${ALL_OPTS[@]}")' /dev/null" < /dev/null
qvm-run $QVMRUN_OPTS --pass-io $UPDATEVM "script --quiet --return --command '/usr/lib/qubes/qubes-download-dom0-updates.sh --doit --nogui $ALL_OPTS' /dev/null"
RETCODE=$?
if [ "$CHECK_ONLY" == "1" ]; then
exit $RETCODE
@ -214,7 +182,7 @@ while pidof -x qubes-receive-updates >/dev/null; do sleep 0.5; done
if [ -r /var/lib/qubes/updates/errors ]; then
echo "*** ERROR while receiving updates:" >&2
cat /var/lib/qubes/updates/errors >&2
echo "--> if you want to use packages that were downloaded correctly, use dnf directly now" >&2
echo "--> if you want to use packages that were downloaded correctly, use yum directly now" >&2
exit 1
fi
@ -222,43 +190,57 @@ if [ -z "$YUM_ACTION" ]; then
YUM_ACTION=upgrade
fi
if [ ${#PKGS[@]} -gt 0 ]; then
if [ -n "$TEMPLATE" ]; then
TEMPLATE_NETVM=$(qvm-prefs --force-root $TEMPLATE netvm)
if [ "x$PKGS" != "x" ]; then
if [[ -n "$BAK_TEMPLATE_ROOT" ]] ; then # Handle template details
# Backup root.img and private.img just in case
echo "Creating img backup files"
mv "$BAK_TEMPLATE_ROOT" "$BAK_TEMPLATE_ROOT-bak"
mv "$BAK_TEMPLATE_PRIVATE" "$BAK_TEMPLATE_PRIVATE-bak"
TDIR=`qvm-prefs --force-root $TEMPLATE dir`
if [ -f "$TDIR/firewall.xml" ]; then
mv "$TDIR/firewall.xml" "$TDIR/firewall.xml-bak"
fi
rm -f "$TDIR/volatile.img"
echo "--> Creating private.img..."
truncate -s 2G $BAK_TEMPLATE_PRIVATE
mkfs.ext4 -m 0 -q -F $BAK_TEMPLATE_PRIVATE
chown root:qubes $BAK_TEMPLATE_PRIVATE
chmod 0660 $BAK_TEMPLATE_PRIVATE
fi
dnf "${YUM_OPTS[@]}" $YUM_ACTION "${PKGS[@]}" ; RETCODE=$?
if [ -n "$TEMPLATE_BACKUP" -a "$RETCODE" -eq 0 ]; then
# Remove backup, if we made one. Better to do this only on success and
# potentially leave extra backups around than do it on an exit trap and
# clean up more reliably but potentially brick a system.
qvm-remove -f -- "$TEMPLATE_BACKUP"
fi
yum $YUM_OPTS $YUM_ACTION $PKGS ; RETCODE=$?
if [ -n "$TEMPLATE" -a -n "$TEMPLATE_NETVM" -a x"$TEMPLATE_NETVM" != xNone ]; then
if ! qvm-prefs --force-root -s $TEMPLATE netvm $TEMPLATE_NETVM; then
echo "ERROR: NetVM setting could not be restored!" >&2
if [[ -n "$BAK_TEMPLATE_ROOT" ]] ; then # Handle template details
if [[ $RETCODE -eq 0 && -f "$BAK_TEMPLATE_ROOT" ]]; then
# Reinstall went OK, remove backup files.
rm -f "$BAK_TEMPLATE_ROOT-bak"
rm -f "$BAK_TEMPLATE_PRIVATE-bak"
else
echo "Yum exit: Restoring img files"
mv "$BAK_TEMPLATE_ROOT-bak" "$BAK_TEMPLATE_ROOT"
mv "$BAK_TEMPLATE_PRIVATE-bak" "$BAK_TEMPLATE_PRIVATE"
fi
if [ -f "$TDIR/firewall.xml-bak" ]; then
mv "$TDIR/firewall.xml-bak" "$TDIR/firewall.xml"
fi
if ! qvm-prefs --force-root -s $TEMPLATE netvm $TEMPLATE_NETVM ; then
echo "ERROR: NetVM setting could not be restored!"
exit 1
fi
fi
elif [ -f /var/lib/qubes/updates/repodata/repomd.xml ]; then
# Above file exists only when at least one package was downloaded
if [ "$GUI" == "1" ]; then
# refresh packagekit metadata, GUI utilities use it
pkcon refresh force
$guiapp
else
dnf check-update
if [ $? -eq 100 ]; then # Run dnf with options
dnf "${YUM_OPTS[@]}" $YUM_ACTION
yum check-update
if [ $? -eq 100 ]; then # Run yum with options
yum $YUM_OPTS $YUM_ACTION
fi
fi
dnf -q check-update && qvm-features dom0 updates-available ''
yum -q check-update && rm -f $UPDATES_STAT_FILE
else
qvm-features dom0 updates-available ''
echo "No updates available" >&2
if [ "$GUI" == "1" ]; then
zenity --info --title='Dom0 updates' --text='No updates available'
fi
rm -f $UPDATES_STAT_FILE
echo "No updates avaliable" >&2
fi

@ -1,6 +1,10 @@
#!/bin/bash
if [ "$(qvm-features dom0 service.qubes-update-check || echo 1)" != 1 ]; then
NOTIFY_ICON=/usr/share/qubes/icons/dom0-update-avail.svg
UPDATES_STAT_FILE=/var/lib/qubes/updates/dom0-updates-available
UPDATES_DISABLE_FLAG=/var/lib/qubes/updates/disable-updates
if [ -f "$UPDATES_DISABLE_FLAG" ]; then
exit 0
fi
@ -13,4 +17,7 @@ if [ "$RETCODE" -ne 100 ]; then
exit $RETCODE
fi
qvm-features dom0 updates-available 1
# Touch stat file for qubes-manager
touch "$UPDATES_STAT_FILE"
chgrp qubes "$UPDATES_STAT_FILE"
chmod g+w "$UPDATES_STAT_FILE"

@ -1,4 +1,4 @@
#!/usr/bin/python3
#!/usr/bin/python2
#
# The Qubes OS Project, http://www.qubes-os.org
#
@ -18,49 +18,50 @@
# 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 os.path
import re
import sys
import subprocess
import shutil
import glob
import grp
import qubesadmin
from qubes.qubes import QubesVmCollection
updates_dir = "/var/lib/qubes/updates"
updates_rpm_dir = updates_dir + "/rpm"
updates_repodata_dir = updates_dir + "/repodata"
updates_error_file = updates_dir + "/errors"
updates_error_file_handle = None
comps_file = None
if os.path.exists('/usr/share/qubes/Qubes-comps.xml'):
comps_file = '/usr/share/qubes/Qubes-comps.xml'
package_regex = re.compile(r"^[A-Za-z0-9._+-]{1,128}\.rpm$")
package_regex = re.compile(r"^[A-Za-z0-9._+-]{1,128}.rpm$")
# example valid outputs:
# .....rpm: rsa sha1 (md5) pgp md5 OK
# .....rpm: (sha1) dsa sha1 md5 gpg OK
# .....rpm: digests signatures OK
# example INVALID outputs:
# .....rpm: sha1 md5 OK
# .....rpm: RSA sha1 ((MD5) PGP) md5 NOT OK (MISSING KEYS: (MD5) PGP#246110c1)
# .....rpm: digests OK
gpg_ok_regex = re.compile(r": [a-z0-9() ]* (pgp|gpg|signatures) [a-z0-9 ]*OK$")
def dom0updates_fatal(msg):
print(msg, file=sys.stderr)
with open(updates_error_file, "a") as updates_error_file_handle:
updates_error_file_handle.write(msg + "\n")
shutil.rmtree(updates_rpm_dir)
exit(1)
gpg_ok_regex = re.compile(r": [a-z0-9() ]* (pgp|gpg) [a-z0-9 ]*OK$")
def dom0updates_fatal(pkg, msg):
global updates_error_file_handle
print >> sys.stderr, msg
if updates_error_file_handle is None:
updates_error_file_handle = open(updates_error_file, "a")
updates_error_file_handle.write(msg + "\n")
os.remove(pkg)
def handle_dom0updates(updatevm):
source = os.getenv("QREXEC_REMOTE_DOMAIN")
global updates_error_file_handle
source=os.getenv("QREXEC_REMOTE_DOMAIN")
if source != updatevm.name:
print('Domain ' + str(source) + ' not allowed to send dom0 updates',
file=sys.stderr)
print >> sys.stderr, 'Domain ' + str(source) + ' not allowed to send dom0 updates'
exit(1)
# Clean old packages
if os.path.exists(updates_rpm_dir):
@ -71,18 +72,16 @@ def handle_dom0updates(updatevm):
os.remove(updates_error_file)
os.environ['LC_ALL'] = 'C'
qubes_gid = grp.getgrnam('qubes').gr_gid
old_umask = os.umask(0o002)
old_umask = os.umask(002)
os.mkdir(updates_rpm_dir)
os.chown(updates_rpm_dir, -1, qubes_gid)
os.chmod(updates_rpm_dir, 0o0775)
try:
subprocess.check_call(["/usr/libexec/qubes/qfile-dom0-unpacker",
str(os.getuid()), updates_rpm_dir])
# Verify received files
for untrusted_f in os.listdir(updates_rpm_dir):
if not package_regex.match(untrusted_f):
raise Exception(
'Domain ' + source + ' sent unexpected file')
os.chmod(updates_rpm_dir, 0775)
subprocess.check_call(["/usr/libexec/qubes/qfile-dom0-unpacker", str(os.getuid()), updates_rpm_dir])
# Verify received files
for untrusted_f in os.listdir(updates_rpm_dir):
if not package_regex.match(untrusted_f):
dom0updates_fatal(updates_rpm_dir + '/' + untrusted_f, 'Domain ' + source + ' sent unexpected file: ' + untrusted_f)
else:
f = untrusted_f
assert '/' not in f
assert '\0' not in f
@ -90,42 +89,44 @@ def handle_dom0updates(updatevm):
full_path = updates_rpm_dir + "/" + f
if os.path.islink(full_path) or not os.path.isfile(full_path):
raise Exception(
'Domain ' + source + ' sent not regular file')
p = subprocess.Popen(["/bin/rpm", "-K", full_path],
dom0updates_fatal(full_path, 'Domain ' + source + ' sent not regular file')
p = subprocess.Popen (["/bin/rpm", "-K", full_path],
stdout=subprocess.PIPE)
output = p.communicate()[0].decode('ascii')
output = p.communicate()[0]
if p.returncode != 0:
raise Exception(
'Error while verifing %s signature: %s' % (f, output))
dom0updates_fatal(full_path, 'Error while verifing %s signature: %s' % (f, output))
if not gpg_ok_regex.search(output.strip()):
raise Exception(
'Domain ' + source + ' sent not signed rpm: ' + f)
except Exception as e:
dom0updates_fatal(str(e))
dom0updates_fatal(full_path, 'Domain ' + source + ' sent not signed rpm: ' + f)
if updates_error_file_handle is not None:
updates_error_file_handle.close()
# After updates received - create repo metadata
createrepo_cmd = ["/usr/bin/createrepo_c"]
createrepo_cmd = ["/usr/bin/createrepo"]
if comps_file:
createrepo_cmd += ["-g", comps_file]
createrepo_cmd += ["-q", updates_dir]
subprocess.check_call(createrepo_cmd)
os.chown(updates_repodata_dir, -1, qubes_gid)
os.chmod(updates_repodata_dir, 0o0775)
os.chmod(updates_repodata_dir, 0775)
# Clean old cache
subprocess.call(["sudo", "/usr/bin/yum", "-q", "clean", "all"],
stdout=sys.stderr)
subprocess.call(["sudo", "/usr/bin/yum", "-q", "clean", "all"], stdout=sys.stderr)
# This will fail because of "smart" detection of no-network, but it will invalidate the cache
try:
null = open('/dev/null','w')
subprocess.call(["/usr/bin/pkcon", "refresh"], stdout=null)
null.close()
except:
pass
os.umask(old_umask)
exit(0)
def main():
app = qubesadmin.Qubes()
qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_reading()
qvm_collection.load()
qvm_collection.unlock_db()
updatevm = app.updatevm
if updatevm is None:
exit(1)
updatevm = qvm_collection.get_updatevm_vm()
handle_dom0updates(updatevm)
if __name__ == '__main__':
main()
main()

@ -0,0 +1,4 @@
# Apprently some of the drivers required when using a processor with AESNI for LUKS
# are missing in the initramfs, so lets include them manually here:
add_drivers+=" xts aesni-intel aes-x86_64 crc32c-intel ghash-clmulni-intel twofish-x86_64 "

@ -1,16 +0,0 @@
#!/usr/bin/bash
# Add roadrunner2/macbook12-spi-driver drivers to initramfs for supporting keyboard, touchpad, touchbar in the MacBooks.
# Pre-requisite: these drivers need to be included in the Linux kernel package.
check() {
grep -q ^MacBook /sys/devices/virtual/dmi/id/product_name || return 255
}
installkernel() {
hostonly='' instmods intel_lpss intel_lpss_pci spi_pxa2xx_platform spi_pxa2xx_pci applespi apple_ib_tb
}
install() {
echo "options apple_ib_tb fnmode=2" >> "${initdir}/etc/modprobe.d/macbook12-spi-driver.conf"
echo "options applespi fnremap=1" >> "${initdir}/etc/modprobe.d/macbook12-spi-driver.conf"
}

@ -1,26 +0,0 @@
#!/bin/bash
set -e -o pipefail
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2015 Marek Marczykowski-Górecki <marmarek@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.
#
#
echo "${0##*/} is not supported in dom0; use ${0##*/}-to-vm instead."
exit 1

@ -36,7 +36,7 @@ mkfifo -- "$RESPONSE"
# can't use $@ with --localcmd, and $* would fail on whitespace
/usr/lib/qubes/qfile-dom0-agent "$@" <"$RESPONSE" |
qvm-run --pass-io --service -- "$VM" "qubes.Filecopy" >"$RESPONSE"
qvm-run --pass-io "$VM" "QUBESRPC qubes.Filecopy dom0" >"$RESPONSE"
if [ "${0##*/}" = "qvm-move-to-vm" ]; then
rm -rf -- "$@"

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

@ -0,0 +1,10 @@
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
This copyright and license notice covers the images in this directory.
************************************************************************
TITLE: Crystal Project Icons
AUTHOR: Everaldo Coelho
SITE: http://www.everaldo.com
CONTACT: everaldo@everaldo.com
Copyright (c) 2006-2007 Everaldo Coelho.

@ -0,0 +1 @@
dom0-update-avail icon from gnome-packagekit project distributed under GPLv2

@ -0,0 +1 @@
Color padlock images downloaded from www.openclipart.org

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

@ -0,0 +1,17 @@
#!/bin/sh
sync_qubes_vms_wallclock()
{
# Sync all VMs (based on dom0 clock if network time sync fails)
/usr/bin/qvm-sync-clock --force --verbose
}
case "$1" in
thaw|resume) sync_qubes_vms_wallclock ;;
# Kill qvm-sync-clock (if running) to not desync time after resume
suspend|hibernate)
killall qvm-sync-clock 2> /dev/null
exit 0
;;
*) exit 0 ;;
esac

@ -0,0 +1,37 @@
#!/bin/sh
get_running_netvms() {
# Actually get running VMs with PCI devices attached
RUNNING_VMS=`xl list | tail -n +3 | cut -f 1 -d " "`
RUNNING_NETVMS=""
for VM in $RUNNING_VMS; do
if [ -n "`xl pci-list $VM|tail -n +2`" ]; then
echo "${VM%-dm}"
fi
done
}
suspend_net()
{
for VM in `get_running_netvms`; do
qvm-run -u root --pass-io $VM 'QUBESRPC qubes.SuspendPre dom0'
done
# Ignore exit status from netvm...
return 0
}
resume_net()
{
for VM in `get_running_netvms`; do
qvm-run -u root --pass-io $VM 'QUBESRPC qubes.SuspendPost dom0'
done
# Ignore exit status from netvm...
return 0
}
case "$1" in
resume) resume_net ;;
suspend) suspend_net ;;
*) exit 0 ;;
esac

@ -1,12 +1,27 @@
#!/bin/sh
case "$1" in
suspend|hibernate)
qubesd-query -e --fail -c /var/run/qubesd.internal.sock \
dom0 internal.SuspendPre dom0 | tr '\0' ' '
;;
resume|thaw)
qubesd-query -e --fail -c /var/run/qubesd.internal.sock \
dom0 internal.SuspendPost dom0 | tr '\0' ' '
;;
esac
#!/usr/bin/python
from qubes.qubes import QubesVmCollection,QubesException
import sys
qc = QubesVmCollection()
qc.lock_db_for_reading()
qc.load()
qc.unlock_db()
if sys.argv[1] in ["suspend", "hibernate"]:
for vm in qc.values():
if vm.is_running():
try:
vm.run_service("qubes.SuspendPreAll", user="root")
vm.suspend()
except Exception as e:
print >>sys.stderr, "Failed to suspend VM %s: %s" % (vm.name, e.message)
elif sys.argv[1] in ["resume", "thaw"]:
for vm in qc.values():
if vm.get_power_state() in ["Paused", "Suspended"]:
try:
vm.resume()
vm.run_service("qubes.SuspendPostAll", user="root")
except Exception as e:
print >>sys.stderr, "Failed to resume VM %s: %s" % (vm.name, e.message)

@ -7,8 +7,12 @@ StopWhenUnneeded=yes
Type=oneshot
RemainAfterExit=yes
StandardOutput=syslog
ExecStartPre=/usr/lib64/pm-utils/sleep.d/01qubes-sync-vms-clock suspend suspend
ExecStartPre=/usr/lib64/pm-utils/sleep.d/51qubes-suspend-netvm suspend suspend
ExecStart=/usr/lib64/pm-utils/sleep.d/52qubes-pause-vms suspend suspend
ExecStop=/usr/lib64/pm-utils/sleep.d/52qubes-pause-vms resume suspend
ExecStopPost=/usr/lib64/pm-utils/sleep.d/51qubes-suspend-netvm resume suspend
ExecStopPost=/usr/lib64/pm-utils/sleep.d/01qubes-sync-vms-clock resume suspend
[Install]
WantedBy=sleep.target

@ -0,0 +1,12 @@
CC=gcc
CFLAGS+=-I. -g -O2 -Wall -Wextra -Werror -pie -fPIC `pkg-config --cflags vchan-$(BACKEND_VMM)`
LIBS=`pkg-config --libs vchan-$(BACKEND_VMM)` -lqrexec-utils
all: qrexec-daemon qrexec-client
qrexec-daemon: qrexec-daemon.o
$(CC) -pie -g -o qrexec-daemon qrexec-daemon.o $(LIBS)
qrexec-client: qrexec-client.o
$(CC) -pie -g -o qrexec-client qrexec-client.o $(LIBS)
clean:
rm -f *.o *~ qrexec-daemon qrexec-client

@ -0,0 +1,64 @@
Currently (after commit 2600134e3bb781fca25fe77e464f8b875741dc83),
qrexec_agent can request a service (specified by a "exec_index") to be
executed on a different VM or dom0. Access control is enforced in dom0 via
files in /etc/qubes_rpc/policy. File copy, Open in Dispvm, sync appmenus,
upload updates to dom0 - they all have been ported to the new API.
See the quick HOWTO section on how to add a new service. Note we have
qvm-open-in-vm utility practically for free.
CHANGES
Besides flexibility offered by /etc/qubes_rpc/policy, writing a client
is much simpler now. The workflow used to be (using "filecopy" service as
an example):
a) "filecopy_ui" process places job description in some spool directory,
signals qrexec_agent to signal qrexec_daemon
b) qrexec_daemon executes "qrexec_client -d domain filecopy_worker ...."
and "filecopy_worker" process needed to parse spool and retrieve job
description from there. Particularly, "filecopy_ui" had no connection to
remote.
Now, the flow is:
a) qrexec_client_vm process obtains 3 unix socket descriptors from
qrexec_agent, dup stdin/out/err to them; forms "existing_process_handle" from
them
b) qrexec_client_vm signals qrexec_agent to signal qrexec_daemon, with a
"exec_index" (so, type of service) as an argument
c) qrexec_daemon executed "qrexec_client -d domain -c existing_process_handle ...."
d) qrexec_client_vm execve filecopy_program.
Thus, there is only one service program, and it has direct access to remote via
stdin/stdout.
HOWTO
Let's add a new "test.Add" service, that will add two numbers. We need the
following files in the template fs:
==========================
/usr/bin/our_test_add_client:
#!/bin/sh
echo $1 $2
exec cat >&2
# more correct: exec cat >&$SAVED_FD_1, but do not scare the reader
==========================
/usr/bin/our_test_add_server:
#!/bin/sh
read arg1 arg2
echo $(($arg1+$arg2))
==========================
/etc/qubes_rpc/test.Add:
/usr/bin/our_test_add_server
Now, on the client side, we start the client via
/usr/lib/qubes/qrexec_client_vm target_vm test.Add /usr/bin/our_test_add_client 11 22
Because there is no policy yet, dom0 will ask you to create one (of cource you
can do it before the first run of our_test_add_client). So, in dom0, create (by now,
with a file editor) the /etc/qubes_rpc/policy/test.Add file with
anyvm anyvm ask
content. The format of the /etc/qubes_rpc/policy/* files is
srcvm destvm (allow|deny|ask)[,user=user_to_run_as][,target=VM_to_redirect_to]
You can specify srcvm and destvm by name, or by one of "anyvm", "dispvm", "dom0"
reserved keywords.
Then, when you confirm the operation, you will get the result in the client vm.

@ -0,0 +1,795 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@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.
*
*/
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <getopt.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/select.h>
#include <errno.h>
#include <assert.h>
#include "qrexec.h"
#include "libqrexec-utils.h"
// whether qrexec-client should replace problematic bytes with _ before printing the output
int replace_chars_stdout = 0;
int replace_chars_stderr = 0;
#define VCHAN_BUFFER_SIZE 65536
int local_stdin_fd, local_stdout_fd;
pid_t local_pid = 0;
/* flag if this is "remote" end of service call. In this case swap STDIN/STDOUT
* msg types and send exit code at the end */
int is_service = 0;
int child_exited = 0;
extern char **environ;
static int handle_agent_handshake(libvchan_t *vchan, int remote_send_first)
{
struct msg_header hdr;
struct peer_info info;
int who = 0; // even - send to remote, odd - receive from remote
while (who < 2) {
if ((who+remote_send_first) & 1) {
if (!read_vchan_all(vchan, &hdr, sizeof(hdr))) {
perror("daemon handshake");
return -1;
}
if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) {
fprintf(stderr, "Invalid daemon MSG_HELLO\n");
return -1;
}
if (!read_vchan_all(vchan, &info, sizeof(info))) {
perror("daemon handshake");
return -1;
}
if (info.version != QREXEC_PROTOCOL_VERSION) {
fprintf(stderr, "Incompatible daemon protocol version "
"(daemon %d, client %d)\n",
info.version, QREXEC_PROTOCOL_VERSION);
return -1;
}
} else {
hdr.type = MSG_HELLO;
hdr.len = sizeof(info);
info.version = QREXEC_PROTOCOL_VERSION;
if (!write_vchan_all(vchan, &hdr, sizeof(hdr))) {
fprintf(stderr, "Failed to send MSG_HELLO hdr to daemon\n");
return -1;
}
if (!write_vchan_all(vchan, &info, sizeof(info))) {
fprintf(stderr, "Failed to send MSG_HELLO to daemon\n");
return -1;
}
}
who++;
}
return 0;
}
static int handle_daemon_handshake(int fd)
{
struct msg_header hdr;
struct peer_info info;
/* daemon send MSG_HELLO first */
if (!read_all(fd, &hdr, sizeof(hdr))) {
perror("daemon handshake");
return -1;
}
if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) {
fprintf(stderr, "Invalid daemon MSG_HELLO\n");
return -1;
}
if (!read_all(fd, &info, sizeof(info))) {
perror("daemon handshake");
return -1;
}
if (info.version != QREXEC_PROTOCOL_VERSION) {
fprintf(stderr, "Incompatible daemon protocol version "
"(daemon %d, client %d)\n",
info.version, QREXEC_PROTOCOL_VERSION);
return -1;
}
hdr.type = MSG_HELLO;
hdr.len = sizeof(info);
info.version = QREXEC_PROTOCOL_VERSION;
if (!write_all(fd, &hdr, sizeof(hdr))) {
fprintf(stderr, "Failed to send MSG_HELLO hdr to daemon\n");
return -1;
}
if (!write_all(fd, &info, sizeof(info))) {
fprintf(stderr, "Failed to send MSG_HELLO to daemon\n");
return -1;
}
return 0;
}
static int connect_unix_socket(const char *domname)
{
int s, len;
struct sockaddr_un remote;
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
perror("socket");
return -1;
}
remote.sun_family = AF_UNIX;
snprintf(remote.sun_path, sizeof remote.sun_path,
QREXEC_DAEMON_SOCKET_DIR "/qrexec.%s", domname);
len = strlen(remote.sun_path) + sizeof(remote.sun_family);
if (connect(s, (struct sockaddr *) &remote, len) == -1) {
perror("connect");
exit(1);
}
if (handle_daemon_handshake(s) < 0)
exit(1);
return s;
}
static void sigchld_handler(int x __attribute__((__unused__)))
{
child_exited = 1;
signal(SIGCHLD, sigchld_handler);
}
/* called from do_fork_exec */
_Noreturn void do_exec(char *prog)
{
/* avoid calling qubes-rpc-multiplexer through shell */
exec_qubes_rpc_if_requested(prog, environ);
/* if above haven't executed qubes-rpc-multiplexer, pass it to shell */
execl("/bin/bash", "bash", "-c", prog, NULL);
perror("exec bash");
exit(1);
}
static void do_exit(int code)
{
int status;
/* restore flags, as we may have not the only copy of this file descriptor
*/
if (local_stdin_fd != -1)
set_block(local_stdin_fd);
close(local_stdin_fd);
close(local_stdout_fd);
// sever communication lines; wait for child, if any
// so that qrexec-daemon can count (recursively) spawned processes correctly
waitpid(-1, &status, 0);
exit(code);
}
static void prepare_local_fds(char *cmdline)
{
if (!cmdline) {
local_stdin_fd = 1;
local_stdout_fd = 0;
return;
}
signal(SIGCHLD, sigchld_handler);
do_fork_exec(cmdline, &local_pid, &local_stdin_fd, &local_stdout_fd,
NULL);
}
/* ask the daemon to allocate vchan port */
static void negotiate_connection_params(int s, int other_domid, unsigned type,
void *cmdline_param, int cmdline_size,
int *data_domain, int *data_port)
{
struct msg_header hdr;
struct exec_params params;
hdr.type = type;
hdr.len = sizeof(params) + cmdline_size;
params.connect_domain = other_domid;
params.connect_port = 0;
if (!write_all(s, &hdr, sizeof(hdr))
|| !write_all(s, &params, sizeof(params))
|| !write_all(s, cmdline_param, cmdline_size)) {
perror("write daemon");
do_exit(1);
}
/* the daemon will respond with the same message with connect_port filled
* and empty cmdline */
if (!read_all(s, &hdr, sizeof(hdr))) {
perror("read daemon");
do_exit(1);
}
assert(hdr.type == type);
if (hdr.len != sizeof(params)) {
fprintf(stderr, "Invalid response for 0x%x\n", type);
do_exit(1);
}
if (!read_all(s, &params, sizeof(params))) {
perror("read daemon");
do_exit(1);
}
*data_port = params.connect_port;
*data_domain = params.connect_domain;
}
static void send_service_connect(int s, char *conn_ident,
int connect_domain, int connect_port)
{
struct msg_header hdr;
struct exec_params exec_params;
struct service_params srv_params;
hdr.type = MSG_SERVICE_CONNECT;
hdr.len = sizeof(exec_params) + sizeof(srv_params);
exec_params.connect_domain = connect_domain;
exec_params.connect_port = connect_port;
strncpy(srv_params.ident, conn_ident, sizeof(srv_params.ident));
if (!write_all(s, &hdr, sizeof(hdr))
|| !write_all(s, &exec_params, sizeof(exec_params))
|| !write_all(s, &srv_params, sizeof(srv_params))) {
perror("write daemon");
do_exit(1);
}
}
static void send_exit_code(libvchan_t *vchan, int status)
{
struct msg_header hdr;
hdr.type = MSG_DATA_EXIT_CODE;
hdr.len = sizeof(int);
if (libvchan_send(vchan, &hdr, sizeof(hdr)) != sizeof(hdr)) {
fprintf(stderr, "Failed to write exit code to the agent\n");
do_exit(1);
}
if (libvchan_send(vchan, &status, sizeof(status)) != sizeof(status)) {
fprintf(stderr, "Failed to write exit code(2) to the agent\n");
do_exit(1);
}
}
static void handle_input(libvchan_t *vchan)
{
char buf[MAX_DATA_CHUNK];
int ret;
size_t max_len;
struct msg_header hdr;
max_len = libvchan_buffer_space(vchan)-sizeof(hdr);
if (max_len > sizeof(buf))
max_len = sizeof(buf);
if (max_len == 0)
return;
ret = read(local_stdout_fd, buf, max_len);
if (ret < 0) {
perror("read");
do_exit(1);
}
hdr.type = is_service ? MSG_DATA_STDOUT : MSG_DATA_STDIN;
hdr.len = ret;
if (libvchan_send(vchan, &hdr, sizeof(hdr)) != sizeof(hdr)) {
fprintf(stderr, "Failed to write STDIN data to the agent\n");
do_exit(1);
}
if (ret == 0) {
close(local_stdout_fd);
local_stdout_fd = -1;
if (local_stdin_fd == -1) {
// if not a remote end of service call, wait for exit status
if (is_service) {
// if pipe in opposite direction already closed, no need to stay alive
if (local_pid == 0) {
/* if this is "remote" service end and no real local process
* exists (using own stdin/out) send also fake exit code */
send_exit_code(vchan, 0);
do_exit(0);
}
}
}
}
if (!write_vchan_all(vchan, buf, ret)) {
if (!libvchan_is_open(vchan)) {
// agent disconnected its end of socket, so no future data will be
// send there; there is no sense to read from child stdout
//
// since vchan socket is buffered it doesn't mean all data was
// received from the agent
close(local_stdout_fd);
local_stdout_fd = -1;
if (local_stdin_fd == -1) {
// since child does no longer accept data on its stdin, doesn't
// make sense to process the data from the daemon
//
// we don't know real exit VM process code (exiting here, before
// MSG_DATA_EXIT_CODE message)
do_exit(1);
}
} else
perror("write agent");
}
}
void do_replace_chars(char *buf, int len) {
int i;
unsigned char c;
for (i = 0; i < len; i++) {
c = buf[i];
if ((c < '\040' || c > '\176') && /* not printable ASCII */
(c != '\t') && /* not tab */
(c != '\n') && /* not newline */
(c != '\r') && /* not return */
(c != '\b') && /* not backspace */
(c != '\a')) /* not bell */
buf[i] = '_';
}
}
static int handle_vchan_data(libvchan_t *vchan, struct buffer *stdin_buf)
{
int status;
struct msg_header hdr;
char buf[MAX_DATA_CHUNK];
if (local_stdin_fd != -1) {
switch(flush_client_data(local_stdin_fd, stdin_buf)) {
case WRITE_STDIN_ERROR:
perror("write stdin");
close(local_stdin_fd);
local_stdin_fd = -1;
break;
case WRITE_STDIN_BUFFERED:
return WRITE_STDIN_BUFFERED;
case WRITE_STDIN_OK:
break;
}
}
if (libvchan_recv(vchan, &hdr, sizeof hdr) < 0) {
perror("read vchan");
do_exit(1);
}
if (hdr.len > MAX_DATA_CHUNK) {
fprintf(stderr, "client_header.len=%d\n", hdr.len);
do_exit(1);
}
if (!read_vchan_all(vchan, buf, hdr.len)) {
perror("read daemon");
do_exit(1);
}
switch (hdr.type) {
/* both directions because we can serve as either end of service call */
case MSG_DATA_STDIN:
case MSG_DATA_STDOUT:
if (local_stdin_fd == -1)
break;
if (replace_chars_stdout)
do_replace_chars(buf, hdr.len);
if (hdr.len == 0) {
/* restore flags, as we may have not the only copy of this file descriptor
*/
if (local_stdin_fd != -1)
set_block(local_stdin_fd);
close(local_stdin_fd);
local_stdin_fd = -1;
} else {
switch (write_stdin(local_stdin_fd, buf, hdr.len, stdin_buf)) {
case WRITE_STDIN_BUFFERED:
return WRITE_STDIN_BUFFERED;
case WRITE_STDIN_ERROR:
if (errno == EPIPE) {
// local process have closed its stdin, handle data in oposite
// direction (if any) before exit
close(local_stdin_fd);
local_stdin_fd = -1;
} else {
perror("write local stdout");
do_exit(1);
}
break;
case WRITE_STDIN_OK:
break;
}
}
break;
case MSG_DATA_STDERR:
if (replace_chars_stderr)
do_replace_chars(buf, hdr.len);
write_all(2, buf, hdr.len);
break;
case MSG_DATA_EXIT_CODE:
libvchan_close(vchan);
if (hdr.len < sizeof(status))
status = 255;
else
memcpy(&status, buf, sizeof(status));
flush_client_data(local_stdin_fd, stdin_buf);
do_exit(status);
break;
default:
fprintf(stderr, "unknown msg %d\n", hdr.type);
do_exit(1);
}
/* intentionally do not distinguish between _ERROR and _OK, because in case
* of write error, we simply eat the data - no way to report it to the
* other side */
return WRITE_STDIN_OK;
}
static void check_child_status(libvchan_t *vchan)
{
pid_t pid;
int status;
pid = waitpid(local_pid, &status, WNOHANG);
if (pid < 0) {
perror("waitpid");
do_exit(1);
}
if (pid == 0 || !WIFEXITED(status))
return;
if (is_service)
send_exit_code(vchan, WEXITSTATUS(status));
do_exit(status);
}
static void select_loop(libvchan_t *vchan)
{
fd_set select_set;
fd_set wr_set;
int max_fd;
int ret;
int vchan_fd;
sigset_t selectmask;
struct timespec zero_timeout = { 0, 0 };
struct timespec select_timeout = { 10, 0 };
struct buffer stdin_buf;
sigemptyset(&selectmask);
sigaddset(&selectmask, SIGCHLD);
sigprocmask(SIG_BLOCK, &selectmask, NULL);
sigemptyset(&selectmask);
buffer_init(&stdin_buf);
/* remember to set back to blocking mode before closing the FD - this may
* be not the only copy and some processes may misbehave when get
* nonblocking FD for input/output
*/
set_nonblock(local_stdin_fd);
for (;;) {
vchan_fd = libvchan_fd_for_select(vchan);
FD_ZERO(&select_set);
FD_ZERO(&wr_set);
FD_SET(vchan_fd, &select_set);
max_fd = vchan_fd;
if (local_stdout_fd != -1 &&
(size_t)libvchan_buffer_space(vchan) > sizeof(struct msg_header)) {
FD_SET(local_stdout_fd, &select_set);
if (local_stdout_fd > max_fd)
max_fd = local_stdout_fd;
}
if (child_exited && local_stdout_fd == -1)
check_child_status(vchan);
if (local_stdin_fd != -1 && buffer_len(&stdin_buf)) {
FD_SET(local_stdin_fd, &wr_set);
if (local_stdin_fd > max_fd)
max_fd = local_stdin_fd;
}
if ((local_stdin_fd == -1 || buffer_len(&stdin_buf) == 0) &&
libvchan_data_ready(vchan) > 0) {
/* check for other FDs, but exit immediately */
ret = pselect(max_fd + 1, &select_set, &wr_set, NULL,
&zero_timeout, &selectmask);
} else
ret = pselect(max_fd + 1, &select_set, &wr_set, NULL,
&select_timeout, &selectmask);
if (ret < 0) {
if (errno == EINTR && local_pid > 0) {
continue;
} else {
perror("select");
do_exit(1);
}
}
if (ret == 0) {
if (!libvchan_is_open(vchan)) {
/* remote disconnected witout a proper signaling */
do_exit(1);
}
}
if (FD_ISSET(vchan_fd, &select_set))
libvchan_wait(vchan);
if (buffer_len(&stdin_buf) &&
local_stdin_fd != -1 &&
FD_ISSET(local_stdin_fd, &wr_set)) {
if (flush_client_data(local_stdin_fd, &stdin_buf) == WRITE_STDIN_ERROR) {
perror("write stdin");
close(local_stdin_fd);
local_stdin_fd = -1;
}
}
while (libvchan_data_ready(vchan))
if (handle_vchan_data(vchan, &stdin_buf) != WRITE_STDIN_OK)
break;
if (local_stdout_fd != -1
&& FD_ISSET(local_stdout_fd, &select_set))
handle_input(vchan);
}
}
static void usage(char *name)
{
fprintf(stderr,
"usage: %s [-w timeout] [-t] [-T] -d domain_name ["
"-l local_prog|"
"-c request_id,src_domain_name,src_domain_id|"
"-e] remote_cmdline\n"
"-e means exit after sending cmd,\n"
"-t enables replacing problematic bytes with '_' in command output, -T is the same for stderr\n"
"-c: connect to existing process (response to trigger service call)\n"
"-w timeout: override default connection timeout of 5s (set 0 for no timeout)\n",
name);
exit(1);
}
static void parse_connect(char *str, char **request_id,
char **src_domain_name, int *src_domain_id)
{
int i=0;
char *token = NULL;
char *separators = ",";
token = strtok(str, separators);
while (token)
{
switch (i)
{
case 0:
*request_id = token;
if (strlen(*request_id) >= sizeof(struct service_params)) {
fprintf(stderr, "Invalid -c parameter (request_id too long, max %lu)\n",
sizeof(struct service_params)-1);
exit(1);
}
break;
case 1:
*src_domain_name = token;
break;
case 2:
*src_domain_id = atoi(token);
break;
default:
fprintf(stderr, "Invalid -c parameter (should be: \"-c request_id,src_domain_name,src_domain_id\")\n");
exit(1);
}
token = strtok(NULL, separators);
i++;
}
}
static void sigalrm_handler(int x __attribute__((__unused__)))
{
fprintf(stderr, "vchan connection timeout\n");
do_exit(1);
}
static void wait_for_vchan_client_with_timeout(libvchan_t *conn, int timeout) {
struct timeval start_tv, now_tv, timeout_tv;
if (timeout && gettimeofday(&start_tv, NULL) == -1) {
perror("gettimeofday");
do_exit(1);
}
while (conn && libvchan_is_open(conn) == VCHAN_WAITING) {
if (timeout) {
fd_set rdset;
int fd = libvchan_fd_for_select(conn);
/* calculate how much time left until connection timeout expire */
if (gettimeofday(&now_tv, NULL) == -1) {
perror("gettimeofday");
do_exit(1);
}
timersub(&start_tv, &now_tv, &timeout_tv);
timeout_tv.tv_sec += timeout;
if (timeout_tv.tv_sec < 0) {
fprintf(stderr, "vchan connection timeout\n");
libvchan_close(conn);
do_exit(1);
}
FD_ZERO(&rdset);
FD_SET(fd, &rdset);
switch (select(fd+1, &rdset, NULL, NULL, &timeout_tv)) {
case -1:
if (errno == EINTR)
break;
/* fallthough */
case 0:
fprintf(stderr, "vchan connection timeout (or error)\n");
libvchan_close(conn);
do_exit(1);
}
}
libvchan_wait(conn);
}
}
int main(int argc, char **argv)
{
int opt;
char *domname = NULL;
libvchan_t *data_vchan = NULL;
int data_port;
int data_domain;
int msg_type;
int s;
int just_exec = 0;
int connect_existing = 0;
char *local_cmdline = NULL;
char *remote_cmdline = NULL;
char *request_id;
char *src_domain_name = NULL;
int src_domain_id = 0; /* if not -c given, the process is run in dom0 */
int connection_timeout = 5;
struct service_params svc_params;
while ((opt = getopt(argc, argv, "d:l:ec:tTw:")) != -1) {
switch (opt) {
case 'd':
domname = strdup(optarg);
break;
case 'l':
local_cmdline = strdup(optarg);
break;
case 'e':
just_exec = 1;
break;
case 'c':
parse_connect(optarg, &request_id, &src_domain_name, &src_domain_id);
connect_existing = 1;
is_service = 1;
break;
case 't':
replace_chars_stdout = 1;
break;
case 'T':
replace_chars_stderr = 1;
break;
case 'w':
connection_timeout = atoi(optarg);
break;
default:
usage(argv[0]);
}
}
if (optind >= argc || !domname)
usage(argv[0]);
remote_cmdline = argv[optind];
register_exec_func(&do_exec);
if (just_exec + connect_existing + (local_cmdline != 0) > 1) {
fprintf(stderr, "ERROR: only one of -e, -l, -c can be specified\n");
usage(argv[0]);
}
if (strcmp(domname, "dom0") == 0 && !connect_existing) {
fprintf(stderr, "ERROR: when target domain is 'dom0', -c must be specified\n");
usage(argv[0]);
}
if (strcmp(domname, "dom0") == 0) {
if (connect_existing) {
msg_type = MSG_SERVICE_CONNECT;
strncpy(svc_params.ident, request_id, sizeof(svc_params.ident));
} else if (just_exec)
msg_type = MSG_JUST_EXEC;
else
msg_type = MSG_EXEC_CMDLINE;
assert(src_domain_name);
setenv("QREXEC_REMOTE_DOMAIN", src_domain_name, 1);
s = connect_unix_socket(src_domain_name);
negotiate_connection_params(s,
0, /* dom0 */
msg_type,
connect_existing ? (void*)&svc_params : (void*)remote_cmdline,
connect_existing ? sizeof(svc_params) : strlen(remote_cmdline) + 1,
&data_domain,
&data_port);
prepare_local_fds(remote_cmdline);
if (connect_existing) {
void (*old_handler)(int);
/* libvchan_client_init is blocking and does not support connection
* timeout, so use alarm(2) for that... */
old_handler = signal(SIGALRM, sigalrm_handler);
alarm(connection_timeout);
data_vchan = libvchan_client_init(data_domain, data_port);
alarm(0);
signal(SIGALRM, old_handler);
} else {
data_vchan = libvchan_server_init(data_domain, data_port,
VCHAN_BUFFER_SIZE, VCHAN_BUFFER_SIZE);
wait_for_vchan_client_with_timeout(data_vchan, connection_timeout);
}
if (!data_vchan || !libvchan_is_open(data_vchan)) {
fprintf(stderr, "Failed to open data vchan connection\n");
do_exit(1);
}
if (handle_agent_handshake(data_vchan, connect_existing) < 0)
do_exit(1);
select_loop(data_vchan);
} else {
if (just_exec)
msg_type = MSG_JUST_EXEC;
else
msg_type = MSG_EXEC_CMDLINE;
s = connect_unix_socket(domname);
negotiate_connection_params(s,
src_domain_id,
msg_type,
remote_cmdline,
strlen(remote_cmdline) + 1,
&data_domain,
&data_port);
close(s);
setenv("QREXEC_REMOTE_DOMAIN", domname, 1);
prepare_local_fds(local_cmdline);
if (connect_existing) {
s = connect_unix_socket(src_domain_name);
send_service_connect(s, request_id, data_domain, data_port);
close(s);
} else {
data_vchan = libvchan_server_init(data_domain, data_port,
VCHAN_BUFFER_SIZE, VCHAN_BUFFER_SIZE);
if (!data_vchan) {
fprintf(stderr, "Failed to start data vchan server\n");
do_exit(1);
}
wait_for_vchan_client_with_timeout(data_vchan, connection_timeout);
if (!libvchan_is_open(data_vchan)) {
fprintf(stderr, "Failed to open data vchan connection\n");
do_exit(1);
}
if (handle_agent_handshake(data_vchan, 0) < 0)
do_exit(1);
select_loop(data_vchan);
}
}
return 0;
}
// vim:ts=4:sw=4:et:

@ -0,0 +1,857 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@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.
*
*/
#include <sys/select.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <assert.h>
#include "qrexec.h"
#include "libqrexec-utils.h"
enum client_state {
CLIENT_INVALID = 0, // table slot not used
CLIENT_HELLO, // waiting for client hello
CLIENT_CMDLINE, // waiting for cmdline from client
CLIENT_RUNNING // waiting for client termination (to release vchan port)
};
enum vchan_port_state {
VCHAN_PORT_UNUSED = -1
};
struct _client {
int state; // enum client_state
};
struct _policy_pending {
pid_t pid;
struct service_params params;
int reserved_vchan_port;
};
#define VCHAN_BASE_DATA_PORT (VCHAN_BASE_PORT+1)
/*
The "clients" array is indexed by client's fd.
Thus its size must be equal MAX_FDS; defining MAX_CLIENTS for clarity.
*/
#define MAX_CLIENTS MAX_FDS
struct _client clients[MAX_CLIENTS]; // data on all qrexec_client connections
struct _policy_pending policy_pending[MAX_CLIENTS];
int policy_pending_max = -1;
/* indexed with vchan port number relative to VCHAN_BASE_DATA_PORT; stores
* either VCHAN_PORT_* or remote domain id for used port */
int used_vchan_ports[MAX_CLIENTS];
int max_client_fd = -1; // current max fd of all clients; so that we need not to scan all the "clients" table
int qrexec_daemon_unix_socket_fd; // /var/run/qubes/qrexec.xid descriptor
const char *default_user = "user";
const char default_user_keyword[] = "DEFAULT:";
#define default_user_keyword_len_without_colon (sizeof(default_user_keyword)-2)
int opt_quiet = 0;
#ifdef __GNUC__
# define UNUSED(x) UNUSED_ ## x __attribute__((__unused__))
#else
# define UNUSED(x) UNUSED_ ## x
#endif
volatile int children_count;
libvchan_t *vchan;
void sigusr1_handler(int UNUSED(x))
{
if (!opt_quiet)
fprintf(stderr, "connected\n");
exit(0);
}
void sigchld_parent_handler(int UNUSED(x))
{
children_count--;
/* starting value is 0 so we see dead real qrexec-daemon as -1 */
if (children_count < 0) {
if (!opt_quiet)
fprintf(stderr, "failed\n");
else
fprintf(stderr, "Connection to the VM failed\n");
exit(1);
}
}
static void sigchld_handler(int UNUSED(x));
char *remote_domain_name; // guess what
int remote_domain_id;
void unlink_qrexec_socket()
{
char socket_address[40];
char link_to_socket_name[strlen(remote_domain_name) + sizeof(socket_address)];
snprintf(socket_address, sizeof(socket_address),
QREXEC_DAEMON_SOCKET_DIR "/qrexec.%d", remote_domain_id);
snprintf(link_to_socket_name, sizeof link_to_socket_name,
QREXEC_DAEMON_SOCKET_DIR "/qrexec.%s", remote_domain_name);
unlink(socket_address);
unlink(link_to_socket_name);
}
void handle_vchan_error(const char *op)
{
fprintf(stderr, "Error while vchan %s, exiting\n", op);
exit(1);
}
int create_qrexec_socket(int domid, const char *domname)
{
char socket_address[40];
char link_to_socket_name[strlen(domname) + sizeof(socket_address)];
snprintf(socket_address, sizeof(socket_address),
QREXEC_DAEMON_SOCKET_DIR "/qrexec.%d", domid);
snprintf(link_to_socket_name, sizeof link_to_socket_name,
QREXEC_DAEMON_SOCKET_DIR "/qrexec.%s", domname);
unlink(link_to_socket_name);
if (symlink(socket_address, link_to_socket_name)) {
fprintf(stderr, "symlink(%s,%s) failed: %s\n", socket_address,
link_to_socket_name, strerror (errno));
}
atexit(unlink_qrexec_socket);
return get_server_socket(socket_address);
}
#define MAX_STARTUP_TIME_DEFAULT 60
static void incompatible_protocol_error_message(
const char *domain_name, int remote_version)
{
char text[1024];
int ret;
struct stat buf;
ret=stat("/usr/bin/kdialog", &buf);
#define KDIALOG_CMD "kdialog --title 'Qrexec daemon' --warningyesno "
#define ZENITY_CMD "zenity --title 'Qrexec daemon' --question --text "
snprintf(text, sizeof(text),
"%s"
"'Domain %s uses incompatible qrexec protocol (%d instead of %d). "
"You need to update either dom0 or VM packages.\n"
"To access this VM console do not close this error message and call:\n"
"sudo xl console vmname'",
ret==0 ? KDIALOG_CMD : ZENITY_CMD,
domain_name, remote_version, QREXEC_PROTOCOL_VERSION);
#undef KDIALOG_CMD
#undef ZENITY_CMD
system(text);
}
int handle_agent_hello(libvchan_t *ctrl, const char *domain_name)
{
struct msg_header hdr;
struct peer_info info;
if (libvchan_recv(ctrl, &hdr, sizeof(hdr)) != sizeof(hdr)) {
fprintf(stderr, "Failed to read agent HELLO hdr\n");
return -1;
}
if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) {
fprintf(stderr, "Invalid HELLO packet received: type %d, len %d\n", hdr.type, hdr.len);
return -1;
}
if (libvchan_recv(ctrl, &info, sizeof(info)) != sizeof(info)) {
fprintf(stderr, "Failed to read agent HELLO body\n");
return -1;
}
if (info.version != QREXEC_PROTOCOL_VERSION) {
fprintf(stderr, "Incompatible agent protocol version (remote %d, local %d)\n", info.version, QREXEC_PROTOCOL_VERSION);
incompatible_protocol_error_message(domain_name, info.version);
return -1;
}
/* send own HELLO */
/* those messages are the same as received from agent, but set it again for
* readability */
hdr.type = MSG_HELLO;
hdr.len = sizeof(info);
info.version = QREXEC_PROTOCOL_VERSION;
if (libvchan_send(ctrl, &hdr, sizeof(hdr)) != sizeof(hdr)) {
fprintf(stderr, "Failed to send HELLO hdr to agent\n");
return -1;
}
if (libvchan_send(ctrl, &info, sizeof(info)) != sizeof(info)) {
fprintf(stderr, "Failed to send HELLO hdr to agent\n");
return -1;
}
return 0;
}
/* do the preparatory tasks, needed before entering the main event loop */
void init(int xid)
{
char qrexec_error_log_name[256];
int logfd;
int i;
pid_t pid;
int startup_timeout = MAX_STARTUP_TIME_DEFAULT;
const char *startup_timeout_str = NULL;
if (xid <= 0) {
fprintf(stderr, "domain id=0?\n");
exit(1);
}
startup_timeout_str = getenv("QREXEC_STARTUP_TIMEOUT");
if (startup_timeout_str) {
startup_timeout = atoi(startup_timeout_str);
if (startup_timeout <= 0)
// invalid or negative number
startup_timeout = MAX_STARTUP_TIME_DEFAULT;
}
signal(SIGUSR1, sigusr1_handler);
signal(SIGCHLD, sigchld_parent_handler);
switch (pid=fork()) {
case -1:
perror("fork");
exit(1);
case 0:
break;
default:
if (getenv("QREXEC_STARTUP_NOWAIT"))
exit(0);
if (!opt_quiet)
fprintf(stderr, "Waiting for VM's qrexec agent.");
for (i=0;i<startup_timeout;i++) {
sleep(1);
if (!opt_quiet)
fprintf(stderr, ".");
if (i==startup_timeout-1) {
break;
}
}
fprintf(stderr, "Cannot connect to '%s' qrexec agent for %d seconds, giving up\n", remote_domain_name, startup_timeout);
exit(1);
}
close(0);
snprintf(qrexec_error_log_name, sizeof(qrexec_error_log_name),
"/var/log/qubes/qrexec.%s.log", remote_domain_name);
umask(0007); // make the log readable by the "qubes" group
logfd =
open(qrexec_error_log_name, O_WRONLY | O_CREAT | O_TRUNC,
0660);
if (logfd < 0) {
perror("open");
exit(1);
}
dup2(logfd, 1);
dup2(logfd, 2);
chdir("/var/run/qubes");
if (setsid() < 0) {
perror("setsid()");
exit(1);
}
vchan = libvchan_client_init(xid, VCHAN_BASE_PORT);
if (!vchan) {
perror("cannot connect to qrexec agent");
exit(1);
}
if (handle_agent_hello(vchan, remote_domain_name) < 0) {
exit(1);
}
if (setgid(getgid()) < 0) {
perror("setgid()");
exit(1);
}
if (setuid(getuid()) < 0) {
perror("setuid()");
exit(1);
}
/* initialize clients state arrays */
for (i = 0; i < MAX_CLIENTS; i++) {
clients[i].state = CLIENT_INVALID;
policy_pending[i].pid = 0;
used_vchan_ports[i] = VCHAN_PORT_UNUSED;
}
/* When running as root, make the socket accessible; perms on /var/run/qubes still apply */
umask(0);
qrexec_daemon_unix_socket_fd =
create_qrexec_socket(xid, remote_domain_name);
umask(0077);
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, sigchld_handler);
signal(SIGUSR1, SIG_DFL);
kill(getppid(), SIGUSR1); // let the parent know we are ready
}
static int send_client_hello(int fd)
{
struct msg_header hdr;
struct peer_info info;
hdr.type = MSG_HELLO;
hdr.len = sizeof(info);
info.version = QREXEC_PROTOCOL_VERSION;
if (!write_all(fd, &hdr, sizeof(hdr))) {
fprintf(stderr, "Failed to send MSG_HELLO hdr to client %d\n", fd);
return -1;
}
if (!write_all(fd, &info, sizeof(info))) {
fprintf(stderr, "Failed to send MSG_HELLO to client %d\n", fd);
return -1;
}
return 0;
}
static int allocate_vchan_port(int new_state)
{
int i;
for (i = 0; i < MAX_CLIENTS; i++) {
if (used_vchan_ports[i] == VCHAN_PORT_UNUSED) {
used_vchan_ports[i] = new_state;
return VCHAN_BASE_DATA_PORT+i;
}
}
return -1;
}
static void release_vchan_port(int port, int expected_remote_id)
{
/* release only if was reserved for connection to given domain */
if (used_vchan_ports[port-VCHAN_BASE_DATA_PORT] == expected_remote_id) {
used_vchan_ports[port-VCHAN_BASE_DATA_PORT] = VCHAN_PORT_UNUSED;
}
}
static void handle_new_client()
{
int fd = do_accept(qrexec_daemon_unix_socket_fd);
if (fd >= MAX_CLIENTS) {
fprintf(stderr, "too many clients ?\n");
exit(1);
}
if (send_client_hello(fd) < 0) {
close(fd);
clients[fd].state = CLIENT_INVALID;
return;
}
clients[fd].state = CLIENT_HELLO;
if (fd > max_client_fd)
max_client_fd = fd;
}
static void terminate_client(int fd)
{
clients[fd].state = CLIENT_INVALID;
close(fd);
}
static int handle_cmdline_body_from_client(int fd, struct msg_header *hdr)
{
struct exec_params params;
int len = hdr->len-sizeof(params);
char buf[len];
int use_default_user = 0;
int i;
if (!read_all(fd, &params, sizeof(params))) {
terminate_client(fd);
return 0;
}
if (!read_all(fd, buf, len)) {
terminate_client(fd);
return 0;
}
if (hdr->type == MSG_SERVICE_CONNECT) {
/* if the service was accepted, do not send spurious
* MSG_SERVICE_REFUSED when service process itself exit with non-zero
* code */
for (i = 0; i <= policy_pending_max; i++) {
if (policy_pending[i].pid &&
strncmp(policy_pending[i].params.ident, buf, len) == 0) {
policy_pending[i].pid = 0;
while (policy_pending_max > 0 &&
policy_pending[policy_pending_max].pid == 0)
policy_pending_max--;
break;
}
}
}
if (!params.connect_port) {
struct exec_params client_params;
/* allocate port and send it to the client */
params.connect_port = allocate_vchan_port(params.connect_domain);
if (params.connect_port <= 0) {
fprintf(stderr, "Failed to allocate new vchan port, too many clients?\n");
terminate_client(fd);
return 0;
}
client_params.connect_port = params.connect_port;
client_params.connect_domain = remote_domain_id;
hdr->len = sizeof(client_params);
if (!write_all(fd, hdr, sizeof(*hdr))) {
terminate_client(fd);
release_vchan_port(params.connect_port, params.connect_domain);
return 0;
}
if (!write_all(fd, &client_params, sizeof(client_params))) {
terminate_client(fd);
release_vchan_port(params.connect_port, params.connect_domain);
return 0;
}
/* restore original len value */
hdr->len = len+sizeof(params);
} else {
assert(params.connect_port >= VCHAN_BASE_DATA_PORT);
assert(params.connect_port < VCHAN_BASE_DATA_PORT+MAX_CLIENTS);
}
if (!strncmp(buf, default_user_keyword, default_user_keyword_len_without_colon+1)) {
use_default_user = 1;
hdr->len -= default_user_keyword_len_without_colon;
hdr->len += strlen(default_user);
}
if (libvchan_send(vchan, hdr, sizeof(*hdr)) < 0)
handle_vchan_error("send");
if (libvchan_send(vchan, &params, sizeof(params)) < 0)
handle_vchan_error("send params");
if (use_default_user) {
if (libvchan_send(vchan, default_user, strlen(default_user)) < 0)
handle_vchan_error("send default_user");
if (libvchan_send(vchan, buf+default_user_keyword_len_without_colon,
len-default_user_keyword_len_without_colon) < 0)
handle_vchan_error("send buf");
} else
if (libvchan_send(vchan, buf, len) < 0)
handle_vchan_error("send buf");
return 1;
}
static void handle_cmdline_message_from_client(int fd)
{
struct msg_header hdr;
if (!read_all(fd, &hdr, sizeof hdr)) {
terminate_client(fd);
return;
}
switch (hdr.type) {
case MSG_EXEC_CMDLINE:
case MSG_JUST_EXEC:
case MSG_SERVICE_CONNECT:
break;
default:
terminate_client(fd);
return;
}
if (!handle_cmdline_body_from_client(fd, &hdr))
// client disconnected while sending cmdline, above call already
// cleaned up client info
return;
clients[fd].state = CLIENT_RUNNING;
}
static void handle_client_hello(int fd)
{
struct msg_header hdr;
struct peer_info info;
if (!read_all(fd, &hdr, sizeof hdr)) {
terminate_client(fd);
return;
}
if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) {
fprintf(stderr, "Invalid HELLO packet received from client %d: "
"type %d, len %d\n", fd, hdr.type, hdr.len);
terminate_client(fd);
return;
}
if (!read_all(fd, &info, sizeof info)) {
terminate_client(fd);
return;
}
if (info.version != QREXEC_PROTOCOL_VERSION) {
fprintf(stderr, "Incompatible client protocol version (remote %d, local %d)\n", info.version, QREXEC_PROTOCOL_VERSION);
terminate_client(fd);
return;
}
clients[fd].state = CLIENT_CMDLINE;
}
/* handle data received from one of qrexec_client processes */
static void handle_message_from_client(int fd)
{
char buf[MAX_DATA_CHUNK];
switch (clients[fd].state) {
case CLIENT_HELLO:
handle_client_hello(fd);
return;
case CLIENT_CMDLINE:
handle_cmdline_message_from_client(fd);
return;
case CLIENT_RUNNING:
// expected EOF
if (read(fd, buf, sizeof(buf)) != 0) {
fprintf(stderr, "Unexpected data received from client %d\n", fd);
}
terminate_client(fd);
return;
default:
fprintf(stderr, "Invalid client state %d\n", clients[fd].state);
exit(1);
}
}
/*
* The signal handler executes asynchronously; therefore all it should do is
* to set a flag "signal has arrived", and let the main even loop react to this
* flag in appropriate moment.
*/
int child_exited;
static void sigchld_handler(int UNUSED(x))
{
child_exited = 1;
signal(SIGCHLD, sigchld_handler);
}
static void send_service_refused(libvchan_t *vchan, struct service_params *params) {
struct msg_header hdr;
hdr.type = MSG_SERVICE_REFUSED;
hdr.len = sizeof(*params);
if (libvchan_send(vchan, &hdr, sizeof(hdr)) != sizeof(hdr)) {
fprintf(stderr, "Failed to send MSG_SERVICE_REFUSED hdr to agent\n");
exit(1);
}
if (libvchan_send(vchan, params, sizeof(*params)) != sizeof(*params)) {
fprintf(stderr, "Failed to send MSG_SERVICE_REFUSED to agent\n");
exit(1);
}
}
/* clean zombies, check for denied service calls */
static void reap_children()
{
int status;
int i;
pid_t pid;
while ((pid=waitpid(-1, &status, WNOHANG)) > 0) {
for (i = 0; i <= policy_pending_max; i++) {
if (policy_pending[i].pid == pid) {
status = WEXITSTATUS(status);
if (status != 0) {
send_service_refused(vchan, &policy_pending[i].params);
}
/* in case of allowed calls, we will do the rest in
* MSG_SERVICE_CONNECT from client handler */
policy_pending[i].pid = 0;
while (policy_pending_max > 0 &&
policy_pending[policy_pending_max].pid == 0)
policy_pending_max--;
break;
}
}
}
child_exited = 0;
}
static int find_policy_pending_slot() {
int i;
for (i = 0; i < MAX_CLIENTS; i++) {
if (policy_pending[i].pid == 0) {
if (i > policy_pending_max)
policy_pending_max = i;
return i;
}
}
return -1;
}
static void sanitize_name(char * untrusted_s_signed, char *extra_allowed_chars)
{
unsigned char * untrusted_s;
for (untrusted_s=(unsigned char*)untrusted_s_signed; *untrusted_s; untrusted_s++) {
if (*untrusted_s >= 'a' && *untrusted_s <= 'z')
continue;
if (*untrusted_s >= 'A' && *untrusted_s <= 'Z')
continue;
if (*untrusted_s >= '0' && *untrusted_s <= '9')
continue;
if (*untrusted_s == '$' && strchr(extra_allowed_chars, '@')) {
*untrusted_s = '@';
continue;
}
if (*untrusted_s == '_' ||
*untrusted_s == '-' ||
*untrusted_s == '.')
continue;
if (extra_allowed_chars && strchr(extra_allowed_chars, *untrusted_s))
continue;
*untrusted_s = '_';
}
}
#define ENSURE_NULL_TERMINATED(x) x[sizeof(x)-1] = 0
/*
* Called when agent sends a message asking to execute a predefined command.
*/
static void handle_execute_service(void)
{
int i;
int policy_pending_slot;
pid_t pid;
struct trigger_service_params untrusted_params, params;
char remote_domain_id_str[10];
if (libvchan_recv(vchan, &untrusted_params, sizeof(untrusted_params)) < 0)
handle_vchan_error("recv params");
/* sanitize start */
ENSURE_NULL_TERMINATED(untrusted_params.service_name);
ENSURE_NULL_TERMINATED(untrusted_params.target_domain);
ENSURE_NULL_TERMINATED(untrusted_params.request_id.ident);
sanitize_name(untrusted_params.service_name, "+");
sanitize_name(untrusted_params.target_domain, "@");
sanitize_name(untrusted_params.request_id.ident, " ");
params = untrusted_params;
/* sanitize end */
policy_pending_slot = find_policy_pending_slot();
if (policy_pending_slot < 0) {
fprintf(stderr, "Service request denied, too many pending requests\n");
send_service_refused(vchan, &untrusted_params.request_id);
return;
}
switch (pid=fork()) {
case -1:
perror("fork");
exit(1);
case 0:
break;
default:
policy_pending[policy_pending_slot].pid = pid;
policy_pending[policy_pending_slot].params = untrusted_params.request_id;
return;
}
for (i = 3; i < MAX_FDS; i++)
close(i);
signal(SIGCHLD, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
snprintf(remote_domain_id_str, sizeof(remote_domain_id_str), "%d",
remote_domain_id);
execl("/usr/lib/qubes/qrexec-policy", "qrexec-policy", "--",
remote_domain_id_str, remote_domain_name, params.target_domain,
params.service_name, params.request_id.ident, NULL);
perror("execl");
_exit(1);
}
static void handle_connection_terminated()
{
struct exec_params untrusted_params, params;
if (libvchan_recv(vchan, &untrusted_params, sizeof(untrusted_params)) < 0)
handle_vchan_error("recv params");
/* sanitize start */
if (untrusted_params.connect_port < VCHAN_BASE_DATA_PORT ||
untrusted_params.connect_port >= VCHAN_BASE_DATA_PORT+MAX_CLIENTS) {
fprintf(stderr, "Invalid port in MSG_CONNECTION_TERMINATED (%d)\n",
untrusted_params.connect_port);
exit(1);
}
/* untrusted_params.connect_domain even if invalid will not harm - in worst
* case the port will not be released */
params = untrusted_params;
/* sanitize end */
release_vchan_port(params.connect_port, params.connect_domain);
}
static void sanitize_message_from_agent(struct msg_header *untrusted_header)
{
switch (untrusted_header->type) {
case MSG_TRIGGER_SERVICE:
if (untrusted_header->len != sizeof(struct trigger_service_params)) {
fprintf(stderr, "agent sent invalid MSG_TRIGGER_SERVICE packet\n");
exit(1);
}
break;
case MSG_CONNECTION_TERMINATED:
if (untrusted_header->len != sizeof(struct exec_params)) {
fprintf(stderr, "agent sent invalid MSG_CONNECTION_TERMINATED packet\n");
exit(1);
}
break;
default:
fprintf(stderr, "unknown mesage type 0x%x from agent\n",
untrusted_header->type);
exit(1);
}
}
static void handle_message_from_agent(void)
{
struct msg_header hdr, untrusted_hdr;
if (libvchan_recv(vchan, &untrusted_hdr, sizeof(untrusted_hdr)) < 0)
handle_vchan_error("recv hdr");
/* sanitize start */
sanitize_message_from_agent(&untrusted_hdr);
hdr = untrusted_hdr;
/* sanitize end */
// fprintf(stderr, "got %x %x %x\n", hdr.type, hdr.client_id,
// hdr.len);
switch (hdr.type) {
case MSG_TRIGGER_SERVICE:
handle_execute_service();
return;
case MSG_CONNECTION_TERMINATED:
handle_connection_terminated();
return;
}
}
/*
* Scan the "clients" table, add ones we want to read from (because the other
* end has not send MSG_XOFF on them) to read_fdset, add ones we want to write
* to (because its pipe is full) to write_fdset. Return the highest used file
* descriptor number, needed for the first select() parameter.
*/
static int fill_fdsets_for_select(fd_set * read_fdset, fd_set * write_fdset)
{
int i;
int max = -1;
FD_ZERO(read_fdset);
FD_ZERO(write_fdset);
for (i = 0; i <= max_client_fd; i++) {
if (clients[i].state != CLIENT_INVALID) {
FD_SET(i, read_fdset);
max = i;
}
}
FD_SET(qrexec_daemon_unix_socket_fd, read_fdset);
if (qrexec_daemon_unix_socket_fd > max)
max = qrexec_daemon_unix_socket_fd;
return max;
}
int main(int argc, char **argv)
{
fd_set read_fdset, write_fdset;
int i, opt;
int max;
sigset_t chld_set;
while ((opt=getopt(argc, argv, "q")) != -1) {
switch (opt) {
case 'q':
opt_quiet = 1;
break;
default: /* '?' */
fprintf(stderr, "usage: %s [-q] domainid domain-name [default user]\n", argv[0]);
exit(1);
}
}
if (argc - optind < 2 || argc - optind > 3) {
fprintf(stderr, "usage: %s [-q] domainid domain-name [default user]\n", argv[0]);
exit(1);
}
remote_domain_id = atoi(argv[optind]);
remote_domain_name = argv[optind+1];
if (argc - optind >= 3)
default_user = argv[optind+2];
init(remote_domain_id);
sigemptyset(&chld_set);
sigaddset(&chld_set, SIGCHLD);
signal(SIGCHLD, sigchld_handler);
/*
* The main event loop. Waits for one of the following events:
* - message from client
* - message from agent
* - new client
* - child exited
*/
for (;;) {
max = fill_fdsets_for_select(&read_fdset, &write_fdset);
if (libvchan_buffer_space(vchan) <= (int)sizeof(struct msg_header))
FD_ZERO(&read_fdset); // vchan full - don't read from clients
sigprocmask(SIG_BLOCK, &chld_set, NULL);
if (child_exited)
reap_children();
wait_for_vchan_or_argfd(vchan, max, &read_fdset, &write_fdset);
sigprocmask(SIG_UNBLOCK, &chld_set, NULL);
if (FD_ISSET(qrexec_daemon_unix_socket_fd, &read_fdset))
handle_new_client();
while (libvchan_data_ready(vchan))
handle_message_from_agent();
for (i = 0; i <= max_client_fd; i++)
if (clients[i].state != CLIENT_INVALID
&& FD_ISSET(i, &read_fdset))
handle_message_from_client(i);
}
}
// vim:ts=4:sw=4:et:

@ -0,0 +1,265 @@
#!/usr/bin/python
import sys
import os
import os.path
import subprocess
from qubes.qubes import vmm
from qubes.qubes import QubesVmCollection
import qubes.guihelpers
import libvirt
from optparse import OptionParser
import fcntl
POLICY_FILE_DIR="/etc/qubes-rpc/policy"
# XXX: Backward compatibility, to be removed soon
DEPRECATED_POLICY_FILE_DIR="/etc/qubes_rpc/policy"
QREXEC_CLIENT="/usr/lib/qubes/qrexec-client"
QUBES_RPC_MULTIPLEXER_PATH="/usr/lib/qubes/qubes-rpc-multiplexer"
class UserChoice:
ALLOW=0
DENY=1
ALWAYS_ALLOW=2
def line_to_dict(line):
tokens=line.split()
if len(tokens) < 3:
return None
if tokens[0][0] == '#':
return None
dict={}
dict['source']=tokens[0]
dict['dest']=tokens[1]
dict['full-action']=tokens[2]
action_list=tokens[2].split(',')
dict['action']=action_list.pop(0)
for iter in action_list:
paramval=iter.split("=")
dict["action."+paramval[0]]=paramval[1]
# Warn if we're ignoring extra data after a space, such as:
# vm1 vm2 allow, user=foo
if len(tokens) > 3:
print >>sys.stderr, "Trailing data ignored in %s" % line
return dict
def read_policy_file(service_name):
policy_file = os.path.join(POLICY_FILE_DIR, service_name)
if not os.path.isfile(policy_file):
# fallback to policy without specific argument set (if any)
policy_file = os.path.join(POLICY_FILE_DIR, service_name.split("+")[0])
if not os.path.isfile(policy_file):
policy_file = os.path.join(DEPRECATED_POLICY_FILE_DIR, service_name)
if not os.path.isfile(policy_file):
return None
print >>sys.stderr, "RPC service '%s' uses deprecated policy location, please move to %s" % (service_name, POLICY_FILE_DIR)
policy_list=list()
f = open(policy_file)
fcntl.flock(f, fcntl.LOCK_SH)
for iter in f.readlines():
iter = iter.replace('$', '@')
dict = line_to_dict(iter)
if dict is not None:
policy_list.append(dict)
f.close()
return policy_list
def is_match(item, config_term):
return (item != "dom0" and config_term == "@anyvm") or item == config_term
def get_default_policy():
dict={}
dict["action"]="deny"
return dict
def find_policy(policy, domain, target):
for iter in policy:
if not is_match(domain, iter["source"]):
continue
if not is_match(target, iter["dest"]):
continue
return iter
return get_default_policy()
def validate_target(target):
# special targets
if target in ['@dispvm']:
return True
qc = QubesVmCollection()
qc.lock_db_for_reading()
qc.load()
qc.unlock_db()
return qc.get_vm_by_name(target)
def spawn_target_if_necessary(vm):
if vm.is_running():
return
# use qvm-run instead of vm.start() to make sure that nothing is written
# to stdout and nothing is read from stdin
null = open("/dev/null", "r+")
subprocess.call(["qvm-run", "-a", "--tray", "-q", vm.name, "true"],
stdin=null, stdout=null)
null.close()
def do_execute(domain, target, user, service_name, process_ident, vm=None):
if target == "@dispvm":
cmd = "/usr/lib/qubes/qfile-daemon-dvm " + service_name + " " + domain + " " +user
os.execl(QREXEC_CLIENT, "qrexec-client",
"-d", "dom0", "-c", process_ident, cmd)
else:
if isinstance(vm, qubes.qubes.QubesVm):
spawn_target_if_necessary(vm)
if target == "dom0":
cmd = "QUBESRPC " + service_name + " " + domain
else:
cmd = user + ":QUBESRPC "+ service_name + " " + domain
# stderr should be logged in source/target VM
null = open(os.devnull, 'w')
os.dup2(null.fileno(), 2)
os.execl(QREXEC_CLIENT, "qrexec-client",
"-d", target, "-c", process_ident, cmd)
def confirm_execution(domain, target, service_name):
text = "Do you allow domain \"" +domain + "\" to execute " + service_name
text+= " operation on the domain \"" + target +"\"?<br>"
text+= " \"Yes to All\" option will automatically allow this operation in the future."
return qubes.guihelpers.ask(text, yestoall=True)
def add_always_allow(domain, target, service_name, options):
policy_file=POLICY_FILE_DIR+"/"+service_name
if not os.path.isfile(policy_file):
return None
f = open(policy_file, 'r+')
fcntl.flock(f, fcntl.LOCK_EX)
lines = []
for l in f.readlines():
lines.append(l)
lines.insert(0, "%s\t%s\tallow%s\n" % (domain, target, options))
f.seek(0)
f.write("".join(lines))
f.close()
def info_dialog(msg_type, text):
if msg_type not in ['info', 'warning', 'error', 'entry']:
raise ValueError("Invalid msg_type value")
if msg_type in ['info', 'warning', 'error']:
try:
subprocess.call(["/usr/bin/zenity", "--{}".format(msg_type), "--text",
text])
except OSError:
kdialog_msg_type = {
'info': 'msgbox',
'warning': 'sorry',
'error': 'error'
}[msg_type]
subprocess.call(["/usr/bin/kdialog", "--{}".format(kdialog_msg_type), text])
else:
response = subprocess.check_output(["/usr/bin/zenity", "--{}".format(msg_type), "--text",
text])
return response
def policy_editor(domain, target, service_name):
text = "No policy definition found for " + service_name + " action. \n"
text+= "Type YES if you want to create a default policy file"
response = info_dialog('entry', text)
if response.strip() == 'YES':
create_policy(service_name)
def create_policy(service_name):
policyFile = "/etc/qubes-rpc/policy/"+service_name
policy = open(policyFile, "w")
policy.write("## Note that policy parsing stops at the first match,\n")
policy.write("## so adding anything below \"@anyvm @anyvm action\" line will have no effect\n")
policy.write("\n")
policy.write("## Please use a single # to start your custom comments\n")
policy.write("\n")
policy.write("@anyvm @anyvm ask\n")
policy.close()
def main():
usage = "usage: %prog [options] <src-domain-id> <src-domain> <target-domain> <service> <process-ident>"
parser = OptionParser (usage)
parser.add_option ("--assume-yes-for-ask", action="store_true", dest="assume_yes_for_ask", default=False,
help="Allow run of service without confirmation if policy say 'ask'")
parser.add_option ("--just-evaluate", action="store_true", dest="just_evaluate", default=False,
help="Do not run the service, only evaluate policy; retcode=0 means 'allow'")
(options, args) = parser.parse_args ()
domain_id=args[0]
domain=args[1]
target=args[2]
service_name=args[3]
process_ident=args[4]
# Add source domain information, required by qrexec-client for establishing
# connection
process_ident+=","+domain+","+domain_id
vm = validate_target(target)
if vm is None:
print >> sys.stderr, "Rpc failed (unknown domain):", domain, target, service_name
text = "Domain '%s' doesn't exist (service %s called by domain %s)." % (
target, service_name, domain)
info_dialog("error", text)
exit(1)
policy_list=read_policy_file(service_name)
if policy_list==None:
policy_editor(domain, target, service_name)
policy_list=read_policy_file(service_name)
if policy_list==None:
policy_list=list()
policy_dict=find_policy(policy_list, domain, target)
if policy_dict["action"] == "ask" and options.assume_yes_for_ask:
policy_dict["action"] = "allow"
if policy_dict["action"] == "ask":
user_choice = confirm_execution(domain, target, service_name)
if user_choice == UserChoice.ALWAYS_ALLOW:
add_always_allow(domain, target, service_name, policy_dict["full-action"].lstrip('ask'))
policy_dict["action"] = "allow"
elif user_choice == UserChoice.ALLOW:
policy_dict["action"] = "allow"
else:
policy_dict["action"] = "deny"
if options.just_evaluate:
if policy_dict["action"] == "allow":
exit(0)
else:
exit(1)
if policy_dict["action"] == "allow":
if policy_dict.has_key("action.target"):
target=policy_dict["action.target"]
vm = validate_target(target)
if vm is None:
print >> sys.stderr, "Rpc failed (unknown domain specified by policy):", domain, target, service_name
text = "Domain '%s' doesn't exist (service %s called by domain %s)." % (
target, service_name, domain)
info_dialog("error", text)
exit(1)
if policy_dict.has_key("action.user"):
user=policy_dict["action.user"]
else:
user="DEFAULT"
print >> sys.stderr, "Rpc allowed:", domain, target, service_name
do_execute(domain, target, user, service_name, process_ident, vm=vm)
print >> sys.stderr, "Rpc denied:", domain, target, service_name
exit(1)
main()

@ -0,0 +1,38 @@
#!/bin/sh
mkfifo /tmp/qrexec-rpc-stderr.$$
logger -t "$1-$2" -f /tmp/qrexec-rpc-stderr.$$ >/dev/null 2>&1 </dev/null &
exec 2>/tmp/qrexec-rpc-stderr.$$
rm -f /tmp/qrexec-rpc-stderr.$$
QUBES_RPC=/etc/qubes-rpc
LOCAL_QUBES_RPC=/usr/local/etc/qubes-rpc
if ! [ $# = 2 ] ; then
echo $0: bad argument count, usage: $0 SERVICE-NAME REMOTE-DOMAIN-NAME >&2
exit 1
fi
export QREXEC_REMOTE_DOMAIN="$2"
export QREXEC_SERVICE_FULL_NAME="$1"
SERVICE_WITHOUT_ARGUMENT="${1%%+*}"
if [ "${QREXEC_SERVICE_FULL_NAME}" != "${SERVICE_WITHOUT_ARGUMENT}" ]; then
export QREXEC_SERVICE_ARGUMENT="${QREXEC_SERVICE_FULL_NAME#*+}"
fi
for CFG_FILE in $LOCAL_QUBES_RPC/"$1" $QUBES_RPC/"$1" \
$LOCAL_QUBES_RPC/"${SERVICE_WITHOUT_ARGUMENT}" \
$QUBES_RPC/"${SERVICE_WITHOUT_ARGUMENT}"; do
if [ -s "$CFG_FILE" ]; then
break
fi
done
if [ -x "$CFG_FILE" ] ; then
exec "$CFG_FILE" ${QREXEC_SERVICE_ARGUMENT}
echo "$0: failed to execute handler for" "$1" >&2
exit 1
else
exec /bin/sh -- "$CFG_FILE" ${QREXEC_SERVICE_ARGUMENT}
echo "$0: failed to execute handler for" "$1" >&2
exit 1
fi

@ -1,7 +0,0 @@
## Note that policy parsing stops at the first match,
## so adding anything below "$anyvm $anyvm action" line will have no effect
## Please use a single # to start your custom comments
dom0 dom0 allow
$anyvm $anyvm deny

@ -1,7 +0,0 @@
## Note that policy parsing stops at the first match,
## so adding anything below "$anyvm $anyvm action" line will have no effect
## Please use a single # to start your custom comments
dom0 dom0 allow
$anyvm $anyvm deny

@ -1,7 +0,0 @@
## Note that policy parsing stops at the first match,
## so adding anything below "$anyvm $anyvm action" line will have no effect
## Please use a single # to start your custom comments
dom0 dom0 allow
$anyvm $anyvm deny

@ -1,24 +0,0 @@
#!/usr/bin/python3
# `ok` on stdout indicates success; any stderr output indicates an error
# (probably an exception)
import dnf
import os
import sys
os.umask(0o022)
base = dnf.Base()
base.read_all_repos()
reponame = sys.argv[1]
repo = base.repos[reponame]
base.conf.write_raw_configfile(repo.repofile,
repo.id,
base.conf.substitutions,
{'enabled': '0'})
print('ok')

@ -1,24 +0,0 @@
#!/usr/bin/python3
# `ok` on stdout indicates success; any stderr output indicates an error
# (probably an exception)
import dnf
import os
import sys
os.umask(0o022)
base = dnf.Base()
base.read_all_repos()
reponame = sys.argv[1]
repo = base.repos[reponame]
base.conf.write_raw_configfile(repo.repofile,
repo.id,
base.conf.substitutions,
{'enabled': '1'})
print('ok')

@ -1,17 +0,0 @@
#!/usr/bin/python3
# Records in the output are separated by newlines; fields are separated by \0
# Each record is unique_id:pretty_name:enabled
import dnf
base = dnf.Base()
base.read_all_repos()
first = True
for repo in base.repos.all():
l = [repo.id, repo.name, 'enabled' if repo.enabled else 'disabled']
if not first: print()
first = False
print('\0'.join(l), end='')

@ -24,10 +24,15 @@
%{!?python_sitearch: %define python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)")}
%{!?version: %define version %(cat version)}
%define _dracutmoddir /usr/lib/dracut/modules.d
%if %{fedora} < 17
%define _dracutmoddir /usr/share/dracut/modules.d
%endif
Name: qubes-core-dom0-linux
Version: @VERSION@
Version: %{version}
Release: 1%{?dist}
Summary: Linux-specific files for Qubes dom0
@ -39,19 +44,15 @@ URL: http://www.qubes-os.org
BuildRequires: ImageMagick
BuildRequires: pandoc
BuildRequires: qubes-utils-devel >= 3.1.3
BuildRequires: gcc
BuildRequires: qubes-libvchan-devel
BuildRequires: python
Requires: qubes-core-dom0
Requires: python3-qubesadmin
Requires: qubes-core-qrexec-dom0
Requires: qubes-core-admin-client
Requires: qubes-utils >= 3.1.3
Requires: qubes-utils-libs >= 4.0.16
Conflicts: qubes-core-dom0 < 4.0.23
Requires: qubes-utils-libs >= 3.2.7
Requires: %{name}-kernel-install
Requires: xdotool
Requires: createrepo_c
Source0: %{name}-%{version}.tar.gz
%define _builddir %(pwd)
%description
Linux customizations required to use system as Qubes dom0.
@ -70,19 +71,41 @@ Obsoletes: os-prober
Kernel install hook for Xen-based system.
%prep
%setup -q
# we operate on the current directory, so no need to unpack anything
# symlink is to generate useful debuginfo packages
rm -f %{name}-%{version}
ln -sf . %{name}-%{version}
%setup -T -D
%build
export BACKEND_VMM=@BACKEND_VMM@
python -m compileall appmenus-scripts
python -O -m compileall appmenus-scripts
(cd dom0-updates; make)
(cd qrexec; make)
(cd file-copy-vm; make)
(cd doc; make manpages)
%install
## Appmenus
install -d $RPM_BUILD_ROOT/etc/qubes-rpc/policy
cp qubesappmenus/qubes.SyncAppMenus.policy $RPM_BUILD_ROOT/etc/qubes-rpc/policy/qubes.SyncAppMenus
### Appmenus
mkdir -p $RPM_BUILD_ROOT%{python_sitearch}/qubes/modules
cp appmenus-scripts/qubes-core-appmenus.py $RPM_BUILD_ROOT%{python_sitearch}/qubes/modules/10appmenus.py
cp appmenus-scripts/qubes-core-appmenus.pyc $RPM_BUILD_ROOT%{python_sitearch}/qubes/modules/10appmenus.pyc
cp appmenus-scripts/qubes-core-appmenus.pyo $RPM_BUILD_ROOT%{python_sitearch}/qubes/modules/10appmenus.pyo
mkdir -p $RPM_BUILD_ROOT/usr/libexec/qubes-appmenus
cp appmenus-scripts/*.sh $RPM_BUILD_ROOT/usr/libexec/qubes-appmenus/
cp appmenus-scripts/qubes-receive-appmenus $RPM_BUILD_ROOT/usr/libexec/qubes-appmenus/
install -D appmenus-scripts/qvm-sync-appmenus $RPM_BUILD_ROOT/usr/bin/qvm-sync-appmenus
mkdir -p $RPM_BUILD_ROOT/etc/qubes-rpc/policy
cp appmenus-scripts/qubes.SyncAppMenus $RPM_BUILD_ROOT/etc/qubes-rpc/
cp appmenus-scripts/qubes.SyncAppMenus.policy $RPM_BUILD_ROOT/etc/qubes-rpc/policy/qubes.SyncAppMenus
mkdir -p $RPM_BUILD_ROOT/usr/share/qubes-appmenus/
cp -r appmenus-files/* $RPM_BUILD_ROOT/usr/share/qubes-appmenus/
### Dom0 updates
install -D dom0-updates/qubes-dom0-updates.cron $RPM_BUILD_ROOT/etc/cron.daily/qubes-dom0-updates.cron
@ -96,14 +119,19 @@ install -m 0664 -D dom0-updates/qubes.ReceiveUpdates.policy $RPM_BUILD_ROOT/etc/
install -d $RPM_BUILD_ROOT/var/lib/qubes/updates
# Qrexec services
mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes/qubes-rpc $RPM_BUILD_ROOT/etc/qubes-rpc/policy
cp qubes-rpc/* $RPM_BUILD_ROOT/usr/lib/qubes/qubes-rpc/
for i in qubes-rpc/*; do ln -s ../../usr/lib/qubes/$i $RPM_BUILD_ROOT/etc/qubes-rpc/$(basename $i); done
cp qubes-rpc-policy/* $RPM_BUILD_ROOT/etc/qubes-rpc/policy/
# Qrexec
mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes/
cp qrexec/qrexec-daemon $RPM_BUILD_ROOT/usr/lib/qubes/
cp qrexec/qrexec-client $RPM_BUILD_ROOT/usr/lib/qubes/
# XXX: Backward compatibility
ln -s qrexec-client $RPM_BUILD_ROOT/usr/lib/qubes/qrexec_client
cp qrexec/qrexec-policy $RPM_BUILD_ROOT/usr/lib/qubes/
cp qrexec/qubes-rpc-multiplexer $RPM_BUILD_ROOT/usr/lib/qubes
### pm-utils
mkdir -p $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d
cp pm-utils/01qubes-sync-vms-clock $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d/
cp pm-utils/51qubes-suspend-netvm $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d/
cp pm-utils/52qubes-pause-vms $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d/
mkdir -p $RPM_BUILD_ROOT/usr/lib/systemd/system
cp pm-utils/qubes-suspend.service $RPM_BUILD_ROOT/usr/lib/systemd/system/
@ -123,33 +151,29 @@ install -m 0440 -D system-config/qubes.sudoers $RPM_BUILD_ROOT/etc/sudoers.d/qub
install -D system-config/polkit-1-qubes-allow-all.rules $RPM_BUILD_ROOT/etc/polkit-1/rules.d/00-qubes-allow-all.rules
install -D system-config/qubes-dom0.modules $RPM_BUILD_ROOT/etc/sysconfig/modules/qubes-dom0.modules
install -D system-config/qubes-sync-clock.cron $RPM_BUILD_ROOT/etc/cron.d/qubes-sync-clock.cron
install -D system-config/lvm-cleanup.cron-daily $RPM_BUILD_ROOT/etc/cron.daily/lvm-cleanup
install -d $RPM_BUILD_ROOT/etc/udev/rules.d
install -m 644 system-config/00-qubes-ignore-devices.rules $RPM_BUILD_ROOT/etc/udev/rules.d/
install -m 644 system-config/12-qubes-ignore-lvm-devices.rules $RPM_BUILD_ROOT/etc/udev/rules.d/
install -m 644 system-config/60-persistent-storage.rules $RPM_BUILD_ROOT/etc/udev/rules.d/
install -m 644 -D system-config/disable-lesspipe.sh $RPM_BUILD_ROOT/etc/profile.d/zz-disable-lesspipe.sh
install -m 755 -D system-config/kernel-grub2.install $RPM_BUILD_ROOT/usr/lib/kernel/install.d/80-grub2.install
install -m 755 -D system-config/kernel-xen-efi.install $RPM_BUILD_ROOT/usr/lib/kernel/install.d/90-xen-efi.install
install -m 755 -D system-config/kernel-remove-bls.install $RPM_BUILD_ROOT/usr/lib/kernel/install.d/99-remove-bls.install
install -m 644 -D system-config/75-qubes-dom0.preset \
$RPM_BUILD_ROOT/usr/lib/systemd/system-preset/75-qubes-dom0.preset
install -m 644 -D system-config/75-qubes-dom0-user.preset \
$RPM_BUILD_ROOT/usr/lib/systemd/user-preset/75-qubes-dom0-user.preset
install -m 644 -D system-config/99-qubes-default-disable.preset \
$RPM_BUILD_ROOT/usr/lib/systemd/system-preset/99-qubes-default-disable.preset
install -d $RPM_BUILD_ROOT/etc/dnf/protected.d
install -m 0644 system-config/dnf-protected-qubes-core-dom0.conf \
$RPM_BUILD_ROOT/etc/dnf/protected.d/qubes-core-dom0.conf
touch $RPM_BUILD_ROOT/var/lib/qubes/.qubes-exclude-block-devices
install -m 755 tools/qvm-xkill $RPM_BUILD_ROOT/usr/bin/
# file copy to VM
install -m 755 file-copy-vm/qfile-dom0-agent $RPM_BUILD_ROOT/usr/lib/qubes/
install -m 755 file-copy-vm/qvm-copy-to-vm $RPM_BUILD_ROOT/usr/bin/
install -m 755 file-copy-vm/qvm-copy $RPM_BUILD_ROOT/usr/bin/
ln -s qvm-copy-to-vm $RPM_BUILD_ROOT/usr/bin/qvm-move-to-vm
ln -s qvm-copy $RPM_BUILD_ROOT/usr/bin/qvm-move
### Icons
mkdir -p $RPM_BUILD_ROOT/usr/share/qubes/icons
for icon in icons/*.png; do
convert -resize 48 $icon $RPM_BUILD_ROOT/usr/share/qubes/$icon
done
### Documentation
(cd doc; make DESTDIR=$RPM_BUILD_ROOT install)
@ -161,22 +185,27 @@ fi
%post
for i in /usr/share/qubes/icons/*.png ; do
xdg-icon-resource install --noupdate --novendor --size 48 $i
done
xdg-icon-resource forceupdate
xdg-desktop-menu install /usr/share/qubes-appmenus/qubes-dispvm.directory /usr/share/qubes-appmenus/qubes-dispvm-*.desktop
/usr/lib/qubes/patch-dnf-yum-config
systemctl enable qubes-suspend.service >/dev/null 2>&1
# migrate dom0-updates check disable flag
if [ $1 -ge 2 ]; then
if [ -e /var/lib/qubes/updates/disable-updates ]; then
qvm-features dom0 service.qubes-update-check ''
rm -f /var/lib/qubes/updates/disable-updates
fi
fi
%preun
if [ "$1" = 0 ] ; then
# no more packages left
for i in /usr/share/qubes/icons/*.png ; do
xdg-icon-resource uninstall --novendor --size 48 $i
done
xdg-desktop-menu uninstall /usr/share/qubes-appmenus/qubes-dispvm.directory /usr/share/qubes-appmenus/qubes-dispvm-*.desktop
systemctl disable qubes-suspend.service > /dev/null 2>&1
fi
@ -192,7 +221,29 @@ rm -f /lib/udev/rules.d/69-xorg-vmmouse.rules
chmod -x /etc/grub.d/10_linux
%files
%attr(2775,root,qubes) %dir /etc/qubes-rpc
%attr(2775,root,qubes) %dir /etc/qubes-rpc/policy
/etc/qubes-rpc/policy/qubes.SyncAppMenus
/etc/qubes-rpc/qubes.SyncAppMenus
%{python_sitearch}/qubes/modules/10appmenus.py
%{python_sitearch}/qubes/modules/10appmenus.pyc
%{python_sitearch}/qubes/modules/10appmenus.pyo
/usr/libexec/qubes-appmenus/convert-apptemplate2vm.sh
/usr/libexec/qubes-appmenus/convert-dirtemplate2vm.sh
/usr/libexec/qubes-appmenus/create-apps-for-appvm.sh
/usr/libexec/qubes-appmenus/qubes-receive-appmenus
/usr/libexec/qubes-appmenus/remove-appvm-appmenus.sh
/usr/share/qubes-appmenus/qubes-appmenu-select.desktop
/usr/share/qubes-appmenus/qubes-dispvm-firefox.desktop
/usr/share/qubes-appmenus/qubes-dispvm-xterm.desktop
/usr/share/qubes-appmenus/qubes-dispvm.directory
/usr/share/qubes-appmenus/qubes-servicevm.directory.template
/usr/share/qubes-appmenus/qubes-start.desktop
/usr/share/qubes-appmenus/qubes-templatevm.directory.template
/usr/share/qubes-appmenus/qubes-vm.directory.template
/usr/share/qubes-appmenus/hvm
/usr/share/qubes/icons/*.png
/usr/bin/qvm-sync-appmenus
# Dom0 updates
/etc/cron.daily/qubes-dom0-updates.cron
/etc/yum.real.repos.d/qubes-cached.repo
@ -203,27 +254,25 @@ chmod -x /etc/grub.d/10_linux
/etc/qubes-rpc/qubes.ReceiveUpdates
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.ReceiveUpdates
%attr(0770,root,qubes) %dir /var/lib/qubes/updates
# Qrexec services
/etc/qubes-rpc/qubes.repos.*
/usr/lib/qubes/qubes-rpc/qubes.repos.*
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.repos.List
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.repos.Enable
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.repos.Disable
# Dracut module
/etc/dracut.conf.d/*
%dir %{_dracutmoddir}/90macbook12-spi-driver
%{_dracutmoddir}/90macbook12-spi-driver/*
%dir %{_dracutmoddir}/90qubes-pciback
%{_dracutmoddir}/90qubes-pciback/*
%dir %{_dracutmoddir}/90extra-modules
%{_dracutmoddir}/90extra-modules/*
# Qrexec
%attr(4750,root,qubes) /usr/lib/qubes/qrexec-daemon
/usr/lib/qubes/qrexec-client
/usr/lib/qubes/qrexec_client
/usr/lib/qubes/qubes-rpc-multiplexer
/usr/lib/qubes/qrexec-policy
# file copy
/usr/bin/qvm-copy-to-vm
/usr/bin/qvm-move-to-vm
/usr/bin/qvm-copy
/usr/bin/qvm-move
/usr/lib/qubes/qfile-dom0-agent
# pm-utils
/usr/lib64/pm-utils/sleep.d/01qubes-sync-vms-clock
/usr/lib64/pm-utils/sleep.d/51qubes-suspend-netvm
/usr/lib64/pm-utils/sleep.d/52qubes-pause-vms
/usr/lib/systemd/system/qubes-suspend.service
# Others
@ -233,15 +282,12 @@ chmod -x /etc/grub.d/10_linux
/etc/polkit-1/rules.d/00-qubes-allow-all.rules
/etc/security/limits.d/99-qubes.conf
%config /etc/udev/rules.d/00-qubes-ignore-devices.rules
%config /etc/udev/rules.d/12-qubes-ignore-lvm-devices.rules
%config(noreplace) /etc/udev/rules.d/60-persistent-storage.rules
%attr(0644,root,root) /etc/cron.d/qubes-sync-clock.cron
/etc/cron.daily/lvm-cleanup
%config(noreplace) /etc/profile.d/zz-disable-lesspipe.sh
%config(noreplace) /etc/dnf/protected.d/qubes-core-dom0.conf
/usr/lib/systemd/system-preset/75-qubes-dom0.preset
/usr/lib/systemd/system-preset/99-qubes-default-disable.preset
/usr/lib/systemd/user-preset/75-qubes-dom0-user.preset
/var/lib/qubes/.qubes-exclude-block-devices
/usr/bin/qvm-xkill
# Man
%{_mandir}/man1/qvm-*.1*
%{_mandir}/man1/qubes-*.1*
@ -252,4 +298,3 @@ chmod -x /etc/grub.d/10_linux
/usr/lib/kernel/install.d/99-remove-bls.install
%changelog
@CHANGELOG@

@ -1,5 +1,7 @@
%{!?version: %define version %(cat version_vaio_fixes)}
Name: qubes-core-dom0-vaio-fixes
Version: @VERSION1@
Version: %{version}
Release: 1%{?dist}
Summary: Additional scripts for supporting suspend on Vaio Z laptops
Requires: alsa-utils
@ -9,7 +11,7 @@ Vendor: Invisible Things Lab
License: GPL
URL: http://www.qubes-os.org
Source0: qubes-core-dom0-linux-@VERSION@.tar.gz
%define _builddir %(pwd)
%description
Additional scripts for supporting suspend on Vaio Z laptops.
@ -17,20 +19,14 @@ Additional scripts for supporting suspend on Vaio Z laptops.
Due to broken Linux GPU drivers we need to do some additional actions during
suspend/resume.
%prep
%setup -q -n qubes-core-dom0-linux-@VERSION@
%install
mkdir -p $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d
install -D vaio-fixes/00sony-vaio-audio $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d/
install -D vaio-fixes/99sony-vaio-audio $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d/
cp vaio-fixes/00sony-vaio-audio $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d/
cp vaio-fixes/99sony-vaio-audio $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d/
mkdir -p $RPM_BUILD_ROOT/etc/modprobe.d/
install -D vaio-fixes/snd-hda-intel-sony-vaio.conf $RPM_BUILD_ROOT/etc/modprobe.d/
cp vaio-fixes/snd-hda-intel-sony-vaio.conf $RPM_BUILD_ROOT/etc/modprobe.d/
%files
/usr/lib64/pm-utils/sleep.d/00sony-vaio-audio
/usr/lib64/pm-utils/sleep.d/99sony-vaio-audio
/etc/modprobe.d/snd-hda-intel-sony-vaio.conf
%changelog
@CHANGELOG@

@ -1,7 +1,6 @@
# do not edit this file, it will be overwritten on update
ACTION!="remove", SUBSYSTEM=="block", KERNEL=="loop*", ATTR{loop/backing_file}=="*/appvms/*/*.img*|*/vm-templates/*/*.img*", ENV{DM_UDEV_DISABLE_DISK_RULES_FLAG}="1", ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}="1"
ACTION!="remove", SUBSYSTEM=="block", KERNEL=="xvd*", ENV{DM_UDEV_DISABLE_DISK_RULES_FLAG}="1", ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}="1"
ACTION!="remove", SUBSYSTEM=="block", KERNEL=="loop*", ENV{DM_UDEV_DISABLE_DISK_RULES_FLAG}="1"
# ENV{DM_NAME} not available yet
# Template VM disks
ACTION!="remove", SUBSYSTEM=="block", ATTR{dm/name}=="snapshot-*", ENV{DM_UDEV_DISABLE_DISK_RULES_FLAG}="1"

@ -1,4 +0,0 @@
# do not edit this file, it will be overwritten on update
# Skip VM images managed by lvm storage pool
ACTION!="remove", SUBSYSTEM=="block", ENV{DM_LV_NAME}=="vm-*", ENV{DM_UDEV_DISABLE_DISK_RULES_FLAG}="1", ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}="1"

@ -0,0 +1,89 @@
# Qubes: Prevent probing of domU controlled disk contents. Note that it would
# nevertheless be insecure to attach block devices from domU to dom0 (xvd*) due
# to automatic kernel partition table scanners -- which are disabled for loop*
# devices created without LO_FLAGS_PARTSCAN.
SUBSYSTEM=="block", KERNEL=="loop*|xvd*", GOTO="persistent_storage_end"
# do not edit this file, it will be overwritten on update
# persistent storage links: /dev/disk/{by-id,by-uuid,by-label,by-path}
# scheme based on "Linux persistent device names", 2004, Hannes Reinecke <hare@suse.de>
ACTION=="remove", GOTO="persistent_storage_end"
SUBSYSTEM!="block", GOTO="persistent_storage_end"
KERNEL!="loop*|mmcblk*[0-9]|msblk*[0-9]|mspblk*[0-9]|nvme*|sd*|sr*|vd*|xvd*|bcache*|cciss*|dasd*", GOTO="persistent_storage_end"
# ignore partitions that span the entire disk
TEST=="whole_disk", GOTO="persistent_storage_end"
# for partitions import parent information
ENV{DEVTYPE}=="partition", IMPORT{parent}="ID_*"
# virtio-blk
KERNEL=="vd*[!0-9]", ATTRS{serial}=="?*", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/virtio-$env{ID_SERIAL}"
KERNEL=="vd*[0-9]", ATTRS{serial}=="?*", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/virtio-$env{ID_SERIAL}-part%n"
# ATA
KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", IMPORT{program}="ata_id --export $devnode"
# ATAPI devices (SPC-3 or later)
KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{type}=="5", ATTRS{scsi_level}=="[6-9]*", IMPORT{program}="ata_id --export $devnode"
# Run ata_id on non-removable USB Mass Storage (SATA/PATA disks in enclosures)
KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", ATTR{removable}=="0", SUBSYSTEMS=="usb", IMPORT{program}="ata_id --export $devnode"
# Fall back usb_id for USB devices
KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
# SCSI devices
KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="scsi"
KERNEL=="cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="cciss"
KERNEL=="sd*|sr*|cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
KERNEL=="sd*|cciss*", ENV{DEVTYPE}=="partition", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}-part%n"
# FireWire
KERNEL=="sd*[!0-9]|sr*", ATTRS{ieee1394_id}=="?*", SYMLINK+="disk/by-id/ieee1394-$attr{ieee1394_id}"
KERNEL=="sd*[0-9]", ATTRS{ieee1394_id}=="?*", SYMLINK+="disk/by-id/ieee1394-$attr{ieee1394_id}-part%n"
# MMC
KERNEL=="mmcblk[0-9]", SUBSYSTEMS=="mmc", ATTRS{name}=="?*", ATTRS{serial}=="?*", \
ENV{ID_NAME}="$attr{name}", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/mmc-$env{ID_NAME}_$env{ID_SERIAL}"
KERNEL=="mmcblk[0-9]p[0-9]", ENV{ID_NAME}=="?*", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/mmc-$env{ID_NAME}_$env{ID_SERIAL}-part%n"
# Memstick
KERNEL=="msblk[0-9]|mspblk[0-9]", SUBSYSTEMS=="memstick", ATTRS{name}=="?*", ATTRS{serial}=="?*", \
ENV{ID_NAME}="$attr{name}", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/memstick-$env{ID_NAME}_$env{ID_SERIAL}"
KERNEL=="msblk[0-9]p[0-9]|mspblk[0-9]p[0-9]", ENV{ID_NAME}=="?*", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/memstick-$env{ID_NAME}_$env{ID_SERIAL}-part%n"
# by-path
ENV{DEVTYPE}=="disk", DEVPATH!="*/virtual/*", IMPORT{builtin}="path_id"
ENV{DEVTYPE}=="disk", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}"
ENV{DEVTYPE}=="partition", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}-part%n"
# probe filesystem metadata of optical drives which have a media inserted
KERNEL=="sr*", ENV{DISK_EJECT_REQUEST}!="?*", ENV{ID_CDROM_MEDIA_TRACK_COUNT_DATA}=="?*", ENV{ID_CDROM_MEDIA_SESSION_LAST_OFFSET}=="?*", \
IMPORT{builtin}="blkid --offset=$env{ID_CDROM_MEDIA_SESSION_LAST_OFFSET}"
# single-session CDs do not have ID_CDROM_MEDIA_SESSION_LAST_OFFSET
KERNEL=="sr*", ENV{DISK_EJECT_REQUEST}!="?*", ENV{ID_CDROM_MEDIA_TRACK_COUNT_DATA}=="?*", ENV{ID_CDROM_MEDIA_SESSION_LAST_OFFSET}=="", \
IMPORT{builtin}="blkid --noraid"
# probe filesystem metadata of disks
KERNEL!="sr*", IMPORT{builtin}="blkid"
# by-label/by-uuid links (filesystem metadata)
ENV{ID_FS_USAGE}=="filesystem|other|crypto", ENV{ID_FS_UUID_ENC}=="?*", SYMLINK+="disk/by-uuid/$env{ID_FS_UUID_ENC}"
ENV{ID_FS_USAGE}=="filesystem|other", ENV{ID_FS_LABEL_ENC}=="?*", SYMLINK+="disk/by-label/$env{ID_FS_LABEL_ENC}"
# by-id (World Wide Name)
ENV{DEVTYPE}=="disk", ENV{ID_WWN_WITH_EXTENSION}=="?*", SYMLINK+="disk/by-id/wwn-$env{ID_WWN_WITH_EXTENSION}"
ENV{DEVTYPE}=="partition", ENV{ID_WWN_WITH_EXTENSION}=="?*", SYMLINK+="disk/by-id/wwn-$env{ID_WWN_WITH_EXTENSION}-part%n"
# by-partlabel/by-partuuid links (partition metadata)
ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_ENTRY_UUID}=="?*", SYMLINK+="disk/by-partuuid/$env{ID_PART_ENTRY_UUID}"
ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_ENTRY_NAME}=="?*", SYMLINK+="disk/by-partlabel/$env{ID_PART_ENTRY_NAME}"
# add symlink to GPT root disk
ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_GPT_AUTO_ROOT}=="1", SYMLINK+="gpt-auto-root"
LABEL="persistent_storage_end"

@ -1,2 +0,0 @@
enable dbus.socket
enable dbus-daemon.service

@ -16,10 +16,7 @@ enable lvm2-monitor.*
enable lvm2-lvmetad.*
enable dm-event.*
enable dmraid-activation.service
enable fstrim.timer
enable dbus.socket
enable dbus-daemon.service
enable abrtd.service
enable abrt-ccpp.service
@ -31,7 +28,6 @@ enable xenstored.service
enable xenstored.socket
enable xenstored_ro.socket
enable xenconsoled.service
enable xen-init-dom0.service
enable libvirtd.service
enable virlockd.socket
@ -48,8 +44,7 @@ enable qubes-db-dom0.service
enable qubes-qmemman.service
enable qubes-suspend.service
enable qubes-setupdvm.service
enable qubes-qrexec-policy-daemon.service
enable qubesd.service
enable qubes-block-cleaner.service
enable anti-evil-maid-unseal.service
enable anti-evil-maid-check-mount-devs.service
enable anti-evil-maid-seal.service

@ -19,10 +19,5 @@ case "$COMMAND" in
;;
esac
if [ -x /usr/sbin/grub2-mkconfig ]; then
if [ -e /boot/grub2/grub.cfg ]; then
grub2-mkconfig -o /boot/grub2/grub.cfg
fi
if [ -e /boot/efi/EFI/qubes/grub.cfg ]; then
grub2-mkconfig -o /boot/efi/EFI/qubes/grub.cfg
fi
grub2-mkconfig -o /boot/grub2/grub.cfg
fi

@ -24,7 +24,7 @@ else
EFI_DIR="$ESP_MOUNTPOINT$EFI_DIR"
fi
if [ ! -r "$EFI_DIR/xen.cfg" ]; then
if [ ! -d "$EFI_DIR" ]; then
# non-EFI system
exit 0;
fi

@ -1,3 +0,0 @@
#!/bin/sh
find /etc/lvm/archive/ -type f -mtime +1 -name '*.vg' -delete

@ -1 +1 @@
0 */1 * * * root /usr/bin/qvm-sync-clock > /dev/null 2>&1 || true
*/6 * * * * root /usr/bin/qvm-sync-clock > /dev/null 2>&1 || true

@ -0,0 +1,10 @@
#!/bin/sh
set -e
ID=$(xdotool selectwindow)
xprop -id "$ID" _QUBES_VMNAME | grep -q ' = ' \
|| { echo "${0##*/}: Not killing dom0 window $ID" >&2; exit 1; }
xdotool windowkill "$ID"

@ -1,2 +1 @@
4.1.3
1.6.1
3.2.18

Loading…
Cancel
Save