Compare commits
181 Commits
release3.2
...
master
Author | SHA1 | Date |
---|---|---|
Andy | 2df917e7a5 | 4 years ago |
3hhh | c600b1b39c | 4 years ago |
3hhh | f4f5731bdc | 4 years ago |
Frédéric Pierret (fepitre) | 0d53697917 | 4 years ago |
Marek Marczykowski-Górecki | a4006f5046 | 4 years ago |
Marek Marczykowski-Górecki | 264ded8101 | 4 years ago |
Marek Marczykowski-Górecki | 4a88c520ac | 5 years ago |
Marta Marczykowska-Górecka | 761b5b1ef4 | 5 years ago |
Marek Marczykowski-Górecki | 257d9e5b78 | 5 years ago |
Marek Marczykowski-Górecki | 9cf273d187 | 5 years ago |
Frédéric Pierret (fepitre) | cf76a3cbbb | 5 years ago |
Marek Marczykowski-Górecki | e5e006d933 | 5 years ago |
Marek Marczykowski-Górecki | 2dadbcfdcb | 5 years ago |
Marek Marczykowski-Górecki | 9962fab124 | 5 years ago |
M. Vefa Bicakci | 1089a7a07b | 5 years ago |
Marek Marczykowski-Górecki | c56c4a7a9d | 5 years ago |
Marek Marczykowski-Górecki | 895415aee1 | 5 years ago |
Marek Marczykowski-Górecki | 8800a08150 | 5 years ago |
AJ Jordan | 82806b53e2 | 5 years ago |
AJ Jordan | 3786197ab2 | 5 years ago |
AJ Jordan | 75faa22dff | 5 years ago |
AJ Jordan | 2283af8ce5 | 5 years ago |
AJ Jordan | 00c37b0b5b | 5 years ago |
AJ Jordan | 05658f0850 | 5 years ago |
AJ Jordan | 0af2769aca | 5 years ago |
AJ Jordan | 529f5a1cd0 | 5 years ago |
AJ Jordan | ce70209310 | 5 years ago |
AJ Jordan | 888073df05 | 5 years ago |
Marek Marczykowski-Górecki | 6fa3e19f7e | 5 years ago |
Marek Marczykowski-Górecki | 15c55a4ef5 | 5 years ago |
Marek Marczykowski-Górecki | 2ec29a4d4c | 5 years ago |
Marek Marczykowski-Górecki | 330f155168 | 5 years ago |
Marek Marczykowski-Górecki | fb1c284774 | 5 years ago |
Marek Marczykowski-Górecki | d705fa6ed4 | 5 years ago |
Marek Marczykowski-Górecki | 61ec339c2d | 5 years ago |
Marek Marczykowski-Górecki | b6e3f360c9 | 5 years ago |
Marek Marczykowski-Górecki | edf406c172 | 5 years ago |
Marek Marczykowski-Górecki | 1ffa0d69cb | 5 years ago |
Marek Marczykowski-Górecki | d5e667d0ee | 5 years ago |
Marek Marczykowski-Górecki | e5deabe0aa | 5 years ago |
Marek Marczykowski-Górecki | 59ecf8eb83 | 5 years ago |
Marek Marczykowski-Górecki | 72a3459119 | 6 years ago |
Marek Marczykowski-Górecki | 9c3a4e7b00 | 6 years ago |
Marek Marczykowski-Górecki | d7c23e1b7f | 6 years ago |
Marek Marczykowski-Górecki | 07c286fad5 | 6 years ago |
Marek Marczykowski-Górecki | 8555ff4ced | 6 years ago |
Marek Marczykowski-Górecki | c03fbecb4e | 6 years ago |
fepitre | b36f298815 | 6 years ago |
Frédéric Pierret (fepitre) | 7064279316 | 6 years ago |
Marek Marczykowski-Górecki | 10960564cf | 6 years ago |
Kushal Das | 0a94e59325 | 6 years ago |
Marek Marczykowski-Górecki | 3cc4f5bed3 | 6 years ago |
Marek Marczykowski-Górecki | 4495000703 | 6 years ago |
Marta Marczykowska-Górecka | 879b62c353 | 6 years ago |
Marek Marczykowski-Górecki | 912861c8b0 | 6 years ago |
Marek Marczykowski-Górecki | b1c038e882 | 6 years ago |
Marek Marczykowski-Górecki | a10d724bb1 | 6 years ago |
AJ Jordan | a5d6dd3001 | 6 years ago |
Marta Marczykowska-Górecka | 9a039f0753 | 6 years ago |
Marek Marczykowski-Górecki | 6d08882978 | 6 years ago |
Marek Marczykowski-Górecki | 9eefe23f4c | 6 years ago |
Marek Marczykowski-Górecki | 53730c4ba2 | 6 years ago |
Marek Marczykowski-Górecki | 861ddc9ce0 | 6 years ago |
Marek Marczykowski-Górecki | 03959b670c | 6 years ago |
Frédéric Pierret | ee878fa40a | 6 years ago |
Frédéric Pierret | 5b78f21921 | 6 years ago |
Frédéric Pierret | a2139b95b5 | 6 years ago |
Frédéric Pierret | 3ae3eae48b | 6 years ago |
Frédéric Pierret | 9f591b0578 | 6 years ago |
Marek Marczykowski-Górecki | 565fb3dc3a | 6 years ago |
Marek Marczykowski-Górecki | bcf7c9e978 | 6 years ago |
Marek Marczykowski-Górecki | 7c1cad00b0 | 6 years ago |
Marek Marczykowski-Górecki | bae443dfce | 6 years ago |
Marek Marczykowski-Górecki | d924270bb1 | 6 years ago |
Marek Marczykowski-Górecki | d1f3be0eed | 6 years ago |
Marek Marczykowski-Górecki | 6cef3f3966 | 6 years ago |
Marek Marczykowski-Górecki | cd23a035c5 | 6 years ago |
donoban | dbb22f6335 | 6 years ago |
donoban | 31548737c6 | 6 years ago |
donoban | 994bd72363 | 6 years ago |
Marek Marczykowski-Górecki | d48f5599d3 | 6 years ago |
Marek Marczykowski-Górecki | cfc424667a | 6 years ago |
Marek Marczykowski-Górecki | e8c8515211 | 6 years ago |
Marek Marczykowski-Górecki | c129ce2e4d | 6 years ago |
Marek Marczykowski-Górecki | d54d953af1 | 6 years ago |
Marek Marczykowski-Górecki | 32b0c659a1 | 6 years ago |
Miguel Jacq | 6a792ed056 | 6 years ago |
Marek Marczykowski-Górecki | bdebfe330a | 6 years ago |
Rusty Bird | 629d02948f | 6 years ago |
Marek Marczykowski-Górecki | da61441bf9 | 6 years ago |
Marek Marczykowski-Górecki | 21c951201c | 6 years ago |
Marek Marczykowski-Górecki | b07706fd7a | 6 years ago |
BaN-Co | 3a176fa658 | 6 years ago |
Marek Marczykowski-Górecki | 8689170368 | 7 years ago |
Marek Marczykowski-Górecki | 6c8537fab1 | 7 years ago |
Jean-Philippe Ouellet | c69662eb28 | 7 years ago |
Marek Marczykowski-Górecki | be9e759697 | 7 years ago |
Marek Marczykowski-Górecki | 7902979470 | 7 years ago |
Jean-Philippe Ouellet | 552fd062ea | 7 years ago |
Jean-Philippe Ouellet | aeb04e24e2 | 7 years ago |
Jean-Philippe Ouellet | 686db90032 | 7 years ago |
Marek Marczykowski-Górecki | 7a644b6d61 | 7 years ago |
Marek Marczykowski-Górecki | 21df9d55bb | 7 years ago |
Marek Marczykowski-Górecki | b79aa05014 | 7 years ago |
Marek Marczykowski-Górecki | 68dd013585 | 7 years ago |
Marek Marczykowski-Górecki | 54d5c7b35c | 7 years ago |
Frédéric Pierret | 69d230d065 | 7 years ago |
Marek Marczykowski-Górecki | a93a846687 | 7 years ago |
Marek Marczykowski-Górecki | 6ba03ed65b | 7 years ago |
Marek Marczykowski-Górecki | 1f6546f484 | 7 years ago |
Marek Marczykowski-Górecki | 5c84a0be92 | 7 years ago |
Andrew (anoa) | 02ced3a639 | 7 years ago |
Marek Marczykowski-Górecki | afa673ff46 | 7 years ago |
Marek Marczykowski-Górecki | f609afddb6 | 7 years ago |
Marta Marczykowska-Górecka | 6d424f91a5 | 7 years ago |
Marek Marczykowski-Górecki | 955762b71e | 7 years ago |
Marek Marczykowski-Górecki | 6ffac092ed | 7 years ago |
Marek Marczykowski-Górecki | e0ce4a8348 | 7 years ago |
Marek Marczykowski-Górecki | 2fb94bd3e6 | 7 years ago |
Marek Marczykowski-Górecki | 005fed6cdf | 7 years ago |
Christopher Laprise | 6d251d5c58 | 7 years ago |
Marek Marczykowski-Górecki | 51abb471b9 | 7 years ago |
Marek Marczykowski-Górecki | 9b75dd1321 | 7 years ago |
Marek Marczykowski-Górecki | 8719e5d74c | 7 years ago |
Marek Marczykowski-Górecki | e4cf07c107 | 7 years ago |
Marek Marczykowski-Górecki | b69f263c10 | 7 years ago |
Marek Marczykowski-Górecki | e62acf815a | 7 years ago |
Marek Marczykowski-Górecki | 1447ecad57 | 7 years ago |
Marek Marczykowski-Górecki | 1057309951 | 7 years ago |
Marek Marczykowski-Górecki | e6cd559b82 | 7 years ago |
Marek Marczykowski-Górecki | d9202f8d14 | 7 years ago |
Rusty Bird | 6c8df74b7f | 7 years ago |
Marek Marczykowski-Górecki | 6681ad79bc | 7 years ago |
Marek Marczykowski-Górecki | 8fd4d9e853 | 7 years ago |
Marek Marczykowski-Górecki | ad2a976924 | 7 years ago |
Marek Marczykowski-Górecki | e36dba5acb | 7 years ago |
Marek Marczykowski-Górecki | 22cf6df02f | 7 years ago |
Marek Marczykowski-Górecki | ea6f47bf33 | 7 years ago |
Marek Marczykowski-Górecki | 1502eb4d59 | 7 years ago |
Marek Marczykowski-Górecki | 83308758f0 | 7 years ago |
Jean-Philippe Ouellet | ce56a4cdf3 | 7 years ago |
Marek Marczykowski-Górecki | 723d32b8b7 | 7 years ago |
Marek Marczykowski-Górecki | 452b6c4ae2 | 7 years ago |
Marek Marczykowski-Górecki | 514c27d681 | 7 years ago |
Marek Marczykowski-Górecki | b253fdba33 | 7 years ago |
Marek Marczykowski-Górecki | b370eea13c | 8 years ago |
Marek Marczykowski-Górecki | 4efedd2951 | 8 years ago |
Marek Marczykowski-Górecki | 90b18a1ec1 | 8 years ago |
Marek Marczykowski-Górecki | ef47bda417 | 8 years ago |
Marek Marczykowski-Górecki | 85b509cedc | 8 years ago |
Marek Marczykowski-Górecki | 40b139ab82 | 8 years ago |
Marek Marczykowski-Górecki | a513b33da3 | 8 years ago |
Marek Marczykowski-Górecki | a72d53ae1b | 8 years ago |
Marek Marczykowski-Górecki | cce22c9517 | 8 years ago |
Marek Marczykowski-Górecki | 009e2e6adb | 8 years ago |
Marek Marczykowski-Górecki | 849b295384 | 8 years ago |
Marek Marczykowski-Górecki | 9192bb0d44 | 8 years ago |
Marek Marczykowski-Górecki | 9a5bd57d1b | 8 years ago |
Marek Marczykowski-Górecki | 8a780cb7f5 | 8 years ago |
Marek Marczykowski-Górecki | 92c3ba578a | 8 years ago |
Marek Marczykowski-Górecki | da9205c78a | 8 years ago |
Marek Marczykowski-Górecki | 3abee97e13 | 8 years ago |
Marek Marczykowski-Górecki | 86ef6906ad | 8 years ago |
Marek Marczykowski-Górecki | fe209dfd24 | 8 years ago |
Marek Marczykowski-Górecki | daf1fd4759 | 8 years ago |
Marek Marczykowski-Górecki | c34427e264 | 8 years ago |
Marek Marczykowski-Górecki | bf140ae175 | 8 years ago |
Marek Marczykowski-Górecki | 19609705fc | 8 years ago |
Marek Marczykowski-Górecki | aef9aa2dd3 | 8 years ago |
Marek Marczykowski-Górecki | 0fbc644fd9 | 8 years ago |
Marek Marczykowski-Górecki | f688cba49c | 8 years ago |
Marek Marczykowski-Górecki | 7dccbd1ead | 8 years ago |
Marek Marczykowski-Górecki | 9690f52dc5 | 8 years ago |
Marek Marczykowski-Górecki | e846f26bd3 | 8 years ago |
Marek Marczykowski-Górecki | 54a5dd92cf | 8 years ago |
Marek Marczykowski-Górecki | c32fbe14aa | 8 years ago |
Marek Marczykowski-Górecki | acee13bf53 | 8 years ago |
Marek Marczykowski-Górecki | d446f849d8 | 8 years ago |
Marek Marczykowski-Górecki | b91eace873 | 8 years ago |
Marek Marczykowski-Górecki | 0568d2ae3b | 8 years ago |
Wojtek Porczyk | 6ca61dc709 | 8 years ago |
@ -1,7 +1,13 @@
|
|||||||
sudo: required
|
sudo: required
|
||||||
dist: trusty
|
dist: bionic
|
||||||
language: generic
|
language: generic
|
||||||
install: git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder
|
install: git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder
|
||||||
script: ~/qubes-builder/scripts/travis-build
|
script: ~/qubes-builder/scripts/travis-build
|
||||||
env:
|
env:
|
||||||
- DIST_DOM0=fc23 USE_QUBES_REPO_VERSION=3.2 USE_QUBES_REPO_TESTING=1
|
- 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
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
[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
|
|
@ -1,8 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Version=1.0
|
|
||||||
Type=Application
|
|
||||||
Terminal=false
|
|
||||||
Name=Explorer
|
|
||||||
Comment=Browse files
|
|
||||||
Categories=Utility;Core;
|
|
||||||
Exec=explorer
|
|
@ -1,8 +0,0 @@
|
|||||||
[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
|
|
@ -1,10 +0,0 @@
|
|||||||
[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;
|
|
@ -1,10 +0,0 @@
|
|||||||
[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;
|
|
@ -1,10 +0,0 @@
|
|||||||
[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;
|
|
@ -1,5 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Encoding=UTF-8
|
|
||||||
Type=Directory
|
|
||||||
Name=DisposableVM
|
|
||||||
Icon=dispvm-red
|
|
@ -1,5 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Encoding=UTF-8
|
|
||||||
Type=Directory
|
|
||||||
Name=ServiceVM: %VMNAME%
|
|
||||||
Icon=%XDGICON%
|
|
@ -1,10 +0,0 @@
|
|||||||
[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;
|
|
@ -1,5 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Encoding=UTF-8
|
|
||||||
Type=Directory
|
|
||||||
Name=Template: %VMNAME%
|
|
||||||
Icon=%XDGICON%
|
|
@ -1,5 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Encoding=UTF-8
|
|
||||||
Type=Directory
|
|
||||||
Name=Domain: %VMNAME%
|
|
||||||
Icon=%XDGICON%
|
|
@ -1,20 +0,0 @@
|
|||||||
#!/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
|
|
||||||
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
|||||||
#!/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
|
|
||||||
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
|||||||
#!/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
|
|
@ -1,394 +0,0 @@
|
|||||||
#!/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)
|
|
@ -1,392 +0,0 @@
|
|||||||
#!/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()
|
|
@ -1 +0,0 @@
|
|||||||
/usr/libexec/qubes-appmenus/qubes-receive-appmenus
|
|
@ -1,56 +0,0 @@
|
|||||||
#!/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()
|
|
@ -1,2 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
exec /usr/libexec/qubes-appmenus/qubes-receive-appmenus $@
|
|
@ -1,23 +0,0 @@
|
|||||||
#!/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
|
|
@ -1,4 +0,0 @@
|
|||||||
# 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 salsa20-x86_64 twofish-x86_64 "
|
|
@ -0,0 +1,16 @@
|
|||||||
|
#!/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"
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
#!/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
|
Before Width: | Height: | Size: 169 KiB |
Before Width: | Height: | Size: 181 KiB |
@ -1,10 +0,0 @@
|
|||||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
||||||
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.
|
|
@ -1 +0,0 @@
|
|||||||
dom0-update-avail icon from gnome-packagekit project distributed under GPLv2
|
|
@ -1 +0,0 @@
|
|||||||
Color padlock images downloaded from www.openclipart.org
|
|
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 192 KiB |
Before Width: | Height: | Size: 187 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 188 KiB |
Before Width: | Height: | Size: 188 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 177 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 185 KiB |
@ -1,17 +0,0 @@
|
|||||||
#!/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
|
|
@ -1,37 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
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,27 +1,12 @@
|
|||||||
#!/usr/bin/python
|
#!/bin/sh
|
||||||
|
|
||||||
from qubes.qubes import QubesVmCollection,QubesException
|
case "$1" in
|
||||||
import sys
|
suspend|hibernate)
|
||||||
|
qubesd-query -e --fail -c /var/run/qubesd.internal.sock \
|
||||||
qc = QubesVmCollection()
|
dom0 internal.SuspendPre dom0 | tr '\0' ' '
|
||||||
qc.lock_db_for_reading()
|
;;
|
||||||
qc.load()
|
resume|thaw)
|
||||||
qc.unlock_db()
|
qubesd-query -e --fail -c /var/run/qubesd.internal.sock \
|
||||||
|
dom0 internal.SuspendPost dom0 | tr '\0' ' '
|
||||||
if sys.argv[1] in ["suspend", "hibernate"]:
|
;;
|
||||||
for vm in qc.values():
|
esac
|
||||||
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)
|
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
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
|
|
@ -1,64 +0,0 @@
|
|||||||
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.
|
|
||||||
|
|
@ -1,787 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
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 */
|
|
||||||
void do_exec(const char *prog)
|
|
||||||
{
|
|
||||||
execl("/bin/bash", "bash", "-c", prog, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
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, ¶ms, 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, ¶ms, 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:
|
|
@ -1,854 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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, ¶ms, 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, ¶ms, 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 == '$' ||
|
|
||||||
*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:
|
|
@ -1,257 +0,0 @@
|
|||||||
#!/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():
|
|
||||||
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 = QUBES_RPC_MULTIPLEXER_PATH + " " + 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"]
|
|
||||||
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()
|
|
@ -1,38 +0,0 @@
|
|||||||
#!/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
|
|
@ -0,0 +1,7 @@
|
|||||||
|
## 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
|
@ -0,0 +1,7 @@
|
|||||||
|
## 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
|
@ -0,0 +1,7 @@
|
|||||||
|
## 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
|
@ -0,0 +1,24 @@
|
|||||||
|
#!/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')
|
@ -0,0 +1,24 @@
|
|||||||
|
#!/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')
|
@ -0,0 +1,17 @@
|
|||||||
|
#!/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='')
|
@ -0,0 +1,4 @@
|
|||||||
|
# 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"
|
@ -1,89 +0,0 @@
|
|||||||
# 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"
|
|
@ -0,0 +1,2 @@
|
|||||||
|
enable dbus.socket
|
||||||
|
enable dbus-daemon.service
|
@ -0,0 +1 @@
|
|||||||
|
qubes-core-dom0
|
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
find /etc/lvm/archive/ -type f -mtime +1 -name '*.vg' -delete
|
@ -1 +1 @@
|
|||||||
*/6 * * * * root /usr/bin/qvm-sync-clock > /dev/null 2>&1 || true
|
0 */1 * * * root /usr/bin/qvm-sync-clock > /dev/null 2>&1 || true
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
#!/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 +0,0 @@
|
|||||||
1.6.1
|
|