Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
2df917e7a5 | |||
![]() |
c600b1b39c | ||
![]() |
f4f5731bdc | ||
![]() |
0d53697917 | ||
![]() |
a4006f5046 | ||
![]() |
264ded8101 | ||
![]() |
4a88c520ac | ||
![]() |
761b5b1ef4 | ||
![]() |
257d9e5b78 | ||
![]() |
9cf273d187 | ||
![]() |
cf76a3cbbb | ||
![]() |
e5e006d933 | ||
![]() |
2dadbcfdcb | ||
![]() |
9962fab124 | ||
![]() |
1089a7a07b | ||
![]() |
c56c4a7a9d | ||
![]() |
895415aee1 | ||
![]() |
8800a08150 | ||
![]() |
82806b53e2 | ||
![]() |
3786197ab2 | ||
![]() |
75faa22dff | ||
![]() |
2283af8ce5 | ||
![]() |
00c37b0b5b | ||
![]() |
05658f0850 | ||
![]() |
0af2769aca | ||
![]() |
529f5a1cd0 | ||
![]() |
ce70209310 | ||
![]() |
888073df05 | ||
![]() |
6fa3e19f7e | ||
![]() |
15c55a4ef5 | ||
![]() |
2ec29a4d4c | ||
![]() |
330f155168 | ||
![]() |
fb1c284774 | ||
![]() |
d705fa6ed4 | ||
![]() |
61ec339c2d |
11
.travis.yml
11
.travis.yml
@ -1,8 +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=fc25 USE_QUBES_REPO_VERSION=4.0 USE_QUBES_REPO_TESTING=1
|
- DIST_DOM0=fc31 USE_QUBES_REPO_VERSION=4.1 USE_QUBES_REPO_TESTING=1
|
||||||
- DIST_DOM0=fc29 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,5 +1,28 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
escape_args() {
|
||||||
|
local eargs=""
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
printf -v eargs '%s%q ' "$eargs" "$arg"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "${eargs%?}"
|
||||||
|
}
|
||||||
|
|
||||||
|
find_regex_in_args() {
|
||||||
|
local regex="${1}"
|
||||||
|
shift 1
|
||||||
|
|
||||||
|
for arg in "${@}"; do
|
||||||
|
if echo "${arg}" | grep -q -e "${regex}"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
UPDATEVM=`qubes-prefs --force-root updatevm`
|
UPDATEVM=`qubes-prefs --force-root updatevm`
|
||||||
UPDATES_STAT_FILE=/var/lib/qubes/updates/dom0-updates-available
|
UPDATES_STAT_FILE=/var/lib/qubes/updates/dom0-updates-available
|
||||||
|
|
||||||
@ -22,11 +45,11 @@ if [ "$1" = "--help" ]; then
|
|||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PKGS=
|
PKGS=()
|
||||||
YUM_OPTS=
|
YUM_OPTS=()
|
||||||
GUI=
|
GUI=
|
||||||
CHECK_ONLY=
|
CHECK_ONLY=
|
||||||
ALL_OPTS="$*"
|
ALL_OPTS=( "${@}" )
|
||||||
YUM_ACTION=
|
YUM_ACTION=
|
||||||
QVMRUN_OPTS=
|
QVMRUN_OPTS=
|
||||||
CLEAN=
|
CLEAN=
|
||||||
@ -51,10 +74,10 @@ while [ $# -gt 0 ]; do
|
|||||||
YUM_ACTION=${1#--action=}
|
YUM_ACTION=${1#--action=}
|
||||||
;;
|
;;
|
||||||
-*)
|
-*)
|
||||||
YUM_OPTS="$YUM_OPTS $1"
|
YUM_OPTS+=( "${1}" )
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
PKGS="$PKGS $1"
|
PKGS+=( "${1}" )
|
||||||
if [ -z "$YUM_ACTION" ]; then
|
if [ -z "$YUM_ACTION" ]; then
|
||||||
YUM_ACTION=install
|
YUM_ACTION=install
|
||||||
fi
|
fi
|
||||||
@ -66,13 +89,16 @@ done
|
|||||||
# Prevent implicit update of template - this would override user changes -
|
# Prevent implicit update of template - this would override user changes -
|
||||||
# but do allow explicit template upgrade, downgrade, reinstall
|
# but do allow explicit template upgrade, downgrade, reinstall
|
||||||
if [ "$YUM_ACTION" == "reinstall" ] || [ "$YUM_ACTION" == "upgrade" ] || [ "$YUM_ACTION" == "upgrade-to" ] \
|
if [ "$YUM_ACTION" == "reinstall" ] || [ "$YUM_ACTION" == "upgrade" ] || [ "$YUM_ACTION" == "upgrade-to" ] \
|
||||||
|| [ "$YUM_ACTION" == "downgrade" ] && [[ "$PKGS" == *"qubes-template-"* ]]; then
|
|| [ "$YUM_ACTION" == "downgrade" ] && find_regex_in_args '^qubes-template-' "${PKGS[@]}"; then
|
||||||
TEMPLATE_EXCLUDE_OPTS=""
|
TEMPLATE_EXCLUDE_OPTS=()
|
||||||
echo "WARNING: Replacing a template will erase all files in template's /home and /rw !"
|
echo "WARNING: Replacing a template will erase all files in template's /home and /rw !"
|
||||||
|
|
||||||
ONEPKG=`cut -f 1 -d ' ' <<<$PKGS`
|
# At least one package name matches the regex '^qubes-template-',
|
||||||
if [[ "$ONEPKG" == "qubes-template-"* ]] && [[ "$ONEPKG" == "${PKGS#\ }" ]]; then # test "$PKGS" minus space
|
# so if there is only one package name in the array, then the
|
||||||
ONEPKG=`sed -r 's/-[0-9]+(\.[0-9-]+)+(\.noarch)*$//' <<<$ONEPKG` # Remove version suffix
|
# code can safely assume that the array includes only a template
|
||||||
|
# package name.
|
||||||
|
if [[ ${#PKGS[@]} -eq 1 ]]; then
|
||||||
|
ONEPKG="$(echo "${PKGS[0]}" | sed -r 's/-[0-9]+(\.[0-9-]+)+(\.noarch)*$//')" # Remove version suffix
|
||||||
TEMPLATE=${ONEPKG#qubes-template-} # Remove prefix
|
TEMPLATE=${ONEPKG#qubes-template-} # Remove prefix
|
||||||
|
|
||||||
if qvm-shutdown --wait $TEMPLATE ; then
|
if qvm-shutdown --wait $TEMPLATE ; then
|
||||||
@ -96,12 +122,13 @@ if [ "$YUM_ACTION" == "reinstall" ] || [ "$YUM_ACTION" == "upgrade" ] || [ "$YUM
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
elif [ "$YUM_ACTION" == "search" ] || [ "$YUM_ACTION" == "info" ]; then # No need to shutdown for search/info
|
elif [ "$YUM_ACTION" == "search" ] || [ "$YUM_ACTION" == "info" ]; then # No need to shutdown for search/info
|
||||||
TEMPLATE_EXCLUDE_OPTS=""
|
TEMPLATE_EXCLUDE_OPTS=()
|
||||||
else
|
else
|
||||||
TEMPLATE_EXCLUDE_OPTS="--exclude=`rpm -qa --qf '%{NAME},' qubes-template-\*|head -c -1`"
|
TEMPLATE_EXCLUDE_OPTS=( "--exclude=$(rpm -qa --qf '%{NAME},' qubes-template-\*|head -c -1)" )
|
||||||
fi
|
fi
|
||||||
YUM_OPTS="$TEMPLATE_EXCLUDE_OPTS $YUM_OPTS"
|
|
||||||
ALL_OPTS="$TEMPLATE_EXCLUDE_OPTS $ALL_OPTS"
|
YUM_OPTS=( "${TEMPLATE_EXCLUDE_OPTS[@]}" "${YUM_OPTS[@]}" )
|
||||||
|
ALL_OPTS=( "${TEMPLATE_EXCLUDE_OPTS[@]}" "${ALL_OPTS[@]}" )
|
||||||
|
|
||||||
ID=$(id -ur)
|
ID=$(id -ur)
|
||||||
if [ $ID != 0 -a -z "$GUI" -a -z "$CHECK_ONLY" ] ; then
|
if [ $ID != 0 -a -z "$GUI" -a -z "$CHECK_ONLY" ] ; then
|
||||||
@ -109,7 +136,7 @@ if [ $ID != 0 -a -z "$GUI" -a -z "$CHECK_ONLY" ] ; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$GUI" == "1" -a -n "$PKGS" ]; then
|
if [ "$GUI" == "1" -a ${#PKGS[@]} -ne 0 ]; then
|
||||||
echo "ERROR: GUI mode can be used only for updates" >&2
|
echo "ERROR: GUI mode can be used only for updates" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@ -171,10 +198,10 @@ echo "Using $UPDATEVM as UpdateVM to download updates for Dom0; this may take so
|
|||||||
qvm-run --nogui -q -u root $UPDATEVM 'mkdir -m 775 -p /var/lib/qubes/dom0-updates/' || exit 1
|
qvm-run --nogui -q -u root $UPDATEVM 'mkdir -m 775 -p /var/lib/qubes/dom0-updates/' || exit 1
|
||||||
qvm-run --nogui -q -u root $UPDATEVM 'chown user:user /var/lib/qubes/dom0-updates/' || exit 1
|
qvm-run --nogui -q -u root $UPDATEVM 'chown user:user /var/lib/qubes/dom0-updates/' || exit 1
|
||||||
qvm-run --nogui -q $UPDATEVM 'rm -rf /var/lib/qubes/dom0-updates/etc' || exit 1
|
qvm-run --nogui -q $UPDATEVM 'rm -rf /var/lib/qubes/dom0-updates/etc' || exit 1
|
||||||
tar c /var/lib/rpm /etc/yum.repos.d /etc/yum.conf 2>/dev/null | \
|
tar c /var/lib/rpm /etc/yum.repos.d /etc/yum.conf /etc/dnf/dnf.conf 2>/dev/null | \
|
||||||
qvm-run --nogui -q --pass-io "$UPDATEVM" 'LC_MESSAGES=C tar x -C /var/lib/qubes/dom0-updates 2>&1 | grep -v -E "s in the future"'
|
qvm-run --nogui -q --pass-io "$UPDATEVM" 'LC_MESSAGES=C tar x -C /var/lib/qubes/dom0-updates 2>&1 | grep -v -E "s in the future"'
|
||||||
|
|
||||||
qvm-run $QVMRUN_OPTS --pass-io $UPDATEVM "script --quiet --return --command '/usr/lib/qubes/qubes-download-dom0-updates.sh --doit --nogui $ALL_OPTS' /dev/null"
|
qvm-run $QVMRUN_OPTS --pass-io $UPDATEVM "script --quiet --return --command '/usr/lib/qubes/qubes-download-dom0-updates.sh --doit --nogui $(escape_args "${ALL_OPTS[@]}")' /dev/null" < /dev/null
|
||||||
RETCODE=$?
|
RETCODE=$?
|
||||||
if [ "$CHECK_ONLY" == "1" ]; then
|
if [ "$CHECK_ONLY" == "1" ]; then
|
||||||
exit $RETCODE
|
exit $RETCODE
|
||||||
@ -195,18 +222,18 @@ if [ -z "$YUM_ACTION" ]; then
|
|||||||
YUM_ACTION=upgrade
|
YUM_ACTION=upgrade
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$PKGS" ]; then
|
if [ ${#PKGS[@]} -gt 0 ]; then
|
||||||
if [ -n "$TEMPLATE" ]; then
|
if [ -n "$TEMPLATE" ]; then
|
||||||
TEMPLATE_NETVM=$(qvm-prefs --force-root $TEMPLATE netvm)
|
TEMPLATE_NETVM=$(qvm-prefs --force-root $TEMPLATE netvm)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
dnf $YUM_OPTS $YUM_ACTION $PKGS ; RETCODE=$?
|
dnf "${YUM_OPTS[@]}" $YUM_ACTION "${PKGS[@]}" ; RETCODE=$?
|
||||||
|
|
||||||
if [ -n "$TEMPLATE_BACKUP" -a "$RETCODE" -eq 0 ]; then
|
if [ -n "$TEMPLATE_BACKUP" -a "$RETCODE" -eq 0 ]; then
|
||||||
# Remove backup, if we made one. Better to do this only on success and
|
# Remove backup, if we made one. Better to do this only on success and
|
||||||
# potentially leave extra backups around than do it on an exit trap and
|
# potentially leave extra backups around than do it on an exit trap and
|
||||||
# clean up more reliably but potentially brick a system.
|
# clean up more reliably but potentially brick a system.
|
||||||
qvm-remove -- "$TEMPLATE_BACKUP"
|
qvm-remove -f -- "$TEMPLATE_BACKUP"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$TEMPLATE" -a -n "$TEMPLATE_NETVM" -a x"$TEMPLATE_NETVM" != xNone ]; then
|
if [ -n "$TEMPLATE" -a -n "$TEMPLATE_NETVM" -a x"$TEMPLATE_NETVM" != xNone ]; then
|
||||||
@ -224,7 +251,7 @@ elif [ -f /var/lib/qubes/updates/repodata/repomd.xml ]; then
|
|||||||
else
|
else
|
||||||
dnf check-update
|
dnf check-update
|
||||||
if [ $? -eq 100 ]; then # Run dnf with options
|
if [ $? -eq 100 ]; then # Run dnf with options
|
||||||
dnf $YUM_OPTS $YUM_ACTION
|
dnf "${YUM_OPTS[@]}" $YUM_ACTION
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
dnf -q check-update && qvm-features dom0 updates-available ''
|
dnf -q check-update && qvm-features dom0 updates-available ''
|
||||||
|
@ -31,34 +31,32 @@ updates_dir = "/var/lib/qubes/updates"
|
|||||||
updates_rpm_dir = updates_dir + "/rpm"
|
updates_rpm_dir = updates_dir + "/rpm"
|
||||||
updates_repodata_dir = updates_dir + "/repodata"
|
updates_repodata_dir = updates_dir + "/repodata"
|
||||||
updates_error_file = updates_dir + "/errors"
|
updates_error_file = updates_dir + "/errors"
|
||||||
updates_error_file_handle = None
|
|
||||||
|
|
||||||
comps_file = None
|
comps_file = None
|
||||||
if os.path.exists('/usr/share/qubes/Qubes-comps.xml'):
|
if os.path.exists('/usr/share/qubes/Qubes-comps.xml'):
|
||||||
comps_file = '/usr/share/qubes/Qubes-comps.xml'
|
comps_file = '/usr/share/qubes/Qubes-comps.xml'
|
||||||
|
|
||||||
package_regex = re.compile(r"^[A-Za-z0-9._+-]{1,128}.rpm$")
|
package_regex = re.compile(r"^[A-Za-z0-9._+-]{1,128}\.rpm$")
|
||||||
# example valid outputs:
|
# example valid outputs:
|
||||||
# .....rpm: rsa sha1 (md5) pgp md5 OK
|
# .....rpm: rsa sha1 (md5) pgp md5 OK
|
||||||
# .....rpm: (sha1) dsa sha1 md5 gpg OK
|
# .....rpm: (sha1) dsa sha1 md5 gpg OK
|
||||||
|
# .....rpm: digests signatures OK
|
||||||
# example INVALID outputs:
|
# example INVALID outputs:
|
||||||
# .....rpm: sha1 md5 OK
|
# .....rpm: sha1 md5 OK
|
||||||
# .....rpm: RSA sha1 ((MD5) PGP) md5 NOT OK (MISSING KEYS: (MD5) PGP#246110c1)
|
# .....rpm: RSA sha1 ((MD5) PGP) md5 NOT OK (MISSING KEYS: (MD5) PGP#246110c1)
|
||||||
gpg_ok_regex = re.compile(r": [a-z0-9() ]* (pgp|gpg) [a-z0-9 ]*OK$")
|
# .....rpm: digests OK
|
||||||
|
gpg_ok_regex = re.compile(r": [a-z0-9() ]* (pgp|gpg|signatures) [a-z0-9 ]*OK$")
|
||||||
|
|
||||||
|
|
||||||
def dom0updates_fatal(pkg, msg):
|
def dom0updates_fatal(msg):
|
||||||
global updates_error_file_handle
|
|
||||||
print(msg, file=sys.stderr)
|
print(msg, file=sys.stderr)
|
||||||
if updates_error_file_handle is None:
|
with open(updates_error_file, "a") as updates_error_file_handle:
|
||||||
updates_error_file_handle = open(updates_error_file, "a")
|
updates_error_file_handle.write(msg + "\n")
|
||||||
updates_error_file_handle.write(msg + "\n")
|
shutil.rmtree(updates_rpm_dir)
|
||||||
os.remove(pkg)
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
def handle_dom0updates(updatevm):
|
def handle_dom0updates(updatevm):
|
||||||
global updates_error_file_handle
|
|
||||||
|
|
||||||
source = os.getenv("QREXEC_REMOTE_DOMAIN")
|
source = os.getenv("QREXEC_REMOTE_DOMAIN")
|
||||||
if source != updatevm.name:
|
if source != updatevm.name:
|
||||||
print('Domain ' + str(source) + ' not allowed to send dom0 updates',
|
print('Domain ' + str(source) + ' not allowed to send dom0 updates',
|
||||||
@ -77,14 +75,14 @@ def handle_dom0updates(updatevm):
|
|||||||
os.mkdir(updates_rpm_dir)
|
os.mkdir(updates_rpm_dir)
|
||||||
os.chown(updates_rpm_dir, -1, qubes_gid)
|
os.chown(updates_rpm_dir, -1, qubes_gid)
|
||||||
os.chmod(updates_rpm_dir, 0o0775)
|
os.chmod(updates_rpm_dir, 0o0775)
|
||||||
subprocess.check_call(["/usr/libexec/qubes/qfile-dom0-unpacker",
|
try:
|
||||||
str(os.getuid()), updates_rpm_dir])
|
subprocess.check_call(["/usr/libexec/qubes/qfile-dom0-unpacker",
|
||||||
# Verify received files
|
str(os.getuid()), updates_rpm_dir])
|
||||||
for untrusted_f in os.listdir(updates_rpm_dir):
|
# Verify received files
|
||||||
if not package_regex.match(untrusted_f):
|
for untrusted_f in os.listdir(updates_rpm_dir):
|
||||||
dom0updates_fatal(updates_rpm_dir + '/' + untrusted_f,
|
if not package_regex.match(untrusted_f):
|
||||||
'Domain ' + source + ' sent unexpected file: ' + untrusted_f)
|
raise Exception(
|
||||||
else:
|
'Domain ' + source + ' sent unexpected file')
|
||||||
f = untrusted_f
|
f = untrusted_f
|
||||||
assert '/' not in f
|
assert '/' not in f
|
||||||
assert '\0' not in f
|
assert '\0' not in f
|
||||||
@ -92,19 +90,19 @@ def handle_dom0updates(updatevm):
|
|||||||
|
|
||||||
full_path = updates_rpm_dir + "/" + f
|
full_path = updates_rpm_dir + "/" + f
|
||||||
if os.path.islink(full_path) or not os.path.isfile(full_path):
|
if os.path.islink(full_path) or not os.path.isfile(full_path):
|
||||||
dom0updates_fatal(
|
raise Exception(
|
||||||
full_path, 'Domain ' + source + ' sent not regular file')
|
'Domain ' + source + ' sent not regular file')
|
||||||
p = subprocess.Popen(["/bin/rpm", "-K", full_path],
|
p = subprocess.Popen(["/bin/rpm", "-K", full_path],
|
||||||
stdout=subprocess.PIPE)
|
stdout=subprocess.PIPE)
|
||||||
output = p.communicate()[0].decode('ascii')
|
output = p.communicate()[0].decode('ascii')
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
dom0updates_fatal(full_path,
|
raise Exception(
|
||||||
'Error while verifing %s signature: %s' % (f, output))
|
'Error while verifing %s signature: %s' % (f, output))
|
||||||
if not gpg_ok_regex.search(output.strip()):
|
if not gpg_ok_regex.search(output.strip()):
|
||||||
dom0updates_fatal(full_path,
|
raise Exception(
|
||||||
'Domain ' + source + ' sent not signed rpm: ' + f)
|
'Domain ' + source + ' sent not signed rpm: ' + f)
|
||||||
if updates_error_file_handle is not None:
|
except Exception as e:
|
||||||
updates_error_file_handle.close()
|
dom0updates_fatal(str(e))
|
||||||
# After updates received - create repo metadata
|
# After updates received - create repo metadata
|
||||||
createrepo_cmd = ["/usr/bin/createrepo_c"]
|
createrepo_cmd = ["/usr/bin/createrepo_c"]
|
||||||
if comps_file:
|
if comps_file:
|
||||||
@ -128,4 +126,6 @@ def main():
|
|||||||
exit(1)
|
exit(1)
|
||||||
handle_dom0updates(updatevm)
|
handle_dom0updates(updatevm)
|
||||||
|
|
||||||
main()
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
16
dracut/modules.d/90macbook12-spi-driver/module-setup.sh
Executable file
16
dracut/modules.d/90macbook12-spi-driver/module-setup.sh
Executable file
@ -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"
|
||||||
|
}
|
@ -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,818 +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;
|
|
||||||
|
|
||||||
extern char **environ;
|
|
||||||
|
|
||||||
static int handle_agent_handshake(libvchan_t *vchan, int remote_send_first)
|
|
||||||
{
|
|
||||||
struct msg_header hdr;
|
|
||||||
struct peer_info info;
|
|
||||||
int who = 0; // even - send to remote, odd - receive from remote
|
|
||||||
|
|
||||||
while (who < 2) {
|
|
||||||
if ((who+remote_send_first) & 1) {
|
|
||||||
if (!read_vchan_all(vchan, &hdr, sizeof(hdr))) {
|
|
||||||
perror("daemon handshake");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) {
|
|
||||||
fprintf(stderr, "Invalid daemon MSG_HELLO\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (!read_vchan_all(vchan, &info, sizeof(info))) {
|
|
||||||
perror("daemon handshake");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.version != QREXEC_PROTOCOL_VERSION) {
|
|
||||||
fprintf(stderr, "Incompatible daemon protocol version "
|
|
||||||
"(daemon %d, client %d)\n",
|
|
||||||
info.version, QREXEC_PROTOCOL_VERSION);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
hdr.type = MSG_HELLO;
|
|
||||||
hdr.len = sizeof(info);
|
|
||||||
info.version = QREXEC_PROTOCOL_VERSION;
|
|
||||||
|
|
||||||
if (!write_vchan_all(vchan, &hdr, sizeof(hdr))) {
|
|
||||||
fprintf(stderr, "Failed to send MSG_HELLO hdr to daemon\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (!write_vchan_all(vchan, &info, sizeof(info))) {
|
|
||||||
fprintf(stderr, "Failed to send MSG_HELLO to daemon\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
who++;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int handle_daemon_handshake(int fd)
|
|
||||||
{
|
|
||||||
struct msg_header hdr;
|
|
||||||
struct peer_info info;
|
|
||||||
|
|
||||||
/* daemon send MSG_HELLO first */
|
|
||||||
if (!read_all(fd, &hdr, sizeof(hdr))) {
|
|
||||||
perror("daemon handshake");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) {
|
|
||||||
fprintf(stderr, "Invalid daemon MSG_HELLO\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (!read_all(fd, &info, sizeof(info))) {
|
|
||||||
perror("daemon handshake");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.version != QREXEC_PROTOCOL_VERSION) {
|
|
||||||
fprintf(stderr, "Incompatible daemon protocol version "
|
|
||||||
"(daemon %d, client %d)\n",
|
|
||||||
info.version, QREXEC_PROTOCOL_VERSION);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
hdr.type = MSG_HELLO;
|
|
||||||
hdr.len = sizeof(info);
|
|
||||||
info.version = QREXEC_PROTOCOL_VERSION;
|
|
||||||
|
|
||||||
if (!write_all(fd, &hdr, sizeof(hdr))) {
|
|
||||||
fprintf(stderr, "Failed to send MSG_HELLO hdr to daemon\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (!write_all(fd, &info, sizeof(info))) {
|
|
||||||
fprintf(stderr, "Failed to send MSG_HELLO to daemon\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int connect_unix_socket(const char *domname)
|
|
||||||
{
|
|
||||||
int s, len;
|
|
||||||
struct sockaddr_un remote;
|
|
||||||
|
|
||||||
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
|
|
||||||
perror("socket");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
remote.sun_family = AF_UNIX;
|
|
||||||
snprintf(remote.sun_path, sizeof remote.sun_path,
|
|
||||||
QREXEC_DAEMON_SOCKET_DIR "/qrexec.%s", domname);
|
|
||||||
len = strlen(remote.sun_path) + sizeof(remote.sun_family);
|
|
||||||
if (connect(s, (struct sockaddr *) &remote, len) == -1) {
|
|
||||||
perror("connect");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
if (handle_daemon_handshake(s) < 0)
|
|
||||||
exit(1);
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sigchld_handler(int x __attribute__((__unused__)))
|
|
||||||
{
|
|
||||||
child_exited = 1;
|
|
||||||
signal(SIGCHLD, sigchld_handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* called from do_fork_exec */
|
|
||||||
_Noreturn void do_exec(char *prog)
|
|
||||||
{
|
|
||||||
/* avoid calling qubes-rpc-multiplexer through shell */
|
|
||||||
exec_qubes_rpc_if_requested(prog, environ);
|
|
||||||
|
|
||||||
/* if above haven't executed qubes-rpc-multiplexer, pass it to shell */
|
|
||||||
execl("/bin/bash", "bash", "-c", prog, NULL);
|
|
||||||
perror("exec bash");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void do_exit(int code)
|
|
||||||
{
|
|
||||||
int status;
|
|
||||||
/* restore flags, as we may have not the only copy of this file descriptor
|
|
||||||
*/
|
|
||||||
if (local_stdin_fd != -1)
|
|
||||||
set_block(local_stdin_fd);
|
|
||||||
close(local_stdin_fd);
|
|
||||||
close(local_stdout_fd);
|
|
||||||
// sever communication lines; wait for child, if any
|
|
||||||
// so that qrexec-daemon can count (recursively) spawned processes correctly
|
|
||||||
waitpid(-1, &status, 0);
|
|
||||||
exit(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void prepare_local_fds(char *cmdline)
|
|
||||||
{
|
|
||||||
if (!cmdline) {
|
|
||||||
local_stdin_fd = 1;
|
|
||||||
local_stdout_fd = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
signal(SIGCHLD, sigchld_handler);
|
|
||||||
do_fork_exec(cmdline, &local_pid, &local_stdin_fd, &local_stdout_fd,
|
|
||||||
NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ask the daemon to allocate vchan port */
|
|
||||||
static void negotiate_connection_params(int s, int other_domid, unsigned type,
|
|
||||||
void *cmdline_param, int cmdline_size,
|
|
||||||
int *data_domain, int *data_port)
|
|
||||||
{
|
|
||||||
struct msg_header hdr;
|
|
||||||
struct exec_params params;
|
|
||||||
hdr.type = type;
|
|
||||||
hdr.len = sizeof(params) + cmdline_size;
|
|
||||||
params.connect_domain = other_domid;
|
|
||||||
params.connect_port = 0;
|
|
||||||
if (!write_all(s, &hdr, sizeof(hdr))
|
|
||||||
|| !write_all(s, ¶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) - 1);
|
|
||||||
srv_params.ident[sizeof(srv_params.ident) - 1] = '\0';
|
|
||||||
|
|
||||||
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] [-W] [-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"
|
|
||||||
"-W waits for connection end even in case of VM-VM (-c)\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;
|
|
||||||
}
|
|
||||||
fprintf(stderr, "vchan connection error\n");
|
|
||||||
libvchan_close(conn);
|
|
||||||
do_exit(1);
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
fprintf(stderr, "vchan connection timeout\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 wait_connection_end = 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:W")) != -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;
|
|
||||||
case 'W':
|
|
||||||
wait_connection_end = 1;
|
|
||||||
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) - 1);
|
|
||||||
svc_params.ident[sizeof(svc_params.ident) - 1] = '\0';
|
|
||||||
} 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);
|
|
||||||
if (wait_connection_end && connect_existing)
|
|
||||||
/* save socket fd, 's' will be reused for the other qrexec-daemon
|
|
||||||
* connection */
|
|
||||||
wait_connection_end = s;
|
|
||||||
else
|
|
||||||
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);
|
|
||||||
if (wait_connection_end) {
|
|
||||||
/* wait for EOF */
|
|
||||||
fd_set read_fd;
|
|
||||||
FD_ZERO(&read_fd);
|
|
||||||
FD_SET(wait_connection_end, &read_fd);
|
|
||||||
select(wait_connection_end+1, &read_fd, NULL, NULL, 0);
|
|
||||||
}
|
|
||||||
} 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,871 +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];
|
|
||||||
|
|
||||||
/* notify client (close its connection) when connection initiated by it was
|
|
||||||
* terminated - used by qrexec-policy to cleanup (disposable) VM; indexed with
|
|
||||||
* vchan port number relative to VCHAN_BASE_DATA_PORT; stores fd of given
|
|
||||||
* client or -1 if none requested */
|
|
||||||
int vchan_port_notify_client[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' --sorry "
|
|
||||||
#define ZENITY_CMD "zenity --title 'Qrexec daemon' --warning --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 run:\n"
|
|
||||||
"sudo xl console -t pv %s'",
|
|
||||||
ret==0 ? KDIALOG_CMD : ZENITY_CMD,
|
|
||||||
domain_name, remote_version, QREXEC_PROTOCOL_VERSION, domain_name);
|
|
||||||
#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(3);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
vchan_port_notify_client[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 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)
|
|
||||||
{
|
|
||||||
int port;
|
|
||||||
clients[fd].state = CLIENT_INVALID;
|
|
||||||
close(fd);
|
|
||||||
/* if client requested vchan connection end notify, cancel it */
|
|
||||||
for (port = 0; port < MAX_CLIENTS; port++) {
|
|
||||||
if (vchan_port_notify_client[port] == fd)
|
|
||||||
vchan_port_notify_client[port] = VCHAN_PORT_UNUSED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
/* notify client if requested - it will clear notification request */
|
|
||||||
if (vchan_port_notify_client[port-VCHAN_BASE_DATA_PORT] != VCHAN_PORT_UNUSED)
|
|
||||||
terminate_client(vchan_port_notify_client[port-VCHAN_BASE_DATA_PORT]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
/* notify the client when this connection got terminated */
|
|
||||||
vchan_port_notify_client[params.connect_port-VCHAN_BASE_DATA_PORT] = fd;
|
|
||||||
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 == '.')
|
|
||||||
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/bin/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,45 +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 -o $# = 4 ] ; then
|
|
||||||
echo "$0: bad argument count, usage: $0 SERVICE-NAME REMOTE-DOMAIN-NAME [REQUESTED_TARGET_TYPE REQUESTED_TARGET]" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
export QREXEC_REQUESTED_TARGET_TYPE="$3"
|
|
||||||
if [ "$QREXEC_REQUESTED_TARGET_TYPE" = "name" ]; then
|
|
||||||
export QREXEC_REQUESTED_TARGET="$4"
|
|
||||||
elif [ "$QREXEC_REQUESTED_TARGET_TYPE" = "keyword" ]; then
|
|
||||||
export QREXEC_REQUESTED_TARGET_KEYWORD="$4"
|
|
||||||
fi
|
|
||||||
# else: requested target type unknown or not given, ignore
|
|
||||||
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
|
|
7
qubes-rpc-policy/qubes.repos.Disable
Normal file
7
qubes-rpc-policy/qubes.repos.Disable
Normal file
@ -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
|
7
qubes-rpc-policy/qubes.repos.Enable
Normal file
7
qubes-rpc-policy/qubes.repos.Enable
Normal file
@ -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
|
7
qubes-rpc-policy/qubes.repos.List
Normal file
7
qubes-rpc-policy/qubes.repos.List
Normal file
@ -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
|
24
qubes-rpc/qubes.repos.Disable
Executable file
24
qubes-rpc/qubes.repos.Disable
Executable file
@ -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')
|
24
qubes-rpc/qubes.repos.Enable
Executable file
24
qubes-rpc/qubes.repos.Enable
Executable file
@ -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')
|
17
qubes-rpc/qubes.repos.List
Executable file
17
qubes-rpc/qubes.repos.List
Executable file
@ -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='')
|
@ -39,10 +39,10 @@ URL: http://www.qubes-os.org
|
|||||||
BuildRequires: ImageMagick
|
BuildRequires: ImageMagick
|
||||||
BuildRequires: pandoc
|
BuildRequires: pandoc
|
||||||
BuildRequires: qubes-utils-devel >= 3.1.3
|
BuildRequires: qubes-utils-devel >= 3.1.3
|
||||||
BuildRequires: qubes-libvchan-devel
|
|
||||||
BuildRequires: gcc
|
BuildRequires: gcc
|
||||||
Requires: qubes-core-dom0
|
Requires: qubes-core-dom0
|
||||||
Requires: python3-qubesadmin
|
Requires: python3-qubesadmin
|
||||||
|
Requires: qubes-core-qrexec-dom0
|
||||||
Requires: qubes-core-admin-client
|
Requires: qubes-core-admin-client
|
||||||
Requires: qubes-utils >= 3.1.3
|
Requires: qubes-utils >= 3.1.3
|
||||||
Requires: qubes-utils-libs >= 4.0.16
|
Requires: qubes-utils-libs >= 4.0.16
|
||||||
@ -75,7 +75,6 @@ Kernel install hook for Xen-based system.
|
|||||||
%build
|
%build
|
||||||
export BACKEND_VMM=@BACKEND_VMM@
|
export BACKEND_VMM=@BACKEND_VMM@
|
||||||
(cd dom0-updates; make)
|
(cd dom0-updates; make)
|
||||||
(cd qrexec; make)
|
|
||||||
(cd file-copy-vm; make)
|
(cd file-copy-vm; make)
|
||||||
(cd doc; make manpages)
|
(cd doc; make manpages)
|
||||||
|
|
||||||
@ -97,14 +96,11 @@ install -m 0664 -D dom0-updates/qubes.ReceiveUpdates.policy $RPM_BUILD_ROOT/etc/
|
|||||||
|
|
||||||
install -d $RPM_BUILD_ROOT/var/lib/qubes/updates
|
install -d $RPM_BUILD_ROOT/var/lib/qubes/updates
|
||||||
|
|
||||||
# Qrexec
|
# Qrexec services
|
||||||
mkdir -p $RPM_BUILD_ROOT/usr/bin $RPM_BUILD_ROOT/usr/sbin
|
mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes/qubes-rpc $RPM_BUILD_ROOT/etc/qubes-rpc/policy
|
||||||
install qrexec/qrexec-daemon $RPM_BUILD_ROOT/usr/sbin/
|
cp qubes-rpc/* $RPM_BUILD_ROOT/usr/lib/qubes/qubes-rpc/
|
||||||
install qrexec/qrexec-client $RPM_BUILD_ROOT/usr/bin/
|
for i in qubes-rpc/*; do ln -s ../../usr/lib/qubes/$i $RPM_BUILD_ROOT/etc/qubes-rpc/$(basename $i); done
|
||||||
# XXX: Backward compatibility
|
cp qubes-rpc-policy/* $RPM_BUILD_ROOT/etc/qubes-rpc/policy/
|
||||||
ln -s ../../bin/qrexec-client $RPM_BUILD_ROOT/usr/lib/qubes/qrexec-client
|
|
||||||
ln -s ../../sbin/qrexec-daemon $RPM_BUILD_ROOT/usr/lib/qubes/qrexec-daemon
|
|
||||||
cp qrexec/qubes-rpc-multiplexer $RPM_BUILD_ROOT/usr/lib/qubes
|
|
||||||
|
|
||||||
### pm-utils
|
### pm-utils
|
||||||
mkdir -p $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d
|
mkdir -p $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d
|
||||||
@ -127,6 +123,7 @@ install -m 0440 -D system-config/qubes.sudoers $RPM_BUILD_ROOT/etc/sudoers.d/qub
|
|||||||
install -D system-config/polkit-1-qubes-allow-all.rules $RPM_BUILD_ROOT/etc/polkit-1/rules.d/00-qubes-allow-all.rules
|
install -D system-config/polkit-1-qubes-allow-all.rules $RPM_BUILD_ROOT/etc/polkit-1/rules.d/00-qubes-allow-all.rules
|
||||||
install -D system-config/qubes-dom0.modules $RPM_BUILD_ROOT/etc/sysconfig/modules/qubes-dom0.modules
|
install -D system-config/qubes-dom0.modules $RPM_BUILD_ROOT/etc/sysconfig/modules/qubes-dom0.modules
|
||||||
install -D system-config/qubes-sync-clock.cron $RPM_BUILD_ROOT/etc/cron.d/qubes-sync-clock.cron
|
install -D system-config/qubes-sync-clock.cron $RPM_BUILD_ROOT/etc/cron.d/qubes-sync-clock.cron
|
||||||
|
install -D system-config/lvm-cleanup.cron-daily $RPM_BUILD_ROOT/etc/cron.daily/lvm-cleanup
|
||||||
install -d $RPM_BUILD_ROOT/etc/udev/rules.d
|
install -d $RPM_BUILD_ROOT/etc/udev/rules.d
|
||||||
install -m 644 system-config/00-qubes-ignore-devices.rules $RPM_BUILD_ROOT/etc/udev/rules.d/
|
install -m 644 system-config/00-qubes-ignore-devices.rules $RPM_BUILD_ROOT/etc/udev/rules.d/
|
||||||
install -m 644 system-config/12-qubes-ignore-lvm-devices.rules $RPM_BUILD_ROOT/etc/udev/rules.d/
|
install -m 644 system-config/12-qubes-ignore-lvm-devices.rules $RPM_BUILD_ROOT/etc/udev/rules.d/
|
||||||
@ -136,6 +133,8 @@ install -m 755 -D system-config/kernel-xen-efi.install $RPM_BUILD_ROOT/usr/lib/k
|
|||||||
install -m 755 -D system-config/kernel-remove-bls.install $RPM_BUILD_ROOT/usr/lib/kernel/install.d/99-remove-bls.install
|
install -m 755 -D system-config/kernel-remove-bls.install $RPM_BUILD_ROOT/usr/lib/kernel/install.d/99-remove-bls.install
|
||||||
install -m 644 -D system-config/75-qubes-dom0.preset \
|
install -m 644 -D system-config/75-qubes-dom0.preset \
|
||||||
$RPM_BUILD_ROOT/usr/lib/systemd/system-preset/75-qubes-dom0.preset
|
$RPM_BUILD_ROOT/usr/lib/systemd/system-preset/75-qubes-dom0.preset
|
||||||
|
install -m 644 -D system-config/75-qubes-dom0-user.preset \
|
||||||
|
$RPM_BUILD_ROOT/usr/lib/systemd/user-preset/75-qubes-dom0-user.preset
|
||||||
install -m 644 -D system-config/99-qubes-default-disable.preset \
|
install -m 644 -D system-config/99-qubes-default-disable.preset \
|
||||||
$RPM_BUILD_ROOT/usr/lib/systemd/system-preset/99-qubes-default-disable.preset
|
$RPM_BUILD_ROOT/usr/lib/systemd/system-preset/99-qubes-default-disable.preset
|
||||||
install -d $RPM_BUILD_ROOT/etc/dnf/protected.d
|
install -d $RPM_BUILD_ROOT/etc/dnf/protected.d
|
||||||
@ -204,19 +203,20 @@ chmod -x /etc/grub.d/10_linux
|
|||||||
/etc/qubes-rpc/qubes.ReceiveUpdates
|
/etc/qubes-rpc/qubes.ReceiveUpdates
|
||||||
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.ReceiveUpdates
|
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.ReceiveUpdates
|
||||||
%attr(0770,root,qubes) %dir /var/lib/qubes/updates
|
%attr(0770,root,qubes) %dir /var/lib/qubes/updates
|
||||||
|
# Qrexec services
|
||||||
|
/etc/qubes-rpc/qubes.repos.*
|
||||||
|
/usr/lib/qubes/qubes-rpc/qubes.repos.*
|
||||||
|
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.repos.List
|
||||||
|
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.repos.Enable
|
||||||
|
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.repos.Disable
|
||||||
# Dracut module
|
# Dracut module
|
||||||
/etc/dracut.conf.d/*
|
/etc/dracut.conf.d/*
|
||||||
|
%dir %{_dracutmoddir}/90macbook12-spi-driver
|
||||||
|
%{_dracutmoddir}/90macbook12-spi-driver/*
|
||||||
%dir %{_dracutmoddir}/90qubes-pciback
|
%dir %{_dracutmoddir}/90qubes-pciback
|
||||||
%{_dracutmoddir}/90qubes-pciback/*
|
%{_dracutmoddir}/90qubes-pciback/*
|
||||||
%dir %{_dracutmoddir}/90extra-modules
|
%dir %{_dracutmoddir}/90extra-modules
|
||||||
%{_dracutmoddir}/90extra-modules/*
|
%{_dracutmoddir}/90extra-modules/*
|
||||||
# Qrexec
|
|
||||||
/usr/sbin/qrexec-daemon
|
|
||||||
/usr/bin/qrexec-client
|
|
||||||
/usr/lib/qubes/qubes-rpc-multiplexer
|
|
||||||
# compat symlinks
|
|
||||||
/usr/lib/qubes/qrexec-client
|
|
||||||
/usr/lib/qubes/qrexec-daemon
|
|
||||||
# file copy
|
# file copy
|
||||||
/usr/bin/qvm-copy-to-vm
|
/usr/bin/qvm-copy-to-vm
|
||||||
/usr/bin/qvm-move-to-vm
|
/usr/bin/qvm-move-to-vm
|
||||||
@ -235,10 +235,12 @@ chmod -x /etc/grub.d/10_linux
|
|||||||
%config /etc/udev/rules.d/00-qubes-ignore-devices.rules
|
%config /etc/udev/rules.d/00-qubes-ignore-devices.rules
|
||||||
%config /etc/udev/rules.d/12-qubes-ignore-lvm-devices.rules
|
%config /etc/udev/rules.d/12-qubes-ignore-lvm-devices.rules
|
||||||
%attr(0644,root,root) /etc/cron.d/qubes-sync-clock.cron
|
%attr(0644,root,root) /etc/cron.d/qubes-sync-clock.cron
|
||||||
|
/etc/cron.daily/lvm-cleanup
|
||||||
%config(noreplace) /etc/profile.d/zz-disable-lesspipe.sh
|
%config(noreplace) /etc/profile.d/zz-disable-lesspipe.sh
|
||||||
%config(noreplace) /etc/dnf/protected.d/qubes-core-dom0.conf
|
%config(noreplace) /etc/dnf/protected.d/qubes-core-dom0.conf
|
||||||
/usr/lib/systemd/system-preset/75-qubes-dom0.preset
|
/usr/lib/systemd/system-preset/75-qubes-dom0.preset
|
||||||
/usr/lib/systemd/system-preset/99-qubes-default-disable.preset
|
/usr/lib/systemd/system-preset/99-qubes-default-disable.preset
|
||||||
|
/usr/lib/systemd/user-preset/75-qubes-dom0-user.preset
|
||||||
/var/lib/qubes/.qubes-exclude-block-devices
|
/var/lib/qubes/.qubes-exclude-block-devices
|
||||||
# Man
|
# Man
|
||||||
%{_mandir}/man1/qvm-*.1*
|
%{_mandir}/man1/qvm-*.1*
|
||||||
|
2
system-config/75-qubes-dom0-user.preset
Normal file
2
system-config/75-qubes-dom0-user.preset
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
enable dbus.socket
|
||||||
|
enable dbus-daemon.service
|
@ -18,6 +18,8 @@ enable dm-event.*
|
|||||||
enable dmraid-activation.service
|
enable dmraid-activation.service
|
||||||
enable fstrim.timer
|
enable fstrim.timer
|
||||||
|
|
||||||
|
enable dbus.socket
|
||||||
|
enable dbus-daemon.service
|
||||||
|
|
||||||
enable abrtd.service
|
enable abrtd.service
|
||||||
enable abrt-ccpp.service
|
enable abrt-ccpp.service
|
||||||
@ -46,6 +48,7 @@ enable qubes-db-dom0.service
|
|||||||
enable qubes-qmemman.service
|
enable qubes-qmemman.service
|
||||||
enable qubes-suspend.service
|
enable qubes-suspend.service
|
||||||
enable qubes-setupdvm.service
|
enable qubes-setupdvm.service
|
||||||
|
enable qubes-qrexec-policy-daemon.service
|
||||||
enable qubesd.service
|
enable qubesd.service
|
||||||
enable anti-evil-maid-unseal.service
|
enable anti-evil-maid-unseal.service
|
||||||
enable anti-evil-maid-check-mount-devs.service
|
enable anti-evil-maid-check-mount-devs.service
|
||||||
|
@ -19,5 +19,10 @@ case "$COMMAND" in
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
if [ -x /usr/sbin/grub2-mkconfig ]; then
|
if [ -x /usr/sbin/grub2-mkconfig ]; then
|
||||||
grub2-mkconfig -o /boot/grub2/grub.cfg
|
if [ -e /boot/grub2/grub.cfg ]; then
|
||||||
|
grub2-mkconfig -o /boot/grub2/grub.cfg
|
||||||
|
fi
|
||||||
|
if [ -e /boot/efi/EFI/qubes/grub.cfg ]; then
|
||||||
|
grub2-mkconfig -o /boot/efi/EFI/qubes/grub.cfg
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
@ -24,7 +24,7 @@ else
|
|||||||
EFI_DIR="$ESP_MOUNTPOINT$EFI_DIR"
|
EFI_DIR="$ESP_MOUNTPOINT$EFI_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -d "$EFI_DIR" ]; then
|
if [ ! -r "$EFI_DIR/xen.cfg" ]; then
|
||||||
# non-EFI system
|
# non-EFI system
|
||||||
exit 0;
|
exit 0;
|
||||||
fi
|
fi
|
||||||
|
3
system-config/lvm-cleanup.cron-daily
Executable file
3
system-config/lvm-cleanup.cron-daily
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
find /etc/lvm/archive/ -type f -mtime +1 -name '*.vg' -delete
|
Loading…
Reference in New Issue
Block a user