ソースを参照

Merge branch 'qrexec-sockets3'

Marek Marczykowski-Górecki 9 年 前
コミット
9bbfb4a567
6 ファイル変更369 行追加105 行削除
  1. 5 2
      qrexec/Makefile
  2. 93 46
      qrexec/qrexec-agent-data.c
  3. 149 57
      qrexec/qrexec-agent.c
  4. 11 0
      qrexec/qrexec-agent.h
  5. 110 0
      qrexec/qrexec-fork-server.c
  6. 1 0
      rpm_spec/core-vm.spec

+ 5 - 2
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
 

+ 93 - 46
qrexec/qrexec-agent-data.c

@@ -29,6 +29,7 @@
 #include <sys/stat.h>
 #include <sys/wait.h>
 #include <sys/select.h>
+#include <sys/socket.h>
 #include <fcntl.h>
 #include <libvchan.h>
 #include "qrexec.h"
@@ -38,52 +39,71 @@
 #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;
 
 static void sigchld_handler(int __attribute__((__unused__))x)
 {
-	child_exited = 1;
-	signal(SIGCHLD, sigchld_handler);
+    child_exited = 1;
+    signal(SIGCHLD, sigchld_handler);
 }
 
-
-void no_colon_in_cmd()
+static void sigusr1_handler(int __attribute__((__unused__))x)
 {
-	fprintf(stderr,
-		"cmdline is supposed to be in user:command form\n");
-	exit(1);
+    stdio_socket_requested = 1;
+    signal(SIGUSR1, SIG_IGN);
 }
 
-void do_exec(char *cmd)
+int handle_handshake(libvchan_t *ctrl)
 {
-	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);
+    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;
 }
 
+
 int handle_just_exec(char *cmdline)
 {
-	int fdn, pid;
+    int fdn, pid;
 
     switch (pid = fork()) {
         case -1:
@@ -97,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
@@ -146,7 +166,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 +211,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 +277,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 +296,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 +376,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 +395,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 +437,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:

+ 149 - 57
qrexec/qrexec-agent.c

@@ -19,7 +19,10 @@
  *
  */
 
+#define _GNU_SOURCE
 #include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/un.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <signal.h>
@@ -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;
 };
@@ -55,57 +59,43 @@ int passfd_socket;
 
 int meminfo_write_started = 0;
 
-void do_exec(const char *cmd);
-
-void handle_vchan_error(const char *op)
+void no_colon_in_cmd()
 {
-    fprintf(stderr, "Error while vchan %s, exiting\n", op);
+    fprintf(stderr,
+            "cmdline is supposed to be in user:command form\n");
     exit(1);
 }
 
-int handle_handshake(libvchan_t *ctrl)
+void do_exec(const char *cmd)
 {
-    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;
+    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);
+}
 
-    return 0;
+void handle_vchan_error(const char *op)
+{
+    fprintf(stderr, "Error while vchan %s, exiting\n", op);
+    exit(1);
 }
 
 void init()
@@ -161,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;
@@ -190,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);
 }
 
@@ -262,27 +321,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, &params, 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, &params, sizeof(params)) < 0)
-            handle_vchan_error("send");
-        connection_info[id].pid = 0;
+        release_connection(id);
     }
     child_exited = 0;
 }
@@ -290,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);
 
@@ -299,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;
 }
 
@@ -339,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;
@@ -372,5 +462,7 @@ int main()
 
         if (FD_ISSET(trigger_fd, &rdset))
             handle_trigger_io();
+
+        handle_terminated_fork_client(&rdset);
     }
 }

+ 11 - 0
qrexec/qrexec-agent.h

@@ -19,9 +19,20 @@
  *
  */
 
+#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);
 
 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];
+};

+ 110 - 0
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  <marmarek@invisiblethingslab.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include "qrexec.h"
+#include <libvchan.h>
+#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;
+}

+ 1 - 0
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