Compare commits

...

35 Commits

Author SHA1 Message Date
Andy 2df917e7a5
dracut: add roadrunner2/macbook12-spi-driver on MacBook HW
4 years ago
3hhh c600b1b39c
Proper argument escaping for special characters.
4 years ago
3hhh f4f5731bdc
Don't pass stdin to VMs unless necessary.
4 years ago
Frédéric Pierret (fepitre) 0d53697917
travis: switch to dom0 Fedora 31
4 years ago
Marek Marczykowski-Górecki a4006f5046
version 4.1.3
4 years ago
Marek Marczykowski-Górecki 264ded8101
Merge remote-tracking branch 'origin/pr/53'
4 years ago
Marek Marczykowski-Górecki 4a88c520ac
kernel-install: consider both grub2 and grub2-efi configs
4 years ago
Marta Marczykowska-Górecka 761b5b1ef4
Added enabling of qrexec-policy-daemon.service
5 years ago
Marek Marczykowski-Górecki 257d9e5b78
version 4.1.2
5 years ago
Marek Marczykowski-Górecki 9cf273d187
qubes-dom0-update: fix removing backup template after the operation
5 years ago
Frédéric Pierret (fepitre) cf76a3cbbb
travis: switch to bionic
5 years ago
Marek Marczykowski-Górecki e5e006d933
Fix various issues with qubes-dom0-update
5 years ago
Marek Marczykowski-Górecki 2dadbcfdcb
version 4.1.1
5 years ago
Marek Marczykowski-Górecki 9962fab124
repos rpc: use dnf native method of writing repository configuration
5 years ago
M. Vefa Bicakci 1089a7a07b
qubes-dom0-update: Quote arguments
5 years ago
Marek Marczykowski-Górecki c56c4a7a9d
kernel-install: adjust EFI check to look for xen.cfg
5 years ago
Marek Marczykowski-Górecki 895415aee1
version 4.1.0
5 years ago
Marek Marczykowski-Górecki 8800a08150
Merge remote-tracking branch 'origin/pr/48'
5 years ago
AJ Jordan 82806b53e2
Add some comments to qubes.repos.List
5 years ago
AJ Jordan 3786197ab2
Don't write a trailing newline in qubes.repos.List
5 years ago
AJ Jordan 75faa22dff
Add qubes.repos.* services to the RPMs
5 years ago
AJ Jordan 2283af8ce5
Print `ok` for repo enable/disable success
5 years ago
AJ Jordan 00c37b0b5b
Use qrexec service arguments
5 years ago
AJ Jordan 05658f0850
Properly set the umask for repo files
5 years ago
AJ Jordan 0af2769aca
Enable/disable repos atomically
5 years ago
AJ Jordan 529f5a1cd0
Use Python whitespace conventions
5 years ago
AJ Jordan ce70209310
Rename admin.repos.* to qubes.repos.*
5 years ago
AJ Jordan 888073df05
Add admin.repos.* qrexec services
5 years ago
Marek Marczykowski-Górecki 6fa3e19f7e
travis: drop R4.0, switch to xenial
5 years ago
Marek Marczykowski-Górecki 15c55a4ef5
Remove qrexec related files
5 years ago
Marek Marczykowski-Górecki 2ec29a4d4c
Cleanup lvm archived metadata files
5 years ago
Marek Marczykowski-Górecki 330f155168
dom0-update: support rpm -K output of rpm 4.14
5 years ago
Marek Marczykowski-Górecki fb1c284774
dom0-update: send dnf.conf to updatevm
5 years ago
Marek Marczykowski-Górecki d705fa6ed4
system-config: enable dbus in system- and user- presets
5 years ago
Marek Marczykowski-Górecki 61ec339c2d
qrexec: add version negotiation
5 years ago

@ -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()

@ -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, &params, sizeof(params))
|| !write_all(s, cmdline_param, cmdline_size)) {
perror("write daemon");
do_exit(1);
}
/* the daemon will respond with the same message with connect_port filled
* and empty cmdline */
if (!read_all(s, &hdr, sizeof(hdr))) {
perror("read daemon");
do_exit(1);
}
assert(hdr.type == type);
if (hdr.len != sizeof(params)) {
fprintf(stderr, "Invalid response for 0x%x\n", type);
do_exit(1);
}
if (!read_all(s, &params, sizeof(params))) {
perror("read daemon");
do_exit(1);
}
*data_port = params.connect_port;
*data_domain = params.connect_domain;
}
static void send_service_connect(int s, char *conn_ident,
int connect_domain, int connect_port)
{
struct msg_header hdr;
struct exec_params exec_params;
struct service_params srv_params;
hdr.type = MSG_SERVICE_CONNECT;
hdr.len = sizeof(exec_params) + sizeof(srv_params);
exec_params.connect_domain = connect_domain;
exec_params.connect_port = connect_port;
strncpy(srv_params.ident, conn_ident, sizeof(srv_params.ident) - 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, &params, sizeof(params))) {
terminate_client(fd);
return 0;
}
if (!read_all(fd, buf, len)) {
terminate_client(fd);
return 0;
}
if (hdr->type == MSG_SERVICE_CONNECT) {
/* if the service was accepted, do not send spurious
* MSG_SERVICE_REFUSED when service process itself exit with non-zero
* code */
for (i = 0; i <= policy_pending_max; i++) {
if (policy_pending[i].pid &&
strncmp(policy_pending[i].params.ident, buf, len) == 0) {
policy_pending[i].pid = 0;
while (policy_pending_max > 0 &&
policy_pending[policy_pending_max].pid == 0)
policy_pending_max--;
break;
}
}
}
if (!params.connect_port) {
struct exec_params client_params;
/* allocate port and send it to the client */
params.connect_port = allocate_vchan_port(params.connect_domain);
if (params.connect_port <= 0) {
fprintf(stderr, "Failed to allocate new vchan port, too many clients?\n");
terminate_client(fd);
return 0;
}
/* 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, &params, sizeof(params)) < 0)
handle_vchan_error("send params");
if (use_default_user) {
if (libvchan_send(vchan, default_user, strlen(default_user)) < 0)
handle_vchan_error("send default_user");
if (libvchan_send(vchan, buf+default_user_keyword_len_without_colon,
len-default_user_keyword_len_without_colon) < 0)
handle_vchan_error("send buf");
} else
if (libvchan_send(vchan, buf, len) < 0)
handle_vchan_error("send buf");
return 1;
}
static void handle_cmdline_message_from_client(int fd)
{
struct msg_header hdr;
if (!read_all(fd, &hdr, sizeof hdr)) {
terminate_client(fd);
return;
}
switch (hdr.type) {
case MSG_EXEC_CMDLINE:
case MSG_JUST_EXEC:
case MSG_SERVICE_CONNECT:
break;
default:
terminate_client(fd);
return;
}
if (!handle_cmdline_body_from_client(fd, &hdr))
// client disconnected while sending cmdline, above call already
// cleaned up client info
return;
clients[fd].state = CLIENT_RUNNING;
}
static void handle_client_hello(int fd)
{
struct msg_header hdr;
struct peer_info info;
if (!read_all(fd, &hdr, sizeof hdr)) {
terminate_client(fd);
return;
}
if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) {
fprintf(stderr, "Invalid HELLO packet received from client %d: "
"type %d, len %d\n", fd, hdr.type, hdr.len);
terminate_client(fd);
return;
}
if (!read_all(fd, &info, sizeof info)) {
terminate_client(fd);
return;
}
if (info.version != QREXEC_PROTOCOL_VERSION) {
fprintf(stderr, "Incompatible client protocol version (remote %d, local %d)\n", info.version, QREXEC_PROTOCOL_VERSION);
terminate_client(fd);
return;
}
clients[fd].state = CLIENT_CMDLINE;
}
/* handle data received from one of qrexec_client processes */
static void handle_message_from_client(int fd)
{
char buf[MAX_DATA_CHUNK];
switch (clients[fd].state) {
case CLIENT_HELLO:
handle_client_hello(fd);
return;
case CLIENT_CMDLINE:
handle_cmdline_message_from_client(fd);
return;
case CLIENT_RUNNING:
// expected EOF
if (read(fd, buf, sizeof(buf)) != 0) {
fprintf(stderr, "Unexpected data received from client %d\n", fd);
}
terminate_client(fd);
return;
default:
fprintf(stderr, "Invalid client state %d\n", clients[fd].state);
exit(1);
}
}
/*
* The signal handler executes asynchronously; therefore all it should do is
* to set a flag "signal has arrived", and let the main even loop react to this
* flag in appropriate moment.
*/
int child_exited;
static void sigchld_handler(int UNUSED(x))
{
child_exited = 1;
signal(SIGCHLD, sigchld_handler);
}
static void send_service_refused(libvchan_t *vchan, struct service_params *params) {
struct msg_header hdr;
hdr.type = MSG_SERVICE_REFUSED;
hdr.len = sizeof(*params);
if (libvchan_send(vchan, &hdr, sizeof(hdr)) != sizeof(hdr)) {
fprintf(stderr, "Failed to send MSG_SERVICE_REFUSED hdr to agent\n");
exit(1);
}
if (libvchan_send(vchan, params, sizeof(*params)) != sizeof(*params)) {
fprintf(stderr, "Failed to send MSG_SERVICE_REFUSED to agent\n");
exit(1);
}
}
/* clean zombies, check for denied service calls */
static void reap_children()
{
int status;
int i;
pid_t pid;
while ((pid=waitpid(-1, &status, WNOHANG)) > 0) {
for (i = 0; i <= policy_pending_max; i++) {
if (policy_pending[i].pid == pid) {
status = WEXITSTATUS(status);
if (status != 0) {
send_service_refused(vchan, &policy_pending[i].params);
}
/* in case of allowed calls, we will do the rest in
* MSG_SERVICE_CONNECT from client handler */
policy_pending[i].pid = 0;
while (policy_pending_max > 0 &&
policy_pending[policy_pending_max].pid == 0)
policy_pending_max--;
break;
}
}
}
child_exited = 0;
}
static int find_policy_pending_slot() {
int i;
for (i = 0; i < MAX_CLIENTS; i++) {
if (policy_pending[i].pid == 0) {
if (i > policy_pending_max)
policy_pending_max = i;
return i;
}
}
return -1;
}
static void sanitize_name(char * untrusted_s_signed, char *extra_allowed_chars)
{
unsigned char * untrusted_s;
for (untrusted_s=(unsigned char*)untrusted_s_signed; *untrusted_s; untrusted_s++) {
if (*untrusted_s >= 'a' && *untrusted_s <= 'z')
continue;
if (*untrusted_s >= 'A' && *untrusted_s <= 'Z')
continue;
if (*untrusted_s >= '0' && *untrusted_s <= '9')
continue;
if (*untrusted_s == '_' ||
*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

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

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

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

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

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

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

@ -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*

@ -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

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

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

Loading…
Cancel
Save