From b13844afe13b68c7544e6d27c6eda08d20130a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Fri, 27 Dec 2013 06:07:33 +0100 Subject: [PATCH] qrexec: new protocol - direct data vchan connections --- qrexec/Makefile | 4 +- qrexec/qrexec-agent-data.c | 418 +++++++++++++++++++++++++++++ qrexec/qrexec-agent.c | 535 ++++++++++--------------------------- qrexec/qrexec-agent.h | 27 ++ qrexec/qrexec-client-vm.c | 15 +- 5 files changed, 593 insertions(+), 406 deletions(-) create mode 100644 qrexec/qrexec-agent-data.c create mode 100644 qrexec/qrexec-agent.h diff --git a/qrexec/Makefile b/qrexec/Makefile index ab07c76..06e2489 100644 --- a/qrexec/Makefile +++ b/qrexec/Makefile @@ -3,8 +3,8 @@ CFLAGS+=-I. -g -Wall -Wextra -Werror -pie -fPIC `pkg-config --cflags vchan-$(BAC LIBS=`pkg-config --libs vchan-$(BACKEND_VMM)` -lqrexec-utils all: qrexec-agent qrexec-client-vm -qrexec-agent: qrexec-agent.o - $(CC) -pie -g -o qrexec-agent qrexec-agent.o $(LIBS) +qrexec-agent: qrexec-agent.o qrexec-agent-data.o + $(CC) -pie -g -o qrexec-agent qrexec-agent.o qrexec-agent-data.o $(LIBS) qrexec-client-vm: qrexec-client-vm.o $(CC) -pie -g -o qrexec-client-vm qrexec-client-vm.o clean: diff --git a/qrexec/qrexec-agent-data.c b/qrexec/qrexec-agent-data.c new file mode 100644 index 0000000..5620255 --- /dev/null +++ b/qrexec/qrexec-agent-data.c @@ -0,0 +1,418 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2013 Marek Marczykowski-Górecki + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qrexec.h" +#include "libqrexec-utils.h" +#include "qrexec-agent.h" + +#define VCHAN_BUFFER_SIZE 65536 + +static volatile int child_exited; +int stdout_msg_type = MSG_DATA_STDOUT; +pid_t child_process_pid; + +static void sigchld_handler(int __attribute__((__unused__))x) +{ + child_exited = 1; + signal(SIGCHLD, sigchld_handler); +} + + +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(RPC_REQUEST_COMMAND) + 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, RPC_REQUEST_COMMAND " ", strlen(RPC_REQUEST_COMMAND)+1)==0) { + strcpy(buf, QUBES_RPC_MULTIPLEXER_PATH); + strcpy(buf + strlen(QUBES_RPC_MULTIPLEXER_PATH), realcmd + strlen(RPC_REQUEST_COMMAND)); + realcmd = buf; + } + signal(SIGCHLD, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + + execl("/bin/su", "su", "-", cmd, "-c", realcmd, NULL); + perror("execl"); + exit(1); +} + +int handle_just_exec(char *cmdline) +{ + int fdn, pid; + + switch (pid = fork()) { + case -1: + perror("fork"); + return -1; + case 0: + fdn = open("/dev/null", O_RDWR); + fix_fds(fdn, fdn, fdn); + do_exec(cmdline); + perror("execl"); + exit(1); + default:; + } + fprintf(stderr, "executed (nowait) %s pid %d\n", cmdline, pid); + return 0; +} + +void send_exit_code(libvchan_t *data_vchan, int status) +{ + struct msg_header hdr; + hdr.type = MSG_DATA_EXIT_CODE; + hdr.len = sizeof(status); + if (libvchan_send(data_vchan, &hdr, sizeof(hdr)) < 0) + handle_vchan_error("write hdr"); + if (libvchan_send(data_vchan, &status, sizeof(status)) < 0) + handle_vchan_error("write status"); + fprintf(stderr, "send exit code %d\n", status); +} + +/* handle data from specified FD and send over vchan link + * Return: + * -1 - vchan error occurred + * 0 - EOF received, do not attempt to access this FD again + * 1 - some data processed, call it again when buffer space and more data + * available + */ +int handle_input(libvchan_t *vchan, int fd, int msg_type) +{ + char buf[MAX_DATA_CHUNK]; + int len; + struct msg_header hdr; + + hdr.type = msg_type; + while (libvchan_buffer_space(vchan) > (int)sizeof(struct msg_header)) { + len = libvchan_buffer_space(vchan)-sizeof(struct msg_header); + if (len > (int)sizeof(buf)) + len = sizeof(buf); + len = read(fd, buf, len); + if (len < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return 1; + else + return -1; + } + hdr.len = len; + if (libvchan_send(vchan, &hdr, sizeof(hdr)) < 0) + return -1; + + if (len && !write_vchan_all(vchan, buf, len)) + return -1; + + if (len == 0) { + close(fd); + return 0; + } + } + return 1; +} + +/* handle data from vchan and send it to specified FD + * Return: + * -2 - remote process terminated, do not send more data to it + * -1 - vchan error occurred + * 0 - EOF received, do not attempt to access this FD again + * 1 - some data processed, call it again when buffer space and more data + * available + */ +int handle_remote_data(libvchan_t *data_vchan, int stdin_fd) +{ + struct msg_header hdr; + char buf[MAX_DATA_CHUNK]; + int status; + + /* TODO: set stdin_fd to non-blocking mode and handle its buffering */ + while (libvchan_data_ready(data_vchan) > 0) { + if (libvchan_recv(data_vchan, &hdr, sizeof(hdr)) < 0) + return -1; + if (hdr.len > MAX_DATA_CHUNK) { + fprintf(stderr, "Too big data chunk received: %d > %d\n", + hdr.len, MAX_DATA_CHUNK); + return -1; + } + if (!read_vchan_all(data_vchan, buf, hdr.len)) + return -1; + + switch (hdr.type) { + /* handle both directions because this can be either server or client + * of VM-VM connection */ + case MSG_DATA_STDIN: + case MSG_DATA_STDOUT: + if (stdin_fd < 0) + /* discard the data */ + continue; + if (hdr.len == 0) { + close(stdin_fd); + stdin_fd = -1; + return 0; + } else { + /* FIXME: use buffered write here to prevent deadlock */ + if (!write_all(stdin_fd, buf, hdr.len)) { + if (errno == EPIPE) { + close(stdin_fd); + stdin_fd = -1; + } else { + perror("write"); + } + return 0; + } + } + break; + case MSG_DATA_STDERR: + /* stderr of remote service, log locally */ + if (!write_all(2, buf, hdr.len)) { + perror("write"); + /* only log the error */ + } + break; + case MSG_DATA_EXIT_CODE: + /* remote process exited, so there is no sense to send any data + * to it */ + status = *(unsigned int *)buf; + fprintf(stderr, "Remote service process exited with code %d\n", status); + return -2; + } + } + return 1; +} + +void process_child_io(libvchan_t *data_vchan, + int stdin_fd, int stdout_fd, int stderr_fd) +{ + fd_set rdset, wrset; + int vchan_fd; + sigset_t selectmask; + int child_process_status = -1; + int ret, max_fd; + struct timespec zero_timeout = { 0, 0 }; + + sigemptyset(&selectmask); + sigaddset(&selectmask, SIGCHLD); + sigprocmask(SIG_BLOCK, &selectmask, NULL); + sigemptyset(&selectmask); + + set_nonblock(stdout_fd); + set_nonblock(stderr_fd); + + while (1) { + if (child_exited) { + pid_t pid; + int status; + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + if (pid == child_process_pid) { + child_process_status = WEXITSTATUS(status); + if (stdin_fd >= 0) { + close(stdin_fd); + stdin_fd = -1; + } + } + } + child_exited = 0; + } + + /* if all done, exit the loop */ + if ((!child_process_pid || child_process_status > -1) && + stdin_fd == -1 && stdout_fd == -1 && stderr_fd == -1) { + if (child_process_status > -1) { + send_exit_code(data_vchan, child_process_status); + } + break; + } + /* otherwise handle the events */ + + FD_ZERO(&rdset); + FD_ZERO(&wrset); + max_fd = -1; + vchan_fd = libvchan_fd_for_select(data_vchan); + if (libvchan_buffer_space(data_vchan) > (int)sizeof(struct msg_header)) { + if (stdout_fd >= 0) { + FD_SET(stdout_fd, &rdset); + if (stdout_fd > max_fd) + max_fd = stdout_fd; + } + if (stderr_fd >= 0) { + FD_SET(stderr_fd, &rdset); + if (stderr_fd > max_fd) + max_fd = stderr_fd; + } + } + FD_SET(vchan_fd, &rdset); + if (vchan_fd > max_fd) + max_fd = vchan_fd; + + if (libvchan_data_ready(data_vchan) > 0) { + /* check for other FDs, but exit immediately */ + ret = pselect(max_fd + 1, &rdset, &wrset, NULL, &zero_timeout, &selectmask); + } else + ret = pselect(max_fd + 1, &rdset, &wrset, NULL, NULL, &selectmask); + if (ret < 0) { + if (errno == EINTR) + continue; + else { + perror("pselect"); + /* TODO */ + break; + } + } + + /* clear event pending flag */ + if (FD_ISSET(vchan_fd, &rdset)) { + if (libvchan_wait(data_vchan) < 0) + handle_vchan_error("wait"); + } + + if (stdout_fd >= 0 && FD_ISSET(stdout_fd, &rdset)) { + switch (handle_input(data_vchan, stdout_fd, stdout_msg_type)) { + case -1: + handle_vchan_error("send"); + break; + case 0: + stdout_fd = -1; + break; + } + } + if (stderr_fd >= 0 && FD_ISSET(stderr_fd, &rdset)) { + switch (handle_input(data_vchan, stderr_fd, MSG_DATA_STDERR)) { + case -1: + handle_vchan_error("send"); + break; + case 0: + stderr_fd = -1; + break; + } + } + /* handle_remote_data will check if any data is available */ + switch (handle_remote_data(data_vchan, stdin_fd)) { + case -1: + handle_vchan_error("read"); + break; + case 0: + stdin_fd = -1; + break; + case -2: + /* remote process exited, no sense in sending more data to it */ + close(stdout_fd); + stdout_fd = -1; + close(stderr_fd); + stderr_fd = -1; + break; + } + } +} + +pid_t handle_new_process(int type, int connect_domain, int connect_port, + char *cmdline, int cmdline_len) +{ + struct service_params *svc_params = (struct service_params*)cmdline; + libvchan_t *data_vchan; + pid_t pid; + int stdin_fd, stdout_fd, stderr_fd; + + if (type == MSG_SERVICE_CONNECT) { + if (cmdline_len != sizeof(*svc_params)) { + fprintf(stderr, "Invalid MSG_SERVICE_CONNECT packet (cmdline len %d)\n", cmdline_len); + return -1; + } + sscanf(cmdline, "%d %d %d", &stdin_fd, &stdout_fd, &stderr_fd); + } + + switch (pid=fork()){ + case -1: + perror("fork"); + return -1; + case 0: + break; + default: + if (type == MSG_SERVICE_CONNECT) { + /* no longer needed in parent process */ + close(stdin_fd); + close(stdout_fd); + close(stderr_fd); + } + return pid; + } + + /* child process */ + if (type == MSG_SERVICE_CONNECT) { + data_vchan = libvchan_server_init(connect_domain, connect_port, + VCHAN_BUFFER_SIZE, VCHAN_BUFFER_SIZE); + if (data_vchan) + libvchan_wait(data_vchan); + } else { + data_vchan = libvchan_client_init(connect_domain, connect_port); + } + if (!data_vchan) { + fprintf(stderr, "Data vchan connection failed\n"); + exit(1); + } + handle_handshake(data_vchan); + + signal(SIGCHLD, sigchld_handler); + + switch (type) { + case MSG_JUST_EXEC: + send_exit_code(data_vchan, handle_just_exec(cmdline)); + libvchan_close(data_vchan); + break; + case MSG_EXEC_CMDLINE: + do_fork_exec(cmdline, &pid, &stdin_fd, &stdout_fd, &stderr_fd); + fprintf(stderr, "executed %s pid %d\n", cmdline, pid); + child_process_pid = pid; + process_child_io(data_vchan, stdin_fd, stdout_fd, stderr_fd); + break; + case MSG_SERVICE_CONNECT: + child_process_pid = 0; + stdout_msg_type = MSG_DATA_STDIN; + process_child_io(data_vchan, stdin_fd, stdout_fd, stderr_fd); + break; + } + exit(0); + /* suppress warning */ + return 0; +} diff --git a/qrexec/qrexec-agent.c b/qrexec/qrexec-agent.c index 2e83a7d..63ed128 100644 --- a/qrexec/qrexec-agent.c +++ b/qrexec/qrexec-agent.c @@ -31,75 +31,103 @@ #include #include #include +#include #include "qrexec.h" #include #include "libqrexec-utils.h" +#include "qrexec-agent.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; +struct _connection_info { int pid; - int is_blocked; - int is_close_after_flush_needed; - struct buffer buffer; + int connect_domain; + int connect_port; }; int max_process_fd = -1; -/* indexed by file descriptor */ -struct _process_fd process_fd[MAX_FDS]; +/* */ +struct _connection_info connection_info[MAX_FDS]; -/* indexed by client id, which is descriptor number of a client in daemon */ -struct _client_info client_info[MAX_FDS]; - -libvchan_t *vchan; +libvchan_t *ctrl_vchan; int trigger_fd; int passfd_socket; int meminfo_write_started = 0; -void do_exec(const char *cmd); -void handle_vchan_error(const char *op) { +void handle_vchan_error(const char *op) +{ fprintf(stderr, "Error while vchan %s, exiting\n", op); exit(1); } +int handle_handshake(libvchan_t *ctrl) +{ + struct msg_header hdr; + struct peer_info info; + + /* send own HELLO */ + hdr.type = MSG_HELLO; + hdr.len = sizeof(info); + info.version = QREXEC_PROTOCOL_VERSION; + + if (libvchan_send(ctrl, &hdr, sizeof(hdr)) != sizeof(hdr)) { + fprintf(stderr, "Failed to send HELLO hdr to agent\n"); + return -1; + } + + if (libvchan_send(ctrl, &info, sizeof(info)) != sizeof(info)) { + fprintf(stderr, "Failed to send HELLO hdr to agent\n"); + return -1; + } + + /* receive MSG_HELLO from remote */ + if (libvchan_recv(ctrl, &hdr, sizeof(hdr)) != sizeof(hdr)) { + fprintf(stderr, "Failed to read agent HELLO hdr\n"); + return -1; + } + + if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) { + fprintf(stderr, "Invalid HELLO packet received: type %d, len %d\n", hdr.type, hdr.len); + return -1; + } + + if (libvchan_recv(ctrl, &info, sizeof(info)) != sizeof(info)) { + fprintf(stderr, "Failed to read agent HELLO body\n"); + return -1; + } + + if (info.version != QREXEC_PROTOCOL_VERSION) { + fprintf(stderr, "Incompatible agent protocol version (remote %d, local %d)\n", info.version, QREXEC_PROTOCOL_VERSION); + return -1; + } + + + return 0; +} + void init() { /* FIXME: This 0 is remote domain ID */ - vchan = libvchan_server_init(0, REXEC_PORT, 4096, 4096); - if (!vchan) + ctrl_vchan = libvchan_server_init(0, VCHAN_BASE_PORT, 4096, 4096); + if (!ctrl_vchan) handle_vchan_error("server_init"); + if (handle_handshake(ctrl_vchan) < 0) + exit(1); 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); - register_exec_func(do_exec); /* wait for qrexec daemon */ - while (!libvchan_is_open(vchan)) - libvchan_wait(vchan); + while (!libvchan_is_open(ctrl_vchan)) + libvchan_wait(ctrl_vchan); } -void wake_meminfo_writer() { +void wake_meminfo_writer() +{ FILE *f; int pid; @@ -130,266 +158,82 @@ void wake_meminfo_writer() { meminfo_write_started = 1; } -void no_colon_in_cmd() +void register_vchan_connection(pid_t pid, int domain, int port) { - fprintf(stderr, - "cmdline is supposed to be in user:command form\n"); - exit(1); -} + int i; -void do_exec(const char *cmd) -{ - char buf[strlen(QUBES_RPC_MULTIPLEXER_PATH) + strlen(cmd) - strlen(QUBES_RPC_MAGIC_CMD) + 1]; - char *realcmd = index(cmd, ':'), *user; - if (!realcmd) - no_colon_in_cmd(); - /* mark end of username and move to command */ - user=strndup(cmd,realcmd-cmd); - 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; + for (i = 0; i < MAX_FDS; i++) { + if (connection_info[i].pid == 0) { + connection_info[i].pid = pid; + connection_info[i].connect_domain = domain; + connection_info[i].connect_port = port; + return; + } } - signal(SIGCHLD, SIG_DFL); - signal(SIGPIPE, SIG_DFL); - execl("/bin/su", "su", "-", user, "-c", realcmd, NULL); - perror("execl"); - exit(1); + fprintf(stderr, "No free slot for child %d (connection to %d:%d)\n", pid, domain, port); } -void handle_just_exec(int len) +void handle_server_exec_request(struct msg_header *hdr) { - char buf[len]; - int fdn, pid; + struct exec_params params; + char buf[hdr->len-sizeof(params)]; + pid_t child_agent; - if (libvchan_recv(vchan, buf, len) < 0) - handle_vchan_error("read"); - 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); + assert(hdr->len >= sizeof(params)); + + if (libvchan_recv(ctrl_vchan, ¶ms, sizeof(params)) < 0) + handle_vchan_error("read exec params"); + if (libvchan_recv(ctrl_vchan, buf, hdr->len-sizeof(params)) < 0) + handle_vchan_error("read exec cmd"); + + child_agent = handle_new_process(hdr->type, + params.connect_domain, params.connect_port, + buf, hdr->len-sizeof(params)); + + register_vchan_connection(child_agent, + params.connect_domain, params.connect_port); } -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; - - if (libvchan_recv(vchan, buf, len) < 0) - handle_vchan_error("read"); - - 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) +void handle_service_refused(struct msg_header *hdr) { + struct service_params params; int stdin_fd, stdout_fd, stderr_fd; - char buf[len]; - if (libvchan_recv(vchan, buf, len) < 0) - handle_vchan_error("read"); - 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; - i >= 0 && process_fd[i].type == FDTYPE_INVALID; 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; - if (libvchan_send(vchan, &s_hdr, sizeof(s_hdr)) < 0) - handle_vchan_error("write hdr"); - if (libvchan_send(vchan, &status, sizeof(status)) < 0) - handle_vchan_error("write 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]; - - if (libvchan_recv(vchan, buf, len) < 0) - handle_vchan_error("read"); - 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; + if (hdr->len != sizeof(params)) { + fprintf(stderr, "Invalid msg 0x%x length (%d)\n", MSG_SERVICE_REFUSED, hdr->len); + exit(1); } - switch (write_stdin - (vchan, 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); - } + if (libvchan_recv(ctrl_vchan, ¶ms, sizeof(params)) < 0) + handle_vchan_error("read exec params"); + sscanf(params.ident, "%d %d %d", &stdin_fd, &stdout_fd, &stderr_fd); + /* TODO: send some signal? some response? */ + close(stdin_fd); + close(stdout_fd); + close(stderr_fd); } -void set_blocked_outerr(int client_id, int val) +void handle_server_cmd() { - process_fd[client_info[client_id].stdout_fd].is_blocked = val; - process_fd[client_info[client_id].stderr_fd].is_blocked = val; -} + struct msg_header s_hdr; -void handle_server_data() -{ - struct server_header s_hdr; - if (libvchan_recv(vchan, &s_hdr, sizeof(s_hdr)) < 0) + if (libvchan_recv(ctrl_vchan, &s_hdr, sizeof(s_hdr)) < 0) handle_vchan_error("read 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: + case MSG_EXEC_CMDLINE: + case MSG_JUST_EXEC: + case MSG_SERVICE_CONNECT: wake_meminfo_writer(); - handle_exec(s_hdr.client_id, s_hdr.len); + handle_server_exec_request(&s_hdr); break; - case MSG_SERVER_TO_AGENT_JUST_EXEC: - wake_meminfo_writer(); - handle_just_exec(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); + case MSG_SERVICE_REFUSED: + handle_service_refused(&s_hdr); break; default: fprintf(stderr, "msg type from daemon is %d ?\n", @@ -398,54 +242,6 @@ void handle_server_data() } } -void handle_process_data(int fd) -{ - struct server_header s_hdr; - char buf[MAX_DATA_CHUNK]; - int ret; - unsigned int len; - - len = libvchan_buffer_space(vchan); - 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) { - if (libvchan_send(vchan, &s_hdr, sizeof(s_hdr)) < 0) - handle_vchan_error("write hdr"); - if (libvchan_send(vchan, buf, ret) < 0) - handle_vchan_error("write buf"); - } - 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 __attribute__((__unused__))) @@ -454,37 +250,36 @@ void sigchld_handler(int x __attribute__((__unused__))) signal(SIGCHLD, sigchld_handler); } -int find_info(int pid) +int find_connection(int pid) { int i; for (i = 0; i < MAX_FDS; i++) - if (client_info[i].pid == pid) + if (connection_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; + int id; + struct msg_header hdr; + struct exec_params params; while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { - client_id = find_info(pid); - if (client_id < 0) + id = find_connection(pid); + if (id < 0) continue; - client_info[client_id].is_exited = 1; - client_info[client_id].exit_status = status; - possibly_remove_process(client_id); + hdr.type = MSG_CONNECTION_TERMINATED; + hdr.len = sizeof(struct exec_params); + params.connect_domain = connection_info[id].connect_domain; + params.connect_port = connection_info[id].connect_port; + if (libvchan_send(ctrl_vchan, &hdr, sizeof(hdr)) < 0) + handle_vchan_error("send"); + if (libvchan_send(ctrl_vchan, ¶ms, sizeof(params)) < 0) + handle_vchan_error("send"); + connection_info[id].pid = 0; } child_exited = 0; } @@ -492,62 +287,18 @@ void reap_children() 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 - (vchan, 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); @@ -561,21 +312,19 @@ void handle_new_passfd() } } - void handle_trigger_io() { - struct server_header s_hdr; - struct trigger_connect_params params; + struct msg_header hdr; + struct trigger_service_params params; int ret; - s_hdr.client_id = 0; - s_hdr.len = 0; + hdr.len = sizeof(params); ret = read(trigger_fd, ¶ms, sizeof(params)); if (ret == sizeof(params)) { - s_hdr.type = MSG_AGENT_TO_SERVER_TRIGGER_CONNECT_EXISTING; - if (libvchan_send(vchan, &s_hdr, sizeof(s_hdr)) < 0) + hdr.type = MSG_TRIGGER_SERVICE; + if (libvchan_send(ctrl_vchan, &hdr, sizeof(hdr)) < 0) handle_vchan_error("write hdr"); - if (libvchan_send(vchan, ¶ms, sizeof(params)) < 0) + if (libvchan_send(ctrl_vchan, ¶ms, sizeof(params)) < 0) handle_vchan_error("write params"); } // trigger_fd is nonblock - so no need to reopen @@ -591,7 +340,6 @@ int main() { fd_set rdset, wrset; int max; - int i; sigset_t chld_set; init(); @@ -606,27 +354,20 @@ int main() if (child_exited) reap_children(); max = fill_fds_for_select(&rdset, &wrset); - if (libvchan_buffer_space(vchan) <= - sizeof(struct server_header)) + if (libvchan_buffer_space(ctrl_vchan) <= + (int)sizeof(struct msg_header)) FD_ZERO(&rdset); - wait_for_vchan_or_argfd(vchan, max, &rdset, &wrset); + wait_for_vchan_or_argfd(ctrl_vchan, max, &rdset, &wrset); sigprocmask(SIG_UNBLOCK, &chld_set, NULL); if (FD_ISSET(passfd_socket, &rdset)) handle_new_passfd(); - while (libvchan_data_ready(vchan)) - handle_server_data(); + while (libvchan_data_ready(ctrl_vchan)) + handle_server_cmd(); 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); } } diff --git a/qrexec/qrexec-agent.h b/qrexec/qrexec-agent.h new file mode 100644 index 0000000..38c554b --- /dev/null +++ b/qrexec/qrexec-agent.h @@ -0,0 +1,27 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2013 Marek Marczykowski-Górecki + * + * 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. + * + */ + +int handle_handshake(libvchan_t *ctrl); +void handle_vchan_error(const char *op); + +pid_t handle_new_process(int type, + int connect_domain, int connect_port, + char *cmdline, int cmdline_len); diff --git a/qrexec/qrexec-client-vm.c b/qrexec/qrexec-client-vm.c index aacd4c4..737ebc4 100644 --- a/qrexec/qrexec-client-vm.c +++ b/qrexec/qrexec-client-vm.c @@ -60,7 +60,7 @@ char *get_program_name(char *prog) int main(int argc, char **argv) { int trigger_fd; - struct trigger_connect_params params; + struct trigger_service_params params; int local_fd[3], remote_fd[3]; int i; char *abs_exec_path; @@ -93,15 +93,16 @@ int main(int argc, char **argv) putenv(env); dup2(local_fd[i], i); close(local_fd[i]); - } + } else + 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", + strncpy(params.service_name, argv[2], sizeof(params.service_name)); + strncpy(params.target_domain, argv[1], + sizeof(params.target_domain)); + snprintf(params.request_id.ident, + sizeof(params.request_id.ident), "%d %d %d", remote_fd[0], remote_fd[1], remote_fd[2]); if (write(trigger_fd, ¶ms, sizeof(params)) < 0) {