qrexec: added comments, made identifiers more verbose

This commit is contained in:
Rafal Wojtczuk 2011-05-04 12:52:54 +02:00
parent 675d4ce25b
commit d68183da0c
6 changed files with 235 additions and 162 deletions

View File

@ -61,6 +61,12 @@ void buffer_free(struct buffer *b)
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;

View File

@ -41,8 +41,8 @@ enum {
WRITE_STDIN_ERROR
};
int flush_client_data(int fd, int clid, struct buffer *buffer);
int write_stdin(int fd, int clid, char *data, int len,
int flush_client_data(int fd, int client_id, struct buffer *buffer);
int write_stdin(int fd, int client_id, char *data, int len,
struct buffer *buffer);
void set_nonblock(int fd);
int fork_and_flush_stdin(int fd, struct buffer *buffer);

View File

@ -56,7 +56,7 @@ enum {
struct server_header {
unsigned int type;
unsigned int clid;
unsigned int client_id;
unsigned int len;
};

View File

@ -42,7 +42,7 @@ enum fdtype {
};
struct _process_fd {
int clid;
int client_id;
int type;
int is_blocked;
};
@ -122,7 +122,7 @@ void do_exec(char *cmd)
exit(1);
}
void handle_just_exec(int clid, int len)
void handle_just_exec(int client_id, int len)
{
char buf[len];
int fdn, pid;
@ -143,7 +143,7 @@ void handle_just_exec(int clid, int len)
fprintf(stderr, "executed (nowait) %s pid %d\n", buf, pid);
}
void handle_exec(int clid, int len)
void handle_exec(int client_id, int len)
{
char buf[len];
int pid, stdin_fd, stdout_fd, stderr_fd;
@ -152,10 +152,10 @@ void handle_exec(int clid, int len)
do_fork_exec(buf, &pid, &stdin_fd, &stdout_fd, &stderr_fd);
process_fd[stdout_fd].clid = clid;
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].clid = clid;
process_fd[stderr_fd].client_id = client_id;
process_fd[stderr_fd].type = FDTYPE_STDERR;
process_fd[stderr_fd].is_blocked = 0;
@ -166,13 +166,13 @@ void handle_exec(int clid, int len)
set_nonblock(stdin_fd);
client_info[clid].stdin_fd = stdin_fd;
client_info[clid].stdout_fd = stdout_fd;
client_info[clid].stderr_fd = stderr_fd;
client_info[clid].pid = pid;
client_info[clid].is_blocked = 0;
client_info[clid].is_close_after_flush_needed = 0;
buffer_init(&client_info[clid].buffer);
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].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);
fprintf(stderr, "executed %s pid %d\n", buf, pid);
@ -187,79 +187,79 @@ void update_max_process_fd()
max_process_fd = i;
}
void send_exit_code(int clid, int status)
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.clid = clid;
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 for clid %d pid %d\n", clid,
client_info[clid].pid);
fprintf(stderr, "send exit code for client_id %d pid %d\n", client_id,
client_info[client_id].pid);
}
// erase process data structures, possibly forced by remote
void remove_process(int clid, int status)
void remove_process(int client_id, int status)
{
int i;
if (!client_info[clid].pid)
if (!client_info[client_id].pid)
return;
fork_and_flush_stdin(client_info[clid].stdin_fd, &client_info[clid].buffer);
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[clid].pid, SIGKILL);
kill(client_info[client_id].pid, SIGKILL);
#endif
if (status != -1)
send_exit_code(clid, status);
send_exit_code(client_id, status);
close(client_info[clid].stdin_fd);
client_info[clid].pid = 0;
client_info[clid].stdin_fd = -1;
client_info[clid].is_blocked = 0;
buffer_free(&client_info[clid].buffer);
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].clid == clid) {
&& process_fd[i].client_id == client_id) {
process_fd[i].type = FDTYPE_INVALID;
process_fd[i].clid = -1;
process_fd[i].client_id = -1;
process_fd[i].is_blocked = 0;
close(i);
}
update_max_process_fd();
}
void handle_input(int clid, int len)
void handle_input(int client_id, int len)
{
char buf[len];
read_all_vchan_ext(buf, len);
if (!client_info[clid].pid)
if (!client_info[client_id].pid)
return;
if (len == 0) {
if (client_info[clid].is_blocked)
client_info[clid].is_close_after_flush_needed = 1;
if (client_info[client_id].is_blocked)
client_info[client_id].is_close_after_flush_needed = 1;
else {
close(client_info[clid].stdin_fd);
client_info[clid].stdin_fd = -1;
close(client_info[client_id].stdin_fd);
client_info[client_id].stdin_fd = -1;
}
return;
}
switch (write_stdin
(client_info[clid].stdin_fd, clid, buf, len,
&client_info[clid].buffer)) {
(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[clid].is_blocked = 1;
client_info[client_id].is_blocked = 1;
break;
case WRITE_STDIN_ERROR:
remove_process(clid, 128);
remove_process(client_id, 128);
break;
default:
fprintf(stderr, "unknown write_stdin?\n");
@ -268,10 +268,10 @@ void handle_input(int clid, int len)
}
void set_blocked_outerr(int clid, int val)
void set_blocked_outerr(int client_id, int val)
{
process_fd[client_info[clid].stdout_fd].is_blocked = val;
process_fd[client_info[clid].stderr_fd].is_blocked = 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()
@ -279,27 +279,27 @@ 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.clid,
// 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.clid, 0);
set_blocked_outerr(s_hdr.client_id, 0);
break;
case MSG_XOFF:
set_blocked_outerr(s_hdr.clid, 1);
set_blocked_outerr(s_hdr.client_id, 1);
break;
case MSG_SERVER_TO_AGENT_EXEC_CMDLINE:
handle_exec(s_hdr.clid, s_hdr.len);
handle_exec(s_hdr.client_id, s_hdr.len);
break;
case MSG_SERVER_TO_AGENT_JUST_EXEC:
handle_just_exec(s_hdr.clid, s_hdr.len);
handle_just_exec(s_hdr.client_id, s_hdr.len);
break;
case MSG_SERVER_TO_AGENT_INPUT:
handle_input(s_hdr.clid, s_hdr.len);
handle_input(s_hdr.client_id, s_hdr.len);
break;
case MSG_SERVER_TO_AGENT_CLIENT_END:
remove_process(s_hdr.clid, -1);
remove_process(s_hdr.client_id, -1);
break;
default:
fprintf(stderr, "msg type from daemon is %d ?\n",
@ -320,15 +320,15 @@ void handle_process_data(int fd)
return;
ret = read(fd, buf, len - sizeof s_hdr);
s_hdr.clid = process_fd[fd].clid;
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, clid=%d, type=%d ?\n", fd,
process_fd[fd].clid, process_fd[fd].type);
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;
@ -338,13 +338,13 @@ void handle_process_data(int fd)
}
if (ret == 0) {
process_fd[fd].type = FDTYPE_INVALID;
process_fd[fd].clid = -1;
process_fd[fd].client_id = -1;
process_fd[fd].is_blocked = 0;
close(fd);
update_max_process_fd();
}
if (ret < 0)
remove_process(process_fd[fd].clid, 127);
remove_process(process_fd[fd].client_id, 127);
}
volatile int child_exited;
@ -375,7 +375,7 @@ void handle_process_data_all(fd_set * select_fds)
}
void flush_out_err(int clid)
void flush_out_err(int client_id)
{
fd_set select_set;
int fd_max = -1;
@ -387,7 +387,7 @@ void flush_out_err(int clid)
for (i = 0; i <= max_process_fd; i++) {
if (process_fd[i].type != FDTYPE_INVALID
&& !process_fd[i].is_blocked
&& process_fd[i].clid == clid) {
&& process_fd[i].client_id == client_id) {
FD_SET(i, &select_set);
fd_max = i;
}
@ -411,13 +411,13 @@ void reap_children()
{
int status;
int pid;
int clid;
int client_id;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
clid = find_info(pid);
if (clid < 0)
client_id = find_info(pid);
if (client_id < 0)
continue;
flush_out_err(clid);
remove_process(clid, status);
flush_out_err(client_id);
remove_process(client_id, status);
}
child_exited = 0;
}
@ -450,10 +450,10 @@ int fill_fds_for_select(fd_set * rdset, fd_set * wrset)
return max;
}
void flush_client_data_agent(int clid)
void flush_client_data_agent(int client_id)
{
struct _client_info *info = &client_info[clid];
switch (flush_client_data(info->stdin_fd, clid, &info->buffer)) {
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) {
@ -463,7 +463,7 @@ void flush_client_data_agent(int clid)
}
break;
case WRITE_STDIN_ERROR:
remove_process(clid, 128);
remove_process(client_id, 128);
break;
case WRITE_STDIN_BUFFERED:
break;
@ -479,15 +479,15 @@ void handle_trigger_io()
char buf[5];
int ret;
s_hdr.clid = 0;
s_hdr.client_id = 0;
s_hdr.len = 0;
if ((ret = read(trigger_fd, buf, 4)) == 4) {
buf[4] = 0;
if (!strcmp(buf, "FCPR"))
s_hdr.clid = QREXEC_EXECUTE_FILE_COPY;
s_hdr.client_id = QREXEC_EXECUTE_FILE_COPY;
else if (!strcmp(buf, "DVMR"))
s_hdr.clid = QREXEC_EXECUTE_FILE_COPY_FOR_DISPVM;
if (s_hdr.clid) {
s_hdr.client_id = QREXEC_EXECUTE_FILE_COPY_FOR_DISPVM;
if (s_hdr.client_id) {
s_hdr.type = MSG_AGENT_TO_SERVER_TRIGGER_EXEC;
write_all_vchan_ext(&s_hdr, sizeof s_hdr);
}

View File

@ -34,24 +34,30 @@
#include "glue.h"
enum client_flags {
CLIENT_INVALID = 0,
CLIENT_CMDLINE = 1,
CLIENT_DATA = 2,
CLIENT_DONT_READ = 4,
CLIENT_OUTQ_FULL = 8
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
CLIENT_OUTQ_FULL = 8 // don't write to client, its stdin pipe is full
};
struct _client {
int state;
struct buffer buffer;
int state; // combination of above enum client_flags
struct buffer buffer; // buffered data to client, if any
};
struct _client clients[MAX_FDS];
/*
The "clients" array is indexed by client's fd.
Thus its size must be equal MAX_FDS; defining MAX_CLIENTS for clarity.
*/
int max_client_fd = -1;
int server_fd;
#define MAX_CLIENTS MAX_FDS
struct _client clients[MAX_CLIENTS]; // data on all qrexec_client connections
void handle_usr1(int x)
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
void sigusr1_handler(int x)
{
fprintf(stderr, "connected\n");
exit(0);
@ -59,18 +65,19 @@ void handle_usr1(int x)
void sigchld_handler(int x);
char *remote_domain_name;
char *remote_domain_name; // guess what
/* do the preparatory tasks, needed before entering the main event loop */
void init(int xid)
{
char dbg_log[256];
char qrexec_error_log_name[256];
int logfd;
if (xid <= 0) {
fprintf(stderr, "domain id=0?\n");
exit(1);
}
signal(SIGUSR1, handle_usr1);
signal(SIGUSR1, sigusr1_handler);
switch (fork()) {
case -1:
perror("fork");
@ -86,10 +93,10 @@ void init(int xid)
exit(0);
}
close(0);
snprintf(dbg_log, sizeof(dbg_log),
snprintf(qrexec_error_log_name, sizeof(qrexec_error_log_name),
"/var/log/qubes/qrexec.%d.log", xid);
umask(0007);
logfd = open(dbg_log, O_WRONLY | O_CREAT | O_TRUNC, 0640);
umask(0007); // make the log readable by the "qubes" group
logfd = open(qrexec_error_log_name, O_WRONLY | O_CREAT | O_TRUNC, 0640);
dup2(logfd, 1);
dup2(logfd, 2);
@ -104,18 +111,18 @@ void init(int xid)
setuid(getuid());
/* When running as root, make the socket accessible; perms on /var/run/qubes still apply */
umask(0);
server_fd = get_server_socket(xid, remote_domain_name);
qrexec_daemon_unix_socket_fd = get_server_socket(xid, remote_domain_name);
umask(0077);
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, sigchld_handler);
signal(SIGUSR1, SIG_DFL);
kill(getppid(), SIGUSR1);
kill(getppid(), SIGUSR1); // let the parent know we are ready
}
void handle_new_client()
{
int fd = do_accept(server_fd);
if (fd >= MAX_FDS) {
int fd = do_accept(qrexec_daemon_unix_socket_fd);
if (fd >= MAX_CLIENTS) {
fprintf(stderr, "too many clients ?\n");
exit(1);
}
@ -125,9 +132,13 @@ void handle_new_client()
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 flush_client(int fd)
void terminate_client_and_flush_data(int fd)
{
int i;
struct server_header s_hdr;
@ -143,29 +154,29 @@ void flush_client(int fd)
max_client_fd = i;
}
s_hdr.type = MSG_SERVER_TO_AGENT_CLIENT_END;
s_hdr.clid = fd;
s_hdr.client_id = fd;
s_hdr.len = 0;
write_all_vchan_ext(&s_hdr, sizeof(s_hdr));
}
void pass_to_agent(int fd, struct server_header *s_hdr)
void get_cmdline_body_from_client_and_pass_to_agent(int fd, struct server_header *s_hdr)
{
int len = s_hdr->len;
char buf[len];
if (!read_all(fd, buf, len)) {
flush_client(fd);
terminate_client_and_flush_data(fd);
return;
}
write_all_vchan_ext(s_hdr, sizeof(*s_hdr));
write_all_vchan_ext(buf, len);
}
void handle_client_cmdline(int fd)
void handle_cmdline_message_from_client(int fd)
{
struct client_header hdr;
struct server_header s_hdr;
if (!read_all(fd, &hdr, sizeof hdr)) {
flush_client(fd);
terminate_client_and_flush_data(fd);
return;
}
switch (hdr.type) {
@ -176,59 +187,68 @@ void handle_client_cmdline(int fd)
s_hdr.type = MSG_SERVER_TO_AGENT_JUST_EXEC;
break;
default:
flush_client(fd);
terminate_client_and_flush_data(fd);
return;
}
s_hdr.clid = fd;
s_hdr.client_id = fd;
s_hdr.len = hdr.len;
pass_to_agent(fd, &s_hdr);
get_cmdline_body_from_client_and_pass_to_agent(fd, &s_hdr);
clients[fd].state = CLIENT_DATA;
set_nonblock(fd);
set_nonblock(fd); // so that we can detect full queue without blocking
if (hdr.type == MSG_CLIENT_TO_SERVER_JUST_EXEC)
flush_client(fd);
terminate_client_and_flush_data(fd);
}
void handle_client_data(int 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_client_cmdline(fd);
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");
flush_client(fd);
terminate_client_and_flush_data(fd);
return;
}
s_hdr.clid = fd;
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)
if (ret == 0) // EOF - so don't select() on this client
clients[fd].state |= CLIENT_DONT_READ;
}
void flush_client_data_daemon(int clid)
/*
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(clid, clid, &clients[clid].buffer)) {
case WRITE_STDIN_OK:
clients[clid].state &= ~CLIENT_OUTQ_FULL;
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:
flush_client(clid);
terminate_client_and_flush_data(client_id);
break;
case WRITE_STDIN_BUFFERED:
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");
@ -236,24 +256,29 @@ void flush_client_data_daemon(int clid)
}
}
void pass_to_client(int clid, struct client_header *hdr)
/*
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);
switch (write_stdin
(clid, clid, buf, len + sizeof(*hdr),
&clients[clid].buffer)) {
(client_id, client_id, buf, len + sizeof(*hdr),
&clients[client_id].buffer)) {
case WRITE_STDIN_OK:
break;
case WRITE_STDIN_BUFFERED:
clients[clid].state |= CLIENT_OUTQ_FULL;
case WRITE_STDIN_BUFFERED: // some data have been buffered
clients[client_id].state |= CLIENT_OUTQ_FULL;
break;
case WRITE_STDIN_ERROR:
flush_client(clid);
terminate_client_and_flush_data(client_id);
break;
default:
fprintf(stderr, "unknown write_stdin?\n");
@ -261,6 +286,12 @@ void pass_to_client(int clid, struct client_header *hdr)
}
}
/*
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)
@ -269,6 +300,7 @@ void sigchld_handler(int x)
signal(SIGCHLD, sigchld_handler);
}
/* clean zombies, update children_count */
void reap_children()
{
int status;
@ -277,6 +309,7 @@ void reap_children()
child_exited = 0;
}
/* too many children - wait for one of them to terminate */
void wait_for_child()
{
int status;
@ -285,7 +318,7 @@ void wait_for_child()
}
#define MAX_CHILDREN 10
void check_children_count()
void check_children_count_and_wait_if_too_many()
{
if (children_count > MAX_CHILDREN) {
fprintf(stderr,
@ -296,12 +329,16 @@ void check_children_count()
}
}
void handle_trigger_exec(int req)
/*
Called when agent sends a message asking to execute a predefined command.
*/
void handle_execute_predefined_command(int req)
{
char *rcmd = NULL, *lcmd = NULL;
int i;
check_children_count();
check_children_count_and_wait_if_too_many();
switch (req) {
case QREXEC_EXECUTE_FILE_COPY:
rcmd = "directly:user:/usr/lib/qubes/qfile-agent";
@ -311,7 +348,7 @@ void handle_trigger_exec(int req)
rcmd = "directly:user:/usr/lib/qubes/qfile-agent-dvm";
lcmd = "/usr/lib/qubes/qfile-daemon-dvm";
break;
default:
default: /* cannot happen, already sanitized */
fprintf(stderr, "got trigger exec no %d\n", req);
exit(1);
}
@ -325,7 +362,7 @@ void handle_trigger_exec(int req)
children_count++;
return;
}
for (i = 3; i < 256; i++)
for (i = 3; i < MAX_FDS; i++)
close(i);
signal(SIGCHLD, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
@ -335,10 +372,10 @@ void handle_trigger_exec(int req)
exit(1);
}
void check_clid_in_range(unsigned int untrusted_clid)
void check_client_id_in_range(unsigned int untrusted_client_id)
{
if (untrusted_clid >= MAX_FDS || untrusted_clid < 0) {
fprintf(stderr, "from agent: clid=%d\n", untrusted_clid);
if (untrusted_client_id >= MAX_CLIENTS || untrusted_client_id < 0) {
fprintf(stderr, "from agent: client_id=%d\n", untrusted_client_id);
exit(1);
}
}
@ -349,7 +386,7 @@ void sanitize_message_from_agent(struct server_header *untrusted_header)
int untrusted_cmd;
switch (untrusted_header->type) {
case MSG_AGENT_TO_SERVER_TRIGGER_EXEC:
untrusted_cmd = untrusted_header->clid;
untrusted_cmd = untrusted_header->client_id;
if (untrusted_cmd != QREXEC_EXECUTE_FILE_COPY &&
untrusted_cmd != QREXEC_EXECUTE_FILE_COPY_FOR_DISPVM) {
fprintf(stderr,
@ -361,7 +398,7 @@ void sanitize_message_from_agent(struct server_header *untrusted_header)
case MSG_AGENT_TO_SERVER_STDOUT:
case MSG_SERVER_TO_CLIENT_STDERR:
case MSG_AGENT_TO_SERVER_EXIT_CODE:
check_clid_in_range(untrusted_header->clid);
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",
@ -372,7 +409,7 @@ void sanitize_message_from_agent(struct server_header *untrusted_header)
case MSG_XOFF:
case MSG_XON:
check_clid_in_range(untrusted_header->clid);
check_client_id_in_range(untrusted_header->client_id);
break;
default:
fprintf(stderr, "unknown mesage type %d from agent\n",
@ -381,7 +418,7 @@ void sanitize_message_from_agent(struct server_header *untrusted_header)
}
}
void handle_agent_data()
void handle_message_from_agent()
{
struct client_header hdr;
struct server_header s_hdr, untrusted_s_hdr;
@ -392,20 +429,21 @@ void handle_agent_data()
s_hdr = untrusted_s_hdr;
/* sanitize end */
// fprintf(stderr, "got %x %x %x\n", s_hdr.type, s_hdr.clid,
// 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_EXEC) {
handle_trigger_exec(s_hdr.clid);
handle_execute_predefined_command(s_hdr.client_id);
return;
}
if (s_hdr.type == MSG_XOFF) {
clients[s_hdr.clid].state |= CLIENT_DONT_READ;
clients[s_hdr.client_id].state |= CLIENT_DONT_READ;
return;
}
if (s_hdr.type == MSG_XON) {
clients[s_hdr.clid].state &= ~CLIENT_DONT_READ;
clients[s_hdr.client_id].state &= ~CLIENT_DONT_READ;
return;
}
@ -419,49 +457,56 @@ void handle_agent_data()
case MSG_AGENT_TO_SERVER_EXIT_CODE:
hdr.type = MSG_SERVER_TO_CLIENT_EXIT_CODE;
break;
default: /* cannot happen */
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.clid].state == CLIENT_INVALID) {
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;
}
pass_to_client(s_hdr.clid, &hdr);
get_packet_data_from_agent_and_pass_to_client(s_hdr.client_id, &hdr);
if (s_hdr.type == MSG_AGENT_TO_SERVER_EXIT_CODE)
flush_client(s_hdr.clid);
terminate_client_and_flush_data(s_hdr.client_id);
}
int fill_fds_for_select(fd_set * rdset, fd_set * wrset)
/*
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(rdset);
FD_ZERO(wrset);
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, rdset);
FD_SET(i, read_fdset);
max = i;
}
if (clients[i].state != CLIENT_INVALID
&& clients[i].state & CLIENT_OUTQ_FULL) {
FD_SET(i, wrset);
FD_SET(i, write_fdset);
max = i;
}
}
FD_SET(server_fd, rdset);
if (server_fd > max)
max = server_fd;
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 rdset, wrset;
fd_set read_fdset, write_fdset;
int i;
int max;
@ -470,29 +515,36 @@ int main(int argc, char **argv)
exit(1);
}
init(atoi(argv[1]));
/*
The main event loop. Waits for one of the following events:
- message from client
- message from agent
- new client
- child exited
*/
for (;;) {
max = fill_fds_for_select(&rdset, &wrset);
max = fill_fdsets_for_select(&read_fdset, &write_fdset);
if (buffer_space_vchan_ext() <=
sizeof(struct server_header))
FD_ZERO(&rdset);
FD_ZERO(&read_fdset); // vchan full - don't read from clients
wait_for_vchan_or_argfd(max, &rdset, &wrset);
wait_for_vchan_or_argfd(max, &read_fdset, &write_fdset);
if (FD_ISSET(server_fd, &rdset))
if (FD_ISSET(qrexec_daemon_unix_socket_fd, &read_fdset))
handle_new_client();
while (read_ready_vchan_ext())
handle_agent_data();
handle_message_from_agent();
for (i = 0; i <= max_client_fd; i++)
if (clients[i].state != CLIENT_INVALID
&& FD_ISSET(i, &rdset))
handle_client_data(i);
&& 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, &wrset))
flush_client_data_daemon(i);
&& FD_ISSET(i, &write_fdset))
write_buffered_data_to_client(i);
if (child_exited)
reap_children();

View File

@ -29,7 +29,12 @@
#include "buffer.h"
#include "glue.h"
int flush_client_data(int fd, int clid, struct buffer *buffer)
/*
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;
@ -49,7 +54,7 @@ int flush_client_data(int fd, int clid, struct buffer *buffer)
if (!len) {
struct server_header s_hdr;
s_hdr.type = MSG_XON;
s_hdr.clid = clid;
s_hdr.client_id = client_id;
s_hdr.len = 0;
write_all_vchan_ext(&s_hdr, sizeof s_hdr);
return WRITE_STDIN_OK;
@ -58,7 +63,12 @@ int flush_client_data(int fd, int clid, struct buffer *buffer)
}
int write_stdin(int fd, int clid, char *data, int len,
/*
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;
@ -84,7 +94,7 @@ int write_stdin(int fd, int clid, char *data, int len,
len - written);
s_hdr.type = MSG_XOFF;
s_hdr.clid = clid;
s_hdr.client_id = client_id;
s_hdr.len = 0;
write_all_vchan_ext(&s_hdr, sizeof s_hdr);
@ -108,6 +118,11 @@ void set_block(int fd)
fcntl(fd, F_SETFL, fl & ~O_NONBLOCK);
}
/*
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;