Remove qrexec - moved to separate package

This commit is contained in:
Marek Marczykowski 2013-03-06 18:42:00 +01:00
parent 325cf4b894
commit 3c3252b2a3
18 changed files with 1 additions and 2764 deletions

4
qrexec/.gitignore vendored
View File

@ -1,4 +0,0 @@
qrexec_agent
qrexec_client
qrexec_daemon
qrexec_client_vm

View File

@ -1,16 +0,0 @@
CC=gcc
CFLAGS+=-g -Wall -I../vchan -I../qubes_rpc -pie -fPIC
XENLIBS=-lvchan -lu2mfn -lxenstore -lxenctrl
COMMONIOALL=../qubes_rpc/ioall.o
all: qrexec_daemon qrexec_agent qrexec_client qrexec_client_vm
qrexec_daemon: qrexec_daemon.o unix_server.o $(COMMONIOALL) txrx-vchan.o buffer.o write_stdin.o
$(CC) -pie -L../vchan -L../u2mfn -g -o qrexec_daemon qrexec_daemon.o unix_server.o $(COMMONIOALL) txrx-vchan.o write_stdin.o buffer.o $(XENLIBS)
qrexec_agent: qrexec_agent.o unix_server.o exec.o txrx-vchan.o write_stdin.o buffer.o $(COMMONIOALL)
$(CC) -pie -L../vchan -L../u2mfn -g -o qrexec_agent qrexec_agent.o unix_server.o exec.o txrx-vchan.o write_stdin.o buffer.o $(COMMONIOALL) $(XENLIBS)
qrexec_client: qrexec_client.o $(COMMONIOALL) exec.o
$(CC) -pie -g -o qrexec_client qrexec_client.o $(COMMONIOALL) exec.o
qrexec_client_vm: qrexec_client_vm.o
$(CC) -pie -g -o qrexec_client_vm qrexec_client_vm.o
clean:
rm -f *.o *~ qrexec_daemon qrexec_agent qrexec_client qrexec_client_vm

View File

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

View File

@ -1,99 +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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "buffer.h"
#define BUFFER_LIMIT 50000000
static int total_mem;
static char *limited_malloc(int len)
{
char *ret;
total_mem += len;
if (total_mem > BUFFER_LIMIT) {
fprintf(stderr, "attempt to allocate >BUFFER_LIMIT\n");
exit(1);
}
ret = malloc(len);
if (!ret) {
perror("malloc");
exit(1);
}
return ret;
}
static void limited_free(char *ptr, int len)
{
free(ptr);
total_mem -= len;
}
void buffer_init(struct buffer *b)
{
b->buflen = 0;
b->data = NULL;
}
void buffer_free(struct buffer *b)
{
if (b->buflen)
limited_free(b->data, b->buflen);
buffer_init(b);
}
/*
The following two functions can be made much more efficient.
Yet the profiling output show they are not significant CPU hogs, so
we keep them so simple to make them obviously correct.
*/
void buffer_append(struct buffer *b, char *data, int len)
{
int newsize = len + b->buflen;
char *qdata = limited_malloc(len + b->buflen);
memcpy(qdata, b->data, b->buflen);
memcpy(qdata + b->buflen, data, len);
buffer_free(b);
b->buflen = newsize;
b->data = qdata;
}
void buffer_remove(struct buffer *b, int len)
{
int newsize = b->buflen - len;
char *qdata = limited_malloc(newsize);
memcpy(qdata, b->data + len, newsize);
buffer_free(b);
b->buflen = newsize;
b->data = qdata;
}
int buffer_len(struct buffer *b)
{
return b->buflen;
}
void *buffer_data(struct buffer *b)
{
return b->data;
}

View File

@ -1,32 +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.
*
*/
struct buffer {
char *data;
int buflen;
};
void buffer_init(struct buffer *b);
void buffer_free(struct buffer *b);
void buffer_append(struct buffer *b, char *data, int len);
void buffer_remove(struct buffer *b, int len);
int buffer_len(struct buffer *b);
void *buffer_data(struct buffer *b);

View File

@ -1,74 +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 <stdlib.h>
#include <unistd.h>
#include <stdio.h>
extern void do_exec(char *);
void fix_fds(int fdin, int fdout, int fderr)
{
int i;
for (i = 0; i < 256; i++)
if (i != fdin && i != fdout && i != fderr)
close(i);
dup2(fdin, 0);
dup2(fdout, 1);
dup2(fderr, 2);
close(fdin);
close(fdout);
if (fderr != 2)
close(fderr);
}
void do_fork_exec(char *cmdline, int *pid, int *stdin_fd, int *stdout_fd,
int *stderr_fd)
{
int inpipe[2], outpipe[2], errpipe[2];
if (pipe(inpipe) || pipe(outpipe) || (stderr_fd && pipe(errpipe))) {
perror("pipe");
exit(1);
}
switch (*pid = fork()) {
case -1:
perror("fork");
exit(-1);
case 0:
if (stderr_fd) {
fix_fds(inpipe[0], outpipe[1], errpipe[1]);
} else
fix_fds(inpipe[0], outpipe[1], 2);
do_exec(cmdline);
exit(-1);
default:;
}
close(inpipe[0]);
close(outpipe[1]);
*stdin_fd = inpipe[1];
*stdout_fd = outpipe[0];
if (stderr_fd) {
close(errpipe[1]);
*stderr_fd = errpipe[0];
}
}

View File

@ -1,48 +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>
void do_fork_exec(char *cmdline, int *pid, int *stdin_fd, int *stdout_fd,
int *stderr_fd);
int peer_server_init(int port);
char *peer_client_init(int dom, int port);
void wait_for_vchan_or_argfd(int max, fd_set * rdset, fd_set * wrset);
int read_ready_vchan_ext();
int read_all_vchan_ext(void *buf, int size);
int write_all_vchan_ext(void *buf, int size);
int buffer_space_vchan_ext();
void fix_fds(int fdin, int fdout, int fderr);
int get_server_socket(char *);
int do_accept(int s);
enum {
WRITE_STDIN_OK = 0x200,
WRITE_STDIN_BUFFERED,
WRITE_STDIN_ERROR
};
int flush_client_data(int fd, int client_id, struct buffer *buffer);
int write_stdin(int fd, int client_id, char *data, int len,
struct buffer *buffer);
void set_nonblock(int fd);
int fork_and_flush_stdin(int fd, struct buffer *buffer);

View File

@ -1,106 +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.
*
*/
/* See also http://wiki.qubes-os.org/trac/wiki/Qrexec */
#define QREXEC_DAEMON_SOCKET_DIR "/var/run/qubes"
#define MAX_FDS 256
#define MAX_DATA_CHUNK 4096
#define REXEC_PORT 512
#define QREXEC_AGENT_TRIGGER_PATH "/var/run/qubes/qrexec_agent"
#define QREXEC_AGENT_FDPASS_PATH "/var/run/qubes/qrexec_agent_fdpass"
#define MEMINFO_WRITER_PIDFILE "/var/run/meminfo-writer.pid"
#define QUBES_RPC_MULTIPLEXER_PATH "/usr/lib/qubes/qubes_rpc_multiplexer"
#define QUBES_RPC_MAGIC_CMD "QUBESRPC"
enum {
/* messages from qrexec_client to qrexec_daemon (both in dom0) */
/* start process in VM and pass its stdin/out/err to dom0 */
MSG_CLIENT_TO_SERVER_EXEC_CMDLINE = 0x100,
/* start process in VM discarding its stdin/out/err (connect to /dev/null) */
MSG_CLIENT_TO_SERVER_JUST_EXEC,
/* connect to existing process in VM to receive its stdin/out/err
* struct connect_existing_params passed as data */
MSG_CLIENT_TO_SERVER_CONNECT_EXISTING,
/* messages qrexec_daemon(dom0)->qrexec_agent(VM) */
/* same as MSG_CLIENT_TO_SERVER_CONNECT_EXISTING */
MSG_SERVER_TO_AGENT_CONNECT_EXISTING,
/* same as MSG_CLIENT_TO_SERVER_EXEC_CMDLINE */
MSG_SERVER_TO_AGENT_EXEC_CMDLINE,
/* same as MSG_CLIENT_TO_SERVER_JUST_EXEC */
MSG_SERVER_TO_AGENT_JUST_EXEC,
/* pass data to process stdin */
MSG_SERVER_TO_AGENT_INPUT,
/* detach from process; qrexec_agent should close pipes to process
* stdin/out/err; it's up to the VM child process if it cause its termination */
MSG_SERVER_TO_AGENT_CLIENT_END,
/* flow control, qrexec_daemon->qrexec_agent */
/* suspend reading of named fd from child process */
MSG_XOFF,
/* resume reading of named fd from child process */
MSG_XON,
/* messages qrexec_agent(VM)->qrexec_daemon(dom0) */
/* pass data from process stdout */
MSG_AGENT_TO_SERVER_STDOUT,
/* pass data from process stderr */
MSG_AGENT_TO_SERVER_STDERR,
/* inform that process terminated and pass its exit code; this should be
* send after all data from stdout/err are send */
MSG_AGENT_TO_SERVER_EXIT_CODE,
/* call Qubes RPC service
* struct trigger_connect_params passed as data */
MSG_AGENT_TO_SERVER_TRIGGER_CONNECT_EXISTING,
/* messages qrexec_daemon->qrexec_client (both in dom0) */
/* same as MSG_AGENT_TO_SERVER_STDOUT */
MSG_SERVER_TO_CLIENT_STDOUT,
/* same as MSG_AGENT_TO_SERVER_STDERR */
MSG_SERVER_TO_CLIENT_STDERR,
/* same as MSG_AGENT_TO_SERVER_EXIT_CODE */
MSG_SERVER_TO_CLIENT_EXIT_CODE
};
struct server_header {
unsigned int type;
unsigned int client_id;
unsigned int len;
};
struct client_header {
unsigned int type;
unsigned int len;
};
struct connect_existing_params {
char ident[32];
};
struct trigger_connect_params {
char exec_index[64];
char target_vmname[32];
struct connect_existing_params process_fds;
};

View File

@ -1,595 +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 <signal.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>
#include <sys/stat.h>
#include "qrexec.h"
#include "buffer.h"
#include "glue.h"
enum fdtype {
FDTYPE_INVALID,
FDTYPE_STDOUT,
FDTYPE_STDERR
};
struct _process_fd {
int client_id;
int type;
int is_blocked;
};
struct _client_info {
int stdin_fd;
int stdout_fd;
int stderr_fd;
int exit_status;
int is_exited;
int pid;
int is_blocked;
int is_close_after_flush_needed;
struct buffer buffer;
};
int max_process_fd = -1;
/* indexed by file descriptor */
struct _process_fd process_fd[MAX_FDS];
/* indexed by client id, which is descriptor number of a client in daemon */
struct _client_info client_info[MAX_FDS];
int trigger_fd;
int passfd_socket;
int meminfo_write_started = 0;
void init()
{
peer_server_init(REXEC_PORT);
umask(0);
mkfifo(QREXEC_AGENT_TRIGGER_PATH, 0666);
passfd_socket = get_server_socket(QREXEC_AGENT_FDPASS_PATH);
umask(077);
trigger_fd =
open(QREXEC_AGENT_TRIGGER_PATH, O_RDONLY | O_NONBLOCK);
}
void wake_meminfo_writer() {
FILE *f;
pid_t pid;
if (meminfo_write_started)
/* wake meminfo-writer only once */
return;
f = fopen(MEMINFO_WRITER_PIDFILE, "r");
if (f == NULL) {
/* no meminfo-writer found, ignoring */
return;
}
if (fscanf(f, "%d", &pid) < 1) {
/* no meminfo-writer found, ignoring */
return;
}
fclose(f);
kill(pid, SIGUSR1);
meminfo_write_started = 1;
}
void no_colon_in_cmd()
{
fprintf(stderr,
"cmdline is supposed to be in user:command form\n");
exit(1);
}
void do_exec(char *cmd)
{
char buf[strlen(QUBES_RPC_MULTIPLEXER_PATH) + strlen(cmd) - strlen(QUBES_RPC_MAGIC_CMD) + 1];
char *realcmd = index(cmd, ':');
if (!realcmd)
no_colon_in_cmd();
/* mark end of username and move to command */
*realcmd = 0;
realcmd++;
/* ignore "nogui:" prefix in linux agent */
if (strncmp(realcmd, "nogui:", 6) == 0)
realcmd+=6;
/* replace magic RPC cmd with RPC multiplexer path */
if (strncmp(realcmd, QUBES_RPC_MAGIC_CMD " ", strlen(QUBES_RPC_MAGIC_CMD)+1)==0) {
strcpy(buf, QUBES_RPC_MULTIPLEXER_PATH);
strcpy(buf + strlen(QUBES_RPC_MULTIPLEXER_PATH), realcmd + strlen(QUBES_RPC_MAGIC_CMD));
realcmd = buf;
}
signal(SIGCHLD, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
execl("/bin/su", "su", "-", cmd, "-c", realcmd, NULL);
perror("execl");
exit(1);
}
void handle_just_exec(int client_id, int len)
{
char buf[len];
int fdn, pid;
read_all_vchan_ext(buf, len);
switch (pid = fork()) {
case -1:
perror("fork");
exit(1);
case 0:
fdn = open("/dev/null", O_RDWR);
fix_fds(fdn, fdn, fdn);
do_exec(buf);
perror("execl");
exit(1);
default:;
}
fprintf(stderr, "executed (nowait) %s pid %d\n", buf, pid);
}
void create_info_about_client(int client_id, int pid, int stdin_fd,
int stdout_fd, int stderr_fd)
{
process_fd[stdout_fd].client_id = client_id;
process_fd[stdout_fd].type = FDTYPE_STDOUT;
process_fd[stdout_fd].is_blocked = 0;
process_fd[stderr_fd].client_id = client_id;
process_fd[stderr_fd].type = FDTYPE_STDERR;
process_fd[stderr_fd].is_blocked = 0;
if (stderr_fd > max_process_fd)
max_process_fd = stderr_fd;
if (stdout_fd > max_process_fd)
max_process_fd = stdout_fd;
set_nonblock(stdin_fd);
client_info[client_id].stdin_fd = stdin_fd;
client_info[client_id].stdout_fd = stdout_fd;
client_info[client_id].stderr_fd = stderr_fd;
client_info[client_id].exit_status = 0;
client_info[client_id].is_exited = 0;
client_info[client_id].pid = pid;
client_info[client_id].is_blocked = 0;
client_info[client_id].is_close_after_flush_needed = 0;
buffer_init(&client_info[client_id].buffer);
}
void handle_exec(int client_id, int len)
{
char buf[len];
int pid, stdin_fd, stdout_fd, stderr_fd;
read_all_vchan_ext(buf, len);
do_fork_exec(buf, &pid, &stdin_fd, &stdout_fd, &stderr_fd);
create_info_about_client(client_id, pid, stdin_fd, stdout_fd,
stderr_fd);
fprintf(stderr, "executed %s pid %d\n", buf, pid);
}
void handle_connect_existing(int client_id, int len)
{
int stdin_fd, stdout_fd, stderr_fd;
char buf[len];
read_all_vchan_ext(buf, len);
sscanf(buf, "%d %d %d", &stdin_fd, &stdout_fd, &stderr_fd);
create_info_about_client(client_id, -1, stdin_fd, stdout_fd,
stderr_fd);
client_info[client_id].is_exited = 1; //do not wait for SIGCHLD
}
void update_max_process_fd()
{
int i;
for (i = max_process_fd;
process_fd[i].type == FDTYPE_INVALID && i >= 0; i--);
max_process_fd = i;
}
void send_exit_code(int client_id, int status)
{
struct server_header s_hdr;
s_hdr.type = MSG_AGENT_TO_SERVER_EXIT_CODE;
s_hdr.client_id = client_id;
s_hdr.len = sizeof status;
write_all_vchan_ext(&s_hdr, sizeof s_hdr);
write_all_vchan_ext(&status, sizeof(status));
fprintf(stderr, "send exit code %d for client_id %d pid %d\n",
status, client_id, client_info[client_id].pid);
}
// erase process data structures, possibly forced by remote
void remove_process(int client_id, int status)
{
int i;
if (!client_info[client_id].pid)
return;
if (client_info[client_id].stdin_fd >= 0)
fork_and_flush_stdin(client_info[client_id].stdin_fd,
&client_info[client_id].buffer);
#if 0
// let's let it die by itself, possibly after it has received buffered stdin
kill(client_info[client_id].pid, SIGKILL);
#endif
if (status != -1)
send_exit_code(client_id, status);
close(client_info[client_id].stdin_fd);
client_info[client_id].pid = 0;
client_info[client_id].stdin_fd = -1;
client_info[client_id].is_blocked = 0;
buffer_free(&client_info[client_id].buffer);
for (i = 0; i <= max_process_fd; i++)
if (process_fd[i].type != FDTYPE_INVALID
&& process_fd[i].client_id == client_id) {
process_fd[i].type = FDTYPE_INVALID;
process_fd[i].client_id = -1;
process_fd[i].is_blocked = 0;
close(i);
}
update_max_process_fd();
}
// remove process not immediately after it has exited, but after its stdout and stderr has been drained
// previous method implemented in flush_out_err was broken - it cannot work when peer signalled it is blocked
void possibly_remove_process(int client_id)
{
if (client_info[client_id].stdout_fd == -1 &&
client_info[client_id].stderr_fd == -1 &&
client_info[client_id].is_exited)
remove_process(client_id,
client_info[client_id].exit_status);
}
void handle_input(int client_id, int len)
{
char buf[len];
read_all_vchan_ext(buf, len);
if (!client_info[client_id].pid || client_info[client_id].stdin_fd == -1)
return;
if (len == 0) {
if (client_info[client_id].is_blocked)
client_info[client_id].is_close_after_flush_needed
= 1;
else {
close(client_info[client_id].stdin_fd);
client_info[client_id].stdin_fd = -1;
}
return;
}
switch (write_stdin
(client_info[client_id].stdin_fd, client_id, buf, len,
&client_info[client_id].buffer)) {
case WRITE_STDIN_OK:
break;
case WRITE_STDIN_BUFFERED:
client_info[client_id].is_blocked = 1;
break;
case WRITE_STDIN_ERROR:
// do not remove process, as it still can write data to stdout
close(client_info[client_id].stdin_fd);
client_info[client_id].stdin_fd = -1;
client_info[client_id].is_blocked = 0;
break;
default:
fprintf(stderr, "unknown write_stdin?\n");
exit(1);
}
}
void set_blocked_outerr(int client_id, int val)
{
process_fd[client_info[client_id].stdout_fd].is_blocked = val;
process_fd[client_info[client_id].stderr_fd].is_blocked = val;
}
void handle_server_data()
{
struct server_header s_hdr;
read_all_vchan_ext(&s_hdr, sizeof s_hdr);
// fprintf(stderr, "got %x %x %x\n", s_hdr.type, s_hdr.client_id,
// s_hdr.len);
switch (s_hdr.type) {
case MSG_XON:
set_blocked_outerr(s_hdr.client_id, 0);
break;
case MSG_XOFF:
set_blocked_outerr(s_hdr.client_id, 1);
break;
case MSG_SERVER_TO_AGENT_CONNECT_EXISTING:
handle_connect_existing(s_hdr.client_id, s_hdr.len);
break;
case MSG_SERVER_TO_AGENT_EXEC_CMDLINE:
wake_meminfo_writer();
handle_exec(s_hdr.client_id, s_hdr.len);
break;
case MSG_SERVER_TO_AGENT_JUST_EXEC:
wake_meminfo_writer();
handle_just_exec(s_hdr.client_id, s_hdr.len);
break;
case MSG_SERVER_TO_AGENT_INPUT:
handle_input(s_hdr.client_id, s_hdr.len);
break;
case MSG_SERVER_TO_AGENT_CLIENT_END:
remove_process(s_hdr.client_id, -1);
break;
default:
fprintf(stderr, "msg type from daemon is %d ?\n",
s_hdr.type);
exit(1);
}
}
void handle_process_data(int fd)
{
struct server_header s_hdr;
char buf[MAX_DATA_CHUNK];
int ret;
int len;
len = buffer_space_vchan_ext();
if (len <= sizeof s_hdr)
return;
ret = read(fd, buf, len - sizeof s_hdr);
s_hdr.client_id = process_fd[fd].client_id;
if (process_fd[fd].type == FDTYPE_STDOUT)
s_hdr.type = MSG_AGENT_TO_SERVER_STDOUT;
else if (process_fd[fd].type == FDTYPE_STDERR)
s_hdr.type = MSG_AGENT_TO_SERVER_STDERR;
else {
fprintf(stderr, "fd=%d, client_id=%d, type=%d ?\n", fd,
process_fd[fd].client_id, process_fd[fd].type);
exit(1);
}
s_hdr.len = ret;
if (ret >= 0) {
write_all_vchan_ext(&s_hdr, sizeof s_hdr);
write_all_vchan_ext(buf, ret);
}
if (ret == 0) {
int client_id = process_fd[fd].client_id;
if (process_fd[fd].type == FDTYPE_STDOUT)
client_info[client_id].stdout_fd = -1;
else
client_info[client_id].stderr_fd = -1;
process_fd[fd].type = FDTYPE_INVALID;
process_fd[fd].client_id = -1;
process_fd[fd].is_blocked = 0;
close(fd);
update_max_process_fd();
possibly_remove_process(client_id);
}
if (ret < 0)
remove_process(process_fd[fd].client_id, 127);
}
volatile int child_exited;
void sigchld_handler(int x)
{
child_exited = 1;
signal(SIGCHLD, sigchld_handler);
}
int find_info(int pid)
{
int i;
for (i = 0; i < MAX_FDS; i++)
if (client_info[i].pid == pid)
return i;
return -1;
}
void handle_process_data_all(fd_set * select_fds)
{
int i;
for (i = 0; i <= max_process_fd; i++)
if (process_fd[i].type != FDTYPE_INVALID
&& FD_ISSET(i, select_fds))
handle_process_data(i);
}
void reap_children()
{
int status;
int pid;
int client_id;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
client_id = find_info(pid);
if (client_id < 0)
continue;
client_info[client_id].is_exited = 1;
client_info[client_id].exit_status = status;
possibly_remove_process(client_id);
}
child_exited = 0;
}
int fill_fds_for_select(fd_set * rdset, fd_set * wrset)
{
int max = -1;
int fd, i;
FD_ZERO(rdset);
FD_ZERO(wrset);
for (i = 0; i <= max_process_fd; i++)
if (process_fd[i].type != FDTYPE_INVALID
&& !process_fd[i].is_blocked) {
FD_SET(i, rdset);
max = i;
}
FD_SET(trigger_fd, rdset);
if (trigger_fd > max)
max = trigger_fd;
FD_SET(passfd_socket, rdset);
if (passfd_socket > max)
max = passfd_socket;
for (i = 0; i < MAX_FDS; i++)
if (client_info[i].pid && client_info[i].is_blocked) {
fd = client_info[i].stdin_fd;
FD_SET(fd, wrset);
if (fd > max)
max = fd;
}
return max;
}
void flush_client_data_agent(int client_id)
{
struct _client_info *info = &client_info[client_id];
switch (flush_client_data
(info->stdin_fd, client_id, &info->buffer)) {
case WRITE_STDIN_OK:
info->is_blocked = 0;
if (info->is_close_after_flush_needed) {
close(info->stdin_fd);
info->stdin_fd = -1;
info->is_close_after_flush_needed = 0;
}
break;
case WRITE_STDIN_ERROR:
// do not remove process, as it still can write data to stdout
info->is_blocked = 0;
close(info->stdin_fd);
info->stdin_fd = -1;
info->is_close_after_flush_needed = 0;
break;
case WRITE_STDIN_BUFFERED:
break;
default:
fprintf(stderr, "unknown flush_client_data?\n");
exit(1);
}
}
void handle_new_passfd()
{
int fd = do_accept(passfd_socket);
if (fd >= MAX_FDS) {
fprintf(stderr, "too many clients ?\n");
exit(1);
}
// let client know what fd has been allocated
write(fd, &fd, sizeof(fd));
}
void handle_trigger_io()
{
struct server_header s_hdr;
struct trigger_connect_params params;
int ret;
s_hdr.client_id = 0;
s_hdr.len = 0;
ret = read(trigger_fd, &params, sizeof(params));
if (ret == sizeof(params)) {
s_hdr.type = MSG_AGENT_TO_SERVER_TRIGGER_CONNECT_EXISTING;
write_all_vchan_ext(&s_hdr, sizeof s_hdr);
write_all_vchan_ext(&params, sizeof params);
}
// trigger_fd is nonblock - so no need to reopen
// not really, need to reopen at EOF
if (ret <= 0) {
close(trigger_fd);
trigger_fd =
open(QREXEC_AGENT_TRIGGER_PATH, O_RDONLY | O_NONBLOCK);
}
}
int main()
{
fd_set rdset, wrset;
int max;
int i;
sigset_t chld_set;
init();
signal(SIGCHLD, sigchld_handler);
signal(SIGPIPE, SIG_IGN);
sigemptyset(&chld_set);
sigaddset(&chld_set, SIGCHLD);
for (;;) {
sigprocmask(SIG_BLOCK, &chld_set, NULL);
if (child_exited)
reap_children();
max = fill_fds_for_select(&rdset, &wrset);
if (buffer_space_vchan_ext() <=
sizeof(struct server_header))
FD_ZERO(&rdset);
wait_for_vchan_or_argfd(max, &rdset, &wrset);
sigprocmask(SIG_UNBLOCK, &chld_set, NULL);
if (FD_ISSET(passfd_socket, &rdset))
handle_new_passfd();
while (read_ready_vchan_ext())
handle_server_data();
if (FD_ISSET(trigger_fd, &rdset))
handle_trigger_io();
handle_process_data_all(&rdset);
for (i = 0; i <= MAX_FDS; i++)
if (client_info[i].pid
&& client_info[i].is_blocked
&& FD_ISSET(client_info[i].stdin_fd, &wrset))
flush_client_data_agent(i);
}
}

View File

@ -1,295 +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 <unistd.h>
#include <ioall.h>
#include <sys/wait.h>
#include <errno.h>
#include "qrexec.h"
#include "buffer.h"
#include "glue.h"
int connect_unix_socket(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);
}
return s;
}
void do_exec(char *prog)
{
execl("/bin/bash", "bash", "-c", prog, NULL);
}
int local_stdin_fd, local_stdout_fd;
void do_exit(int code)
{
int status;
// sever communication lines; wait for child, if any
// so that qrexec-daemon can count (recursively) spawned processes correctly
close(local_stdin_fd);
close(local_stdout_fd);
waitpid(-1, &status, 0);
exit(code);
}
void prepare_local_fds(char *cmdline)
{
int pid;
if (!cmdline) {
local_stdin_fd = 1;
local_stdout_fd = 0;
return;
}
do_fork_exec(cmdline, &pid, &local_stdin_fd, &local_stdout_fd,
NULL);
}
void send_cmdline(int s, int type, char *cmdline)
{
struct client_header hdr;
hdr.type = type;
hdr.len = strlen(cmdline) + 1;
if (!write_all(s, &hdr, sizeof(hdr))
|| !write_all(s, cmdline, hdr.len)) {
perror("write daemon");
do_exit(1);
}
}
void handle_input(int s)
{
char buf[MAX_DATA_CHUNK];
int ret;
ret = read(local_stdout_fd, buf, sizeof(buf));
if (ret < 0) {
perror("read");
do_exit(1);
}
if (ret == 0) {
close(local_stdout_fd);
local_stdout_fd = -1;
shutdown(s, SHUT_WR);
if (local_stdin_fd == -1) {
// if pipe in opposite direction already closed, no need to stay alive
do_exit(0);
}
}
if (!write_all(s, buf, ret)) {
if (errno == EPIPE) {
// daemon disconnected its end of socket, so no future data will be
// send there; there is no sense to read from child stdout
//
// since AF_UNIX 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_SERVER_TO_CLIENT_EXIT_CODE message)
do_exit(1);
}
} else
perror("write daemon");
}
}
void handle_daemon_data(int s)
{
int status;
struct client_header hdr;
char buf[MAX_DATA_CHUNK];
if (!read_all(s, &hdr, sizeof hdr)) {
perror("read daemon");
do_exit(1);
}
if (hdr.len > MAX_DATA_CHUNK) {
fprintf(stderr, "client_header.len=%d\n", hdr.len);
do_exit(1);
}
if (!read_all(s, buf, hdr.len)) {
perror("read daemon");
do_exit(1);
}
switch (hdr.type) {
case MSG_SERVER_TO_CLIENT_STDOUT:
if (local_stdin_fd == -1)
break;
if (hdr.len == 0) {
close(local_stdin_fd);
local_stdin_fd = -1;
} else if (!write_all(local_stdin_fd, buf, hdr.len)) {
if (errno == EPIPE) {
// remote side have closed its stdin, handle data in oposite
// direction (if any) before exit
local_stdin_fd = -1;
} else {
perror("write local stdout");
do_exit(1);
}
}
break;
case MSG_SERVER_TO_CLIENT_STDERR:
write_all(2, buf, hdr.len);
break;
case MSG_SERVER_TO_CLIENT_EXIT_CODE:
status = *(unsigned int *) buf;
if (WIFEXITED(status))
do_exit(WEXITSTATUS(status));
else
do_exit(255);
break;
default:
fprintf(stderr, "unknown msg %d\n", hdr.type);
do_exit(1);
}
}
// perhaps we could save a syscall if we include both sides in both
// rdset and wrset; to be investigated
void handle_daemon_only_until_writable(s)
{
fd_set rdset, wrset;
do {
FD_ZERO(&rdset);
FD_ZERO(&wrset);
FD_SET(s, &rdset);
FD_SET(s, &wrset);
if (select(s + 1, &rdset, &wrset, NULL, NULL) < 0) {
perror("select");
do_exit(1);
}
if (FD_ISSET(s, &rdset))
handle_daemon_data(s);
} while (!FD_ISSET(s, &wrset));
}
void select_loop(int s)
{
fd_set select_set;
int max;
for (;;) {
handle_daemon_only_until_writable(s);
FD_ZERO(&select_set);
FD_SET(s, &select_set);
max = s;
if (local_stdout_fd != -1) {
FD_SET(local_stdout_fd, &select_set);
if (s < local_stdout_fd)
max = local_stdout_fd;
}
if (select(max + 1, &select_set, NULL, NULL, NULL) < 0) {
perror("select");
do_exit(1);
}
if (FD_ISSET(s, &select_set))
handle_daemon_data(s);
if (local_stdout_fd != -1
&& FD_ISSET(local_stdout_fd, &select_set))
handle_input(s);
}
}
void usage(char *name)
{
fprintf(stderr,
"usage: %s -d domain_num [-l local_prog] -e -c remote_cmdline\n"
"-e means exit after sending cmd, -c: connect to existing process\n",
name);
exit(1);
}
int main(int argc, char **argv)
{
int opt;
char *domname = NULL;
int s;
int just_exec = 0;
int connect_existing = 0;
char *local_cmdline = NULL;
while ((opt = getopt(argc, argv, "d:l:ec")) != -1) {
switch (opt) {
case 'd':
domname = strdup(optarg);
break;
case 'l':
local_cmdline = strdup(optarg);
break;
case 'e':
just_exec = 1;
break;
case 'c':
connect_existing = 1;
break;
default:
usage(argv[0]);
}
}
if (optind >= argc || !domname)
usage(argv[0]);
s = connect_unix_socket(domname);
setenv("QREXEC_REMOTE_DOMAIN", domname, 1);
prepare_local_fds(local_cmdline);
if (just_exec)
send_cmdline(s, MSG_CLIENT_TO_SERVER_JUST_EXEC,
argv[optind]);
else {
int cmd;
if (connect_existing)
cmd = MSG_CLIENT_TO_SERVER_CONNECT_EXISTING;
else
cmd = MSG_CLIENT_TO_SERVER_EXEC_CMDLINE;
send_cmdline(s, cmd, argv[optind]);
select_loop(s);
}
return 0;
}

View File

@ -1,109 +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.
*
*/
#define _GNU_SOURCE
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include "qrexec.h"
int connect_unix_socket()
{
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;
strncpy(remote.sun_path, QREXEC_AGENT_FDPASS_PATH,
sizeof(remote.sun_path));
len = strlen(remote.sun_path) + sizeof(remote.sun_family);
if (connect(s, (struct sockaddr *) &remote, len) == -1) {
perror("connect");
exit(1);
}
return s;
}
char *get_program_name(char *prog)
{
char *basename = rindex(prog, '/');
if (basename)
return basename + 1;
else
return prog;
}
int main(int argc, char **argv)
{
int trigger_fd;
struct trigger_connect_params params;
int local_fd[3], remote_fd[3];
int i;
char *abs_exec_path;
if (argc < 4) {
fprintf(stderr,
"usage: %s target_vmname program_ident local_program [local program arguments]\n",
argv[0]);
exit(1);
}
trigger_fd = open(QREXEC_AGENT_TRIGGER_PATH, O_WRONLY);
if (trigger_fd < 0) {
perror("open QREXEC_AGENT_TRIGGER_PATH");
exit(1);
}
for (i = 0; i < 3; i++) {
local_fd[i] = connect_unix_socket();
read(local_fd[i], &remote_fd[i], sizeof(remote_fd[i]));
if (i != 2 || getenv("PASS_LOCAL_STDERR")) {
char *env;
asprintf(&env, "SAVED_FD_%d=%d", i, dup(i));
putenv(env);
dup2(local_fd[i], i);
close(local_fd[i]);
}
}
memset(&params, 0, sizeof(params));
strncpy(params.exec_index, argv[2], sizeof(params.exec_index));
strncpy(params.target_vmname, argv[1],
sizeof(params.target_vmname));
snprintf(params.process_fds.ident,
sizeof(params.process_fds.ident), "%d %d %d",
remote_fd[0], remote_fd[1], remote_fd[2]);
write(trigger_fd, &params, sizeof(params));
close(trigger_fd);
abs_exec_path = strdup(argv[3]);
argv[3] = get_program_name(argv[3]);
execv(abs_exec_path, argv + 3);
perror("execv");
return 1;
}

View File

@ -1,687 +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 <ioall.h>
#include <string.h>
#include "qrexec.h"
#include "buffer.h"
#include "glue.h"
enum client_flags {
CLIENT_INVALID = 0, // table slot not used
CLIENT_CMDLINE = 1, // waiting for cmdline from client
CLIENT_DATA = 2, // waiting for data from client
CLIENT_DONT_READ = 4, // don't read from the client, the other side pipe is full, or EOF (additionally marked with CLIENT_EOF)
CLIENT_OUTQ_FULL = 8, // don't write to client, its stdin pipe is full
CLIENT_EOF = 16, // got EOF
CLIENT_EXITED = 32 // only send remaining data from client and remove from list
};
struct _client {
int state; // combination of above enum client_flags
struct buffer buffer; // buffered data to client, if any
};
/*
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
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
char *default_user = "user";
char default_user_keyword[] = "DEFAULT:";
#define default_user_keyword_len_without_colon (sizeof(default_user_keyword)-2)
void sigusr1_handler(int x)
{
fprintf(stderr, "connected\n");
exit(0);
}
void sigchld_handler(int x);
char *remote_domain_name; // guess what
int create_qrexec_socket(int domid, 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);
symlink(socket_address, link_to_socket_name);
return get_server_socket(socket_address);
}
#define MAX_STARTUP_TIME_DEFAULT 60
/* ask on qrexec connect timeout */
int ask_on_connect_timeout(int xid, int timeout)
{
char text[1024];
int ret;
struct stat buf;
ret=stat("/usr/bin/kdialog", &buf);
#define KDIALOG_CMD "kdialog --title 'Qrexec daemon' --warningyesno "
#define ZENITY_CMD "zenity --title 'Qrexec daemon' --question --text "
snprintf(text, sizeof(text),
"%s"
"'Timeout while trying connecting to qrexec agent (Xen domain ID: %d). Do you want to wait next %d seconds?'",
ret==0 ? KDIALOG_CMD : ZENITY_CMD,
xid, timeout);
#undef KDIALOG_CMD
#undef ZENITY_CMD
ret = system(text);
ret = WEXITSTATUS(ret);
// fprintf(stderr, "ret=%d\n", ret);
switch (ret) {
case 1: /* NO */
return 0;
case 0: /*YES */
return 1;
default:
// this can be the case at system startup (netvm), when Xorg isn't running yet
// so just don't give possibility to extend the timeout
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;
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 number
startup_timeout = MAX_STARTUP_TIME_DEFAULT;
}
signal(SIGUSR1, sigusr1_handler);
switch (pid=fork()) {
case -1:
perror("fork");
exit(1);
case 0:
break;
default:
fprintf(stderr, "Waiting for VM's qrexec agent.");
for (i=0;i<startup_timeout;i++) {
sleep(1);
fprintf(stderr, ".");
if (i==startup_timeout-1) {
if (ask_on_connect_timeout(xid, startup_timeout))
i=0;
}
}
fprintf(stderr, "Cannot connect to qrexec agent for %d seconds, giving up\n", startup_timeout);
kill(pid, SIGTERM);
exit(1);
}
close(0);
snprintf(qrexec_error_log_name, sizeof(qrexec_error_log_name),
"/var/log/qubes/qrexec.%d.log", xid);
umask(0007); // make the log readable by the "qubes" group
logfd =
open(qrexec_error_log_name, O_WRONLY | O_CREAT | O_TRUNC,
0640);
if (logfd < 0) {
perror("open");
exit(1);
}
dup2(logfd, 1);
dup2(logfd, 2);
chdir("/var/run/qubes");
if (setsid() < 0) {
perror("setsid()");
exit(1);
}
remote_domain_name = peer_client_init(xid, REXEC_PORT);
setuid(getuid());
/* 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
}
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);
}
clients[fd].state = CLIENT_CMDLINE;
buffer_init(&clients[fd].buffer);
if (fd > max_client_fd)
max_client_fd = fd;
}
/*
we need to track the number of children, so that excessive QREXEC_EXECUTE_*
commands do not fork-bomb dom0
*/
int children_count;
void terminate_client_and_flush_data(int fd)
{
int i;
struct server_header s_hdr;
if (!(clients[fd].state & CLIENT_EXITED) && fork_and_flush_stdin(fd, &clients[fd].buffer))
children_count++;
close(fd);
clients[fd].state = CLIENT_INVALID;
buffer_free(&clients[fd].buffer);
if (max_client_fd == fd) {
for (i = fd; clients[i].state == CLIENT_INVALID && i >= 0;
i--);
max_client_fd = i;
}
s_hdr.type = MSG_SERVER_TO_AGENT_CLIENT_END;
s_hdr.client_id = fd;
s_hdr.len = 0;
write_all_vchan_ext(&s_hdr, sizeof(s_hdr));
}
int get_cmdline_body_from_client_and_pass_to_agent(int fd, struct server_header
*s_hdr)
{
int len = s_hdr->len;
char buf[len];
int use_default_user = 0;
if (!read_all(fd, buf, len)) {
terminate_client_and_flush_data(fd);
return 0;
}
if (!strncmp(buf, default_user_keyword, default_user_keyword_len_without_colon+1)) {
use_default_user = 1;
s_hdr->len -= default_user_keyword_len_without_colon; // -1 because of colon
s_hdr->len += strlen(default_user);
}
write_all_vchan_ext(s_hdr, sizeof(*s_hdr));
if (use_default_user) {
write_all_vchan_ext(default_user, strlen(default_user));
write_all_vchan_ext(buf+default_user_keyword_len_without_colon, len-default_user_keyword_len_without_colon);
} else
write_all_vchan_ext(buf, len);
return 1;
}
void handle_cmdline_message_from_client(int fd)
{
struct client_header hdr;
struct server_header s_hdr;
if (!read_all(fd, &hdr, sizeof hdr)) {
terminate_client_and_flush_data(fd);
return;
}
switch (hdr.type) {
case MSG_CLIENT_TO_SERVER_EXEC_CMDLINE:
s_hdr.type = MSG_SERVER_TO_AGENT_EXEC_CMDLINE;
break;
case MSG_CLIENT_TO_SERVER_JUST_EXEC:
s_hdr.type = MSG_SERVER_TO_AGENT_JUST_EXEC;
break;
case MSG_CLIENT_TO_SERVER_CONNECT_EXISTING:
s_hdr.type = MSG_SERVER_TO_AGENT_CONNECT_EXISTING;
break;
default:
terminate_client_and_flush_data(fd);
return;
}
s_hdr.client_id = fd;
s_hdr.len = hdr.len;
if (!get_cmdline_body_from_client_and_pass_to_agent(fd, &s_hdr))
// client disconnected while sending cmdline, above call already
// cleaned up client info
return;
clients[fd].state = CLIENT_DATA;
set_nonblock(fd); // so that we can detect full queue without blocking
if (hdr.type == MSG_CLIENT_TO_SERVER_JUST_EXEC)
terminate_client_and_flush_data(fd);
}
/* handle data received from one of qrexec_client processes */
void handle_message_from_client(int fd)
{
struct server_header s_hdr;
char buf[MAX_DATA_CHUNK];
int len, ret;
if (clients[fd].state == CLIENT_CMDLINE) {
handle_cmdline_message_from_client(fd);
return;
}
// We have already passed cmdline from client.
// Now the client passes us raw data from its stdin.
len = buffer_space_vchan_ext();
if (len <= sizeof s_hdr)
return;
/* Read at most the amount of data that we have room for in vchan */
ret = read(fd, buf, len - sizeof(s_hdr));
if (ret < 0) {
perror("read client");
terminate_client_and_flush_data(fd);
return;
}
s_hdr.client_id = fd;
s_hdr.len = ret;
s_hdr.type = MSG_SERVER_TO_AGENT_INPUT;
write_all_vchan_ext(&s_hdr, sizeof(s_hdr));
write_all_vchan_ext(buf, ret);
if (ret == 0) // EOF - so don't select() on this client
clients[fd].state |= CLIENT_DONT_READ | CLIENT_EOF;
if (clients[fd].state & CLIENT_EXITED)
//client already exited and all data sent - cleanup now
terminate_client_and_flush_data(fd);
}
/*
Called when there is buffered data for this client, and select() reports
that client's pipe is writable; so we should be able to flush some
buffered data.
*/
void write_buffered_data_to_client(int client_id)
{
switch (flush_client_data
(client_id, client_id, &clients[client_id].buffer)) {
case WRITE_STDIN_OK: // no more buffered data
clients[client_id].state &= ~CLIENT_OUTQ_FULL;
break;
case WRITE_STDIN_ERROR:
// do not write to this fd anymore
clients[client_id].state |= CLIENT_EXITED;
if (clients[client_id].state & CLIENT_EOF)
terminate_client_and_flush_data(client_id);
else
// client will be removed when read returns 0 (EOF)
// clear CLIENT_OUTQ_FULL flag to no select on this fd anymore
clients[client_id].state &= ~CLIENT_OUTQ_FULL;
break;
case WRITE_STDIN_BUFFERED: // no room for all data, don't clear CLIENT_OUTQ_FULL flag
break;
default:
fprintf(stderr, "unknown flush_client_data?\n");
exit(1);
}
}
/*
The header (hdr argument) is already built. Just read the raw data from
the packet, and pass it along with the header to the client.
*/
void get_packet_data_from_agent_and_pass_to_client(int client_id, struct client_header
*hdr)
{
int len = hdr->len;
char buf[sizeof(*hdr) + len];
/* make both the header and data be consecutive in the buffer */
*(struct client_header *) buf = *hdr;
read_all_vchan_ext(buf + sizeof(*hdr), len);
if (clients[client_id].state & CLIENT_EXITED)
// ignore data for no longer running client
return;
switch (write_stdin
(client_id, client_id, buf, len + sizeof(*hdr),
&clients[client_id].buffer)) {
case WRITE_STDIN_OK:
break;
case WRITE_STDIN_BUFFERED: // some data have been buffered
clients[client_id].state |= CLIENT_OUTQ_FULL;
break;
case WRITE_STDIN_ERROR:
// do not write to this fd anymore
clients[client_id].state |= CLIENT_EXITED;
// if already got EOF, remove client
if (clients[client_id].state & CLIENT_EOF)
terminate_client_and_flush_data(client_id);
break;
default:
fprintf(stderr, "unknown write_stdin?\n");
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;
void sigchld_handler(int x)
{
child_exited = 1;
signal(SIGCHLD, sigchld_handler);
}
/* clean zombies, update children_count */
void reap_children()
{
int status;
while (waitpid(-1, &status, WNOHANG) > 0)
children_count--;
child_exited = 0;
}
/* too many children - wait for one of them to terminate */
void wait_for_child()
{
int status;
waitpid(-1, &status, 0);
children_count--;
}
#define MAX_CHILDREN 10
void check_children_count_and_wait_if_too_many()
{
if (children_count > MAX_CHILDREN) {
fprintf(stderr,
"max number of children reached, waiting for child exit...\n");
wait_for_child();
fprintf(stderr, "now children_count=%d, continuing.\n",
children_count);
}
}
void sanitize_name(char * untrusted_s_signed)
{
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 == '-' || *untrusted_s == '.' || *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.
*/
void handle_execute_predefined_command()
{
int i;
struct trigger_connect_params untrusted_params, params;
check_children_count_and_wait_if_too_many();
read_all_vchan_ext(&untrusted_params, sizeof(params));
/* sanitize start */
ENSURE_NULL_TERMINATED(untrusted_params.exec_index);
ENSURE_NULL_TERMINATED(untrusted_params.target_vmname);
ENSURE_NULL_TERMINATED(untrusted_params.process_fds.ident);
sanitize_name(untrusted_params.exec_index);
sanitize_name(untrusted_params.target_vmname);
sanitize_name(untrusted_params.process_fds.ident);
params = untrusted_params;
/* sanitize end */
switch (fork()) {
case -1:
perror("fork");
exit(1);
case 0:
break;
default:
children_count++;
return;
}
for (i = 3; i < MAX_FDS; i++)
close(i);
signal(SIGCHLD, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
execl("/usr/lib/qubes/qrexec_policy", "qrexec_policy",
remote_domain_name, params.target_vmname,
params.exec_index, params.process_fds.ident, NULL);
perror("execl");
exit(1);
}
void check_client_id_in_range(unsigned int untrusted_client_id)
{
if (untrusted_client_id >= MAX_CLIENTS || untrusted_client_id < 0) {
fprintf(stderr, "from agent: client_id=%d\n",
untrusted_client_id);
exit(1);
}
}
void sanitize_message_from_agent(struct server_header *untrusted_header)
{
switch (untrusted_header->type) {
case MSG_AGENT_TO_SERVER_TRIGGER_CONNECT_EXISTING:
break;
case MSG_AGENT_TO_SERVER_STDOUT:
case MSG_AGENT_TO_SERVER_STDERR:
case MSG_AGENT_TO_SERVER_EXIT_CODE:
check_client_id_in_range(untrusted_header->client_id);
if (untrusted_header->len > MAX_DATA_CHUNK
|| untrusted_header->len < 0) {
fprintf(stderr, "agent feeded %d of data bytes?\n",
untrusted_header->len);
exit(1);
}
break;
case MSG_XOFF:
case MSG_XON:
check_client_id_in_range(untrusted_header->client_id);
break;
default:
fprintf(stderr, "unknown mesage type %d from agent\n",
untrusted_header->type);
exit(1);
}
}
void handle_message_from_agent()
{
struct client_header hdr;
struct server_header s_hdr, untrusted_s_hdr;
read_all_vchan_ext(&untrusted_s_hdr, sizeof untrusted_s_hdr);
/* sanitize start */
sanitize_message_from_agent(&untrusted_s_hdr);
s_hdr = untrusted_s_hdr;
/* sanitize end */
// fprintf(stderr, "got %x %x %x\n", s_hdr.type, s_hdr.client_id,
// s_hdr.len);
if (s_hdr.type == MSG_AGENT_TO_SERVER_TRIGGER_CONNECT_EXISTING) {
handle_execute_predefined_command();
return;
}
if (s_hdr.type == MSG_XOFF) {
clients[s_hdr.client_id].state |= CLIENT_DONT_READ;
return;
}
if (s_hdr.type == MSG_XON) {
clients[s_hdr.client_id].state &= ~CLIENT_DONT_READ;
return;
}
switch (s_hdr.type) {
case MSG_AGENT_TO_SERVER_STDOUT:
hdr.type = MSG_SERVER_TO_CLIENT_STDOUT;
break;
case MSG_AGENT_TO_SERVER_STDERR:
hdr.type = MSG_SERVER_TO_CLIENT_STDERR;
break;
case MSG_AGENT_TO_SERVER_EXIT_CODE:
hdr.type = MSG_SERVER_TO_CLIENT_EXIT_CODE;
break;
default: /* cannot happen, already sanitized */
fprintf(stderr, "from agent: type=%d\n", s_hdr.type);
exit(1);
}
hdr.len = s_hdr.len;
if (clients[s_hdr.client_id].state == CLIENT_INVALID) {
// benefit of doubt - maybe client exited earlier
// just eat the packet data and continue
char buf[MAX_DATA_CHUNK];
read_all_vchan_ext(buf, s_hdr.len);
return;
}
get_packet_data_from_agent_and_pass_to_client(s_hdr.client_id,
&hdr);
if (s_hdr.type == MSG_AGENT_TO_SERVER_EXIT_CODE)
terminate_client_and_flush_data(s_hdr.client_id);
}
/*
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.
*/
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
&& !(clients[i].state & CLIENT_DONT_READ)) {
FD_SET(i, read_fdset);
max = i;
}
if (clients[i].state != CLIENT_INVALID
&& clients[i].state & CLIENT_OUTQ_FULL) {
FD_SET(i, write_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;
int max;
sigset_t chld_set;
if (argc != 2 && argc != 3) {
fprintf(stderr, "usage: %s domainid [default user]\n", argv[0]);
exit(1);
}
if (argc == 3)
default_user = argv[2];
init(atoi(argv[1]));
sigemptyset(&chld_set);
sigaddset(&chld_set, SIGCHLD);
/*
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 (buffer_space_vchan_ext() <=
sizeof(struct server_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(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 (read_ready_vchan_ext())
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);
for (i = 0; i <= max_client_fd; i++)
if (clients[i].state != CLIENT_INVALID
&& FD_ISSET(i, &write_fdset))
write_buffered_data_to_client(i);
}
}

View File

@ -1,182 +0,0 @@
#!/usr/bin/python
import sys
import os
import os.path
import subprocess
import xen.lowlevel.xl
import qubes.guihelpers
from optparse import OptionParser
import fcntl
POLICY_FILE_DIR="/etc/qubes_rpc/policy"
QREXEC_CLIENT="/usr/lib/qubes/qrexec_client"
class UserChoice:
ALLOW=0
DENY=1
ALWAYS_ALLOW=2
def line_to_dict(line):
tokens=line.split()
if len(tokens) < 3:
return None
if tokens[0][0] == '#':
return None
dict={}
dict['source']=tokens[0]
dict['dest']=tokens[1]
dict['full-action']=tokens[2]
action_list=tokens[2].split(',')
dict['action']=action_list.pop(0)
for iter in action_list:
paramval=iter.split("=")
dict["action."+paramval[0]]=paramval[1]
return dict
def read_policy_file(exec_index):
policy_file=POLICY_FILE_DIR+"/"+exec_index
if not os.path.isfile(policy_file):
return None
policy_list=list()
f = open(policy_file)
fcntl.flock(f, fcntl.LOCK_SH)
for iter in f.readlines():
dict = line_to_dict(iter)
if dict is not None:
policy_list.append(dict)
f.close()
return policy_list
def is_match(item, config_term):
return (item is not "dom0" and config_term == "$anyvm") or item == config_term
def get_default_policy():
dict={}
dict["action"]="deny"
return dict
def find_policy(policy, domain, target):
for iter in policy:
if not is_match(domain, iter["source"]):
continue
if not is_match(target, iter["dest"]):
continue
return iter
return get_default_policy()
def is_domain_running(target):
xl_ctx = xen.lowlevel.xl.ctx()
domains = xl_ctx.list_domains()
for dominfo in domains:
domname = xl_ctx.domid_to_name(dominfo.domid)
if domname == target:
return True
return False
def spawn_target_if_necessary(target):
if is_domain_running(target):
return
null=open("/dev/null", "r+")
subprocess.call(["qvm-run", "-a", "-q", target, "true"], stdin=null, stdout=null)
null.close()
def do_execute(domain, target, user, exec_index, process_ident):
if target == "dom0":
cmd="/usr/lib/qubes/qubes_rpc_multiplexer "+exec_index + " " + domain
elif target == "$dispvm":
cmd = "/usr/lib/qubes/qfile-daemon-dvm " + exec_index + " " + domain + " " +user
else:
# see the previous commit why "qvm-run -a" is broken and dangerous
# also, dangling "xl" would keep stderr open and may prevent closing connection
spawn_target_if_necessary(target)
cmd= QREXEC_CLIENT + " -d " + target + " '" + user
cmd+=":QUBESRPC "+ exec_index + " " + domain + "'"
os.execl(QREXEC_CLIENT, "qrexec_client", "-d", domain, "-l", cmd, "-c", process_ident)
def confirm_execution(domain, target, exec_index):
text = "Do you allow domain \"" +domain + "\" to execute " + exec_index
text+= " operation on the domain \"" + target +"\"?<br>"
text+= " \"Yes to All\" option will automatically allow this operation in the future."
return qubes.guihelpers.ask(text, yestoall=True)
def add_always_allow(domain, target, exec_index, options):
policy_file=POLICY_FILE_DIR+"/"+exec_index
if not os.path.isfile(policy_file):
return None
f = open(policy_file, 'r+')
fcntl.flock(f, fcntl.LOCK_EX)
lines = []
for l in f.readlines():
lines.append(l)
lines.insert(0, "%s\t%s\tallow%s\n" % (domain, target, options))
f.seek(0)
f.write("".join(lines))
f.close()
def policy_editor(domain, target, exec_index):
text = "No policy definition found for " + exec_index + " action. "
text+= "Please create a policy file in Dom0 in " + "/etc/qubes_rpc/policy/" + exec_index
subprocess.call(["/usr/bin/zenity", "--info", "--text", text])
def main():
usage = "usage: %prog [options] <src-domain> <target-domain> <service> <process-ident>"
parser = OptionParser (usage)
parser.add_option ("--assume-yes-for-ask", action="store_true", dest="assume_yes_for_ask", default=False,
help="Allow run of service without confirmation if policy say 'ask'")
parser.add_option ("--just-evaluate", action="store_true", dest="just_evaluate", default=False,
help="Do not run the service, only evaluate policy; retcode=0 means 'allow'")
(options, args) = parser.parse_args ()
domain=args[0]
target=args[1]
exec_index=args[2]
process_ident=args[3]
policy_list=read_policy_file(exec_index)
if policy_list==None:
policy_editor(domain, target, exec_index)
policy_list=read_policy_file(exec_index)
if policy_list==None:
policy_list=list()
policy_dict=find_policy(policy_list, domain, target)
if policy_dict["action"] == "ask" and options.assume_yes_for_ask:
policy_dict["action"] = "allow"
if policy_dict["action"] == "ask":
user_choice = confirm_execution(domain, target, exec_index)
if user_choice == UserChoice.ALWAYS_ALLOW:
add_always_allow(domain, target, exec_index, policy_dict["full-action"].lstrip('ask'))
policy_dict["action"] = "allow"
elif user_choice == UserChoice.ALLOW:
policy_dict["action"] = "allow"
else:
policy_dict["action"] = "deny"
if options.just_evaluate:
if policy_dict["action"] == "allow":
exit(0)
else:
exit(1)
if policy_dict["action"] == "allow":
if policy_dict.has_key("action.target"):
target=policy_dict["action.target"]
if policy_dict.has_key("action.user"):
user=policy_dict["action.user"]
else:
user="DEFAULT"
do_execute(domain, target, user, exec_index, process_ident)
print >> sys.stderr, "Rpc denied:", domain, target, exec_index
os.execl(QREXEC_CLIENT, "qrexec_client", "-d", domain, "-l", "/bin/false", "-c", process_ident)
main()

View File

@ -1,15 +0,0 @@
#!/bin/sh
QUBES_RPC=/etc/qubes_rpc
if ! [ $# = 2 ] ; then
echo $0: bad argument count >&2
exit 1
fi
CFG_FILE=$QUBES_RPC/"$1"
export QREXEC_REMOTE_DOMAIN="$2"
if [ -s "$CFG_FILE" ] ; then
exec /bin/sh "$CFG_FILE"
echo "$0: failed to execute handler for" "$1" >&2
exit 1
fi
echo "$0: nonexistent or empty" "$CFG_FILE" file >&2
exit 1

View File

@ -1,221 +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 <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <libvchan.h>
#include <xs.h>
#include <xenctrl.h>
static struct libvchan *ctrl;
static int is_server;
int write_all_vchan_ext(void *buf, int size)
{
int written = 0;
int ret;
while (written < size) {
ret =
libvchan_write(ctrl, (char *) buf + written,
size - written);
if (ret <= 0) {
perror("write");
exit(1);
}
written += ret;
}
// fprintf(stderr, "sent %d bytes\n", size);
return size;
}
int read_all_vchan_ext(void *buf, int size)
{
int written = 0;
int ret;
while (written < size) {
ret =
libvchan_read(ctrl, (char *) buf + written,
size - written);
if (ret == 0) {
fprintf(stderr, "EOF\n");
exit(1);
}
if (ret < 0) {
perror("read");
exit(1);
}
written += ret;
}
// fprintf(stderr, "read %d bytes\n", size);
return size;
}
int read_ready_vchan_ext()
{
return libvchan_data_ready(ctrl);
}
int buffer_space_vchan_ext()
{
return libvchan_buffer_space(ctrl);
}
// if the remote domain is destroyed, we get no notification
// thus, we check for the status periodically
#ifdef XENCTRL_HAS_XC_INTERFACE
static xc_interface *xc_handle = NULL;
#else
static int xc_handle = -1;
#endif
void slow_check_for_libvchan_is_eof(struct libvchan *ctrl)
{
struct evtchn_status evst;
evst.port = ctrl->evport;
evst.dom = DOMID_SELF;
if (xc_evtchn_status(xc_handle, &evst)) {
perror("xc_evtchn_status");
exit(1);
}
if (evst.status != EVTCHNSTAT_interdomain) {
fprintf(stderr, "event channel disconnected\n");
exit(0);
}
}
int wait_for_vchan_or_argfd_once(int max, fd_set * rdset, fd_set * wrset)
{
int vfd, ret;
struct timespec tv = { 1, 100000000 };
sigset_t empty_set;
sigemptyset(&empty_set);
vfd = libvchan_fd_for_select(ctrl);
FD_SET(vfd, rdset);
if (vfd > max)
max = vfd;
max++;
ret = pselect(max, rdset, wrset, NULL, &tv, &empty_set);
if (ret < 0) {
if (errno != EINTR) {
perror("select");
exit(1);
} else {
FD_ZERO(rdset);
FD_ZERO(wrset);
fprintf(stderr, "eintr\n");
return 1;
}
}
if (libvchan_is_eof(ctrl)) {
fprintf(stderr, "libvchan_is_eof\n");
exit(0);
}
if (!is_server && ret == 0)
slow_check_for_libvchan_is_eof(ctrl);
if (FD_ISSET(vfd, rdset))
// the following will never block; we need to do this to
// clear libvchan_fd pending state
libvchan_wait(ctrl);
return ret;
}
void wait_for_vchan_or_argfd(int max, fd_set * rdset, fd_set * wrset)
{
fd_set r = *rdset, w = *wrset;
do {
*rdset = r;
*wrset = w;
}
while (wait_for_vchan_or_argfd_once(max, rdset, wrset) == 0);
}
int peer_server_init(int port)
{
is_server = 1;
ctrl = libvchan_server_init(port);
if (!ctrl) {
perror("libvchan_server_init");
exit(1);
}
return 0;
}
char *peer_client_init(int dom, int port)
{
struct xs_handle *xs;
char buf[64];
char *name;
char *dummy;
unsigned int len = 0;
char devbuf[128];
unsigned int count;
char **vec;
// double_buffered = 1; // writes to vchan are buffered, nonblocking
// double_buffer_init();
xs = xs_daemon_open();
if (!xs) {
perror("xs_daemon_open");
exit(1);
}
snprintf(buf, sizeof(buf), "/local/domain/%d/name", dom);
name = xs_read(xs, 0, buf, &len);
if (!name) {
perror("xs_read domainname");
exit(1);
}
snprintf(devbuf, sizeof(devbuf),
"/local/domain/%d/device/vchan/%d/event-channel", dom,
port);
xs_watch(xs, devbuf, devbuf);
do {
vec = xs_read_watch(xs, &count);
if (vec)
free(vec);
len = 0;
dummy = xs_read(xs, 0, devbuf, &len);
}
while (!dummy || !len); // wait for the server to create xenstore entries
free(dummy);
xs_daemon_close(xs);
// now client init should succeed; "while" is redundant
while (!(ctrl = libvchan_client_init(dom, port)));
#ifdef XENCTRL_HAS_XC_INTERFACE
xc_handle = xc_interface_open(NULL, 0, 0);
if (!xc_handle) {
#else
xc_handle = xc_interface_open();
if (xc_handle < 0) {
#endif
perror("xc_interface_open");
exit(1);
}
return name;
}

View File

@ -1,68 +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 <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include "qrexec.h"
int get_server_socket(char *socket_address)
{
struct sockaddr_un sockname;
int s;
unlink(socket_address);
s = socket(AF_UNIX, SOCK_STREAM, 0);
memset(&sockname, 0, sizeof(sockname));
sockname.sun_family = AF_UNIX;
memcpy(sockname.sun_path, socket_address, strlen(socket_address));
if (bind(s, (struct sockaddr *) &sockname, sizeof(sockname)) == -1) {
printf("bind() failed\n");
close(s);
exit(1);
}
// chmod(sockname.sun_path, 0666);
if (listen(s, 5) == -1) {
perror("listen() failed\n");
close(s);
exit(1);
}
return s;
}
int do_accept(int s)
{
struct sockaddr_un peer;
unsigned int addrlen;
int fd;
addrlen = sizeof(peer);
fd = accept(s, (struct sockaddr *) &peer, &addrlen);
if (fd == -1) {
perror("unix accept");
exit(1);
}
return fd;
}

View File

@ -1,137 +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 <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <ioall.h>
#include <stdlib.h>
#include "qrexec.h"
#include "buffer.h"
#include "glue.h"
/*
There is buffered data in "buffer" for client id "client_id", and select()
reports that "fd" is writable. Write as much as possible to fd, if all sent,
notify the peer that this client's pipe is no longer full.
*/
int flush_client_data(int fd, int client_id, struct buffer *buffer)
{
int ret;
int len;
for (;;) {
len = buffer_len(buffer);
if (len > MAX_DATA_CHUNK)
len = MAX_DATA_CHUNK;
ret = write(fd, buffer_data(buffer), len);
if (ret == -1) {
if (errno != EAGAIN) {
return WRITE_STDIN_ERROR;
} else
return WRITE_STDIN_BUFFERED;
}
// we previously called buffer_remove(buffer, len)
// it will be wrong if we change MAX_DATA_CHUNK to something large
// as pipes writes are atomic only to PIPE_MAX limit
buffer_remove(buffer, ret);
len = buffer_len(buffer);
if (!len) {
struct server_header s_hdr;
s_hdr.type = MSG_XON;
s_hdr.client_id = client_id;
s_hdr.len = 0;
write_all_vchan_ext(&s_hdr, sizeof s_hdr);
return WRITE_STDIN_OK;
}
}
}
/*
Write "len" bytes from "data" to "fd". If not all written, buffer the rest
to "buffer", and notify the peer that the client "client_id" pipe is full via
MSG_XOFF message.
*/
int write_stdin(int fd, int client_id, char *data, int len,
struct buffer *buffer)
{
int ret;
int written = 0;
if (buffer_len(buffer)) {
buffer_append(buffer, data, len);
return WRITE_STDIN_BUFFERED;
}
while (written < len) {
ret = write(fd, data + written, len - written);
if (ret == 0) {
perror("write_stdin: write returns 0 ???");
exit(1);
}
if (ret == -1) {
struct server_header s_hdr;
if (errno != EAGAIN)
return WRITE_STDIN_ERROR;
buffer_append(buffer, data + written,
len - written);
s_hdr.type = MSG_XOFF;
s_hdr.client_id = client_id;
s_hdr.len = 0;
write_all_vchan_ext(&s_hdr, sizeof s_hdr);
return WRITE_STDIN_BUFFERED;
}
written += ret;
}
return WRITE_STDIN_OK;
}
/*
Data feed process has exited, so we need to clear all control structures for
the client. However, if we have buffered data for the client (which is rare btw),
fire&forget a separate process to flush them.
*/
int fork_and_flush_stdin(int fd, struct buffer *buffer)
{
int i;
if (!buffer_len(buffer))
return 0;
switch (fork()) {
case -1:
perror("fork");
exit(1);
case 0:
break;
default:
return 1;
}
for (i = 0; i < MAX_FDS; i++)
if (i != fd && i != 2)
close(i);
set_block(fd);
write_all(fd, buffer_data(buffer), buffer_len(buffer));
exit(0);
}

View File

@ -56,7 +56,7 @@ Requires(preun): systemd-units
Requires(postun): systemd-units
%endif
Requires: python, xen-runtime, pciutils, python-inotify, python-daemon, kernel-qubes-dom0
Requires: qubes-core-libs
Requires: qubes-qrexec-dom0
Requires: python-lxml
Conflicts: qubes-gui-dom0 < 1.1.13
Requires: xen >= 4.1.0-2
@ -86,8 +86,6 @@ python -O -m compileall dom0/qvm-core dom0/qmemman
for dir in dom0/restore dom0/qubes_rpc dom0/qmemman; do
(cd $dir; make)
done
(cd vchan; make -f Makefile.linux)
(cd qrexec; make)
%install
@ -152,9 +150,6 @@ cp aux-tools/startup-misc.sh $RPM_BUILD_ROOT/usr/lib/qubes
cp aux-tools/prepare_volatile_img.sh $RPM_BUILD_ROOT/usr/lib/qubes
cp qmemman/server.py $RPM_BUILD_ROOT/usr/lib/qubes/qmemman_daemon.py
cp qmemman/meminfo-writer $RPM_BUILD_ROOT/usr/lib/qubes/
cp ../qrexec/qrexec_daemon $RPM_BUILD_ROOT/usr/lib/qubes/
cp ../qrexec/qrexec_client $RPM_BUILD_ROOT/usr/lib/qubes/
cp ../qrexec/qrexec_policy $RPM_BUILD_ROOT/usr/lib/qubes/
cp qubes_rpc/qfile-dom0-unpacker $RPM_BUILD_ROOT/usr/lib/qubes/
cp qubes_rpc/qubes-notify-updates $RPM_BUILD_ROOT/usr/lib/qubes/
cp qubes_rpc/qubes-receive-appmenus $RPM_BUILD_ROOT/usr/lib/qubes/
@ -176,7 +171,6 @@ cp qubes_rpc/qubes.OpenInVM.policy $RPM_BUILD_ROOT/etc/qubes_rpc/policy/qubes.Op
cp qubes_rpc/qubes.VMShell.policy $RPM_BUILD_ROOT/etc/qubes_rpc/policy/qubes.VMShell
cp qubes_rpc/qubes.SyncAppMenus.policy $RPM_BUILD_ROOT/etc/qubes_rpc/policy/qubes.SyncAppMenus
cp qubes_rpc/qubes.SyncAppMenus $RPM_BUILD_ROOT/etc/qubes_rpc/
cp ../qrexec/qubes_rpc_multiplexer $RPM_BUILD_ROOT/usr/lib/qubes
cp qubes_rpc/qubes.NotifyUpdates.policy $RPM_BUILD_ROOT/etc/qubes_rpc/policy/qubes.NotifyUpdates
cp qubes_rpc/qubes.NotifyUpdates $RPM_BUILD_ROOT/etc/qubes_rpc/
cp qubes_rpc/qubes.ReceiveUpdates.policy $RPM_BUILD_ROOT/etc/qubes_rpc/policy/qubes.ReceiveUpdates
@ -489,10 +483,6 @@ fi
/etc/xen/scripts/block-snapshot
/etc/xen/scripts/block-origin
/etc/xen/scripts/vif-route-qubes
/usr/lib/qubes/qrexec_client
/usr/lib/qubes/qubes_rpc_multiplexer
/usr/lib/qubes/qrexec_policy
%dir /etc/qubes_rpc/policy
%attr(0664,root,qubes) %config(noreplace) /etc/qubes_rpc/policy/qubes.Filecopy
%attr(0664,root,qubes) %config(noreplace) /etc/qubes_rpc/policy/qubes.OpenInVM
%attr(0664,root,qubes) %config(noreplace) /etc/qubes_rpc/policy/qubes.SyncAppMenus
@ -502,7 +492,6 @@ fi
/etc/qubes_rpc/qubes.SyncAppMenus
/etc/qubes_rpc/qubes.NotifyUpdates
/etc/qubes_rpc/qubes.ReceiveUpdates
%attr(4750,root,qubes) /usr/lib/qubes/qrexec_daemon
%attr(2770,root,qubes) %dir /var/log/qubes
%attr(0770,root,qubes) %dir /var/run/qubes
/etc/yum.real.repos.d/qubes-cached.repo