Remove qrexec - moved to separate package
This commit is contained in:
parent
325cf4b894
commit
3c3252b2a3
4
qrexec/.gitignore
vendored
4
qrexec/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
qrexec_agent
|
||||
qrexec_client
|
||||
qrexec_daemon
|
||||
qrexec_client_vm
|
@ -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
|
@ -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,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;
|
||||
}
|
@ -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);
|
@ -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];
|
||||
}
|
||||
}
|
@ -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);
|
106
qrexec/qrexec.h
106
qrexec/qrexec.h
@ -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;
|
||||
};
|
@ -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, ¶ms, 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(¶ms, 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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(¶ms, 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, ¶ms, 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;
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
@ -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()
|
@ -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
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user