Commit Diff


commit - bd726b55be4df8535a2b200a252193649566007a
commit + 881a9dd9c2aebbf73f333dd3d8be4ce5400f717f
blob - d2e6c7b1d6c3249de0298437cdc454de069ffff9
blob + 6301b4682fee97651d2b72dfeab8a87065f65207
--- Makefile
+++ Makefile
@@ -14,12 +14,12 @@ lex.yy.c: lex.l y.tab.c
 y.tab.c: parse.y
 	${YACC} -b y -d parse.y
 
-OBJS = gmid.o iri.o utf8.o lex.yy.o y.tab.o cgi.o sandbox.o
+OBJS = gmid.o iri.o utf8.o lex.yy.o y.tab.o ex.o cgi.o sandbox.o
 gmid: ${OBJS}
 	${CC} ${OBJS} -o gmid ${LDFLAGS}
 
-TAGS: gmid.c iri.c utf8.c
-	-etags gmid.c iri.c utf8.c || true
+TAGS: gmid.c iri.c utf8.c ex.c cgi.c sandbox.c
+	-etags gmid.c iri.c utf8.c ex.c cgi.c sandbox.c || true
 
 clean:
 	rm -f *.o lex.yy.c y.tab.c y.tab.h y.output gmid iri_test
blob - 658208fced0275052cc1b87f657a2d5675ea3f37
blob + 334315b6b01aab28576ef29e8108b4ff7c363a7c
--- README.md
+++ README.md
@@ -2,16 +2,15 @@
 
 > dead simple, zero configuration Gemini server
 
-gmid is a simple and minimal Gemini server.  It requires no
-configuration whatsoever so it's well suited for local development
-machines.
+gmid is a simple and minimal Gemini server.  It can run without
+configuration, so it's well suited for local development, but at the
+same time has a configuration file flexible enough to meet the
+requirements of most capsules.
 
-Care has been taken to assure that gmid doesn't serve files outside
-the given directory, and it won't follow symlinks.  Furthermore, on
-OpenBSD, gmid is also `pledge(2)`ed and `unveil(2)`ed: the set of
-pledges are `stdio rpath inet`, with the addition of `proc exec` if
-CGI scripts are enabled, while the given directory is unveiled with
-`rx`.
+gmid was initially written to serve static files, but can also
+optionally execute CGI scripts.  It was also written with security in
+mind: on FreeBSD and OpenBSD is sandboxed via `capsicum(4)`and
+`pledge(2)`/`unveil(2)` respectively.
 
 
 ## Features
@@ -22,7 +21,7 @@ CGI scripts are enabled, while the given directory is 
  - (very) low memory footprint
  - small codebase, easily hackable
  - virtual hosts
- - sandboxed on OpenBSD and FreeBSD
+ - sandboxed by default on OpenBSD and FreeBSD
 
 
 ## Drawbacks
@@ -31,10 +30,6 @@ CGI scripts are enabled, while the given directory is 
    connection per-second you'd probably want to run multiple gmid
    instances behind relayd/haproxy or a different server.
 
- - the sandbox on FreeBSD is **NOT** activated if CGI scripts are
-   enabled: CGI script cannot be used with the way `capsicum(4)` works
-
-
 ## Building
 
 gmid depends a POSIX libc and libtls.  It can probably be linked
@@ -53,3 +48,20 @@ The Makefile isn't able to produce a statically linked
     strip gmid
 
 to enjoy your ~2.3M statically-linked gmid.
+
+
+## Architecture/Security considerations
+
+gmid is composed by two processes: a listener and an executor.  The
+listener process is the only one that needs internet access and is
+sandboxed.  When a CGI script needs to be executed, the executor
+(outside of the sandbox) sets up a pipe and gives one end to the
+listener, while the other is bound to the CGI script standard output.
+This way, is still possible to execute CGI scripts without restriction
+even if the presence of a sandbox.
+
+On OpenBSD, the listener process runs with the `stdio recvfd rpath
+inet` pledges and has `unveil(2)`ed only the directories that it
+serves; the executor has `stdio sendfd proc exec` as pledges.
+
+On FreeBSD, the executor process is sandboxed with `capsicum(4)`.
blob - 30f554ccbd5631180837cf6d545c069f17529de9
blob + b25aa5d7e844d05820da24098072ea9ab419f316
--- cgi.c
+++ cgi.c
@@ -16,19 +16,12 @@
 
 #include <netdb.h>
 
+#include <err.h>
 #include <errno.h>
 #include <string.h>
 
 #include "gmid.h"
 
-static inline void
-safe_setenv(const char *name, const char *val)
-{
-	if (val == NULL)
-		val = "";
-	setenv(name, val, 1);
-}
-
 /*
  * the inverse of this algorithm, i.e. starting from the start of the
  * path + strlen(cgi), and checking if each component, should be
@@ -76,94 +69,53 @@ int
 start_cgi(const char *spath, const char *relpath, const char *query,
     struct pollfd *fds, struct client *c)
 {
-	pid_t pid;
-	int p[2];		/* read end, write end */
+	char addr[NI_MAXHOST];
+	const char *ruser, *cissuer, *chash;
+	int e;
 
-	if (pipe(p) == -1)
+	e = getnameinfo((struct sockaddr*)&c->addr, sizeof(c->addr),
+	    addr, sizeof(addr),
+	    NULL, 0,
+	    NI_NUMERICHOST);
+	if (e != 0)
 		goto err;
 
-	switch (pid = fork()) {
-	case -1:
-		goto err;
-
-	case 0: {		/* child */
-		char *ex, *requri, *portno;
-		char addr[NI_MAXHOST];
-		char *argv[] = { NULL, NULL, NULL };
-		int ec;
-
-		close(p[0]);
-		if (dup2(p[1], 1) == -1)
-			goto childerr;
-
-		ec = getnameinfo((struct sockaddr*)&c->addr, sizeof(c->addr),
-		    addr, sizeof(addr),
-		    NULL, 0,
-		    NI_NUMERICHOST | NI_NUMERICSERV);
-		if (ec != 0)
-			goto childerr;
-
-		if (asprintf(&portno, "%d", conf.port) == -1)
-			goto childerr;
-
-		if (asprintf(&ex, "%s/%s", c->host->dir, spath) == -1)
-			goto childerr;
-
-		if (asprintf(&requri, "%s%s%s", spath,
-		    *relpath == '\0' ? "" : "/", relpath) == -1)
-			goto childerr;
-
-		argv[0] = argv[1] = ex;
-
-		/* fix the env */
-		safe_setenv("GATEWAY_INTERFACE", "CGI/1.1");
-		safe_setenv("SERVER_SOFTWARE", "gmid");
-		safe_setenv("SERVER_PORT", portno);
-
-		if (!strcmp(c->host->domain, "*"))
-			safe_setenv("SERVER_NAME", c->host->domain)
-
-		safe_setenv("SCRIPT_NAME", spath);
-		safe_setenv("SCRIPT_EXECUTABLE", ex);
-		safe_setenv("REQUEST_URI", requri);
-		safe_setenv("REQUEST_RELATIVE", relpath);
-		safe_setenv("QUERY_STRING", query);
-		safe_setenv("REMOTE_HOST", addr);
-		safe_setenv("REMOTE_ADDR", addr);
-		safe_setenv("DOCUMENT_ROOT", c->host->dir);
-
-		if (tls_peer_cert_provided(c->ctx)) {
-			safe_setenv("AUTH_TYPE", "Certificate");
-			safe_setenv("REMOTE_USER", tls_peer_cert_subject(c->ctx));
-			safe_setenv("TLS_CLIENT_ISSUER", tls_peer_cert_issuer(c->ctx));
-			safe_setenv("TLS_CLIENT_HASH", tls_peer_cert_hash(c->ctx));
-		}
-
-		execvp(ex, argv);
-		goto childerr;
+	if (tls_peer_cert_provided(c->ctx)) {
+		ruser = tls_peer_cert_subject(c->ctx);
+		cissuer = tls_peer_cert_issuer(c->ctx);
+		chash = tls_peer_cert_hash(c->ctx);
+	} else {
+		ruser = NULL;
+		cissuer = NULL;
+		chash = NULL;
 	}
 
-	default:		/* parent */
-		close(p[1]);
-		close(c->fd);
-		c->fd = p[0];
-		c->child = pid;
-		mark_nonblock(c->fd);
-		c->state = S_SENDING;
-		handle_cgi(fds, c);
-		return 0;
-	}
+	if (!send_string(exfd, spath)
+	    || !send_string(exfd, relpath)
+	    || !send_string(exfd, query)
+	    || !send_string(exfd, addr)
+	    || !send_string(exfd, ruser)
+	    || !send_string(exfd, cissuer)
+	    || !send_string(exfd, chash)
+	    || !send_vhost(exfd, c->host))
+		goto err;
 
-err:
-	if (!start_reply(fds, c, TEMP_FAILURE, "internal server error"))
+	close(c->fd);
+	if ((c->fd = recv_fd(exfd)) == -1) {
+		if (!start_reply(fds, c, TEMP_FAILURE, "internal server error"))
+			return 0;
+		goodbye(fds, c);
 		return 0;
-	goodbye(fds, c);
+	}
+	c->child = 1;
+	c->state = S_SENDING;
+	cgi_poll_on_child(fds, c);
+	/* handle_cgi(fds, c); */
 	return 0;
 
-childerr:
-	dprintf(p[1], "%d internal server error\r\n", TEMP_FAILURE);
-	close(p[1]);
-	_exit(1);
+err:
+	/* fatal("cannot talk to the executor process: %s", strerror(errno)); */
+	err(1, "cannot talk to the executor process");
 }
 
 void
blob - /dev/null
blob + 7ae4e9395827f73f056049f42a307889793ddc63 (mode 644)
--- /dev/null
+++ ex.c
@@ -0,0 +1,293 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <err.h>
+#include <errno.h>
+
+#include <fcntl.h>
+#include <signal.h>
+#include <string.h>
+
+#include "gmid.h"
+
+int
+send_string(int fd, const char *str)
+{
+	ssize_t len;
+
+	if (str == NULL)
+		len = 0;
+	else
+		len = strlen(str);
+
+	if (write(fd, &len, sizeof(len)) != sizeof(len))
+		return 0;
+
+	if (len != 0)
+		if (write(fd, str, len) != len)
+			return 0;
+
+	return 1;
+}
+
+int
+recv_string(int fd, char **ret)
+{
+	ssize_t len;
+
+	if (read(fd, &len, sizeof(len)) != sizeof(len))
+		return 0;
+
+	if (len == 0) {
+		*ret = NULL;
+		return 1;
+	}
+
+	if ((*ret = calloc(1, len+1)) == NULL)
+		return 0;
+
+	if (read(fd, *ret, len) != len)
+		return 0;
+	return 1;
+}
+
+int
+send_vhost(int fd, struct vhost *vhost)
+{
+	ssize_t n;
+
+	if (vhost < hosts || vhost > hosts + HOSTSLEN)
+		return 0;
+
+	n = hosts - vhost;
+	return write(fd, &n, sizeof(n)) == sizeof(n);
+}
+
+int
+recv_vhost(int fd, struct vhost **vhost)
+{
+	ssize_t n;
+
+	if (read(fd, &n, sizeof(n)) != sizeof(n))
+		return 0;
+
+	if (n < 0 || n > HOSTSLEN)
+		return 0;
+
+	*vhost = &hosts[n];
+	if ((*vhost)->domain == NULL)
+		return 0;
+	return 1;
+}
+
+/* send d though fd. see /usr/src/usr.sbin/syslogd/privsep_fdpass.c
+ * for an example */
+int
+send_fd(int fd, int d)
+{
+	struct msghdr msg;
+	union {
+		struct cmsghdr hdr;
+		unsigned char buf[CMSG_SPACE(sizeof(int))];
+	} cmsgbuf;
+	struct cmsghdr *cmsg;
+	struct iovec vec;
+	int result = 1;
+	ssize_t n;
+
+	memset(&msg, 0, sizeof(msg));
+
+	if (d >= 0) {
+		msg.msg_control = &cmsgbuf.buf;
+		msg.msg_controllen = sizeof(cmsgbuf.buf);
+		cmsg = CMSG_FIRSTHDR(&msg);
+		cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+		cmsg->cmsg_level = SOL_SOCKET;
+		cmsg->cmsg_type = SCM_RIGHTS;
+		*(int*)CMSG_DATA(cmsg) = d;
+	} else
+		result = 0;
+
+	vec.iov_base = &result;
+	vec.iov_len = sizeof(int);
+	msg.msg_iov = &vec;
+	msg.msg_iovlen = 1;
+
+	if ((n = sendmsg(fd, &msg, 0)) == -1 || n != sizeof(int)) {
+                fprintf(stderr, "sendmsg: got %zu but wanted %zu: (errno) %s",
+		    n, sizeof(int), strerror(errno));
+		return 0;
+	}
+	return 1;
+}
+
+/* receive a descriptor via fd */
+int
+recv_fd(int fd)
+{
+	struct msghdr msg;
+	union {
+		struct cmsghdr hdr;
+		char buf[CMSG_SPACE(sizeof(int))];
+	} cmsgbuf;
+	struct cmsghdr *cmsg;
+	struct iovec vec;
+	ssize_t n;
+	int result;
+
+	memset(&msg, 0, sizeof(msg));
+	vec.iov_base = &result;
+	vec.iov_len = sizeof(int);
+	msg.msg_iov = &vec;
+	msg.msg_iovlen = 1;
+	msg.msg_control = &cmsgbuf.buf;
+	msg.msg_controllen = sizeof(cmsgbuf.buf);
+
+	if ((n = recvmsg(fd, &msg, 0)) != sizeof(int)) {
+		fprintf(stderr, "read %zu bytes bu wanted %zu\n", n, sizeof(int));
+		return -1;
+	}
+
+	if (result) {
+		cmsg = CMSG_FIRSTHDR(&msg);
+		if (cmsg == NULL || cmsg->cmsg_type != SCM_RIGHTS)
+			return -1;
+		return (*(int *)CMSG_DATA(cmsg));
+	} else
+		return -1;
+}
+
+static inline void
+safe_setenv(const char *name, const char *val)
+{
+	if (val == NULL)
+		val = "";
+	setenv(name, val, 1);
+}
+
+/* fd or -1 on error */
+static int
+launch_cgi(const char *spath, const char *relpath, const char *query,
+    const char *addr, const char *ruser, const char *cissuer, const char *chash,
+    struct vhost *vhost)
+{
+	int p[2];		/* read end, write end */
+
+	if (pipe2(p, O_NONBLOCK) == -1)
+		return -1;
+
+	switch (fork()) {
+	case -1:
+		return -1;
+
+	case 0: {		/* child */
+		char *portno, *ex, *requri;
+		char *argv[] = { NULL, NULL, NULL };
+
+		close(p[0]);
+		if (dup2(p[1], 1) == -1)
+			goto childerr;
+
+		if (asprintf(&portno, "%d", conf.port) == -1)
+			goto childerr;
+
+		if (asprintf(&ex, "%s/%s", vhost->dir, spath) == -1)
+			goto childerr;
+
+		if (asprintf(&requri, "%s%s%s", spath,
+		    (relpath != NULL && *relpath == '\0') ? "" : "/", relpath) == -1)
+			goto childerr;
+
+		argv[0] = argv[1] = ex;
+
+		safe_setenv("GATEWAY_INTERFACE", "CGI/1.1");
+		safe_setenv("SERVER_SOFTWARE", "gmid");
+		safe_setenv("SERVER_PORT", portno);
+
+		if (!strcmp(vhost->domain, "*"))
+			safe_setenv("SERVER_NAME", vhost->domain);
+
+		safe_setenv("SCRIPT_NAME", spath);
+		safe_setenv("SCRIPT_EXECUTABLE", ex);
+		safe_setenv("REQUEST_URI", requri);
+		safe_setenv("REQUEST_RELATIVE", relpath);
+		safe_setenv("QUERY_STRING", query);
+		safe_setenv("REMOTE_HOST", addr);
+		safe_setenv("REMOTE_ADDR", addr);
+		safe_setenv("DOCUMENT_ROOT", vhost->dir);
+
+		if (ruser != NULL) {
+			safe_setenv("AUTH_TYPE", "Certificate");
+			safe_setenv("REMOTE_USER", ruser);
+			safe_setenv("TLS_CLIENT_ISSUER", cissuer);
+			safe_setenv("TLS_CLIENT_HASH", chash);
+		}
+
+		execvp(ex, argv);
+		goto childerr;
+	}
+
+	default:
+		close(p[1]);
+		return p[0];
+	}
+
+childerr:
+	dprintf(p[1], "%d internal server error\r\n", TEMP_FAILURE);
+	_exit(1);
+}
+
+int
+executor_main(int fd)
+{
+	char *spath, *relpath, *query, *addr, *ruser, *cissuer, *chash;
+        struct vhost *vhost;
+	int d;
+
+#ifdef __OpenBSD__
+	pledge("stdio sendfd proc exec", NULL);
+#endif
+
+	for (;;) {
+		if (!recv_string(fd, &spath)
+		    || !recv_string(fd, &relpath)
+		    || !recv_string(fd, &query)
+		    || !recv_string(fd, &addr)
+		    || !recv_string(fd, &ruser)
+		    || !recv_string(fd, &cissuer)
+		    || !recv_string(fd, &chash)
+		    || !recv_vhost(fd, &vhost))
+			break;
+
+		d = launch_cgi(spath, relpath, query,
+		    addr, ruser, cissuer, chash, vhost);
+		if (!send_fd(fd, d))
+			break;
+
+		free(spath);
+		free(relpath);
+		free(query);
+		free(addr);
+		free(ruser);
+		free(cissuer);
+		free(chash);
+	}
+
+	/* kill all process in my group.  This means the listener and
+	 * every pending CGI script. */
+	kill(0, SIGINT);
+	return 1;
+}
blob - 7bd722b609bf1105d2ccb9431e3b7645685f0dd1
blob + 56250f46649c67604df41cf2164e1a96ab8de5ff
--- gmid.c
+++ gmid.c
@@ -33,6 +33,8 @@ struct vhost hosts[HOSTSLEN];
 
 int connected_clients;
 int goterror;
+
+int exfd;
 
 struct conf conf;
 
@@ -458,7 +460,7 @@ handle(struct pollfd *fds, struct client *client)
 		/* fallthrough */
 
 	case S_SENDING:
-		if (client->child != -1)
+		if (client->child)
 			handle_cgi(fds, client);
 		else
 			send_file(NULL, NULL, fds, client);
@@ -567,7 +569,8 @@ do_accept(int sock, struct tls *ctx, struct pollfd *fd
 
 			clients[i].state = S_HANDSHAKE;
 			clients[i].fd = -1;
-			clients[i].child = -1;
+			clients[i].child = 0;
+			clients[i].waiting_on_child = 0;
 			clients[i].buf = MAP_FAILED;
 			clients[i].af = AF_INET;
 			clients[i].addr = addr;
@@ -728,6 +731,45 @@ load_vhosts(struct tls_config *tlsconf)
 		if ((h->dirfd = open(h->dir, O_RDONLY | O_DIRECTORY)) == -1)
 			err(1, "open %s for domain %s", h->dir, h->domain);
 	}
+}
+
+int
+listener_main()
+{
+	int sock4, sock6;
+	struct tls *ctx = NULL;
+	struct tls_config *tlsconf;
+
+	if ((tlsconf = tls_config_new()) == NULL)
+		err(1, "tls_config_new");
+
+	/* optionally accept client certs, but don't try to verify them */
+	tls_config_verify_client_optional(tlsconf);
+	tls_config_insecure_noverifycert(tlsconf);
+
+	if (tls_config_set_protocols(tlsconf, conf.protos) == -1)
+		err(1, "tls_config_set_protocols");
+
+	if ((ctx = tls_server()) == NULL)
+		errx(1, "tls_server failure");
+
+	load_vhosts(tlsconf);
+
+	if (tls_configure(ctx, tlsconf) == -1)
+		errx(1, "tls_configure: %s", tls_error(ctx));
+
+	if (!conf.foreground && daemon(0, 1) == -1)
+		exit(1);
+
+	sock4 = make_socket(conf.port, AF_INET);
+	sock6 = -1;
+	if (conf.ipv6)
+		sock6 = make_socket(conf.port, AF_INET6);
+
+	sandbox();
+	loop(ctx, sock4, sock6);
+
+	return 0;
 }
 
 void
@@ -742,9 +784,7 @@ usage(const char *me)
 int
 main(int argc, char **argv)
 {
-	struct tls *ctx = NULL;
-	struct tls_config *tlsconf;
-	int sock4, sock6, ch;
+	int ch, p[2];
 	const char *config_path = NULL;
 	size_t i;
 	int conftest = 0;
@@ -838,41 +878,21 @@ main(int argc, char **argv)
 	if (!conf.foreground)
 		signal(SIGHUP, SIG_IGN);
 
-	if ((tlsconf = tls_config_new()) == NULL)
-		err(1, "tls_config_new");
+	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, p) == -1)
+		fatal("socketpair: %s", strerror(errno));
 
-	/* optionally accept client certs, but don't try to verify them */
-	tls_config_verify_client_optional(tlsconf);
-	tls_config_insecure_noverifycert(tlsconf);
+	switch (fork()) {
+	case -1:
+		fatal("fork: %s", strerror(errno));
 
-	if (tls_config_set_protocols(tlsconf, conf.protos) == -1)
-		err(1, "tls_config_set_protocols");
+	case 0:			/* child */
+		close(p[0]);
+		exfd = p[1];
+		listener_main();
+		_exit(0);
 
-	load_vhosts(tlsconf);
-
-	if ((ctx = tls_server()) == NULL)
-		err(1, "tls_server");
-
-	if (tls_configure(ctx, tlsconf) == -1)
-		errx(1, "tls_configure: %s", tls_error(ctx));
-
-	sock4 = make_socket(conf.port, AF_INET);
-	if (conf.ipv6)
-		sock6 = make_socket(conf.port, AF_INET6);
-	else
-		sock6 = -1;
-
-	if (!conf.foreground && daemon(0, 1) == -1)
-		exit(1);
-
-	sandbox();
-
-	loop(ctx, sock4, sock6);
-
-	close(sock4);
-	close(sock6);
-	tls_free(ctx);
-	tls_config_free(tlsconf);
-
-	return 0;
+	default:		/* parent */
+		close(p[1]);
+		return executor_main(p[0]);
+	}
 }
blob - b595aabb87137c4f2958fbd36c8569e392d6fce1
blob + ed2a6f023dd1d1231cc6c03d473e21739014bd55
--- gmid.h
+++ gmid.h
@@ -72,6 +72,7 @@ struct conf {
 };
 
 extern struct conf conf;
+extern int exfd;
 
 enum {
 	S_HANDSHAKE,
@@ -87,7 +88,7 @@ struct client {
 	int		 code;
 	const char	*meta;
 	int		 fd, waiting_on_child;
-	pid_t		 child;
+	int		 child;
 	char		 sbuf[1024];	  /* static buffer */
 	void		*buf, *i;	  /* mmap buffer */
 	ssize_t		 len, off;	  /* mmap/static buffer  */
@@ -149,6 +150,8 @@ int		 parse_portno(const char*);
 void		 parse_conf(const char*);
 void		 load_vhosts(struct tls_config*);
 
+int		 listener_main();
+
 void		 usage(const char*);
 
 /* provided by lex/yacc */
@@ -157,6 +160,15 @@ extern int yylineno;
 extern int yyparse(void);
 extern int yylex(void);
 
+/* ex.c */
+int		 send_string(int, const char*);
+int		 recv_string(int, char**);
+int		 send_vhost(int, struct vhost*);
+int		 recv_vhost(int, struct vhost**);
+int		 send_fd(int, int);
+int		 recv_fd(int);
+int		 executor_main(int);
+
 /* cgi.c */
 int		 check_for_cgi(char *, char*, struct pollfd*, struct client*);
 int		 start_cgi(const char*, const char*, const char*, struct pollfd*, struct client*);
blob - 6618e5dcce42d7857979866b6b22fe504b86a57c
blob + a8e301db2c554b8c7458a070d392692f8242d9f5
--- sandbox.c
+++ sandbox.c
@@ -15,11 +15,6 @@ sandbox()
 		if (h->cgi != NULL)
 			has_cgi = 1;
 
-	if (has_cgi) {
-		LOGW(NULL, "disabling sandbox because CGI scripts are enabled");
-		return;
-	}
-
 	if (cap_enter() == -1)
 		err(1, "cap_enter");
 }
@@ -41,23 +36,14 @@ void
 sandbox()
 {
 	struct vhost *h;
-	int has_cgi = 0;
 
 	for (h = hosts; h->domain != NULL; ++h) {
 		if (unveil(h->dir, "rx") == -1)
 			err(1, "unveil %s for domain %s", h->dir, h->domain);
-
-		if (h->cgi != NULL)
-			has_cgi = 1;
 	}
 
-	if (pledge("stdio rpath inet proc exec", NULL) == -1)
+	if (pledge("stdio recvfd rpath inet", NULL) == -1)
 		err(1, "pledge");
-
-	/* drop proc and exec if cgi isn't enabled */
-	if (!has_cgi)
-		if (pledge("stdio rpath inet", NULL) == -1)
-			err(1, "pledge");
 }
 
 #else