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
|
||||
dist: trusty
|
||||
dist: bionic
|
||||
language: generic
|
||||
install: git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder
|
||||
script: ~/qubes-builder/scripts/travis-build
|
||||
env:
|
||||
- DIST_DOM0=fc25 USE_QUBES_REPO_VERSION=4.0 USE_QUBES_REPO_TESTING=1
|
||||
- DIST_DOM0=fc29 USE_QUBES_REPO_VERSION=4.1 USE_QUBES_REPO_TESTING=1
|
||||
- DIST_DOM0=fc31 USE_QUBES_REPO_VERSION=4.1 USE_QUBES_REPO_TESTING=1
|
||||
|
||||
# don't build tags which are meant for code signing only
|
||||
branches:
|
||||
except:
|
||||
- /.*_.*/
|
||||
- build
|
||||
|
@ -1,5 +1,28 @@
|
||||
#!/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`
|
||||
UPDATES_STAT_FILE=/var/lib/qubes/updates/dom0-updates-available
|
||||
|
||||
@ -22,11 +45,11 @@ if [ "$1" = "--help" ]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
PKGS=
|
||||
YUM_OPTS=
|
||||
PKGS=()
|
||||
YUM_OPTS=()
|
||||
GUI=
|
||||
CHECK_ONLY=
|
||||
ALL_OPTS="$*"
|
||||
ALL_OPTS=( "${@}" )
|
||||
YUM_ACTION=
|
||||
QVMRUN_OPTS=
|
||||
CLEAN=
|
||||
@ -51,10 +74,10 @@ while [ $# -gt 0 ]; do
|
||||
YUM_ACTION=${1#--action=}
|
||||
;;
|
||||
-*)
|
||||
YUM_OPTS="$YUM_OPTS $1"
|
||||
YUM_OPTS+=( "${1}" )
|
||||
;;
|
||||
*)
|
||||
PKGS="$PKGS $1"
|
||||
PKGS+=( "${1}" )
|
||||
if [ -z "$YUM_ACTION" ]; then
|
||||
YUM_ACTION=install
|
||||
fi
|
||||
@ -66,13 +89,16 @@ done
|
||||
# Prevent implicit update of template - this would override user changes -
|
||||
# but do allow explicit template upgrade, downgrade, reinstall
|
||||
if [ "$YUM_ACTION" == "reinstall" ] || [ "$YUM_ACTION" == "upgrade" ] || [ "$YUM_ACTION" == "upgrade-to" ] \
|
||||
|| [ "$YUM_ACTION" == "downgrade" ] && [[ "$PKGS" == *"qubes-template-"* ]]; then
|
||||
TEMPLATE_EXCLUDE_OPTS=""
|
||||
|| [ "$YUM_ACTION" == "downgrade" ] && find_regex_in_args '^qubes-template-' "${PKGS[@]}"; then
|
||||
TEMPLATE_EXCLUDE_OPTS=()
|
||||
echo "WARNING: Replacing a template will erase all files in template's /home and /rw !"
|
||||
|
||||
ONEPKG=`cut -f 1 -d ' ' <<<$PKGS`
|
||||
if [[ "$ONEPKG" == "qubes-template-"* ]] && [[ "$ONEPKG" == "${PKGS#\ }" ]]; then # test "$PKGS" minus space
|
||||
ONEPKG=`sed -r 's/-[0-9]+(\.[0-9-]+)+(\.noarch)*$//' <<<$ONEPKG` # Remove version suffix
|
||||
# At least one package name matches the regex '^qubes-template-',
|
||||
# so if there is only one package name in the array, then the
|
||||
# code can safely assume that the array includes only a template
|
||||
# package name.
|
||||
if [[ ${#PKGS[@]} -eq 1 ]]; then
|
||||
ONEPKG="$(echo "${PKGS[0]}" | sed -r 's/-[0-9]+(\.[0-9-]+)+(\.noarch)*$//')" # Remove version suffix
|
||||
TEMPLATE=${ONEPKG#qubes-template-} # Remove prefix
|
||||
|
||||
if qvm-shutdown --wait $TEMPLATE ; then
|
||||
@ -96,12 +122,13 @@ if [ "$YUM_ACTION" == "reinstall" ] || [ "$YUM_ACTION" == "upgrade" ] || [ "$YUM
|
||||
exit 1
|
||||
fi
|
||||
elif [ "$YUM_ACTION" == "search" ] || [ "$YUM_ACTION" == "info" ]; then # No need to shutdown for search/info
|
||||
TEMPLATE_EXCLUDE_OPTS=""
|
||||
TEMPLATE_EXCLUDE_OPTS=()
|
||||
else
|
||||
TEMPLATE_EXCLUDE_OPTS="--exclude=`rpm -qa --qf '%{NAME},' qubes-template-\*|head -c -1`"
|
||||
TEMPLATE_EXCLUDE_OPTS=( "--exclude=$(rpm -qa --qf '%{NAME},' qubes-template-\*|head -c -1)" )
|
||||
fi
|
||||
YUM_OPTS="$TEMPLATE_EXCLUDE_OPTS $YUM_OPTS"
|
||||
ALL_OPTS="$TEMPLATE_EXCLUDE_OPTS $ALL_OPTS"
|
||||
|
||||
YUM_OPTS=( "${TEMPLATE_EXCLUDE_OPTS[@]}" "${YUM_OPTS[@]}" )
|
||||
ALL_OPTS=( "${TEMPLATE_EXCLUDE_OPTS[@]}" "${ALL_OPTS[@]}" )
|
||||
|
||||
ID=$(id -ur)
|
||||
if [ $ID != 0 -a -z "$GUI" -a -z "$CHECK_ONLY" ] ; then
|
||||
@ -109,7 +136,7 @@ if [ $ID != 0 -a -z "$GUI" -a -z "$CHECK_ONLY" ] ; then
|
||||
exit 1
|
||||
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
|
||||
exit 1
|
||||
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 'chown user:user /var/lib/qubes/dom0-updates/' || exit 1
|
||||
qvm-run --nogui -q $UPDATEVM 'rm -rf /var/lib/qubes/dom0-updates/etc' || exit 1
|
||||
tar c /var/lib/rpm /etc/yum.repos.d /etc/yum.conf 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 $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=$?
|
||||
if [ "$CHECK_ONLY" == "1" ]; then
|
||||
exit $RETCODE
|
||||
@ -195,18 +222,18 @@ if [ -z "$YUM_ACTION" ]; then
|
||||
YUM_ACTION=upgrade
|
||||
fi
|
||||
|
||||
if [ -n "$PKGS" ]; then
|
||||
if [ ${#PKGS[@]} -gt 0 ]; then
|
||||
if [ -n "$TEMPLATE" ]; then
|
||||
TEMPLATE_NETVM=$(qvm-prefs --force-root $TEMPLATE netvm)
|
||||
fi
|
||||
|
||||
dnf $YUM_OPTS $YUM_ACTION $PKGS ; RETCODE=$?
|
||||
dnf "${YUM_OPTS[@]}" $YUM_ACTION "${PKGS[@]}" ; RETCODE=$?
|
||||
|
||||
if [ -n "$TEMPLATE_BACKUP" -a "$RETCODE" -eq 0 ]; then
|
||||
# Remove backup, if we made one. Better to do this only on success and
|
||||
# potentially leave extra backups around than do it on an exit trap and
|
||||
# clean up more reliably but potentially brick a system.
|
||||
qvm-remove -- "$TEMPLATE_BACKUP"
|
||||
qvm-remove -f -- "$TEMPLATE_BACKUP"
|
||||
fi
|
||||
|
||||
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
|
||||
dnf check-update
|
||||
if [ $? -eq 100 ]; then # Run dnf with options
|
||||
dnf $YUM_OPTS $YUM_ACTION
|
||||
dnf "${YUM_OPTS[@]}" $YUM_ACTION
|
||||
fi
|
||||
fi
|
||||
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_repodata_dir = updates_dir + "/repodata"
|
||||
updates_error_file = updates_dir + "/errors"
|
||||
updates_error_file_handle = None
|
||||
|
||||
comps_file = None
|
||||
if os.path.exists('/usr/share/qubes/Qubes-comps.xml'):
|
||||
comps_file = '/usr/share/qubes/Qubes-comps.xml'
|
||||
|
||||
package_regex = re.compile(r"^[A-Za-z0-9._+-]{1,128}.rpm$")
|
||||
package_regex = re.compile(r"^[A-Za-z0-9._+-]{1,128}\.rpm$")
|
||||
# example valid outputs:
|
||||
# .....rpm: rsa sha1 (md5) pgp md5 OK
|
||||
# .....rpm: (sha1) dsa sha1 md5 gpg OK
|
||||
# .....rpm: digests signatures OK
|
||||
# example INVALID outputs:
|
||||
# .....rpm: sha1 md5 OK
|
||||
# .....rpm: RSA sha1 ((MD5) PGP) md5 NOT OK (MISSING KEYS: (MD5) PGP#246110c1)
|
||||
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):
|
||||
global updates_error_file_handle
|
||||
def dom0updates_fatal(msg):
|
||||
print(msg, file=sys.stderr)
|
||||
if updates_error_file_handle is None:
|
||||
updates_error_file_handle = open(updates_error_file, "a")
|
||||
updates_error_file_handle.write(msg + "\n")
|
||||
os.remove(pkg)
|
||||
with open(updates_error_file, "a") as updates_error_file_handle:
|
||||
updates_error_file_handle.write(msg + "\n")
|
||||
shutil.rmtree(updates_rpm_dir)
|
||||
exit(1)
|
||||
|
||||
|
||||
def handle_dom0updates(updatevm):
|
||||
global updates_error_file_handle
|
||||
|
||||
source = os.getenv("QREXEC_REMOTE_DOMAIN")
|
||||
if source != updatevm.name:
|
||||
print('Domain ' + str(source) + ' not allowed to send dom0 updates',
|
||||
@ -77,14 +75,14 @@ def handle_dom0updates(updatevm):
|
||||
os.mkdir(updates_rpm_dir)
|
||||
os.chown(updates_rpm_dir, -1, qubes_gid)
|
||||
os.chmod(updates_rpm_dir, 0o0775)
|
||||
subprocess.check_call(["/usr/libexec/qubes/qfile-dom0-unpacker",
|
||||
str(os.getuid()), updates_rpm_dir])
|
||||
# Verify received files
|
||||
for untrusted_f in os.listdir(updates_rpm_dir):
|
||||
if not package_regex.match(untrusted_f):
|
||||
dom0updates_fatal(updates_rpm_dir + '/' + untrusted_f,
|
||||
'Domain ' + source + ' sent unexpected file: ' + untrusted_f)
|
||||
else:
|
||||
try:
|
||||
subprocess.check_call(["/usr/libexec/qubes/qfile-dom0-unpacker",
|
||||
str(os.getuid()), updates_rpm_dir])
|
||||
# Verify received files
|
||||
for untrusted_f in os.listdir(updates_rpm_dir):
|
||||
if not package_regex.match(untrusted_f):
|
||||
raise Exception(
|
||||
'Domain ' + source + ' sent unexpected file')
|
||||
f = untrusted_f
|
||||
assert '/' not in f
|
||||
assert '\0' not in f
|
||||
@ -92,19 +90,19 @@ def handle_dom0updates(updatevm):
|
||||
|
||||
full_path = updates_rpm_dir + "/" + f
|
||||
if os.path.islink(full_path) or not os.path.isfile(full_path):
|
||||
dom0updates_fatal(
|
||||
full_path, 'Domain ' + source + ' sent not regular file')
|
||||
raise Exception(
|
||||
'Domain ' + source + ' sent not regular file')
|
||||
p = subprocess.Popen(["/bin/rpm", "-K", full_path],
|
||||
stdout=subprocess.PIPE)
|
||||
output = p.communicate()[0].decode('ascii')
|
||||
if p.returncode != 0:
|
||||
dom0updates_fatal(full_path,
|
||||
raise Exception(
|
||||
'Error while verifing %s signature: %s' % (f, output))
|
||||
if not gpg_ok_regex.search(output.strip()):
|
||||
dom0updates_fatal(full_path,
|
||||
raise Exception(
|
||||
'Domain ' + source + ' sent not signed rpm: ' + f)
|
||||
if updates_error_file_handle is not None:
|
||||
updates_error_file_handle.close()
|
||||
except Exception as e:
|
||||
dom0updates_fatal(str(e))
|
||||
# After updates received - create repo metadata
|
||||
createrepo_cmd = ["/usr/bin/createrepo_c"]
|
||||
if comps_file:
|
||||
@ -128,4 +126,6 @@ def main():
|
||||
exit(1)
|
||||
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: pandoc
|
||||
BuildRequires: qubes-utils-devel >= 3.1.3
|
||||
BuildRequires: qubes-libvchan-devel
|
||||
BuildRequires: gcc
|
||||
Requires: qubes-core-dom0
|
||||
Requires: python3-qubesadmin
|
||||
Requires: qubes-core-qrexec-dom0
|
||||
Requires: qubes-core-admin-client
|
||||
Requires: qubes-utils >= 3.1.3
|
||||
Requires: qubes-utils-libs >= 4.0.16
|
||||
@ -75,7 +75,6 @@ Kernel install hook for Xen-based system.
|
||||
%build
|
||||
export BACKEND_VMM=@BACKEND_VMM@
|
||||
(cd dom0-updates; make)
|
||||
(cd qrexec; make)
|
||||
(cd file-copy-vm; make)
|
||||
(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
|
||||
|
||||
# Qrexec
|
||||
mkdir -p $RPM_BUILD_ROOT/usr/bin $RPM_BUILD_ROOT/usr/sbin
|
||||
install qrexec/qrexec-daemon $RPM_BUILD_ROOT/usr/sbin/
|
||||
install qrexec/qrexec-client $RPM_BUILD_ROOT/usr/bin/
|
||||
# XXX: Backward compatibility
|
||||
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
|
||||
# Qrexec services
|
||||
mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes/qubes-rpc $RPM_BUILD_ROOT/etc/qubes-rpc/policy
|
||||
cp qubes-rpc/* $RPM_BUILD_ROOT/usr/lib/qubes/qubes-rpc/
|
||||
for i in qubes-rpc/*; do ln -s ../../usr/lib/qubes/$i $RPM_BUILD_ROOT/etc/qubes-rpc/$(basename $i); done
|
||||
cp qubes-rpc-policy/* $RPM_BUILD_ROOT/etc/qubes-rpc/policy/
|
||||
|
||||
### pm-utils
|
||||
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/qubes-dom0.modules $RPM_BUILD_ROOT/etc/sysconfig/modules/qubes-dom0.modules
|
||||
install -D system-config/qubes-sync-clock.cron $RPM_BUILD_ROOT/etc/cron.d/qubes-sync-clock.cron
|
||||
install -D system-config/lvm-cleanup.cron-daily $RPM_BUILD_ROOT/etc/cron.daily/lvm-cleanup
|
||||
install -d $RPM_BUILD_ROOT/etc/udev/rules.d
|
||||
install -m 644 system-config/00-qubes-ignore-devices.rules $RPM_BUILD_ROOT/etc/udev/rules.d/
|
||||
install -m 644 system-config/12-qubes-ignore-lvm-devices.rules $RPM_BUILD_ROOT/etc/udev/rules.d/
|
||||
@ -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 644 -D system-config/75-qubes-dom0.preset \
|
||||
$RPM_BUILD_ROOT/usr/lib/systemd/system-preset/75-qubes-dom0.preset
|
||||
install -m 644 -D system-config/75-qubes-dom0-user.preset \
|
||||
$RPM_BUILD_ROOT/usr/lib/systemd/user-preset/75-qubes-dom0-user.preset
|
||||
install -m 644 -D system-config/99-qubes-default-disable.preset \
|
||||
$RPM_BUILD_ROOT/usr/lib/systemd/system-preset/99-qubes-default-disable.preset
|
||||
install -d $RPM_BUILD_ROOT/etc/dnf/protected.d
|
||||
@ -204,19 +203,20 @@ chmod -x /etc/grub.d/10_linux
|
||||
/etc/qubes-rpc/qubes.ReceiveUpdates
|
||||
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.ReceiveUpdates
|
||||
%attr(0770,root,qubes) %dir /var/lib/qubes/updates
|
||||
# Qrexec services
|
||||
/etc/qubes-rpc/qubes.repos.*
|
||||
/usr/lib/qubes/qubes-rpc/qubes.repos.*
|
||||
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.repos.List
|
||||
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.repos.Enable
|
||||
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.repos.Disable
|
||||
# Dracut module
|
||||
/etc/dracut.conf.d/*
|
||||
%dir %{_dracutmoddir}/90macbook12-spi-driver
|
||||
%{_dracutmoddir}/90macbook12-spi-driver/*
|
||||
%dir %{_dracutmoddir}/90qubes-pciback
|
||||
%{_dracutmoddir}/90qubes-pciback/*
|
||||
%dir %{_dracutmoddir}/90extra-modules
|
||||
%{_dracutmoddir}/90extra-modules/*
|
||||
# Qrexec
|
||||
/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
|
||||
/usr/bin/qvm-copy-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/12-qubes-ignore-lvm-devices.rules
|
||||
%attr(0644,root,root) /etc/cron.d/qubes-sync-clock.cron
|
||||
/etc/cron.daily/lvm-cleanup
|
||||
%config(noreplace) /etc/profile.d/zz-disable-lesspipe.sh
|
||||
%config(noreplace) /etc/dnf/protected.d/qubes-core-dom0.conf
|
||||
/usr/lib/systemd/system-preset/75-qubes-dom0.preset
|
||||
/usr/lib/systemd/system-preset/99-qubes-default-disable.preset
|
||||
/usr/lib/systemd/user-preset/75-qubes-dom0-user.preset
|
||||
/var/lib/qubes/.qubes-exclude-block-devices
|
||||
# Man
|
||||
%{_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 fstrim.timer
|
||||
|
||||
enable dbus.socket
|
||||
enable dbus-daemon.service
|
||||
|
||||
enable abrtd.service
|
||||
enable abrt-ccpp.service
|
||||
@ -46,6 +48,7 @@ enable qubes-db-dom0.service
|
||||
enable qubes-qmemman.service
|
||||
enable qubes-suspend.service
|
||||
enable qubes-setupdvm.service
|
||||
enable qubes-qrexec-policy-daemon.service
|
||||
enable qubesd.service
|
||||
enable anti-evil-maid-unseal.service
|
||||
enable anti-evil-maid-check-mount-devs.service
|
||||
|
@ -19,5 +19,10 @@ case "$COMMAND" in
|
||||
;;
|
||||
esac
|
||||
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
|
||||
|
@ -24,7 +24,7 @@ else
|
||||
EFI_DIR="$ESP_MOUNTPOINT$EFI_DIR"
|
||||
fi
|
||||
|
||||
if [ ! -d "$EFI_DIR" ]; then
|
||||
if [ ! -r "$EFI_DIR/xen.cfg" ]; then
|
||||
# non-EFI system
|
||||
exit 0;
|
||||
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