From c1cb78e0e8e2e6090c32e2c1cddc9f8bcbd8399e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Fri, 31 Oct 2014 01:39:46 +0100 Subject: [PATCH 1/3] qrexec: use sockets instead of pipes to communicate with child process The main advantage is possible use of single socket for both stdin and stdout. This is strictly required for using USBIP over qrexec. For compatibility qrexec still creates three socket pairs (instead of pipes) for stdin/out/err respectively. When qrexec-agent receives SIGUSR1, it will close stdout socket and use stdin socket for both directions. Some additional work is needed here to actually allow child process to send that signal - qrexec is running as root, but child as "user" in most cases. --- qrexec/qrexec-agent-data.c | 47 +++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/qrexec/qrexec-agent-data.c b/qrexec/qrexec-agent-data.c index 5620255..0f41222 100644 --- a/qrexec/qrexec-agent-data.c +++ b/qrexec/qrexec-agent-data.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include "qrexec.h" @@ -38,6 +39,7 @@ #define VCHAN_BUFFER_SIZE 65536 static volatile int child_exited; +static volatile int stdio_socket_requested; int stdout_msg_type = MSG_DATA_STDOUT; pid_t child_process_pid; @@ -47,6 +49,12 @@ static void sigchld_handler(int __attribute__((__unused__))x) signal(SIGCHLD, sigchld_handler); } +static void sigusr1_handler(int __attribute__((__unused__))x) +{ + stdio_socket_requested = 1; + signal(SIGUSR1, SIG_IGN); +} + void no_colon_in_cmd() { @@ -146,7 +154,10 @@ int handle_input(libvchan_t *vchan, int fd, int msg_type) return -1; if (len == 0) { - close(fd); + if (shutdown(fd, SHUT_RD) < 0) { + if (errno == ENOTSOCK) + close(fd); + } return 0; } } @@ -188,14 +199,20 @@ int handle_remote_data(libvchan_t *data_vchan, int stdin_fd) /* discard the data */ continue; if (hdr.len == 0) { - close(stdin_fd); + if (shutdown(stdin_fd, SHUT_WR) < 0) { + if (errno == ENOTSOCK) + 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); + if (errno == EPIPE || errno == ECONNRESET) { + if (shutdown(stdin_fd, SHUT_WR) < 0) { + if (errno == ENOTSOCK) + close(stdin_fd); + } stdin_fd = -1; } else { perror("write"); @@ -248,7 +265,10 @@ void process_child_io(libvchan_t *data_vchan, if (pid == child_process_pid) { child_process_status = WEXITSTATUS(status); if (stdin_fd >= 0) { - close(stdin_fd); + if (shutdown(stdin_fd, SHUT_WR) < 0) { + if (errno == ENOTSOCK) + close(stdin_fd); + } stdin_fd = -1; } } @@ -264,6 +284,13 @@ void process_child_io(libvchan_t *data_vchan, } break; } + /* child signaled desire to use single socket for both stdin and stdout */ + if (stdio_socket_requested) { + if (stdout_fd != -1) + close(stdout_fd); + stdout_fd = stdin_fd; + stdio_socket_requested = 0; + } /* otherwise handle the events */ FD_ZERO(&rdset); @@ -337,7 +364,10 @@ void process_child_io(libvchan_t *data_vchan, break; case -2: /* remote process exited, no sense in sending more data to it */ - close(stdout_fd); + if (shutdown(stdout_fd, SHUT_RD) < 0) { + if (errno == ENOTSOCK) + close(stdout_fd); + } stdout_fd = -1; close(stderr_fd); stderr_fd = -1; @@ -353,6 +383,7 @@ pid_t handle_new_process(int type, int connect_domain, int connect_port, libvchan_t *data_vchan; pid_t pid; int stdin_fd, stdout_fd, stderr_fd; + char pid_s[10]; if (type == MSG_SERVICE_CONNECT) { if (cmdline_len != sizeof(*svc_params)) { @@ -394,6 +425,10 @@ pid_t handle_new_process(int type, int connect_domain, int connect_port, handle_handshake(data_vchan); signal(SIGCHLD, sigchld_handler); + signal(SIGUSR1, sigusr1_handler); + snprintf(pid_s, sizeof(pid_s), "%d", getpid()); + setenv("QREXEC_AGENT_PID", pid_s, 1); + /* TODO: use setresuid to allow child process to actually send the signal? */ switch (type) { case MSG_JUST_EXEC: From 4b5960daa3c4bdd708ddb33b0f93ad15ea6d699a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Tue, 17 Feb 2015 04:06:19 +0100 Subject: [PATCH 2/3] qrexec: reorganise code for upcoming change Move (qrexec-agent version of) do_exec to qrexec-agent.c, move handle_handshake to qrexec-agent-data.c (common to all agent binaries). Fix indentation (tabs -> spaces). --- qrexec/qrexec-agent-data.c | 96 +++++++++++++++++++--------------- qrexec/qrexec-agent.c | 104 +++++++++++++++++-------------------- qrexec/qrexec-agent.h | 1 + 3 files changed, 102 insertions(+), 99 deletions(-) diff --git a/qrexec/qrexec-agent-data.c b/qrexec/qrexec-agent-data.c index 0f41222..1378e29 100644 --- a/qrexec/qrexec-agent-data.c +++ b/qrexec/qrexec-agent-data.c @@ -45,8 +45,8 @@ pid_t child_process_pid; static void sigchld_handler(int __attribute__((__unused__))x) { - child_exited = 1; - signal(SIGCHLD, sigchld_handler); + child_exited = 1; + signal(SIGCHLD, sigchld_handler); } static void sigusr1_handler(int __attribute__((__unused__))x) @@ -55,43 +55,55 @@ static void sigusr1_handler(int __attribute__((__unused__))x) signal(SIGUSR1, SIG_IGN); } - -void no_colon_in_cmd() +int handle_handshake(libvchan_t *ctrl) { - fprintf(stderr, - "cmdline is supposed to be in user:command form\n"); - exit(1); + 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 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; + int fdn, pid; switch (pid = fork()) { case -1: @@ -105,20 +117,20 @@ int handle_just_exec(char *cmdline) exit(1); default:; } - fprintf(stderr, "executed (nowait) %s pid %d\n", cmdline, pid); + 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); + 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 diff --git a/qrexec/qrexec-agent.c b/qrexec/qrexec-agent.c index f00e133..f6a96b9 100644 --- a/qrexec/qrexec-agent.c +++ b/qrexec/qrexec-agent.c @@ -55,7 +55,38 @@ int passfd_socket; int meminfo_write_started = 0; -void do_exec(const char *cmd); +void no_colon_in_cmd() +{ + fprintf(stderr, + "cmdline is supposed to be in user:command form\n"); + exit(1); +} + +void do_exec(const 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); +} void handle_vchan_error(const char *op) { @@ -63,51 +94,6 @@ void handle_vchan_error(const char *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 */ @@ -262,27 +248,31 @@ int find_connection(int pid) return -1; } +void release_connection(int id) { + struct msg_header hdr; + struct exec_params params; + + 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; +} void reap_children() { int status; int pid; int id; - struct msg_header hdr; - struct exec_params params; while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { id = find_connection(pid); if (id < 0) continue; - 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; + release_connection(id); } child_exited = 0; } diff --git a/qrexec/qrexec-agent.h b/qrexec/qrexec-agent.h index 38c554b..ebf7e8d 100644 --- a/qrexec/qrexec-agent.h +++ b/qrexec/qrexec-agent.h @@ -21,6 +21,7 @@ int handle_handshake(libvchan_t *ctrl); void handle_vchan_error(const char *op); +void do_exec(const char *cmd); pid_t handle_new_process(int type, int connect_domain, int connect_port, From 700c240d370eadadc64c8252c7c3650127da4fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Tue, 17 Feb 2015 04:18:34 +0100 Subject: [PATCH 3/3] qrexec: add simple "fork server" to spawn new processes inside user session This process should be started from user session (most likely qubes-session). New processes (of that user) will be created as children of that session making logind and such crap happy. This should also solve problems with EOF transmission (no additional "su" process) and prevent loading all the environment multiple times. --- qrexec/Makefile | 7 ++- qrexec/qrexec-agent.c | 108 ++++++++++++++++++++++++++++++++++- qrexec/qrexec-agent.h | 10 ++++ qrexec/qrexec-fork-server.c | 110 ++++++++++++++++++++++++++++++++++++ rpm_spec/core-vm.spec | 1 + 5 files changed, 231 insertions(+), 5 deletions(-) create mode 100644 qrexec/qrexec-fork-server.c diff --git a/qrexec/Makefile b/qrexec/Makefile index 06e2489..abf1961 100644 --- a/qrexec/Makefile +++ b/qrexec/Makefile @@ -2,9 +2,11 @@ CC=gcc CFLAGS+=-I. -g -Wall -Wextra -Werror -pie -fPIC `pkg-config --cflags vchan-$(BACKEND_VMM)` LIBS=`pkg-config --libs vchan-$(BACKEND_VMM)` -lqrexec-utils -all: qrexec-agent qrexec-client-vm +all: qrexec-agent qrexec-client-vm qrexec-fork-server qrexec-agent: qrexec-agent.o qrexec-agent-data.o $(CC) -pie -g -o qrexec-agent qrexec-agent.o qrexec-agent-data.o $(LIBS) +qrexec-fork-server: qrexec-fork-server.o qrexec-agent-data.o + $(CC) -pie -g -o qrexec-fork-server qrexec-fork-server.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: @@ -12,9 +14,10 @@ clean: install: install -d $(DESTDIR)/etc/qubes-rpc - install -d $(DESTDIR)/usr/lib/qubes + install -d $(DESTDIR)/usr/lib/qubes $(DESTDIR)/usr/bin install qrexec-agent $(DESTDIR)/usr/lib/qubes install qrexec-client-vm $(DESTDIR)/usr/lib/qubes ln -s qrexec-client-vm $(DESTDIR)/usr/lib/qubes/qrexec_client_vm + install qrexec-fork-server $(DESTDIR)/usr/bin install qubes-rpc-multiplexer $(DESTDIR)/usr/lib/qubes diff --git a/qrexec/qrexec-agent.c b/qrexec/qrexec-agent.c index f6a96b9..d10d59e 100644 --- a/qrexec/qrexec-agent.c +++ b/qrexec/qrexec-agent.c @@ -19,7 +19,10 @@ * */ +#define _GNU_SOURCE #include +#include +#include #include #include #include @@ -38,7 +41,8 @@ #include "qrexec-agent.h" struct _connection_info { - int pid; + int pid; /* pid of child process handling the data */ + int fd; /* socket to process handling the data (wait for EOF here) */ int connect_domain; int connect_port; }; @@ -147,13 +151,70 @@ void wake_meminfo_writer() meminfo_write_started = 1; } -void register_vchan_connection(pid_t pid, int domain, int port) +int try_fork_server(int type, int connect_domain, int connect_port, + char *cmdline, int cmdline_len) { + char username[cmdline_len]; + char *colon; + char *fork_server_socket_path; + int s, len; + struct sockaddr_un remote; + struct qrexec_cmd_info info; + + strncpy(username, cmdline, cmdline_len); + colon = index(username, ':'); + if (!colon) + return -1; + *colon = '\0'; + + if (asprintf(&fork_server_socket_path, QREXEC_FORK_SERVER_SOCKET, username) < 0) { + fprintf(stderr, "Memory allocation failed\n"); + return -1; + } + + remote.sun_family = AF_UNIX; + strncpy(remote.sun_path, fork_server_socket_path, + sizeof(remote.sun_path)); + free(fork_server_socket_path); + + if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + perror("socket"); + return -1; + } + len = strlen(remote.sun_path) + sizeof(remote.sun_family); + if (connect(s, (struct sockaddr *) &remote, len) == -1) { + if (errno != ECONNREFUSED) + perror("connect"); + close(s); + return -1; + } + + info.type = type; + info.connect_domain = connect_domain; + info.connect_port = connect_port; + info.cmdline_len = cmdline_len-(strlen(username)+1); + if (!write_all(s, &info, sizeof(info))) { + perror("write"); + close(s); + return -1; + } + if (!write_all(s, colon+1, info.cmdline_len)) { + perror("write"); + close(s); + return -1; + } + + return s; +} + + +void register_vchan_connection(pid_t pid, int fd, int domain, int port) { int i; for (i = 0; i < MAX_FDS; i++) { if (connection_info[i].pid == 0) { connection_info[i].pid = pid; + connection_info[i].fd = fd; connection_info[i].connect_domain = domain; connection_info[i].connect_port = port; return; @@ -176,11 +237,23 @@ void handle_server_exec_request(struct msg_header *hdr) if (libvchan_recv(ctrl_vchan, buf, hdr->len-sizeof(params)) < 0) handle_vchan_error("read exec cmd"); + if ((hdr->type == MSG_EXEC_CMDLINE || hdr->type == MSG_JUST_EXEC) && + !strstr(buf, ":nogui:")) { + int child_socket = try_fork_server(hdr->type, + params.connect_domain, params.connect_port, + buf, hdr->len-sizeof(params)); + if (child_socket >= 0) { + register_vchan_connection(-1, child_socket, + params.connect_domain, params.connect_port); + return; + } + } + child_agent = handle_new_process(hdr->type, params.connect_domain, params.connect_port, buf, hdr->len-sizeof(params)); - register_vchan_connection(child_agent, + register_vchan_connection(child_agent, -1, params.connect_domain, params.connect_port); } @@ -280,6 +353,7 @@ void reap_children() int fill_fds_for_select(fd_set * rdset, fd_set * wrset) { int max = -1; + int i; FD_ZERO(rdset); FD_ZERO(wrset); @@ -289,6 +363,14 @@ int fill_fds_for_select(fd_set * rdset, fd_set * wrset) FD_SET(passfd_socket, rdset); if (passfd_socket > max) max = passfd_socket; + + for (i = 0; i < MAX_FDS; i++) { + if (connection_info[i].pid != 0 && connection_info[i].fd != -1) { + FD_SET(connection_info[i].fd, rdset); + if (connection_info[i].fd > max) + max = connection_info[i].fd; + } + } return max; } @@ -329,6 +411,24 @@ void handle_trigger_io() } } +void handle_terminated_fork_client(fd_set *rdset) { + int i, ret; + char buf[2]; + + for (i = 0; i < MAX_FDS; i++) { + if (connection_info[i].pid && connection_info[i].fd >= 0 && + FD_ISSET(connection_info[i].fd, rdset)) { + ret = read(connection_info[i].fd, buf, sizeof(buf)); + if (ret == 0 || (ret == -1 && errno == ECONNRESET)) { + close(connection_info[i].fd); + release_connection(i); + } else { + fprintf(stderr, "Unexpected read on fork-server connection: %d(%s)\n", ret, strerror(errno)); + } + } + } +} + int main() { fd_set rdset, wrset; @@ -362,5 +462,7 @@ int main() if (FD_ISSET(trigger_fd, &rdset)) handle_trigger_io(); + + handle_terminated_fork_client(&rdset); } } diff --git a/qrexec/qrexec-agent.h b/qrexec/qrexec-agent.h index ebf7e8d..d2f10e8 100644 --- a/qrexec/qrexec-agent.h +++ b/qrexec/qrexec-agent.h @@ -19,6 +19,8 @@ * */ +#define QREXEC_FORK_SERVER_SOCKET "/var/run/qubes/qrexec-server.%s.sock" + int handle_handshake(libvchan_t *ctrl); void handle_vchan_error(const char *op); void do_exec(const char *cmd); @@ -26,3 +28,11 @@ void do_exec(const char *cmd); pid_t handle_new_process(int type, int connect_domain, int connect_port, char *cmdline, int cmdline_len); + +struct qrexec_cmd_info { + int type; + int connect_domain; + int connect_port; + int cmdline_len; + char cmdline[0]; +}; diff --git a/qrexec/qrexec-fork-server.c b/qrexec/qrexec-fork-server.c new file mode 100644 index 0000000..da03238 --- /dev/null +++ b/qrexec/qrexec-fork-server.c @@ -0,0 +1,110 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2015 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. + * + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include "qrexec.h" +#include +#include "libqrexec-utils.h" +#include "qrexec-agent.h" + +void do_exec(const char *cmd) +{ + char buf[strlen(QUBES_RPC_MULTIPLEXER_PATH) + strlen(cmd) - strlen(RPC_REQUEST_COMMAND) + 1]; + /* replace magic RPC cmd with RPC multiplexer path */ + if (strncmp(cmd, RPC_REQUEST_COMMAND " ", strlen(RPC_REQUEST_COMMAND)+1)==0) { + strcpy(buf, QUBES_RPC_MULTIPLEXER_PATH); + strcpy(buf + strlen(QUBES_RPC_MULTIPLEXER_PATH), cmd + strlen(RPC_REQUEST_COMMAND)); + cmd = buf; + } + signal(SIGCHLD, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + + execl("/bin/sh", "sh", "-c", cmd, NULL); + perror("execl"); + exit(1); +} + +void handle_vchan_error(const char *op) +{ + fprintf(stderr, "Error while vchan %s, exiting\n", op); + exit(1); +} + +void handle_single_command(int fd, struct qrexec_cmd_info *info) { + char cmdline[info->cmdline_len+1]; + + if (!read_all(fd, cmdline, info->cmdline_len)) + return; + cmdline[info->cmdline_len] = 0; + + handle_new_process(info->type, info->connect_domain, + info->connect_port, + cmdline, info->cmdline_len); +} + +int main(int argc, char **argv) { + int s, fd; + char *socket_path; + struct qrexec_cmd_info info; + struct sockaddr_un peer; + unsigned int addrlen; + + + if (argc == 2) { + socket_path = argv[1]; + } else if (argc == 1) { + /* this will be leaked, but we don't care as the process will then terminate */ + if (asprintf(&socket_path, QREXEC_FORK_SERVER_SOCKET, getenv("USER")) < 0) { + fprintf(stderr, "Memory allocation failed\n"); + exit(1); + } + } else { + fprintf(stderr, "Usage: %s [socket path]\n", argv[0]); + exit(1); + } + + s = get_server_socket(socket_path); + if (fcntl(s, F_SETFD, O_CLOEXEC) < 0) { + perror("fcntl"); + exit(1); + } + signal(SIGCHLD, SIG_IGN); + register_exec_func(do_exec); + + while ((fd = accept(s, (struct sockaddr *) &peer, &addrlen)) >= 0) { + if (read_all(fd, &info, sizeof(info))) { + handle_single_command(fd, &info); + } + close(fd); + addrlen = sizeof(peer); + } + close(s); + unlink(socket_path); + return 0; +} diff --git a/rpm_spec/core-vm.spec b/rpm_spec/core-vm.spec index 3584c25..a9484cf 100644 --- a/rpm_spec/core-vm.spec +++ b/rpm_spec/core-vm.spec @@ -400,6 +400,7 @@ rm -f %{name}-%{version} /usr/bin/qvm-mru-entry /usr/bin/xenstore-watch-qubes /usr/bin/qubes-desktop-run +/usr/bin/qrexec-fork-server %dir /usr/lib/qubes /usr/lib/qubes/vusb-ctl.py* /usr/lib/qubes/dispvm-prerun.sh