qrexec: added comments, made identifiers more verbose
This commit is contained in:
parent
675d4ce25b
commit
d68183da0c
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -56,7 +56,7 @@ enum {
|
||||
|
||||
struct server_header {
|
||||
unsigned int type;
|
||||
unsigned int clid;
|
||||
unsigned int client_id;
|
||||
unsigned int len;
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user