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: 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…
Reference in New Issue
Block a user