Remove qrexec related files

Move them to the core-qrexec repository.

QubesOS/qubes-issues#4955
pull/49/head
Marek Marczykowski-Górecki 5 years ago
parent 2ec29a4d4c
commit 15c55a4ef5
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724

@ -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,874 +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;
int actual_version;
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;
}
actual_version = info.version < QREXEC_PROTOCOL_VERSION ? info.version : QREXEC_PROTOCOL_VERSION;
if (actual_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

@ -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,15 +96,6 @@ 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
### pm-utils
mkdir -p $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d
cp pm-utils/52qubes-pause-vms $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d/
@ -213,13 +203,6 @@ chmod -x /etc/grub.d/10_linux
%{_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

Loading…
Cancel
Save