Remove qrexec related files
Move them to the core-qrexec repository. QubesOS/qubes-issues#4955
This commit is contained in:
parent
2ec29a4d4c
commit
15c55a4ef5
@ -1,12 +0,0 @@
|
|||||||
CC=gcc
|
|
||||||
CFLAGS+=-I. -g -O2 -Wall -Wextra -Werror -pie -fPIC `pkg-config --cflags vchan-$(BACKEND_VMM)`
|
|
||||||
LIBS=`pkg-config --libs vchan-$(BACKEND_VMM)` -lqrexec-utils
|
|
||||||
|
|
||||||
|
|
||||||
all: qrexec-daemon qrexec-client
|
|
||||||
qrexec-daemon: qrexec-daemon.o
|
|
||||||
$(CC) -pie -g -o qrexec-daemon qrexec-daemon.o $(LIBS)
|
|
||||||
qrexec-client: qrexec-client.o
|
|
||||||
$(CC) -pie -g -o qrexec-client qrexec-client.o $(LIBS)
|
|
||||||
clean:
|
|
||||||
rm -f *.o *~ qrexec-daemon qrexec-client
|
|
@ -1,64 +0,0 @@
|
|||||||
Currently (after commit 2600134e3bb781fca25fe77e464f8b875741dc83),
|
|
||||||
qrexec_agent can request a service (specified by a "exec_index") to be
|
|
||||||
executed on a different VM or dom0. Access control is enforced in dom0 via
|
|
||||||
files in /etc/qubes_rpc/policy. File copy, Open in Dispvm, sync appmenus,
|
|
||||||
upload updates to dom0 - they all have been ported to the new API.
|
|
||||||
See the quick HOWTO section on how to add a new service. Note we have
|
|
||||||
qvm-open-in-vm utility practically for free.
|
|
||||||
|
|
||||||
CHANGES
|
|
||||||
|
|
||||||
Besides flexibility offered by /etc/qubes_rpc/policy, writing a client
|
|
||||||
is much simpler now. The workflow used to be (using "filecopy" service as
|
|
||||||
an example):
|
|
||||||
a) "filecopy_ui" process places job description in some spool directory,
|
|
||||||
signals qrexec_agent to signal qrexec_daemon
|
|
||||||
b) qrexec_daemon executes "qrexec_client -d domain filecopy_worker ...."
|
|
||||||
and "filecopy_worker" process needed to parse spool and retrieve job
|
|
||||||
description from there. Particularly, "filecopy_ui" had no connection to
|
|
||||||
remote.
|
|
||||||
Now, the flow is:
|
|
||||||
a) qrexec_client_vm process obtains 3 unix socket descriptors from
|
|
||||||
qrexec_agent, dup stdin/out/err to them; forms "existing_process_handle" from
|
|
||||||
them
|
|
||||||
b) qrexec_client_vm signals qrexec_agent to signal qrexec_daemon, with a
|
|
||||||
"exec_index" (so, type of service) as an argument
|
|
||||||
c) qrexec_daemon executed "qrexec_client -d domain -c existing_process_handle ...."
|
|
||||||
d) qrexec_client_vm execve filecopy_program.
|
|
||||||
|
|
||||||
Thus, there is only one service program, and it has direct access to remote via
|
|
||||||
stdin/stdout.
|
|
||||||
|
|
||||||
HOWTO
|
|
||||||
|
|
||||||
Let's add a new "test.Add" service, that will add two numbers. We need the
|
|
||||||
following files in the template fs:
|
|
||||||
==========================
|
|
||||||
/usr/bin/our_test_add_client:
|
|
||||||
#!/bin/sh
|
|
||||||
echo $1 $2
|
|
||||||
exec cat >&2
|
|
||||||
# more correct: exec cat >&$SAVED_FD_1, but do not scare the reader
|
|
||||||
==========================
|
|
||||||
/usr/bin/our_test_add_server:
|
|
||||||
#!/bin/sh
|
|
||||||
read arg1 arg2
|
|
||||||
echo $(($arg1+$arg2))
|
|
||||||
==========================
|
|
||||||
/etc/qubes_rpc/test.Add:
|
|
||||||
/usr/bin/our_test_add_server
|
|
||||||
|
|
||||||
Now, on the client side, we start the client via
|
|
||||||
/usr/lib/qubes/qrexec_client_vm target_vm test.Add /usr/bin/our_test_add_client 11 22
|
|
||||||
|
|
||||||
Because there is no policy yet, dom0 will ask you to create one (of cource you
|
|
||||||
can do it before the first run of our_test_add_client). So, in dom0, create (by now,
|
|
||||||
with a file editor) the /etc/qubes_rpc/policy/test.Add file with
|
|
||||||
anyvm anyvm ask
|
|
||||||
content. The format of the /etc/qubes_rpc/policy/* files is
|
|
||||||
srcvm destvm (allow|deny|ask)[,user=user_to_run_as][,target=VM_to_redirect_to]
|
|
||||||
|
|
||||||
You can specify srcvm and destvm by name, or by one of "anyvm", "dispvm", "dom0"
|
|
||||||
reserved keywords.
|
|
||||||
Then, when you confirm the operation, you will get the result in the client vm.
|
|
||||||
|
|
@ -1,818 +0,0 @@
|
|||||||
/*
|
|
||||||
* The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
*
|
|
||||||
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/un.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <getopt.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
#include <sys/select.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include "qrexec.h"
|
|
||||||
#include "libqrexec-utils.h"
|
|
||||||
|
|
||||||
// whether qrexec-client should replace problematic bytes with _ before printing the output
|
|
||||||
int replace_chars_stdout = 0;
|
|
||||||
int replace_chars_stderr = 0;
|
|
||||||
|
|
||||||
#define VCHAN_BUFFER_SIZE 65536
|
|
||||||
|
|
||||||
int local_stdin_fd, local_stdout_fd;
|
|
||||||
pid_t local_pid = 0;
|
|
||||||
/* flag if this is "remote" end of service call. In this case swap STDIN/STDOUT
|
|
||||||
* msg types and send exit code at the end */
|
|
||||||
int is_service = 0;
|
|
||||||
int child_exited = 0;
|
|
||||||
|
|
||||||
extern char **environ;
|
|
||||||
|
|
||||||
static int handle_agent_handshake(libvchan_t *vchan, int remote_send_first)
|
|
||||||
{
|
|
||||||
struct msg_header hdr;
|
|
||||||
struct peer_info info;
|
|
||||||
int who = 0; // even - send to remote, odd - receive from remote
|
|
||||||
|
|
||||||
while (who < 2) {
|
|
||||||
if ((who+remote_send_first) & 1) {
|
|
||||||
if (!read_vchan_all(vchan, &hdr, sizeof(hdr))) {
|
|
||||||
perror("daemon handshake");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) {
|
|
||||||
fprintf(stderr, "Invalid daemon MSG_HELLO\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (!read_vchan_all(vchan, &info, sizeof(info))) {
|
|
||||||
perror("daemon handshake");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.version != QREXEC_PROTOCOL_VERSION) {
|
|
||||||
fprintf(stderr, "Incompatible daemon protocol version "
|
|
||||||
"(daemon %d, client %d)\n",
|
|
||||||
info.version, QREXEC_PROTOCOL_VERSION);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
hdr.type = MSG_HELLO;
|
|
||||||
hdr.len = sizeof(info);
|
|
||||||
info.version = QREXEC_PROTOCOL_VERSION;
|
|
||||||
|
|
||||||
if (!write_vchan_all(vchan, &hdr, sizeof(hdr))) {
|
|
||||||
fprintf(stderr, "Failed to send MSG_HELLO hdr to daemon\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (!write_vchan_all(vchan, &info, sizeof(info))) {
|
|
||||||
fprintf(stderr, "Failed to send MSG_HELLO to daemon\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
who++;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int handle_daemon_handshake(int fd)
|
|
||||||
{
|
|
||||||
struct msg_header hdr;
|
|
||||||
struct peer_info info;
|
|
||||||
|
|
||||||
/* daemon send MSG_HELLO first */
|
|
||||||
if (!read_all(fd, &hdr, sizeof(hdr))) {
|
|
||||||
perror("daemon handshake");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) {
|
|
||||||
fprintf(stderr, "Invalid daemon MSG_HELLO\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (!read_all(fd, &info, sizeof(info))) {
|
|
||||||
perror("daemon handshake");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.version != QREXEC_PROTOCOL_VERSION) {
|
|
||||||
fprintf(stderr, "Incompatible daemon protocol version "
|
|
||||||
"(daemon %d, client %d)\n",
|
|
||||||
info.version, QREXEC_PROTOCOL_VERSION);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
hdr.type = MSG_HELLO;
|
|
||||||
hdr.len = sizeof(info);
|
|
||||||
info.version = QREXEC_PROTOCOL_VERSION;
|
|
||||||
|
|
||||||
if (!write_all(fd, &hdr, sizeof(hdr))) {
|
|
||||||
fprintf(stderr, "Failed to send MSG_HELLO hdr to daemon\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (!write_all(fd, &info, sizeof(info))) {
|
|
||||||
fprintf(stderr, "Failed to send MSG_HELLO to daemon\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int connect_unix_socket(const char *domname)
|
|
||||||
{
|
|
||||||
int s, len;
|
|
||||||
struct sockaddr_un remote;
|
|
||||||
|
|
||||||
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
|
|
||||||
perror("socket");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
remote.sun_family = AF_UNIX;
|
|
||||||
snprintf(remote.sun_path, sizeof remote.sun_path,
|
|
||||||
QREXEC_DAEMON_SOCKET_DIR "/qrexec.%s", domname);
|
|
||||||
len = strlen(remote.sun_path) + sizeof(remote.sun_family);
|
|
||||||
if (connect(s, (struct sockaddr *) &remote, len) == -1) {
|
|
||||||
perror("connect");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
if (handle_daemon_handshake(s) < 0)
|
|
||||||
exit(1);
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sigchld_handler(int x __attribute__((__unused__)))
|
|
||||||
{
|
|
||||||
child_exited = 1;
|
|
||||||
signal(SIGCHLD, sigchld_handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* called from do_fork_exec */
|
|
||||||
_Noreturn void do_exec(char *prog)
|
|
||||||
{
|
|
||||||
/* avoid calling qubes-rpc-multiplexer through shell */
|
|
||||||
exec_qubes_rpc_if_requested(prog, environ);
|
|
||||||
|
|
||||||
/* if above haven't executed qubes-rpc-multiplexer, pass it to shell */
|
|
||||||
execl("/bin/bash", "bash", "-c", prog, NULL);
|
|
||||||
perror("exec bash");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void do_exit(int code)
|
|
||||||
{
|
|
||||||
int status;
|
|
||||||
/* restore flags, as we may have not the only copy of this file descriptor
|
|
||||||
*/
|
|
||||||
if (local_stdin_fd != -1)
|
|
||||||
set_block(local_stdin_fd);
|
|
||||||
close(local_stdin_fd);
|
|
||||||
close(local_stdout_fd);
|
|
||||||
// sever communication lines; wait for child, if any
|
|
||||||
// so that qrexec-daemon can count (recursively) spawned processes correctly
|
|
||||||
waitpid(-1, &status, 0);
|
|
||||||
exit(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void prepare_local_fds(char *cmdline)
|
|
||||||
{
|
|
||||||
if (!cmdline) {
|
|
||||||
local_stdin_fd = 1;
|
|
||||||
local_stdout_fd = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
signal(SIGCHLD, sigchld_handler);
|
|
||||||
do_fork_exec(cmdline, &local_pid, &local_stdin_fd, &local_stdout_fd,
|
|
||||||
NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ask the daemon to allocate vchan port */
|
|
||||||
static void negotiate_connection_params(int s, int other_domid, unsigned type,
|
|
||||||
void *cmdline_param, int cmdline_size,
|
|
||||||
int *data_domain, int *data_port)
|
|
||||||
{
|
|
||||||
struct msg_header hdr;
|
|
||||||
struct exec_params params;
|
|
||||||
hdr.type = type;
|
|
||||||
hdr.len = sizeof(params) + cmdline_size;
|
|
||||||
params.connect_domain = other_domid;
|
|
||||||
params.connect_port = 0;
|
|
||||||
if (!write_all(s, &hdr, sizeof(hdr))
|
|
||||||
|| !write_all(s, ¶ms, sizeof(params))
|
|
||||||
|| !write_all(s, cmdline_param, cmdline_size)) {
|
|
||||||
perror("write daemon");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
/* the daemon will respond with the same message with connect_port filled
|
|
||||||
* and empty cmdline */
|
|
||||||
if (!read_all(s, &hdr, sizeof(hdr))) {
|
|
||||||
perror("read daemon");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
assert(hdr.type == type);
|
|
||||||
if (hdr.len != sizeof(params)) {
|
|
||||||
fprintf(stderr, "Invalid response for 0x%x\n", type);
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
if (!read_all(s, ¶ms, sizeof(params))) {
|
|
||||||
perror("read daemon");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
*data_port = params.connect_port;
|
|
||||||
*data_domain = params.connect_domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void send_service_connect(int s, char *conn_ident,
|
|
||||||
int connect_domain, int connect_port)
|
|
||||||
{
|
|
||||||
struct msg_header hdr;
|
|
||||||
struct exec_params exec_params;
|
|
||||||
struct service_params srv_params;
|
|
||||||
|
|
||||||
hdr.type = MSG_SERVICE_CONNECT;
|
|
||||||
hdr.len = sizeof(exec_params) + sizeof(srv_params);
|
|
||||||
|
|
||||||
exec_params.connect_domain = connect_domain;
|
|
||||||
exec_params.connect_port = connect_port;
|
|
||||||
strncpy(srv_params.ident, conn_ident, sizeof(srv_params.ident) - 1);
|
|
||||||
srv_params.ident[sizeof(srv_params.ident) - 1] = '\0';
|
|
||||||
|
|
||||||
if (!write_all(s, &hdr, sizeof(hdr))
|
|
||||||
|| !write_all(s, &exec_params, sizeof(exec_params))
|
|
||||||
|| !write_all(s, &srv_params, sizeof(srv_params))) {
|
|
||||||
perror("write daemon");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void send_exit_code(libvchan_t *vchan, int status)
|
|
||||||
{
|
|
||||||
struct msg_header hdr;
|
|
||||||
|
|
||||||
hdr.type = MSG_DATA_EXIT_CODE;
|
|
||||||
hdr.len = sizeof(int);
|
|
||||||
if (libvchan_send(vchan, &hdr, sizeof(hdr)) != sizeof(hdr)) {
|
|
||||||
fprintf(stderr, "Failed to write exit code to the agent\n");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
if (libvchan_send(vchan, &status, sizeof(status)) != sizeof(status)) {
|
|
||||||
fprintf(stderr, "Failed to write exit code(2) to the agent\n");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void handle_input(libvchan_t *vchan)
|
|
||||||
{
|
|
||||||
char buf[MAX_DATA_CHUNK];
|
|
||||||
int ret;
|
|
||||||
size_t max_len;
|
|
||||||
struct msg_header hdr;
|
|
||||||
|
|
||||||
max_len = libvchan_buffer_space(vchan)-sizeof(hdr);
|
|
||||||
if (max_len > sizeof(buf))
|
|
||||||
max_len = sizeof(buf);
|
|
||||||
if (max_len == 0)
|
|
||||||
return;
|
|
||||||
ret = read(local_stdout_fd, buf, max_len);
|
|
||||||
if (ret < 0) {
|
|
||||||
perror("read");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
hdr.type = is_service ? MSG_DATA_STDOUT : MSG_DATA_STDIN;
|
|
||||||
hdr.len = ret;
|
|
||||||
if (libvchan_send(vchan, &hdr, sizeof(hdr)) != sizeof(hdr)) {
|
|
||||||
fprintf(stderr, "Failed to write STDIN data to the agent\n");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
if (ret == 0) {
|
|
||||||
close(local_stdout_fd);
|
|
||||||
local_stdout_fd = -1;
|
|
||||||
if (local_stdin_fd == -1) {
|
|
||||||
// if not a remote end of service call, wait for exit status
|
|
||||||
if (is_service) {
|
|
||||||
// if pipe in opposite direction already closed, no need to stay alive
|
|
||||||
if (local_pid == 0) {
|
|
||||||
/* if this is "remote" service end and no real local process
|
|
||||||
* exists (using own stdin/out) send also fake exit code */
|
|
||||||
send_exit_code(vchan, 0);
|
|
||||||
do_exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!write_vchan_all(vchan, buf, ret)) {
|
|
||||||
if (!libvchan_is_open(vchan)) {
|
|
||||||
// agent disconnected its end of socket, so no future data will be
|
|
||||||
// send there; there is no sense to read from child stdout
|
|
||||||
//
|
|
||||||
// since vchan socket is buffered it doesn't mean all data was
|
|
||||||
// received from the agent
|
|
||||||
close(local_stdout_fd);
|
|
||||||
local_stdout_fd = -1;
|
|
||||||
if (local_stdin_fd == -1) {
|
|
||||||
// since child does no longer accept data on its stdin, doesn't
|
|
||||||
// make sense to process the data from the daemon
|
|
||||||
//
|
|
||||||
// we don't know real exit VM process code (exiting here, before
|
|
||||||
// MSG_DATA_EXIT_CODE message)
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
perror("write agent");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void do_replace_chars(char *buf, int len) {
|
|
||||||
int i;
|
|
||||||
unsigned char c;
|
|
||||||
|
|
||||||
for (i = 0; i < len; i++) {
|
|
||||||
c = buf[i];
|
|
||||||
if ((c < '\040' || c > '\176') && /* not printable ASCII */
|
|
||||||
(c != '\t') && /* not tab */
|
|
||||||
(c != '\n') && /* not newline */
|
|
||||||
(c != '\r') && /* not return */
|
|
||||||
(c != '\b') && /* not backspace */
|
|
||||||
(c != '\a')) /* not bell */
|
|
||||||
buf[i] = '_';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int handle_vchan_data(libvchan_t *vchan, struct buffer *stdin_buf)
|
|
||||||
{
|
|
||||||
int status;
|
|
||||||
struct msg_header hdr;
|
|
||||||
char buf[MAX_DATA_CHUNK];
|
|
||||||
|
|
||||||
if (local_stdin_fd != -1) {
|
|
||||||
switch(flush_client_data(local_stdin_fd, stdin_buf)) {
|
|
||||||
case WRITE_STDIN_ERROR:
|
|
||||||
perror("write stdin");
|
|
||||||
close(local_stdin_fd);
|
|
||||||
local_stdin_fd = -1;
|
|
||||||
break;
|
|
||||||
case WRITE_STDIN_BUFFERED:
|
|
||||||
return WRITE_STDIN_BUFFERED;
|
|
||||||
case WRITE_STDIN_OK:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (libvchan_recv(vchan, &hdr, sizeof hdr) < 0) {
|
|
||||||
perror("read vchan");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
if (hdr.len > MAX_DATA_CHUNK) {
|
|
||||||
fprintf(stderr, "client_header.len=%d\n", hdr.len);
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
if (!read_vchan_all(vchan, buf, hdr.len)) {
|
|
||||||
perror("read daemon");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (hdr.type) {
|
|
||||||
/* both directions because we can serve as either end of service call */
|
|
||||||
case MSG_DATA_STDIN:
|
|
||||||
case MSG_DATA_STDOUT:
|
|
||||||
if (local_stdin_fd == -1)
|
|
||||||
break;
|
|
||||||
if (replace_chars_stdout)
|
|
||||||
do_replace_chars(buf, hdr.len);
|
|
||||||
if (hdr.len == 0) {
|
|
||||||
/* restore flags, as we may have not the only copy of this file descriptor
|
|
||||||
*/
|
|
||||||
if (local_stdin_fd != -1)
|
|
||||||
set_block(local_stdin_fd);
|
|
||||||
close(local_stdin_fd);
|
|
||||||
local_stdin_fd = -1;
|
|
||||||
} else {
|
|
||||||
switch (write_stdin(local_stdin_fd, buf, hdr.len, stdin_buf)) {
|
|
||||||
case WRITE_STDIN_BUFFERED:
|
|
||||||
return WRITE_STDIN_BUFFERED;
|
|
||||||
case WRITE_STDIN_ERROR:
|
|
||||||
if (errno == EPIPE) {
|
|
||||||
// local process have closed its stdin, handle data in oposite
|
|
||||||
// direction (if any) before exit
|
|
||||||
close(local_stdin_fd);
|
|
||||||
local_stdin_fd = -1;
|
|
||||||
} else {
|
|
||||||
perror("write local stdout");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case WRITE_STDIN_OK:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MSG_DATA_STDERR:
|
|
||||||
if (replace_chars_stderr)
|
|
||||||
do_replace_chars(buf, hdr.len);
|
|
||||||
write_all(2, buf, hdr.len);
|
|
||||||
break;
|
|
||||||
case MSG_DATA_EXIT_CODE:
|
|
||||||
libvchan_close(vchan);
|
|
||||||
if (hdr.len < sizeof(status))
|
|
||||||
status = 255;
|
|
||||||
else
|
|
||||||
memcpy(&status, buf, sizeof(status));
|
|
||||||
|
|
||||||
flush_client_data(local_stdin_fd, stdin_buf);
|
|
||||||
do_exit(status);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fprintf(stderr, "unknown msg %d\n", hdr.type);
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
/* intentionally do not distinguish between _ERROR and _OK, because in case
|
|
||||||
* of write error, we simply eat the data - no way to report it to the
|
|
||||||
* other side */
|
|
||||||
return WRITE_STDIN_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void check_child_status(libvchan_t *vchan)
|
|
||||||
{
|
|
||||||
pid_t pid;
|
|
||||||
int status;
|
|
||||||
|
|
||||||
pid = waitpid(local_pid, &status, WNOHANG);
|
|
||||||
if (pid < 0) {
|
|
||||||
perror("waitpid");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
if (pid == 0 || !WIFEXITED(status))
|
|
||||||
return;
|
|
||||||
if (is_service)
|
|
||||||
send_exit_code(vchan, WEXITSTATUS(status));
|
|
||||||
do_exit(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void select_loop(libvchan_t *vchan)
|
|
||||||
{
|
|
||||||
fd_set select_set;
|
|
||||||
fd_set wr_set;
|
|
||||||
int max_fd;
|
|
||||||
int ret;
|
|
||||||
int vchan_fd;
|
|
||||||
sigset_t selectmask;
|
|
||||||
struct timespec zero_timeout = { 0, 0 };
|
|
||||||
struct timespec select_timeout = { 10, 0 };
|
|
||||||
struct buffer stdin_buf;
|
|
||||||
|
|
||||||
sigemptyset(&selectmask);
|
|
||||||
sigaddset(&selectmask, SIGCHLD);
|
|
||||||
sigprocmask(SIG_BLOCK, &selectmask, NULL);
|
|
||||||
sigemptyset(&selectmask);
|
|
||||||
buffer_init(&stdin_buf);
|
|
||||||
/* remember to set back to blocking mode before closing the FD - this may
|
|
||||||
* be not the only copy and some processes may misbehave when get
|
|
||||||
* nonblocking FD for input/output
|
|
||||||
*/
|
|
||||||
set_nonblock(local_stdin_fd);
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
vchan_fd = libvchan_fd_for_select(vchan);
|
|
||||||
FD_ZERO(&select_set);
|
|
||||||
FD_ZERO(&wr_set);
|
|
||||||
FD_SET(vchan_fd, &select_set);
|
|
||||||
max_fd = vchan_fd;
|
|
||||||
if (local_stdout_fd != -1 &&
|
|
||||||
(size_t)libvchan_buffer_space(vchan) > sizeof(struct msg_header)) {
|
|
||||||
FD_SET(local_stdout_fd, &select_set);
|
|
||||||
if (local_stdout_fd > max_fd)
|
|
||||||
max_fd = local_stdout_fd;
|
|
||||||
}
|
|
||||||
if (child_exited && local_stdout_fd == -1)
|
|
||||||
check_child_status(vchan);
|
|
||||||
if (local_stdin_fd != -1 && buffer_len(&stdin_buf)) {
|
|
||||||
FD_SET(local_stdin_fd, &wr_set);
|
|
||||||
if (local_stdin_fd > max_fd)
|
|
||||||
max_fd = local_stdin_fd;
|
|
||||||
}
|
|
||||||
if ((local_stdin_fd == -1 || buffer_len(&stdin_buf) == 0) &&
|
|
||||||
libvchan_data_ready(vchan) > 0) {
|
|
||||||
/* check for other FDs, but exit immediately */
|
|
||||||
ret = pselect(max_fd + 1, &select_set, &wr_set, NULL,
|
|
||||||
&zero_timeout, &selectmask);
|
|
||||||
} else
|
|
||||||
ret = pselect(max_fd + 1, &select_set, &wr_set, NULL,
|
|
||||||
&select_timeout, &selectmask);
|
|
||||||
if (ret < 0) {
|
|
||||||
if (errno == EINTR && local_pid > 0) {
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
perror("select");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ret == 0) {
|
|
||||||
if (!libvchan_is_open(vchan)) {
|
|
||||||
/* remote disconnected witout a proper signaling */
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (FD_ISSET(vchan_fd, &select_set))
|
|
||||||
libvchan_wait(vchan);
|
|
||||||
if (buffer_len(&stdin_buf) &&
|
|
||||||
local_stdin_fd != -1 &&
|
|
||||||
FD_ISSET(local_stdin_fd, &wr_set)) {
|
|
||||||
if (flush_client_data(local_stdin_fd, &stdin_buf) == WRITE_STDIN_ERROR) {
|
|
||||||
perror("write stdin");
|
|
||||||
close(local_stdin_fd);
|
|
||||||
local_stdin_fd = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (libvchan_data_ready(vchan))
|
|
||||||
if (handle_vchan_data(vchan, &stdin_buf) != WRITE_STDIN_OK)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (local_stdout_fd != -1
|
|
||||||
&& FD_ISSET(local_stdout_fd, &select_set))
|
|
||||||
handle_input(vchan);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void usage(char *name)
|
|
||||||
{
|
|
||||||
fprintf(stderr,
|
|
||||||
"usage: %s [-w timeout] [-W] [-t] [-T] -d domain_name ["
|
|
||||||
"-l local_prog|"
|
|
||||||
"-c request_id,src_domain_name,src_domain_id|"
|
|
||||||
"-e] remote_cmdline\n"
|
|
||||||
"-e means exit after sending cmd,\n"
|
|
||||||
"-t enables replacing problematic bytes with '_' in command output, -T is the same for stderr\n"
|
|
||||||
"-W waits for connection end even in case of VM-VM (-c)\n"
|
|
||||||
"-c: connect to existing process (response to trigger service call)\n"
|
|
||||||
"-w timeout: override default connection timeout of 5s (set 0 for no timeout)\n",
|
|
||||||
name);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void parse_connect(char *str, char **request_id,
|
|
||||||
char **src_domain_name, int *src_domain_id)
|
|
||||||
{
|
|
||||||
int i=0;
|
|
||||||
char *token = NULL;
|
|
||||||
char *separators = ",";
|
|
||||||
|
|
||||||
token = strtok(str, separators);
|
|
||||||
while (token)
|
|
||||||
{
|
|
||||||
switch (i)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
*request_id = token;
|
|
||||||
if (strlen(*request_id) >= sizeof(struct service_params)) {
|
|
||||||
fprintf(stderr, "Invalid -c parameter (request_id too long, max %lu)\n",
|
|
||||||
sizeof(struct service_params)-1);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
*src_domain_name = token;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
*src_domain_id = atoi(token);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fprintf(stderr, "Invalid -c parameter (should be: \"-c request_id,src_domain_name,src_domain_id\")\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
token = strtok(NULL, separators);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sigalrm_handler(int x __attribute__((__unused__)))
|
|
||||||
{
|
|
||||||
fprintf(stderr, "vchan connection timeout\n");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void wait_for_vchan_client_with_timeout(libvchan_t *conn, int timeout) {
|
|
||||||
struct timeval start_tv, now_tv, timeout_tv;
|
|
||||||
|
|
||||||
if (timeout && gettimeofday(&start_tv, NULL) == -1) {
|
|
||||||
perror("gettimeofday");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
while (conn && libvchan_is_open(conn) == VCHAN_WAITING) {
|
|
||||||
if (timeout) {
|
|
||||||
fd_set rdset;
|
|
||||||
int fd = libvchan_fd_for_select(conn);
|
|
||||||
|
|
||||||
/* calculate how much time left until connection timeout expire */
|
|
||||||
if (gettimeofday(&now_tv, NULL) == -1) {
|
|
||||||
perror("gettimeofday");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
timersub(&start_tv, &now_tv, &timeout_tv);
|
|
||||||
timeout_tv.tv_sec += timeout;
|
|
||||||
if (timeout_tv.tv_sec < 0) {
|
|
||||||
fprintf(stderr, "vchan connection timeout\n");
|
|
||||||
libvchan_close(conn);
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
FD_ZERO(&rdset);
|
|
||||||
FD_SET(fd, &rdset);
|
|
||||||
switch (select(fd+1, &rdset, NULL, NULL, &timeout_tv)) {
|
|
||||||
case -1:
|
|
||||||
if (errno == EINTR) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fprintf(stderr, "vchan connection error\n");
|
|
||||||
libvchan_close(conn);
|
|
||||||
do_exit(1);
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
fprintf(stderr, "vchan connection timeout\n");
|
|
||||||
libvchan_close(conn);
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
libvchan_wait(conn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
int opt;
|
|
||||||
char *domname = NULL;
|
|
||||||
libvchan_t *data_vchan = NULL;
|
|
||||||
int data_port;
|
|
||||||
int data_domain;
|
|
||||||
int msg_type;
|
|
||||||
int s;
|
|
||||||
int just_exec = 0;
|
|
||||||
int wait_connection_end = 0;
|
|
||||||
int connect_existing = 0;
|
|
||||||
char *local_cmdline = NULL;
|
|
||||||
char *remote_cmdline = NULL;
|
|
||||||
char *request_id;
|
|
||||||
char *src_domain_name = NULL;
|
|
||||||
int src_domain_id = 0; /* if not -c given, the process is run in dom0 */
|
|
||||||
int connection_timeout = 5;
|
|
||||||
struct service_params svc_params;
|
|
||||||
while ((opt = getopt(argc, argv, "d:l:ec:tTw:W")) != -1) {
|
|
||||||
switch (opt) {
|
|
||||||
case 'd':
|
|
||||||
domname = strdup(optarg);
|
|
||||||
break;
|
|
||||||
case 'l':
|
|
||||||
local_cmdline = strdup(optarg);
|
|
||||||
break;
|
|
||||||
case 'e':
|
|
||||||
just_exec = 1;
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
parse_connect(optarg, &request_id, &src_domain_name, &src_domain_id);
|
|
||||||
connect_existing = 1;
|
|
||||||
is_service = 1;
|
|
||||||
break;
|
|
||||||
case 't':
|
|
||||||
replace_chars_stdout = 1;
|
|
||||||
break;
|
|
||||||
case 'T':
|
|
||||||
replace_chars_stderr = 1;
|
|
||||||
break;
|
|
||||||
case 'w':
|
|
||||||
connection_timeout = atoi(optarg);
|
|
||||||
break;
|
|
||||||
case 'W':
|
|
||||||
wait_connection_end = 1;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
usage(argv[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (optind >= argc || !domname)
|
|
||||||
usage(argv[0]);
|
|
||||||
remote_cmdline = argv[optind];
|
|
||||||
|
|
||||||
register_exec_func(&do_exec);
|
|
||||||
|
|
||||||
if (just_exec + connect_existing + (local_cmdline != 0) > 1) {
|
|
||||||
fprintf(stderr, "ERROR: only one of -e, -l, -c can be specified\n");
|
|
||||||
usage(argv[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcmp(domname, "dom0") == 0 && !connect_existing) {
|
|
||||||
fprintf(stderr, "ERROR: when target domain is 'dom0', -c must be specified\n");
|
|
||||||
usage(argv[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcmp(domname, "dom0") == 0) {
|
|
||||||
if (connect_existing) {
|
|
||||||
msg_type = MSG_SERVICE_CONNECT;
|
|
||||||
strncpy(svc_params.ident, request_id, sizeof(svc_params.ident) - 1);
|
|
||||||
svc_params.ident[sizeof(svc_params.ident) - 1] = '\0';
|
|
||||||
} else if (just_exec)
|
|
||||||
msg_type = MSG_JUST_EXEC;
|
|
||||||
else
|
|
||||||
msg_type = MSG_EXEC_CMDLINE;
|
|
||||||
assert(src_domain_name);
|
|
||||||
setenv("QREXEC_REMOTE_DOMAIN", src_domain_name, 1);
|
|
||||||
s = connect_unix_socket(src_domain_name);
|
|
||||||
negotiate_connection_params(s,
|
|
||||||
0, /* dom0 */
|
|
||||||
msg_type,
|
|
||||||
connect_existing ? (void*)&svc_params : (void*)remote_cmdline,
|
|
||||||
connect_existing ? sizeof(svc_params) : strlen(remote_cmdline) + 1,
|
|
||||||
&data_domain,
|
|
||||||
&data_port);
|
|
||||||
|
|
||||||
prepare_local_fds(remote_cmdline);
|
|
||||||
if (connect_existing) {
|
|
||||||
void (*old_handler)(int);
|
|
||||||
|
|
||||||
/* libvchan_client_init is blocking and does not support connection
|
|
||||||
* timeout, so use alarm(2) for that... */
|
|
||||||
old_handler = signal(SIGALRM, sigalrm_handler);
|
|
||||||
alarm(connection_timeout);
|
|
||||||
data_vchan = libvchan_client_init(data_domain, data_port);
|
|
||||||
alarm(0);
|
|
||||||
signal(SIGALRM, old_handler);
|
|
||||||
} else {
|
|
||||||
data_vchan = libvchan_server_init(data_domain, data_port,
|
|
||||||
VCHAN_BUFFER_SIZE, VCHAN_BUFFER_SIZE);
|
|
||||||
wait_for_vchan_client_with_timeout(data_vchan, connection_timeout);
|
|
||||||
}
|
|
||||||
if (!data_vchan || !libvchan_is_open(data_vchan)) {
|
|
||||||
fprintf(stderr, "Failed to open data vchan connection\n");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
if (handle_agent_handshake(data_vchan, connect_existing) < 0)
|
|
||||||
do_exit(1);
|
|
||||||
select_loop(data_vchan);
|
|
||||||
} else {
|
|
||||||
if (just_exec)
|
|
||||||
msg_type = MSG_JUST_EXEC;
|
|
||||||
else
|
|
||||||
msg_type = MSG_EXEC_CMDLINE;
|
|
||||||
s = connect_unix_socket(domname);
|
|
||||||
negotiate_connection_params(s,
|
|
||||||
src_domain_id,
|
|
||||||
msg_type,
|
|
||||||
remote_cmdline,
|
|
||||||
strlen(remote_cmdline) + 1,
|
|
||||||
&data_domain,
|
|
||||||
&data_port);
|
|
||||||
if (wait_connection_end && connect_existing)
|
|
||||||
/* save socket fd, 's' will be reused for the other qrexec-daemon
|
|
||||||
* connection */
|
|
||||||
wait_connection_end = s;
|
|
||||||
else
|
|
||||||
close(s);
|
|
||||||
setenv("QREXEC_REMOTE_DOMAIN", domname, 1);
|
|
||||||
prepare_local_fds(local_cmdline);
|
|
||||||
if (connect_existing) {
|
|
||||||
s = connect_unix_socket(src_domain_name);
|
|
||||||
send_service_connect(s, request_id, data_domain, data_port);
|
|
||||||
close(s);
|
|
||||||
if (wait_connection_end) {
|
|
||||||
/* wait for EOF */
|
|
||||||
fd_set read_fd;
|
|
||||||
FD_ZERO(&read_fd);
|
|
||||||
FD_SET(wait_connection_end, &read_fd);
|
|
||||||
select(wait_connection_end+1, &read_fd, NULL, NULL, 0);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data_vchan = libvchan_server_init(data_domain, data_port,
|
|
||||||
VCHAN_BUFFER_SIZE, VCHAN_BUFFER_SIZE);
|
|
||||||
if (!data_vchan) {
|
|
||||||
fprintf(stderr, "Failed to start data vchan server\n");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
wait_for_vchan_client_with_timeout(data_vchan, connection_timeout);
|
|
||||||
if (!libvchan_is_open(data_vchan)) {
|
|
||||||
fprintf(stderr, "Failed to open data vchan connection\n");
|
|
||||||
do_exit(1);
|
|
||||||
}
|
|
||||||
if (handle_agent_handshake(data_vchan, 0) < 0)
|
|
||||||
do_exit(1);
|
|
||||||
select_loop(data_vchan);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// vim:ts=4:sw=4:et:
|
|
@ -1,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, ¶ms, sizeof(params))) {
|
|
||||||
terminate_client(fd);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (!read_all(fd, buf, len)) {
|
|
||||||
terminate_client(fd);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hdr->type == MSG_SERVICE_CONNECT) {
|
|
||||||
/* if the service was accepted, do not send spurious
|
|
||||||
* MSG_SERVICE_REFUSED when service process itself exit with non-zero
|
|
||||||
* code */
|
|
||||||
for (i = 0; i <= policy_pending_max; i++) {
|
|
||||||
if (policy_pending[i].pid &&
|
|
||||||
strncmp(policy_pending[i].params.ident, buf, len) == 0) {
|
|
||||||
policy_pending[i].pid = 0;
|
|
||||||
while (policy_pending_max > 0 &&
|
|
||||||
policy_pending[policy_pending_max].pid == 0)
|
|
||||||
policy_pending_max--;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!params.connect_port) {
|
|
||||||
struct exec_params client_params;
|
|
||||||
/* allocate port and send it to the client */
|
|
||||||
params.connect_port = allocate_vchan_port(params.connect_domain);
|
|
||||||
if (params.connect_port <= 0) {
|
|
||||||
fprintf(stderr, "Failed to allocate new vchan port, too many clients?\n");
|
|
||||||
terminate_client(fd);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
/* notify the client when this connection got terminated */
|
|
||||||
vchan_port_notify_client[params.connect_port-VCHAN_BASE_DATA_PORT] = fd;
|
|
||||||
client_params.connect_port = params.connect_port;
|
|
||||||
client_params.connect_domain = remote_domain_id;
|
|
||||||
hdr->len = sizeof(client_params);
|
|
||||||
if (!write_all(fd, hdr, sizeof(*hdr))) {
|
|
||||||
terminate_client(fd);
|
|
||||||
release_vchan_port(params.connect_port, params.connect_domain);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (!write_all(fd, &client_params, sizeof(client_params))) {
|
|
||||||
terminate_client(fd);
|
|
||||||
release_vchan_port(params.connect_port, params.connect_domain);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
/* restore original len value */
|
|
||||||
hdr->len = len+sizeof(params);
|
|
||||||
} else {
|
|
||||||
assert(params.connect_port >= VCHAN_BASE_DATA_PORT);
|
|
||||||
assert(params.connect_port < VCHAN_BASE_DATA_PORT+MAX_CLIENTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!strncmp(buf, default_user_keyword, default_user_keyword_len_without_colon+1)) {
|
|
||||||
use_default_user = 1;
|
|
||||||
hdr->len -= default_user_keyword_len_without_colon;
|
|
||||||
hdr->len += strlen(default_user);
|
|
||||||
}
|
|
||||||
if (libvchan_send(vchan, hdr, sizeof(*hdr)) < 0)
|
|
||||||
handle_vchan_error("send");
|
|
||||||
if (libvchan_send(vchan, ¶ms, sizeof(params)) < 0)
|
|
||||||
handle_vchan_error("send params");
|
|
||||||
if (use_default_user) {
|
|
||||||
if (libvchan_send(vchan, default_user, strlen(default_user)) < 0)
|
|
||||||
handle_vchan_error("send default_user");
|
|
||||||
if (libvchan_send(vchan, buf+default_user_keyword_len_without_colon,
|
|
||||||
len-default_user_keyword_len_without_colon) < 0)
|
|
||||||
handle_vchan_error("send buf");
|
|
||||||
} else
|
|
||||||
if (libvchan_send(vchan, buf, len) < 0)
|
|
||||||
handle_vchan_error("send buf");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void handle_cmdline_message_from_client(int fd)
|
|
||||||
{
|
|
||||||
struct msg_header hdr;
|
|
||||||
if (!read_all(fd, &hdr, sizeof hdr)) {
|
|
||||||
terminate_client(fd);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (hdr.type) {
|
|
||||||
case MSG_EXEC_CMDLINE:
|
|
||||||
case MSG_JUST_EXEC:
|
|
||||||
case MSG_SERVICE_CONNECT:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
terminate_client(fd);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!handle_cmdline_body_from_client(fd, &hdr))
|
|
||||||
// client disconnected while sending cmdline, above call already
|
|
||||||
// cleaned up client info
|
|
||||||
return;
|
|
||||||
clients[fd].state = CLIENT_RUNNING;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void handle_client_hello(int fd)
|
|
||||||
{
|
|
||||||
struct msg_header hdr;
|
|
||||||
struct peer_info info;
|
|
||||||
|
|
||||||
if (!read_all(fd, &hdr, sizeof hdr)) {
|
|
||||||
terminate_client(fd);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) {
|
|
||||||
fprintf(stderr, "Invalid HELLO packet received from client %d: "
|
|
||||||
"type %d, len %d\n", fd, hdr.type, hdr.len);
|
|
||||||
terminate_client(fd);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!read_all(fd, &info, sizeof info)) {
|
|
||||||
terminate_client(fd);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (info.version != QREXEC_PROTOCOL_VERSION) {
|
|
||||||
fprintf(stderr, "Incompatible client protocol version (remote %d, local %d)\n", info.version, QREXEC_PROTOCOL_VERSION);
|
|
||||||
terminate_client(fd);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
clients[fd].state = CLIENT_CMDLINE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* handle data received from one of qrexec_client processes */
|
|
||||||
static void handle_message_from_client(int fd)
|
|
||||||
{
|
|
||||||
char buf[MAX_DATA_CHUNK];
|
|
||||||
|
|
||||||
switch (clients[fd].state) {
|
|
||||||
case CLIENT_HELLO:
|
|
||||||
handle_client_hello(fd);
|
|
||||||
return;
|
|
||||||
case CLIENT_CMDLINE:
|
|
||||||
handle_cmdline_message_from_client(fd);
|
|
||||||
return;
|
|
||||||
case CLIENT_RUNNING:
|
|
||||||
// expected EOF
|
|
||||||
if (read(fd, buf, sizeof(buf)) != 0) {
|
|
||||||
fprintf(stderr, "Unexpected data received from client %d\n", fd);
|
|
||||||
}
|
|
||||||
terminate_client(fd);
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
fprintf(stderr, "Invalid client state %d\n", clients[fd].state);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The signal handler executes asynchronously; therefore all it should do is
|
|
||||||
* to set a flag "signal has arrived", and let the main even loop react to this
|
|
||||||
* flag in appropriate moment.
|
|
||||||
*/
|
|
||||||
|
|
||||||
int child_exited;
|
|
||||||
|
|
||||||
static void sigchld_handler(int UNUSED(x))
|
|
||||||
{
|
|
||||||
child_exited = 1;
|
|
||||||
signal(SIGCHLD, sigchld_handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void send_service_refused(libvchan_t *vchan, struct service_params *params) {
|
|
||||||
struct msg_header hdr;
|
|
||||||
|
|
||||||
hdr.type = MSG_SERVICE_REFUSED;
|
|
||||||
hdr.len = sizeof(*params);
|
|
||||||
|
|
||||||
if (libvchan_send(vchan, &hdr, sizeof(hdr)) != sizeof(hdr)) {
|
|
||||||
fprintf(stderr, "Failed to send MSG_SERVICE_REFUSED hdr to agent\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (libvchan_send(vchan, params, sizeof(*params)) != sizeof(*params)) {
|
|
||||||
fprintf(stderr, "Failed to send MSG_SERVICE_REFUSED to agent\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* clean zombies, check for denied service calls */
|
|
||||||
static void reap_children()
|
|
||||||
{
|
|
||||||
int status;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
pid_t pid;
|
|
||||||
while ((pid=waitpid(-1, &status, WNOHANG)) > 0) {
|
|
||||||
for (i = 0; i <= policy_pending_max; i++) {
|
|
||||||
if (policy_pending[i].pid == pid) {
|
|
||||||
status = WEXITSTATUS(status);
|
|
||||||
if (status != 0) {
|
|
||||||
send_service_refused(vchan, &policy_pending[i].params);
|
|
||||||
}
|
|
||||||
/* in case of allowed calls, we will do the rest in
|
|
||||||
* MSG_SERVICE_CONNECT from client handler */
|
|
||||||
policy_pending[i].pid = 0;
|
|
||||||
while (policy_pending_max > 0 &&
|
|
||||||
policy_pending[policy_pending_max].pid == 0)
|
|
||||||
policy_pending_max--;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
child_exited = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int find_policy_pending_slot() {
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < MAX_CLIENTS; i++) {
|
|
||||||
if (policy_pending[i].pid == 0) {
|
|
||||||
if (i > policy_pending_max)
|
|
||||||
policy_pending_max = i;
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sanitize_name(char * untrusted_s_signed, char *extra_allowed_chars)
|
|
||||||
{
|
|
||||||
unsigned char * untrusted_s;
|
|
||||||
for (untrusted_s=(unsigned char*)untrusted_s_signed; *untrusted_s; untrusted_s++) {
|
|
||||||
if (*untrusted_s >= 'a' && *untrusted_s <= 'z')
|
|
||||||
continue;
|
|
||||||
if (*untrusted_s >= 'A' && *untrusted_s <= 'Z')
|
|
||||||
continue;
|
|
||||||
if (*untrusted_s >= '0' && *untrusted_s <= '9')
|
|
||||||
continue;
|
|
||||||
if (*untrusted_s == '_' ||
|
|
||||||
*untrusted_s == '-' ||
|
|
||||||
*untrusted_s == '.')
|
|
||||||
continue;
|
|
||||||
if (extra_allowed_chars && strchr(extra_allowed_chars, *untrusted_s))
|
|
||||||
continue;
|
|
||||||
*untrusted_s = '_';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#define ENSURE_NULL_TERMINATED(x) x[sizeof(x)-1] = 0
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Called when agent sends a message asking to execute a predefined command.
|
|
||||||
*/
|
|
||||||
|
|
||||||
static void handle_execute_service(void)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
int policy_pending_slot;
|
|
||||||
pid_t pid;
|
|
||||||
struct trigger_service_params untrusted_params, params;
|
|
||||||
char remote_domain_id_str[10];
|
|
||||||
|
|
||||||
if (libvchan_recv(vchan, &untrusted_params, sizeof(untrusted_params)) < 0)
|
|
||||||
handle_vchan_error("recv params");
|
|
||||||
|
|
||||||
/* sanitize start */
|
|
||||||
ENSURE_NULL_TERMINATED(untrusted_params.service_name);
|
|
||||||
ENSURE_NULL_TERMINATED(untrusted_params.target_domain);
|
|
||||||
ENSURE_NULL_TERMINATED(untrusted_params.request_id.ident);
|
|
||||||
sanitize_name(untrusted_params.service_name, "+");
|
|
||||||
sanitize_name(untrusted_params.target_domain, "@:");
|
|
||||||
sanitize_name(untrusted_params.request_id.ident, " ");
|
|
||||||
params = untrusted_params;
|
|
||||||
/* sanitize end */
|
|
||||||
|
|
||||||
policy_pending_slot = find_policy_pending_slot();
|
|
||||||
if (policy_pending_slot < 0) {
|
|
||||||
fprintf(stderr, "Service request denied, too many pending requests\n");
|
|
||||||
send_service_refused(vchan, &untrusted_params.request_id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (pid=fork()) {
|
|
||||||
case -1:
|
|
||||||
perror("fork");
|
|
||||||
exit(1);
|
|
||||||
case 0:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
policy_pending[policy_pending_slot].pid = pid;
|
|
||||||
policy_pending[policy_pending_slot].params = untrusted_params.request_id;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (i = 3; i < MAX_FDS; i++)
|
|
||||||
close(i);
|
|
||||||
signal(SIGCHLD, SIG_DFL);
|
|
||||||
signal(SIGPIPE, SIG_DFL);
|
|
||||||
snprintf(remote_domain_id_str, sizeof(remote_domain_id_str), "%d",
|
|
||||||
remote_domain_id);
|
|
||||||
execl("/usr/bin/qrexec-policy", "qrexec-policy", "--",
|
|
||||||
remote_domain_id_str, remote_domain_name, params.target_domain,
|
|
||||||
params.service_name, params.request_id.ident, NULL);
|
|
||||||
perror("execl");
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void handle_connection_terminated()
|
|
||||||
{
|
|
||||||
struct exec_params untrusted_params, params;
|
|
||||||
|
|
||||||
if (libvchan_recv(vchan, &untrusted_params, sizeof(untrusted_params)) < 0)
|
|
||||||
handle_vchan_error("recv params");
|
|
||||||
/* sanitize start */
|
|
||||||
if (untrusted_params.connect_port < VCHAN_BASE_DATA_PORT ||
|
|
||||||
untrusted_params.connect_port >= VCHAN_BASE_DATA_PORT+MAX_CLIENTS) {
|
|
||||||
fprintf(stderr, "Invalid port in MSG_CONNECTION_TERMINATED (%d)\n",
|
|
||||||
untrusted_params.connect_port);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
/* untrusted_params.connect_domain even if invalid will not harm - in worst
|
|
||||||
* case the port will not be released */
|
|
||||||
params = untrusted_params;
|
|
||||||
/* sanitize end */
|
|
||||||
release_vchan_port(params.connect_port, params.connect_domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sanitize_message_from_agent(struct msg_header *untrusted_header)
|
|
||||||
{
|
|
||||||
switch (untrusted_header->type) {
|
|
||||||
case MSG_TRIGGER_SERVICE:
|
|
||||||
if (untrusted_header->len != sizeof(struct trigger_service_params)) {
|
|
||||||
fprintf(stderr, "agent sent invalid MSG_TRIGGER_SERVICE packet\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MSG_CONNECTION_TERMINATED:
|
|
||||||
if (untrusted_header->len != sizeof(struct exec_params)) {
|
|
||||||
fprintf(stderr, "agent sent invalid MSG_CONNECTION_TERMINATED packet\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fprintf(stderr, "unknown mesage type 0x%x from agent\n",
|
|
||||||
untrusted_header->type);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void handle_message_from_agent(void)
|
|
||||||
{
|
|
||||||
struct msg_header hdr, untrusted_hdr;
|
|
||||||
|
|
||||||
if (libvchan_recv(vchan, &untrusted_hdr, sizeof(untrusted_hdr)) < 0)
|
|
||||||
handle_vchan_error("recv hdr");
|
|
||||||
/* sanitize start */
|
|
||||||
sanitize_message_from_agent(&untrusted_hdr);
|
|
||||||
hdr = untrusted_hdr;
|
|
||||||
/* sanitize end */
|
|
||||||
|
|
||||||
// fprintf(stderr, "got %x %x %x\n", hdr.type, hdr.client_id,
|
|
||||||
// hdr.len);
|
|
||||||
|
|
||||||
switch (hdr.type) {
|
|
||||||
case MSG_TRIGGER_SERVICE:
|
|
||||||
handle_execute_service();
|
|
||||||
return;
|
|
||||||
case MSG_CONNECTION_TERMINATED:
|
|
||||||
handle_connection_terminated();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Scan the "clients" table, add ones we want to read from (because the other
|
|
||||||
* end has not send MSG_XOFF on them) to read_fdset, add ones we want to write
|
|
||||||
* to (because its pipe is full) to write_fdset. Return the highest used file
|
|
||||||
* descriptor number, needed for the first select() parameter.
|
|
||||||
*/
|
|
||||||
static int fill_fdsets_for_select(fd_set * read_fdset, fd_set * write_fdset)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
int max = -1;
|
|
||||||
FD_ZERO(read_fdset);
|
|
||||||
FD_ZERO(write_fdset);
|
|
||||||
for (i = 0; i <= max_client_fd; i++) {
|
|
||||||
if (clients[i].state != CLIENT_INVALID) {
|
|
||||||
FD_SET(i, read_fdset);
|
|
||||||
max = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FD_SET(qrexec_daemon_unix_socket_fd, read_fdset);
|
|
||||||
if (qrexec_daemon_unix_socket_fd > max)
|
|
||||||
max = qrexec_daemon_unix_socket_fd;
|
|
||||||
return max;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
fd_set read_fdset, write_fdset;
|
|
||||||
int i, opt;
|
|
||||||
int max;
|
|
||||||
sigset_t chld_set;
|
|
||||||
|
|
||||||
while ((opt=getopt(argc, argv, "q")) != -1) {
|
|
||||||
switch (opt) {
|
|
||||||
case 'q':
|
|
||||||
opt_quiet = 1;
|
|
||||||
break;
|
|
||||||
default: /* '?' */
|
|
||||||
fprintf(stderr, "usage: %s [-q] domainid domain-name [default user]\n", argv[0]);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (argc - optind < 2 || argc - optind > 3) {
|
|
||||||
fprintf(stderr, "usage: %s [-q] domainid domain-name [default user]\n", argv[0]);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
remote_domain_id = atoi(argv[optind]);
|
|
||||||
remote_domain_name = argv[optind+1];
|
|
||||||
if (argc - optind >= 3)
|
|
||||||
default_user = argv[optind+2];
|
|
||||||
init(remote_domain_id);
|
|
||||||
sigemptyset(&chld_set);
|
|
||||||
sigaddset(&chld_set, SIGCHLD);
|
|
||||||
signal(SIGCHLD, sigchld_handler);
|
|
||||||
/*
|
|
||||||
* The main event loop. Waits for one of the following events:
|
|
||||||
* - message from client
|
|
||||||
* - message from agent
|
|
||||||
* - new client
|
|
||||||
* - child exited
|
|
||||||
*/
|
|
||||||
for (;;) {
|
|
||||||
max = fill_fdsets_for_select(&read_fdset, &write_fdset);
|
|
||||||
if (libvchan_buffer_space(vchan) <= (int)sizeof(struct msg_header))
|
|
||||||
FD_ZERO(&read_fdset); // vchan full - don't read from clients
|
|
||||||
|
|
||||||
sigprocmask(SIG_BLOCK, &chld_set, NULL);
|
|
||||||
if (child_exited)
|
|
||||||
reap_children();
|
|
||||||
wait_for_vchan_or_argfd(vchan, max, &read_fdset, &write_fdset);
|
|
||||||
sigprocmask(SIG_UNBLOCK, &chld_set, NULL);
|
|
||||||
|
|
||||||
if (FD_ISSET(qrexec_daemon_unix_socket_fd, &read_fdset))
|
|
||||||
handle_new_client();
|
|
||||||
|
|
||||||
while (libvchan_data_ready(vchan))
|
|
||||||
handle_message_from_agent();
|
|
||||||
|
|
||||||
for (i = 0; i <= max_client_fd; i++)
|
|
||||||
if (clients[i].state != CLIENT_INVALID
|
|
||||||
&& FD_ISSET(i, &read_fdset))
|
|
||||||
handle_message_from_client(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// vim:ts=4:sw=4:et:
|
|
@ -1,45 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
mkfifo /tmp/qrexec-rpc-stderr.$$
|
|
||||||
logger -t "$1-$2" -f /tmp/qrexec-rpc-stderr.$$ >/dev/null 2>&1 </dev/null &
|
|
||||||
exec 2>/tmp/qrexec-rpc-stderr.$$
|
|
||||||
rm -f /tmp/qrexec-rpc-stderr.$$
|
|
||||||
|
|
||||||
QUBES_RPC=/etc/qubes-rpc
|
|
||||||
LOCAL_QUBES_RPC=/usr/local/etc/qubes-rpc
|
|
||||||
|
|
||||||
if ! [ $# = 2 -o $# = 4 ] ; then
|
|
||||||
echo "$0: bad argument count, usage: $0 SERVICE-NAME REMOTE-DOMAIN-NAME [REQUESTED_TARGET_TYPE REQUESTED_TARGET]" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
export QREXEC_REQUESTED_TARGET_TYPE="$3"
|
|
||||||
if [ "$QREXEC_REQUESTED_TARGET_TYPE" = "name" ]; then
|
|
||||||
export QREXEC_REQUESTED_TARGET="$4"
|
|
||||||
elif [ "$QREXEC_REQUESTED_TARGET_TYPE" = "keyword" ]; then
|
|
||||||
export QREXEC_REQUESTED_TARGET_KEYWORD="$4"
|
|
||||||
fi
|
|
||||||
# else: requested target type unknown or not given, ignore
|
|
||||||
export QREXEC_REMOTE_DOMAIN="$2"
|
|
||||||
export QREXEC_SERVICE_FULL_NAME="$1"
|
|
||||||
SERVICE_WITHOUT_ARGUMENT="${1%%+*}"
|
|
||||||
if [ "${QREXEC_SERVICE_FULL_NAME}" != "${SERVICE_WITHOUT_ARGUMENT}" ]; then
|
|
||||||
export QREXEC_SERVICE_ARGUMENT="${QREXEC_SERVICE_FULL_NAME#*+}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
for CFG_FILE in $LOCAL_QUBES_RPC/"$1" $QUBES_RPC/"$1" \
|
|
||||||
$LOCAL_QUBES_RPC/"${SERVICE_WITHOUT_ARGUMENT}" \
|
|
||||||
$QUBES_RPC/"${SERVICE_WITHOUT_ARGUMENT}"; do
|
|
||||||
if [ -s "$CFG_FILE" ]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -x "$CFG_FILE" ] ; then
|
|
||||||
exec "$CFG_FILE" ${QREXEC_SERVICE_ARGUMENT}
|
|
||||||
echo "$0: failed to execute handler for" "$1" >&2
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
exec /bin/sh -- "$CFG_FILE" ${QREXEC_SERVICE_ARGUMENT}
|
|
||||||
echo "$0: failed to execute handler for" "$1" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
@ -39,10 +39,10 @@ URL: http://www.qubes-os.org
|
|||||||
BuildRequires: ImageMagick
|
BuildRequires: ImageMagick
|
||||||
BuildRequires: pandoc
|
BuildRequires: pandoc
|
||||||
BuildRequires: qubes-utils-devel >= 3.1.3
|
BuildRequires: qubes-utils-devel >= 3.1.3
|
||||||
BuildRequires: qubes-libvchan-devel
|
|
||||||
BuildRequires: gcc
|
BuildRequires: gcc
|
||||||
Requires: qubes-core-dom0
|
Requires: qubes-core-dom0
|
||||||
Requires: python3-qubesadmin
|
Requires: python3-qubesadmin
|
||||||
|
Requires: qubes-core-qrexec-dom0
|
||||||
Requires: qubes-core-admin-client
|
Requires: qubes-core-admin-client
|
||||||
Requires: qubes-utils >= 3.1.3
|
Requires: qubes-utils >= 3.1.3
|
||||||
Requires: qubes-utils-libs >= 4.0.16
|
Requires: qubes-utils-libs >= 4.0.16
|
||||||
@ -75,7 +75,6 @@ Kernel install hook for Xen-based system.
|
|||||||
%build
|
%build
|
||||||
export BACKEND_VMM=@BACKEND_VMM@
|
export BACKEND_VMM=@BACKEND_VMM@
|
||||||
(cd dom0-updates; make)
|
(cd dom0-updates; make)
|
||||||
(cd qrexec; make)
|
|
||||||
(cd file-copy-vm; make)
|
(cd file-copy-vm; make)
|
||||||
(cd doc; make manpages)
|
(cd doc; make manpages)
|
||||||
|
|
||||||
@ -97,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
|
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
|
### pm-utils
|
||||||
mkdir -p $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d
|
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/
|
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/*
|
%{_dracutmoddir}/90qubes-pciback/*
|
||||||
%dir %{_dracutmoddir}/90extra-modules
|
%dir %{_dracutmoddir}/90extra-modules
|
||||||
%{_dracutmoddir}/90extra-modules/*
|
%{_dracutmoddir}/90extra-modules/*
|
||||||
# Qrexec
|
|
||||||
/usr/sbin/qrexec-daemon
|
|
||||||
/usr/bin/qrexec-client
|
|
||||||
/usr/lib/qubes/qubes-rpc-multiplexer
|
|
||||||
# compat symlinks
|
|
||||||
/usr/lib/qubes/qrexec-client
|
|
||||||
/usr/lib/qubes/qrexec-daemon
|
|
||||||
# file copy
|
# file copy
|
||||||
/usr/bin/qvm-copy-to-vm
|
/usr/bin/qvm-copy-to-vm
|
||||||
/usr/bin/qvm-move-to-vm
|
/usr/bin/qvm-move-to-vm
|
||||||
|
Loading…
Reference in New Issue
Block a user