Commit Diff


commit - /dev/null
commit + b0a6bcf7ac45303633103a7a64ed80fc4cf41181
blob - /dev/null
blob + 0c3fa2db9340a5f222bef82bfea91368ba5d7e8c (mode 644)
--- /dev/null
+++ Makefile
@@ -0,0 +1,25 @@
+PROG =		galileo
+
+SRCS =		galileo.c config.c fcgi.c log.c parse.y proc.c proxy.c \
+		xmalloc.c
+
+# XXX
+NOMAN =		Yes
+
+# debug
+CFLAGS +=	-O0 -g3
+
+CFLAGS +=	-I${.CURDIR}
+
+WARNINGS =	yes
+
+CDIAGFLAGS =	-Wall -Wextra -Wpointer-arith -Wuninitialized
+CDIAGFLAGS+=	-Wstrict-prototypes -Wmissing-prototypes -Wunused
+CDIAGFLAGS+=	-Wsign-compare -Wshadow -Wno-unused-parameter
+CDIAGFLAGS+=	-Wno-missing-field-initializers
+CDIAGFLAGS+=	-Werror
+
+LDADD =		-levent -ltls -lutil
+DPADD =		${LIBEVENT} ${LIBTLS} ${LIBUTIL}
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + dfa7674f36e5557932d55a1cc93b02d42b2bb9df (mode 644)
--- /dev/null
+++ config.c
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@omarpolo.com>
+ * Copyright (c) 2011 - 2015 Reyk Floeter <reyk@openbsd.org>
+ *
+ * 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 <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/tree.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+
+#include <sys/stat.h>		/* umask */
+#include <sys/un.h>		/* sockaddr_un */
+
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <imsg.h>
+
+#include "proc.h"
+#include "log.h"
+#include "xmalloc.h"
+
+#include "galileo.h"
+
+int
+config_init(struct galileo *env)
+{
+	/* Global configuration */
+	if (privsep_process == PROC_PARENT)
+		env->sc_prefork = PROXY_NUMPROC;
+
+	/* Other configuration. */
+	TAILQ_INIT(&env->sc_servers);
+
+	env->sc_sock_fd = -1;
+
+	return 0;
+}
+
+void
+config_purge(struct galileo *env)
+{
+	struct server	*srv;
+
+	while ((srv = TAILQ_FIRST(&env->sc_servers)) != NULL) {
+		TAILQ_REMOVE(&env->sc_servers, srv, srv_entry);
+		proxy_purge(srv);
+	}
+}
+
+int
+config_setserver(struct galileo *env, struct server *srv)
+{
+	struct privsep		*ps = env->sc_ps;
+
+	if (proc_compose(ps, PROC_PROXY, IMSG_CFG_SRV, srv, sizeof(*srv))
+	    == -1)
+		fatal("proc_compose");
+	return 0;
+}
+
+int
+config_getserver(struct galileo *env, struct imsg *imsg)
+{
+	struct server	*srv;
+
+	srv = xcalloc(1, sizeof(*srv));
+	if (IMSG_DATA_SIZE(imsg) != sizeof(*srv))
+		fatalx("%s: bad imsg size", __func__);
+
+	memcpy(srv, imsg->data, sizeof(*srv));
+
+	log_debug("%s: server=%s proxy-to=%s:%d (%s)", __func__,
+	    srv->srv_conf.host, srv->srv_conf.proxy_addr,
+	    srv->srv_conf.proxy_port, srv->srv_conf.proxy_name);
+
+	TAILQ_INSERT_TAIL(&env->sc_servers, srv, srv_entry);
+
+	return 0;
+}
+
+int
+config_setsock(struct galileo *env)
+{
+	struct privsep		*ps = env->sc_ps;
+	struct passwd		*pw = ps->ps_pw;
+	struct sockaddr_un	 sun;
+	const char		*path = GALILEO_SOCK;
+	int			 id, fd, old_umask;
+
+	/*
+	 * open listening socket.
+	 *
+	 * XXX: move to server.c as server_privinit like httpd once we
+	 * support more than one listening socket.
+	 */
+	if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0)) == -1) {
+		log_warn("%s: socket", __func__);
+		return (-1);
+	}
+
+	memset(&sun, 0, sizeof(sun));
+	sun.sun_family = AF_UNIX;
+	strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
+
+	if (unlink(path) == -1)
+		if (errno != ENOENT) {
+			log_warn("%s: unlink %s", __func__, path);
+			close(fd);
+			return (-1);
+		}
+
+	old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+	if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
+		log_warn("%s: bind: %s (%d)", __func__, path, geteuid());
+		close(fd);
+		umask(old_umask);
+		return (-1);
+	}
+	umask(old_umask);
+
+	if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) {
+		log_warn("%s: chmod", __func__);
+		close(fd);
+		(void)unlink(path);
+		return (-1);
+	}
+
+	if (chown(path, pw->pw_uid, pw->pw_gid) == -1) {
+		log_warn("%s: chown", __func__);
+		close(fd);
+		(void)unlink(path);
+		return (-1);
+	}
+
+	if (listen(fd, 5) == -1) {
+		log_warn("%s: listen", __func__);
+		close(fd);
+		(void)unlink(path);
+		return (-1);
+	}
+
+	for (id = 0; id < PROC_MAX; ++id) {
+		int n, m;
+
+		if (id == privsep_process || id != PROC_PROXY)
+			continue;
+
+		n = -1;
+		proc_range(ps, id, &n, &m);
+		for (n = 0; n < m; ++n) {
+			int d;
+
+			if ((d = dup(fd)) == -1) {
+				log_warn("%s: dup", __func__);
+				close(fd);
+				return (-1);
+			}
+
+			if (proc_compose_imsg(ps, id, n, IMSG_CFG_SOCK,
+			    -1, d, NULL, 0) == -1) {
+				log_warn("%s: failed to compose "
+				    "IMSG_CFG_SOCK", __func__);
+				close(fd);
+				return (-1);
+			}
+			if (proc_flush_imsg(ps, id, n) == -1) {
+				log_warn("%s: failed to flush", __func__);
+				close(fd);
+				return (-1);
+			}
+		}
+	}
+
+	/* close(fd); */
+	return (0);
+}
+
+int
+config_getsock(struct galileo *env, struct imsg *imsg)
+{
+	/* XXX: make it more like httpd/gotwebd' one */
+	return imsg->fd;
+}
+
+int
+config_setreset(struct galileo *env)
+{
+	struct privsep	*ps = env->sc_ps;
+	int		 id;
+
+	for (id = 0; id < PROC_MAX; ++id)
+		proc_compose(ps, id, IMSG_CTL_RESET, NULL, 0);
+
+	return (0);
+}
+
+int
+config_getreset(struct galileo *env, struct imsg *imsg)
+{
+	config_purge(env);
+
+	return (0);
+}
blob - /dev/null
blob + 9e864662ffe2ebdc2992a50ac0b916acde9240ca (mode 644)
--- /dev/null
+++ fcgi.c
@@ -0,0 +1,651 @@
+/*
+ * Copyright (c) 2022 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 <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "log.h"
+
+#include "galileo.h"
+
+#define MIN(a, b)	((a) < (b) ? (a) : (b))
+
+struct fcgi_header {
+	unsigned char version;
+	unsigned char type;
+	unsigned char req_id1;
+	unsigned char req_id0;
+	unsigned char content_len1;
+	unsigned char content_len0;
+	unsigned char padding;
+	unsigned char reserved;
+} __attribute__((packed));
+
+/*
+ * number of bytes in a FCGI_HEADER.  Future version of the protocol
+ * will not reduce this number.
+ */
+#define FCGI_HEADER_LEN	8
+
+/*
+ * values for the version component
+ */
+#define FCGI_VERSION_1	1
+
+/*
+ * values for the type component
+ */
+#define FCGI_BEGIN_REQUEST	 1
+#define FCGI_ABORT_REQUEST	 2
+#define FCGI_END_REQUEST	 3
+#define FCGI_PARAMS		 4
+#define FCGI_STDIN		 5
+#define FCGI_STDOUT		 6
+#define FCGI_STDERR		 7
+#define FCGI_DATA		 8
+#define FCGI_GET_VALUES		 9
+#define FCGI_GET_VALUES_RESULT	10
+#define FCGI_UNKNOWN_TYPE	11
+#define FCGI_MAXTYPE		(FCGI_UNKNOWN_TYPE)
+
+struct fcgi_begin_req {
+	unsigned char role1;
+	unsigned char role0;
+	unsigned char flags;
+	unsigned char reserved[5];
+};
+
+struct fcgi_begin_req_record {
+	struct fcgi_header	header;
+	struct fcgi_begin_req	body;
+};
+
+/*
+ * mask for flags;
+ */
+#define FCGI_KEEP_CONN		1
+
+/*
+ * values for the role
+ */
+#define FCGI_RESPONDER	1
+#define FCGI_AUTHORIZER	2
+#define FCGI_FILTER	3
+
+struct fcgi_end_req_body {
+	unsigned char app_status3;
+	unsigned char app_status2;
+	unsigned char app_status1;
+	unsigned char app_status0;
+	unsigned char proto_status;
+	unsigned char reserved[3];
+};
+
+/*
+ * values for proto_status
+ */
+#define FCGI_REQUEST_COMPLETE	0
+#define FCGI_CANT_MPX_CONN	1
+#define FCGI_OVERLOADED		2
+#define FCGI_UNKNOWN_ROLE	3
+
+/*
+ * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT
+ * records.
+ */
+#define FCGI_MAX_CONNS	"FCGI_MAX_CONNS"
+#define FCGI_MAX_REQS	"FCGI_MAX_REQS"
+#define FCGI_MPXS_CONNS	"FCGI_MPXS_CONNS"
+
+#define CAT(f0, f1)	((f0) + ((f1) << 8))
+
+enum {
+	FCGI_RECORD_HEADER,
+	FCGI_RECORD_BODY,
+};
+
+volatile int fcgi_inflight;
+
+static int
+fcgi_send_end_req(struct fcgi *fcgi, int id, int as, int ps)
+{
+	struct bufferevent	*bev = fcgi->fcg_bev;
+	struct fcgi_header	 hdr;
+	struct fcgi_end_req_body end;
+
+	memset(&hdr, 0, sizeof(hdr));
+	memset(&end, 0, sizeof(end));
+
+	hdr.version = FCGI_VERSION_1;
+	hdr.type = FCGI_END_REQUEST;
+	hdr.req_id0 = (id & 0xFF);
+	hdr.req_id1 = (id >> 8);
+	hdr.content_len0 = sizeof(end);
+
+	end.app_status0 = (unsigned char)as;
+	end.proto_status = (unsigned char)ps;
+
+	if (bufferevent_write(bev, &hdr, sizeof(hdr)) == -1)
+		return (-1);
+	if (bufferevent_write(bev, &end, sizeof(end)) == -1)
+		return (-1);
+	return (0);
+}
+
+static int
+end_request(struct client *clt, int status, int proto_status)
+{
+	struct fcgi		*fcgi = clt->clt_fcgi;
+	int			 r;
+
+	if (clt_flush(clt) == -1)
+		return (-1);
+
+	r = fcgi_send_end_req(fcgi, clt->clt_id, status,
+	    proto_status);
+	if (r == -1) {
+		fcgi_error(fcgi->fcg_bev, EV_WRITE, fcgi);
+		return (-1);
+	}
+
+	SPLAY_REMOVE(client_tree, &fcgi->fcg_clients, clt);
+	proxy_client_free(clt);
+	return (0);
+}
+
+int
+fcgi_end_request(struct client *clt, int status)
+{
+	return (end_request(clt, status, FCGI_REQUEST_COMPLETE));
+}
+
+int
+fcgi_abort_request(struct client *clt)
+{
+	return (end_request(clt, 1, FCGI_OVERLOADED));
+}
+
+static void
+fcgi_inflight_dec(const char *why)
+{
+	fcgi_inflight--;
+	log_debug("%s: fcgi inflight decremented, now %d, %s",
+	    __func__, fcgi_inflight, why);
+}
+
+void
+fcgi_accept(struct galileo *env)
+{
+	struct fcgi		*fcgi = NULL;
+	socklen_t		 slen;
+	struct sockaddr_storage	 ss;
+	int			 s = -1;
+
+	slen = sizeof(ss);
+	if ((s = accept_reserve(env->sc_sock_fd, (struct sockaddr *)&ss,
+	    &slen, FD_RESERVE, &fcgi_inflight)) == -1) {
+		/*
+		 * Pause accept if we are out of file descriptors, or
+		 * libevent will haunt us here too.
+		 */
+		if (errno == ENFILE || errno == EMFILE) {
+			struct timeval evtpause = { 1, 0 };
+
+			event_del(&env->sc_evsock);
+			evtimer_add(&env->sc_evpause, &evtpause);
+			log_debug("%s: deferring connections", __func__);
+		}
+		return;
+	}
+
+	if ((fcgi = calloc(1, sizeof(*fcgi))) == NULL)
+		goto err;
+
+	fcgi->fcg_id = ++proxy_fcg_id;
+	fcgi->fcg_s = s;
+	fcgi->fcg_env = env;
+	fcgi->fcg_want = FCGI_RECORD_HEADER;
+	fcgi->fcg_toread = sizeof(struct fcgi_header);
+	SPLAY_INIT(&fcgi->fcg_clients);
+
+	/* assume it's enabled until we get a FCGI_BEGIN_REQUEST */
+	fcgi->fcg_keep_conn = 1;
+
+	fcgi->fcg_bev = bufferevent_new(fcgi->fcg_s, fcgi_read, fcgi_write,
+	    fcgi_error, fcgi);
+	if (fcgi->fcg_bev == NULL)
+		goto err;
+
+	bufferevent_enable(fcgi->fcg_bev, EV_READ | EV_WRITE);
+	return;
+
+err:
+	if (s != -1) {
+		close(s);
+		free(fcgi);
+		fcgi_inflight_dec(__func__);
+	}
+}
+
+static int
+parse_len(struct fcgi *fcgi, struct evbuffer *src)
+{
+	unsigned char		 c, x[3];
+
+	fcgi->fcg_toread--;
+	evbuffer_remove(src, &c, 1);
+	if (c >> 7 == 0)
+		return (c);
+
+	if (fcgi->fcg_toread < 3)
+		return (-1);
+
+	fcgi->fcg_toread -= 3;
+	evbuffer_remove(src, x, sizeof(x));
+	return (((c & 0x7F) << 24) | (x[0] << 16) | (x[1] << 8) | x[2]);
+}
+
+static int
+fcgi_parse_params(struct fcgi *fcgi, struct evbuffer *src, struct client *clt)
+{
+	char			 pname[32];
+	char			 server[HOST_NAME_MAX + 1];
+	char			 path[PATH_MAX];
+	int			 nlen, vlen;
+
+	while (fcgi->fcg_toread > 0) {
+		if ((nlen = parse_len(fcgi, src)) < 0 ||
+		    (vlen = parse_len(fcgi, src)) < 0)
+			return (-1);
+
+		if (fcgi->fcg_toread < nlen + vlen)
+			return (-1);
+
+		if ((size_t)nlen > sizeof(pname) - 1) {
+			/* ignore this parameter */
+			fcgi->fcg_toread -= nlen - vlen;
+			evbuffer_drain(src, nlen + vlen);
+			continue;
+		}
+
+		fcgi->fcg_toread -= nlen;
+		evbuffer_remove(src, &pname, nlen);
+		pname[nlen] = '\0';
+
+		if (!strcmp(pname, "SERVER_NAME") &&
+		    (size_t)vlen < sizeof(server)) {
+			fcgi->fcg_toread -= vlen;
+			evbuffer_remove(src, &server, vlen);
+			server[vlen] = '\0';
+
+			if ((clt->clt_server_name = strdup(server)) == NULL)
+				return (-1);
+			log_debug("clt %d: server_name: %s", clt->clt_id,
+			    clt->clt_server_name);
+			continue;
+		}
+
+		if (!strcmp(pname, "SCRIPT_NAME") &&
+		    (size_t)vlen < sizeof(path)) {
+			fcgi->fcg_toread -= vlen;
+			evbuffer_remove(src, &path, vlen);
+			path[vlen] = '\0';
+
+			if ((clt->clt_script_name = strdup(path)) == NULL)
+				return (-1);
+			log_debug("clt %d: script_name: %s", clt->clt_id,
+			    clt->clt_script_name);
+			continue;
+		}
+
+		if (!strcmp(pname, "PATH_INFO") &&
+		    (size_t)vlen < sizeof(path)) {
+			fcgi->fcg_toread -= vlen;
+			evbuffer_remove(src, &path, vlen);
+			path[vlen] = '\0';
+
+			if ((clt->clt_path_info = strdup(path)) == NULL)
+				return (-1);
+			log_debug("clt %d: path_info: %s", clt->clt_id,
+			    clt->clt_path_info);
+			continue;
+		}
+
+		fcgi->fcg_toread -= vlen;
+		evbuffer_drain(src, vlen);
+	}
+
+	return (0);
+}
+
+void
+fcgi_read(struct bufferevent *bev, void *d)
+{
+	struct fcgi		*fcgi = d;
+	struct galileo		*env = fcgi->fcg_env;
+	struct evbuffer		*src = EVBUFFER_INPUT(bev);
+	struct fcgi_header	 hdr;
+	struct fcgi_begin_req	 breq;
+	struct client		*clt, q;
+	int			 role;
+
+	memset(&q, 0, sizeof(q));
+
+	for (;;) {
+		if (EVBUFFER_LENGTH(src) < (size_t)fcgi->fcg_toread)
+			return;
+
+		if (fcgi->fcg_want == FCGI_RECORD_HEADER) {
+			fcgi->fcg_want = FCGI_RECORD_BODY;
+			bufferevent_read(bev, &hdr, sizeof(hdr));
+
+#ifdef DEBUG
+			log_warnx("header: v=%d t=%d id=%d len=%d p=%d",
+			    hdr.version, hdr.type,
+			    CAT(hdr.req_id0, hdr.req_id1),
+			    CAT(hdr.content_len0, hdr.content_len1),
+			    hdr.padding);
+#endif
+
+			if (hdr.version != FCGI_VERSION_1) {
+				log_warnx("unknown fastcgi version: %d",
+				    hdr.version);
+				fcgi_error(bev, EV_READ, d);
+				return;
+			}
+
+			fcgi->fcg_toread = CAT(hdr.content_len0,
+			    hdr.content_len1);
+			if (fcgi->fcg_toread < 0) {
+				log_warnx("invalid record length: %d",
+				    fcgi->fcg_toread);
+				fcgi_error(bev, EV_READ, d);
+				return;
+			}
+
+			fcgi->fcg_padding = hdr.padding;
+			if (fcgi->fcg_padding < 0) {
+				log_warnx("invalid padding: %d",
+				    fcgi->fcg_padding);
+				fcgi_error(bev, EV_READ, d);
+				return;
+			}
+
+			fcgi->fcg_type = hdr.type;
+			fcgi->fcg_rec_id = CAT(hdr.req_id0, hdr.req_id1);
+			continue;
+		}
+
+		q.clt_id = fcgi->fcg_rec_id;
+		clt = SPLAY_FIND(client_tree, &fcgi->fcg_clients, &q);
+
+		switch (fcgi->fcg_type) {
+		case FCGI_BEGIN_REQUEST:
+			if (sizeof(breq) != fcgi->fcg_toread) {
+				log_warnx("unexpected size for "
+				    "FCGI_BEGIN_REQUEST");
+				fcgi_error(bev, EV_READ, d);
+				return;
+			}
+
+			evbuffer_remove(src, &breq, sizeof(breq));
+
+			role = CAT(breq.role0, breq.role1);
+			if (role != FCGI_RESPONDER) {
+				log_warnx("unknown fastcgi role: %d",
+				    role);
+				if (fcgi_send_end_req(fcgi, fcgi->fcg_rec_id,
+				    1, FCGI_UNKNOWN_ROLE) == -1) {
+					fcgi_error(bev, EV_READ, d);
+					return;
+				}
+				break;
+			}
+
+			if (!fcgi->fcg_keep_conn) {
+				log_warnx("trying to reuse the fastcgi "
+				    "socket without marking it as so.");
+				fcgi_error(bev, EV_READ, d);
+				return;
+			}
+			fcgi->fcg_keep_conn = breq.flags & FCGI_KEEP_CONN;
+
+			if (clt != NULL) {
+				log_warnx("ignoring attemp to re-use an "
+				    "active request id (%d)",
+				    fcgi->fcg_rec_id);
+				break;
+			}
+
+			if ((clt = calloc(1, sizeof(*clt))) == NULL) {
+				log_warnx("calloc");
+				break;
+			}
+
+			clt->clt_id = fcgi->fcg_rec_id;
+			clt->clt_fd = -1;
+			clt->clt_fcgi = fcgi;
+			SPLAY_INSERT(client_tree, &fcgi->fcg_clients, clt);
+			break;
+		case FCGI_PARAMS:
+			if (clt == NULL) {
+				log_warnx("got FCGI_PARAMS for inactive id "
+				    "(%d)", fcgi->fcg_rec_id);
+				evbuffer_drain(src, fcgi->fcg_toread);
+				break;
+			}
+			if (fcgi->fcg_toread == 0) {
+				evbuffer_drain(src, fcgi->fcg_toread);
+				proxy_start_request(env, clt);
+				break;
+			}
+			if (fcgi_parse_params(fcgi, src, clt) == -1) {
+				log_warnx("fcgi_parse_params failed");
+				fcgi_error(bev, EV_READ, d);
+				return;
+			}
+			break;
+		case FCGI_STDIN:
+			/* ignore */
+			evbuffer_drain(src, fcgi->fcg_toread);
+			break;
+		case FCGI_ABORT_REQUEST:
+			if (clt == NULL) {
+				log_warnx("got FCGI_ABORT_REQUEST for inactive"
+				    " id (%d)", fcgi->fcg_rec_id);
+				evbuffer_drain(src, fcgi->fcg_toread);
+				break;
+			}
+			if (fcgi_end_request(clt, 1) == -1) {
+				/* calls fcgi_error on failure */
+				return;
+			}
+			break;
+		default:
+			log_warnx("unknown fastcgi record type %d",
+			    fcgi->fcg_type);
+			evbuffer_drain(src, fcgi->fcg_toread);
+			break;
+		}
+
+		/* Prepare for the next record. */
+		evbuffer_drain(src, fcgi->fcg_padding);
+		fcgi->fcg_want = FCGI_RECORD_HEADER;
+		fcgi->fcg_toread = sizeof(struct fcgi_header);
+	}
+}
+
+void
+fcgi_write(struct bufferevent *bev, void *d)
+{
+	struct fcgi		*fcgi = d;
+
+	(void)fcgi;
+}
+
+void
+fcgi_error(struct bufferevent *bev, short event, void *d)
+{
+	struct fcgi		*fcgi = d;
+	struct galileo		*env = fcgi->fcg_env;
+	struct client		*clt;
+
+	log_debug("fcgi failure, shutting down connection (ev: %x)",
+	    event);
+	fcgi_inflight_dec(__func__);
+
+	while ((clt = SPLAY_MIN(client_tree, &fcgi->fcg_clients)) != NULL) {
+		SPLAY_REMOVE(client_tree, &fcgi->fcg_clients, clt);
+		proxy_client_free(clt);
+	}
+
+	close(fcgi->fcg_s);
+	bufferevent_free(fcgi->fcg_bev);
+	SPLAY_REMOVE(fcgi_tree, &env->sc_fcgi_socks, fcgi);
+	free(fcgi);
+
+	return;
+}
+
+int
+clt_flush(struct client *clt)
+{
+	struct fcgi		*fcgi = clt->clt_fcgi;
+	struct bufferevent	*bev = fcgi->fcg_bev;
+	struct fcgi_header	 hdr;
+
+	if (clt->clt_buflen == 0)
+		return (0);
+
+	memset(&hdr, 0, sizeof(hdr));
+	hdr.version = FCGI_VERSION_1;
+	hdr.type = FCGI_STDOUT;
+	hdr.req_id0 = (clt->clt_id & 0xFF);
+	hdr.req_id1 = (clt->clt_id >> 8);
+	hdr.content_len0 = (clt->clt_buflen & 0xFF);
+	hdr.content_len1 = (clt->clt_buflen >> 8);
+
+	if (bufferevent_write(bev, &hdr, sizeof(hdr)) == -1 ||
+	    bufferevent_write(bev, clt->clt_buf, clt->clt_buflen) == -1) {
+		fcgi_error(bev, EV_WRITE, fcgi);
+		return (-1);
+	}
+
+	clt->clt_buflen = 0;
+
+	return (0);
+}
+
+int
+clt_write(struct client *clt, const uint8_t *buf, size_t len)
+{
+	size_t			 left, copy;
+
+	while (len > 0) {
+		left = sizeof(clt->clt_buf) - clt->clt_buflen;
+		if (left == 0) {
+			if (clt_flush(clt) == -1)
+				return (-1);
+			left = sizeof(clt->clt_buf);
+		}
+
+		copy = MIN(left, len);
+
+		memcpy(&clt->clt_buf[clt->clt_buflen], buf, copy);
+		clt->clt_buflen += copy;
+		buf += copy;
+		len -= copy;
+	}
+
+	return (0);
+}
+
+int
+clt_write_bufferevent(struct client *clt, struct bufferevent *bev)
+{
+	struct evbuffer		*src = EVBUFFER_INPUT(bev);
+	size_t			 len, left, copy;
+
+	len = EVBUFFER_LENGTH(src);
+	while (len > 0) {
+		left = sizeof(clt->clt_buf) - clt->clt_buflen;
+		if (left == 0) {
+			if (clt_flush(clt) == -1)
+				return (-1);
+			left = sizeof(clt->clt_buf);
+		}
+
+		copy = bufferevent_read(bev, &clt->clt_buf[clt->clt_buflen],
+		    MIN(left, len));
+		clt->clt_buflen += copy;
+
+		len = EVBUFFER_LENGTH(src);
+	}
+
+	return (0);
+}
+
+int
+clt_printf(struct client *clt, const char *fmt, ...)
+{
+	struct fcgi		*fcgi = clt->clt_fcgi;
+	struct bufferevent	*bev = fcgi->fcg_bev;
+	char			*str;
+	va_list			 ap;
+	int			 r;
+
+	va_start(ap, fmt);
+	r = vasprintf(&str, fmt, ap);
+	va_end(ap);
+	if (r == -1) {
+		fcgi_error(bev, EV_WRITE, fcgi);
+		return (-1);
+	}
+
+	r = clt_write(clt, str, r);
+	free(str);
+	return (r);
+}
+
+int
+fcgi_cmp(struct fcgi *a, struct fcgi *b)
+{
+	return ((int)a->fcg_id - b->fcg_id);
+}
+
+int
+fcgi_client_cmp(struct client *a, struct client *b)
+{
+	return ((int)a->clt_id - b->clt_id);
+}
+
+SPLAY_GENERATE(fcgi_tree, fcgi, fcg_nodes, fcgi_cmp);
+SPLAY_GENERATE(client_tree, client, clt_nodes, fcgi_client_cmp);
+
blob - /dev/null
blob + f0dca409026b865ebce690d75fdd232c7052c2b5 (mode 644)
--- /dev/null
+++ galileo.c
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@omarpolo.com>
+ * Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org>
+ *
+ * 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 <sys/types.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <locale.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <imsg.h>
+
+#include "log.h"
+#include "proc.h"
+#include "xmalloc.h"
+
+#include "galileo.h"
+
+static int		parent_configure(struct galileo *);
+static void		parent_configure_done(struct galileo *);
+static void		parent_reload(struct galileo *);
+static void		parent_sig_handler(int, short, void *);
+static int		parent_dispatch_proxy(int, struct privsep_proc *,
+			    struct imsg *);
+static __dead void	parent_shutdown(struct galileo *);
+
+static struct privsep_proc procs[] = {
+	{ "proxy",	PROC_PROXY, parent_dispatch_proxy, proxy },
+};
+
+int privsep_process;
+
+const char *conffile = CONF_FILE;
+
+static __dead void
+usage(void)
+{
+	fprintf(stderr, "usage: %s [-dnv] [-D macro=value] [-f file]",
+	    getprogname());
+	exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+	struct galileo	*env;
+	struct privsep	*ps;
+	const char	*errstr;
+	const char	*title = NULL;
+	size_t		 i;
+	int		 conftest = 0, debug = 0, verbose = 0;
+	int		 argc0 = argc, ch;
+	int		 proc_id = PROC_PARENT;
+	int		 proc_instance = 0;
+
+	setlocale(LC_CTYPE, "");
+
+	/* log to stderr until daemonized */
+	log_init(1, LOG_DAEMON);
+	log_setverbose(verbose);
+
+	while ((ch = getopt(argc, argv, "D:df:I:nP:v")) != -1) {
+		switch (ch) {
+		case 'D':
+			if (cmdline_symset(optarg) < 0)
+				log_warnx("could not parse macro definition %s",
+				    optarg);
+			break;
+		case 'd':
+			debug = 1;
+			break;
+		case 'f':
+			conffile = optarg;
+			break;
+		case 'I':
+			proc_instance = strtonum(optarg, 0, PROC_MAX_INSTANCES,
+			    &errstr);
+			if (errstr != NULL)
+				fatalx("invalid process instance");
+			break;
+		case 'n':
+			conftest = 1;
+			break;
+		case 'P':
+			title = optarg;
+			proc_id = proc_getid(procs, nitems(procs), title);
+			if (proc_id == PROC_MAX)
+				fatalx("invalid process name");
+			break;
+		case 'v':
+			verbose = 1;
+			break;
+		default:
+			usage();
+		}
+	}
+	argc -= optind;
+	if (argc != 0)
+		usage();
+
+	if (geteuid())
+		fatalx("need root privileges");
+
+	log_setverbose(verbose);
+
+	env = xcalloc(1, sizeof(*env));
+	config_init(env);
+	if (parse_config(conffile, env) == -1)
+		return 1;
+
+	if (conftest) {
+		fprintf(stderr, "configuration OK\n");
+		return 0;
+	}
+
+	ps = xcalloc(1, sizeof(*ps));
+	ps->ps_env = env;
+	env->sc_ps = ps;
+	if ((ps->ps_pw = getpwnam(GALILEO_USER)) == NULL)
+		fatalx("unknown user %s", GALILEO_USER);
+
+	ps->ps_instances[PROC_PROXY] = env->sc_prefork;
+	ps->ps_instance = proc_instance;
+	if (title != NULL)
+		ps->ps_title[proc_id] = title;
+
+	if (*env->sc_chroot == '\0') {
+		if (strlcpy(env->sc_chroot, ps->ps_pw->pw_dir,
+		    sizeof(env->sc_chroot)) >= sizeof(env->sc_chroot))
+			fatalx("chroot path too long!");
+	}
+
+	for (i = 0; i < nitems(procs); ++i)
+		procs[i].p_chroot = env->sc_chroot;
+
+	/* only the parent returns */
+	proc_init(ps, procs, nitems(procs), debug, argc0, argv, proc_id);
+
+	log_procinit("parent");
+	if (!debug && daemon(0, 0) == -1)
+		fatal("failed to daemonize");
+
+	log_init(debug, LOG_DAEMON);
+
+	log_info("startup");
+
+	/* if (pledge("stdio rpath wpath cpath unix fattr sendfd", NULL) == -1) */
+		/* fatal("pledge"); */
+
+	event_init();
+
+	signal(SIGPIPE, SIG_IGN);
+
+	signal_set(&ps->ps_evsigint, SIGINT, parent_sig_handler, ps);
+	signal_set(&ps->ps_evsigterm, SIGTERM, parent_sig_handler, ps);
+	signal_set(&ps->ps_evsigchld, SIGCHLD, parent_sig_handler, ps);
+	signal_set(&ps->ps_evsighup, SIGHUP, parent_sig_handler, ps);
+
+	signal_add(&ps->ps_evsigint, NULL);
+	signal_add(&ps->ps_evsigterm, NULL);
+	signal_add(&ps->ps_evsigchld, NULL);
+	signal_add(&ps->ps_evsighup, NULL);
+
+	proc_connect(ps);
+
+	if (parent_configure(env) == -1)
+		fatalx("configuration failed");
+
+	event_dispatch();
+
+	parent_shutdown(env);
+	/* NOTREACHED */
+
+	return (0);
+}
+
+static int
+parent_configure(struct galileo *env)
+{
+	struct server	*srv;
+	int		 id;
+
+	TAILQ_FOREACH(srv, &env->sc_servers, srv_entry) {
+		if (config_setserver(env, srv) == -1)
+			fatal("send server");
+	}
+
+	/* XXX: eventually they will be more than just one */
+	if (config_setsock(env) == -1)
+		fatal("send socket");
+
+	/* The servers need to reload their config. */
+	env->sc_reload = env->sc_prefork;
+
+	for (id = 0; id < PROC_MAX; id++) {
+		if (id == privsep_process)
+			continue;
+		proc_compose(env->sc_ps, id, IMSG_CFG_DONE, env, sizeof(env));
+	}
+
+	config_purge(env);
+	return 0;
+}
+
+static void
+parent_configure_done(struct galileo *env)
+{
+	int	 id;
+
+	if (env->sc_reload == 0) {
+		log_warnx("configuration already finished");
+		return;
+	}
+
+	env->sc_reload--;
+	if (env->sc_reload == 0) {
+		for (id = 0; id < PROC_MAX; ++id) {
+			if (id == privsep_process)
+				continue;
+
+			proc_compose(env->sc_ps, id, IMSG_CTL_START, NULL, 0);
+		}
+	}
+}
+
+static void
+parent_reload(struct galileo *env)
+{
+	if (env->sc_reload) {
+		log_debug("%s: already in progress: %d pending",
+		    __func__, env->sc_reload);
+	}
+
+	log_debug("%s: config file %s", __func__, conffile);
+
+	config_purge(env);
+
+	if (parse_config(conffile, env) == -1) {
+		log_warnx("failed to load config file: %s", conffile);
+		return;
+	}
+
+	config_setreset(env);
+	parent_configure(env);
+}
+
+static void
+parent_sig_handler(int sig, short ev, void *arg)
+{
+	struct privsep	*ps = arg;
+
+	/*
+	 * Normal signal handler rules don't apply because libevent
+	 * decouples for us.
+	 */
+
+	switch (sig) {
+	case SIGHUP:
+		if (privsep_process != PROC_PARENT)
+			return;
+		log_info("reload requested with SIGHUP");
+		parent_reload(ps->ps_env);
+		break;
+	case SIGCHLD:
+		log_warnx("one child died, quitting.");
+	case SIGTERM:
+	case SIGINT:
+		parent_shutdown(ps->ps_env);
+		break;
+	default:
+		fatalx("unexpected signal %d", sig);
+	}
+}
+
+static int
+parent_dispatch_proxy(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+	struct privsep	*ps = p->p_ps;
+	struct galileo	*env = ps->ps_env;
+
+	switch (imsg->hdr.type) {
+	case IMSG_CFG_DONE:
+		parent_configure_done(env);
+		break;
+	default:
+		return (-1);
+	}
+
+	return (0);
+}
+
+static __dead void
+parent_shutdown(struct galileo *env)
+{
+	config_purge(env);
+
+	proc_kill(env->sc_ps);
+
+	free(env->sc_ps);
+	free(env);
+
+	log_info("parent terminating, pid %d", getpid());
+	exit(0);
+}
+
+int
+accept_reserve(int sockfd, struct sockaddr *addr, socklen_t *addrlen,
+    int reserve, volatile int *counter)
+{
+	int ret;
+	if (getdtablecount() + reserve +
+	    *counter >= getdtablesize()) {
+		errno = EMFILE;
+		return (-1);
+	}
+
+	if ((ret = accept4(sockfd, addr, addrlen, SOCK_NONBLOCK)) > -1) {
+		(*counter)++;
+		log_debug("%s: inflight incremented, now %d",__func__, *counter);
+	}
+	return (ret);
+}
blob - /dev/null
blob + c747646dcc2b530e8588dd5b4af217d827d51fbd (mode 644)
--- /dev/null
+++ galileo.h
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@omarpolo.com>
+ * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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.
+ */
+
+#ifndef nitems
+#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
+#define GALILEO_USER		"www"
+#define GALILEO_SOCK		"/var/www/run/galileo.sock"
+#define CONF_FILE		"/etc/galileo.conf"
+#define FD_RESERVE		5
+#define PROC_MAX_INSTANCES	32
+#define PROXY_NUMPROC		3
+#define PROC_PARENT_SOCK_FILENO	3
+
+enum {
+	IMSG_NONE,
+	IMSG_CFG_START,
+	IMSG_CFG_SRV,
+	IMSG_CFG_SOCK,
+	IMSG_CFG_DONE,
+	IMSG_CTL_START,
+	IMSG_CTL_RESET,
+	IMSG_CTL_RESTART,
+	IMSG_CTL_PROCFD,
+};
+
+struct galileo;
+struct proxy_config;
+
+struct imsg;
+struct privsep;
+struct privsep_proc;
+struct tls;
+
+struct client {
+	uint32_t		 clt_id;
+	int			 clt_fd;
+	struct fcgi		*clt_fcgi;
+	char			*clt_server_name;
+	char			*clt_script_name;
+	char			*clt_path_info;
+	struct proxy_config	*clt_pc;
+	struct event_asr	*clt_evasr;
+	struct addrinfo		*clt_addrinfo;
+	struct addrinfo		*clt_p;
+	struct event		 clt_evconn;
+	int			 clt_evconn_live;
+	struct tls		*clt_ctx;
+	struct bufferevent	*clt_bev;
+	int			 clt_headersdone;
+
+	char			 clt_buf[1024];
+	size_t			 clt_buflen;
+
+	SPLAY_ENTRY(client)	 clt_nodes;
+};
+SPLAY_HEAD(client_tree, client);
+
+struct fcgi {
+	uint32_t		 fcg_id;
+	int			 fcg_s;
+	struct client_tree	 fcg_clients;
+	struct bufferevent	*fcg_bev;
+	int			 fcg_toread;
+	int			 fcg_want;
+	int			 fcg_padding;
+	int			 fcg_type;
+	uint16_t		 fcg_rec_id;
+	int			 fcg_keep_conn;
+
+	struct galileo		*fcg_env;
+
+	SPLAY_ENTRY(fcgi)	 fcg_nodes;
+};
+SPLAY_HEAD(fcgi_tree, fcgi);
+
+struct proxy_config {
+	char		 host[HOST_NAME_MAX + 1];
+	char		 stylesheet[PATH_MAX];
+	char		 proxy_addr[HOST_NAME_MAX + 1];
+	char		 proxy_name[HOST_NAME_MAX + 1];
+	uint16_t	 proxy_port; /* TODO: turn into string */
+};
+
+struct server {
+	TAILQ_ENTRY(server)	 srv_entry;
+	struct proxy_config	 srv_conf;
+};
+TAILQ_HEAD(serverlist, server);
+
+struct galileo {
+	char			 sc_conffile[PATH_MAX];
+	uint16_t		 sc_prefork;
+	char			 sc_chroot[PATH_MAX];
+	struct serverlist	 sc_servers;
+	struct fcgi_tree	 sc_fcgi_socks;
+
+	struct privsep		*sc_ps;
+	int			 sc_reload;
+
+	/* XXX: generalize */
+	int		sc_sock_fd;
+	struct event	sc_evsock;
+	struct event	sc_evpause;
+};
+
+extern int privsep_process;
+
+/* config.c */
+int	 config_init(struct galileo *);
+void	 config_purge(struct galileo *);
+int	 config_setserver(struct galileo *, struct server *);
+int	 config_getserver(struct galileo *, struct imsg *);
+int	 config_setsock(struct galileo *);
+int	 config_getsock(struct galileo *, struct imsg *);
+int	 config_setreset(struct galileo *);
+int	 config_getreset(struct galileo *, struct imsg *);
+
+/* fcgi.c */
+int	 fcgi_end_request(struct client *, int);
+int	 fcgi_abort_request(struct client *);
+void	 fcgi_accept(struct galileo *);
+void	 fcgi_read(struct bufferevent *, void *);
+void	 fcgi_write(struct bufferevent *, void *);
+void	 fcgi_error(struct bufferevent *, short error, void *);
+int	 clt_write_bufferevent(struct client *, struct bufferevent *);
+int	 clt_flush(struct client *);
+int	 clt_write(struct client *, const uint8_t *, size_t);
+int	 clt_printf(struct client *, const char *, ...)
+	     __attribute__((__format__(printf, 2, 3)))
+	     __attribute__((__nonnull__(2)));
+int	 fcgi_cmp(struct fcgi *, struct fcgi *);
+int	 fcgi_client_cmp(struct client *, struct client *);
+
+/* galileo.c */
+int	 accept_reserve(int, struct sockaddr *, socklen_t *, int,
+	     volatile int *);
+/* parse.y */
+int	 parse_config(const char *, struct galileo *);
+int	 cmdline_symset(char *);
+
+/* proxy.c */
+extern volatile int proxy_inflight;
+extern uint32_t proxy_fcg_id;
+
+void	 proxy(struct privsep *, struct privsep_proc *);
+void	 proxy_purge(struct server *);
+void	 proxy_start_request(struct galileo *, struct client *);
+void	 proxy_client_free(struct client *);
+
+SPLAY_PROTOTYPE(fcgi_tree, fcgi, fcg_nodes, fcgi_cmp);
+SPLAY_PROTOTYPE(client_tree, client, clt_nodes, fcgi_client_cmp);
blob - /dev/null
blob + 179235943ad862e0426e472d7b0922bf3fef0371 (mode 644)
--- /dev/null
+++ log.c
@@ -0,0 +1,197 @@
+/*	$OpenBSD: log.c,v 1.1 2018/07/10 16:39:54 florian Exp $	*/
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#include <time.h>
+
+#include "log.h"
+
+static int		 debug;
+static int		 verbose;
+static const char	*log_procname;
+
+void
+log_init(int n_debug, int facility)
+{
+	debug = n_debug;
+	verbose = n_debug;
+	log_procinit(getprogname());
+
+	if (!debug)
+		openlog(getprogname(), LOG_PID | LOG_NDELAY, facility);
+
+	tzset();
+}
+
+void
+log_procinit(const char *procname)
+{
+	if (procname != NULL)
+		log_procname = procname;
+}
+
+void
+log_setverbose(int v)
+{
+	verbose = v;
+}
+
+int
+log_getverbose(void)
+{
+	return (verbose);
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+	va_list	ap;
+
+	va_start(ap, fmt);
+	vlog(pri, fmt, ap);
+	va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+	char	*nfmt;
+	int	 saved_errno = errno;
+
+	if (debug) {
+		/* best effort in out of mem situations */
+		if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+			vfprintf(stderr, fmt, ap);
+			fprintf(stderr, "\n");
+		} else {
+			vfprintf(stderr, nfmt, ap);
+			free(nfmt);
+		}
+		fflush(stderr);
+	} else
+		vsyslog(pri, fmt, ap);
+
+	errno = saved_errno;
+}
+
+void
+log_warn(const char *emsg, ...)
+{
+	char		*nfmt;
+	va_list		 ap;
+	int		 saved_errno = errno;
+
+	/* best effort to even work in out of memory situations */
+	if (emsg == NULL)
+		logit(LOG_ERR, "%s", strerror(saved_errno));
+	else {
+		va_start(ap, emsg);
+
+		if (asprintf(&nfmt, "%s: %s", emsg,
+		    strerror(saved_errno)) == -1) {
+			/* we tried it... */
+			vlog(LOG_ERR, emsg, ap);
+			logit(LOG_ERR, "%s", strerror(saved_errno));
+		} else {
+			vlog(LOG_ERR, nfmt, ap);
+			free(nfmt);
+		}
+		va_end(ap);
+	}
+
+	errno = saved_errno;
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+	va_list	 ap;
+
+	va_start(ap, emsg);
+	vlog(LOG_ERR, emsg, ap);
+	va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+	va_list	 ap;
+
+	va_start(ap, emsg);
+	vlog(LOG_INFO, emsg, ap);
+	va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+	va_list	 ap;
+
+	if (verbose) {
+		va_start(ap, emsg);
+		vlog(LOG_DEBUG, emsg, ap);
+		va_end(ap);
+	}
+}
+
+static void
+vfatalc(int code, const char *emsg, va_list ap)
+{
+	static char	s[BUFSIZ];
+	const char	*sep;
+
+	if (emsg != NULL) {
+		(void)vsnprintf(s, sizeof(s), emsg, ap);
+		sep = ": ";
+	} else {
+		s[0] = '\0';
+		sep = "";
+	}
+	if (code)
+		logit(LOG_CRIT, "fatal in %s: %s%s%s",
+		    log_procname, s, sep, strerror(code));
+	else
+		logit(LOG_CRIT, "fatal in %s%s%s", log_procname, sep, s);
+}
+
+void
+fatal(const char *emsg, ...)
+{
+	va_list	ap;
+
+	va_start(ap, emsg);
+	vfatalc(errno, emsg, ap);
+	va_end(ap);
+	exit(1);
+}
+
+void
+fatalx(const char *emsg, ...)
+{
+	va_list	ap;
+
+	va_start(ap, emsg);
+	vfatalc(0, emsg, ap);
+	va_end(ap);
+	exit(1);
+}
blob - /dev/null
blob + 0fa046fc3afbe8c893d40f492481a4d3055dd2bc (mode 644)
--- /dev/null
+++ log.h
@@ -0,0 +1,45 @@
+/*	$OpenBSD: log.h,v 1.2 2021/12/13 18:28:40 deraadt Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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.
+ */
+
+#ifndef LOG_H
+#define LOG_H
+
+#include <stdarg.h>
+
+void	log_init(int, int);
+void	log_procinit(const char *);
+void	log_setverbose(int);
+int	log_getverbose(void);
+void	log_warn(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	log_warnx(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	log_info(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	log_debug(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	logit(int, const char *, ...)
+	    __attribute__((__format__ (printf, 2, 3)));
+void	vlog(int, const char *, va_list)
+	    __attribute__((__format__ (printf, 2, 0)));
+__dead void fatal(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+__dead void fatalx(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+
+#endif /* LOG_H */
blob - /dev/null
blob + 4ac86591014a4994c6134ee8be8fa3a93ef90893 (mode 644)
--- /dev/null
+++ parse.y
@@ -0,0 +1,782 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@omarpolo.com>
+ * Copyright (c) 2007-2016 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 2001 Markus Friedl.  All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
+ *
+ * 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 <sys/types.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/uio.h>
+
+#include <err.h>
+#include <event.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <imsg.h>
+
+#include "log.h"
+#include "proc.h"
+
+#include "galileo.h"
+
+TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
+static struct file {
+	TAILQ_ENTRY(file)	 entry;
+	FILE			*stream;
+	char			*name;
+	size_t			 ungetpos;
+	size_t			 ungetsize;
+	u_char			*ungetbuf;
+	int			 eof_reached;
+	int			 lineno;
+	int			 errors;
+} *file, *topfile;
+struct file	*pushfile(const char *, int);
+int		 popfile(void);
+int		 yyparse(void);
+int		 yylex(void);
+int		 yyerror(const char *, ...)
+    __attribute__((__format__ (printf, 1, 2)))
+    __attribute__((__nonnull__ (1)));
+int		 kw_cmp(const void *, const void *);
+int		 lookup(char *);
+int		 igetc(void);
+int		 lgetc(int);
+void		 lungetc(int);
+int		 findeol(void);
+
+TAILQ_HEAD(symhead, sym)	 symhead = TAILQ_HEAD_INITIALIZER(symhead);
+struct sym {
+	TAILQ_ENTRY(sym)	 entry;
+	int			 used;
+	int			 persist;
+	char			*nam;
+	char			*val;
+};
+int		 symset(const char *, const char *, int);
+char		*symget(const char *);
+
+int		 getservice(const char *);
+
+static struct galileo	*conf = NULL;
+static struct server	*srv = NULL;
+static int		 errors;
+
+typedef struct {
+	union {
+		int64_t		 number;
+		char		*string;
+	} v;
+	int lineno;
+} YYSTYPE;
+
+%}
+
+%token	INCLUDE ERROR
+%token	CHROOT HOSTNAME PORT PREFORK PROXY SERVER SOURCE STYLESHEET
+%type	<v.number>	NUMBER
+%type	<v.number>	port
+%type	<v.string>	STRING
+%type	<v.string>	string
+
+%%
+
+grammar		: /* empty */
+		| grammar include '\n'
+		| grammar '\n'
+		| grammar varset '\n'
+		| grammar main '\n'
+		| grammar server '\n'
+		| grammar error '\n'		{ file->errors++; }
+		;
+
+include		: INCLUDE string {
+			struct file *nfile;
+
+			if ((nfile = pushfile($2, 0)) == NULL) {
+				yyerror("failed to include file %s", $2);
+				free($2);
+				YYERROR;
+			}
+			free($2);
+
+			file = nfile;
+			lungetc('\n');
+		}
+		;
+
+varset		: STRING '=' STRING {
+			char *s = $1;
+			while (*s++) {
+				if (isspace((unsigned char)*s)) {
+					yyerror("macro name cannot contain "
+					    "whitespace");
+					free($1);
+					free($3);
+					YYERROR;
+				}
+			}
+			if (symset($1, $3, 0) == -1)
+				fatalx("cannot store variable");
+			free($1);
+			free($3);
+		}
+		;
+
+main		: PREFORK NUMBER {
+			if ($2 <= 0 || $2 > PROC_MAX_INSTANCES) {
+				yyerror("invalid number of preforked "
+				    "servers: %lld", $2);
+				YYERROR;
+			}
+			conf->sc_prefork = $2;
+		}
+		| CHROOT STRING {
+			size_t n;
+
+			n = strlcpy(conf->sc_chroot, $2,
+			    sizeof(conf->sc_chroot));
+			if (n >= sizeof(conf->sc_chroot))
+				yyerror("chroot path too long!");
+			free($2);
+		}
+		;
+
+server		: SERVER STRING {
+			struct server	*s;
+			size_t		 n;
+
+			if ((s = calloc(1, sizeof(*s))) == NULL)
+				fatal("calloc");
+
+			n = strlcpy(s->srv_conf.host, $2,
+			    sizeof(s->srv_conf.host));
+			if (n >= sizeof(s->srv_conf.host)) {
+				yyerror("server name too long");
+				free($2);
+				free(s);
+				YYERROR;
+			}
+			free($2);
+
+			srv = s;
+			TAILQ_INSERT_TAIL(&conf->sc_servers, srv, srv_entry);
+		} '{' optnl serveropts_l '}' {
+			/* check if duplicate */
+			/* eventually load the tls certs */
+
+			srv = NULL;
+		}
+		;
+
+serveropts_l	: serveropts_l serveroptsl nl
+		| serveroptsl optnl
+		;
+
+serveroptsl	: PROXY STRING	{
+			/* ... */
+		}
+		| PROXY '{' optnl proxyopts_l '}'
+		| STYLESHEET string { /* ... */ }
+		;
+
+proxyopts_l	: proxyopts_l proxyoptsl nl
+		| proxyoptsl optnl
+		;
+
+proxyoptsl	: SOURCE STRING PORT port {
+			size_t n;
+
+			n = strlcpy(srv->srv_conf.proxy_addr, $2,
+			    sizeof(srv->srv_conf.proxy_addr));
+			if (n >= sizeof(srv->srv_conf.proxy_addr))
+				yyerror("proxy source too long!");
+			srv->srv_conf.proxy_port = $4;
+
+			free($2);
+		}
+		| HOSTNAME STRING {
+			size_t n;
+
+			n = strlcpy(srv->srv_conf.proxy_name, $2,
+			    sizeof(srv->srv_conf.proxy_name));
+			if (n >= sizeof(srv->srv_conf.proxy_name))
+				yyerror("proxy hostname too long!");
+			free($2);
+		}
+		;
+
+port		: NUMBER {
+			if ($1 <= 0 || $1 > (int)USHRT_MAX) {
+				yyerror("invalid port: %lld", $1);
+				YYERROR;
+			}
+			$$ = $1;
+		}
+		| STRING {
+			int val;
+
+			if ((val = getservice($1)) == -1) {
+				yyerror("invalid port: %s", $1);
+				free($1);
+				YYERROR;
+			}
+			free($1);
+			$$ = val;
+		}
+		;
+
+string		: STRING string {
+			if (asprintf(&$$, "%s%s", $1, $2) == -1)
+				fatal("asprintf string");
+			free($1);
+			free($2);
+		}
+		| STRING
+		;
+
+optnl		: '\n' optnl
+		|
+		;
+
+nl		: '\n' optnl
+		;
+
+%%
+
+struct keywords {
+	const char	*k_name;
+	int		 k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+	va_list	 ap;
+	char	*msg;
+
+	file->errors++;
+	va_start(ap, fmt);
+	if (vasprintf(&msg, fmt, ap) == -1)
+		fatal("yyerror vasprintf");
+	va_end(ap);
+	log_warnx("%s:%d: %s", file->name, yylval.lineno, msg);
+	free(msg);
+	return (0);
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+	return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+lookup(char *s)
+{
+	/* this has to be sorted always */
+	static const struct keywords keywords[] = {
+		{ "chroot",	CHROOT },
+		{ "hostname",	HOSTNAME },
+		{ "include",	INCLUDE },
+		{ "port",	PORT },
+		{ "prefork",	PREFORK },
+		{ "proxy",	PROXY },
+		{ "server",	SERVER },
+		{ "source",	SOURCE },
+		{ "stylesheet",	STYLESHEET},
+	};
+	const struct keywords	*p;
+
+	p = bsearch(s, keywords, nitems(keywords), sizeof(keywords[0]),
+	    kw_cmp);
+
+	if (p)
+		return (p->k_val);
+	else
+		return (STRING);
+}
+
+#define START_EXPAND	1
+#define DONE_EXPAND	2
+
+static int	expanding;
+
+int
+igetc(void)
+{
+	int	c;
+
+	while (1) {
+		if (file->ungetpos > 0)
+			c = file->ungetbuf[--file->ungetpos];
+		else
+			c = getc(file->stream);
+
+		if (c == START_EXPAND)
+			expanding = 1;
+		else if (c == DONE_EXPAND)
+			expanding = 0;
+		else
+			break;
+	}
+	return (c);
+}
+
+int
+lgetc(int quotec)
+{
+	int		c, next;
+
+	if (quotec) {
+		if ((c = igetc()) == EOF) {
+			yyerror("reached end of file while parsing "
+			    "quoted string");
+			if (file == topfile || popfile() == EOF)
+				return (EOF);
+			return (quotec);
+		}
+		return (c);
+	}
+
+	while ((c = igetc()) == '\\') {
+		next = igetc();
+		if (next != '\n') {
+			c = next;
+			break;
+		}
+		yylval.lineno = file->lineno;
+		file->lineno++;
+	}
+	if (c == '\t' || c == ' ') {
+		/* Compress blanks to a single space. */
+		do {
+			c = getc(file->stream);
+		} while (c == '\t' || c == ' ');
+		ungetc(c, file->stream);
+		c = ' ';
+	}
+
+	if (c == EOF) {
+		/*
+		 * Fake EOL when hit EOF for the first time. This gets line
+		 * count right if last line in included file is syntactically
+		 * invalid and has no newline.
+		 */
+		if (file->eof_reached == 0) {
+			file->eof_reached = 1;
+			return ('\n');
+		}
+		while (c == EOF) {
+			if (file == topfile || popfile() == EOF)
+				return (EOF);
+			c = igetc();
+		}
+	}
+	return (c);
+}
+
+void
+lungetc(int c)
+{
+	if (c == EOF)
+		return;
+
+	if (file->ungetpos >= file->ungetsize) {
+		void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
+		if (p == NULL)
+			err(1, "%s", __func__);
+		file->ungetbuf = p;
+		file->ungetsize *= 2;
+	}
+	file->ungetbuf[file->ungetpos++] = c;
+}
+
+int
+findeol(void)
+{
+	int	c;
+
+	/* skip to either EOF or the first real EOL */
+	while (1) {
+		c = lgetc(0);
+		if (c == '\n') {
+			file->lineno++;
+			break;
+		}
+		if (c == EOF)
+			break;
+	}
+	return (ERROR);
+}
+
+int
+yylex(void)
+{
+	char	 buf[8096];
+	char	*p, *val;
+	int	 quotec, next, c;
+	int	 token;
+
+top:
+	p = buf;
+	while ((c = lgetc(0)) == ' ' || c == '\t')
+		; /* nothing */
+
+	yylval.lineno = file->lineno;
+	if (c == '#')
+		while ((c = lgetc(0)) != '\n' && c != EOF)
+			; /* nothing */
+	if (c == '$' && !expanding) {
+		while (1) {
+			if ((c = lgetc(0)) == EOF)
+				return (0);
+
+			if (p + 1 >= buf + sizeof(buf) - 1) {
+				yyerror("string too long");
+				return (findeol());
+			}
+			if (isalnum(c) || c == '_') {
+				*p++ = c;
+				continue;
+			}
+			*p = '\0';
+			lungetc(c);
+			break;
+		}
+		val = symget(buf);
+		if (val == NULL) {
+			yyerror("macro '%s' not defined", buf);
+			return (findeol());
+		}
+		p = val + strlen(val) - 1;
+		lungetc(DONE_EXPAND);
+		while (p >= val) {
+			lungetc((unsigned char)*p);
+			p--;
+		}
+		lungetc(START_EXPAND);
+		goto top;
+	}
+
+	switch (c) {
+	case '\'':
+	case '"':
+		quotec = c;
+		while (1) {
+			if ((c = lgetc(quotec)) == EOF)
+				return (0);
+			if (c == '\n') {
+				file->lineno++;
+				continue;
+			} else if (c == '\\') {
+				if ((next = lgetc(quotec)) == EOF)
+					return (0);
+				if (next == quotec || next == ' ' ||
+				    next == '\t')
+					c = next;
+				else if (next == '\n') {
+					file->lineno++;
+					continue;
+				} else
+					lungetc(next);
+			} else if (c == quotec) {
+				*p = '\0';
+				break;
+			} else if (c == '\0') {
+				yyerror("syntax error");
+				return (findeol());
+			}
+			if (p + 1 >= buf + sizeof(buf) - 1) {
+				yyerror("string too long");
+				return (findeol());
+			}
+			*p++ = c;
+		}
+		yylval.v.string = strdup(buf);
+		if (yylval.v.string == NULL)
+			fatal("yylex: strdup");
+		return (STRING);
+	}
+
+#define allowed_to_end_number(x) \
+	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
+
+	if (c == '-' || isdigit(c)) {
+		do {
+			*p++ = c;
+			if ((size_t)(p-buf) >= sizeof(buf)) {
+				yyerror("string too long");
+				return (findeol());
+			}
+		} while ((c = lgetc(0)) != EOF && isdigit(c));
+		lungetc(c);
+		if (p == buf + 1 && buf[0] == '-')
+			goto nodigits;
+		if (c == EOF || allowed_to_end_number(c)) {
+			const char *errstr = NULL;
+
+			*p = '\0';
+			yylval.v.number = strtonum(buf, LLONG_MIN,
+			    LLONG_MAX, &errstr);
+			if (errstr) {
+				yyerror("\"%s\" invalid number: %s",
+				    buf, errstr);
+				return (findeol());
+			}
+			return (NUMBER);
+		} else {
+nodigits:
+			while (p > buf + 1)
+				lungetc((unsigned char)*--p);
+			c = (unsigned char)*--p;
+			if (c == '-')
+				return (c);
+		}
+	}
+
+#define allowed_in_string(x) \
+	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+	x != '{' && x != '}' && \
+	x != '!' && x != '=' && x != '#' && \
+	x != ','))
+
+	if (isalnum(c) || c == ':' || c == '_' || c == '/') {
+		do {
+			*p++ = c;
+			if ((size_t)(p-buf) >= sizeof(buf)) {
+				yyerror("string too long");
+				return (findeol());
+			}
+		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
+		lungetc(c);
+		*p = '\0';
+		if ((token = lookup(buf)) == STRING)
+			if ((yylval.v.string = strdup(buf)) == NULL)
+				fatal("yylex: strdup");
+		return (token);
+	}
+	if (c == '\n') {
+		yylval.lineno = file->lineno;
+		file->lineno++;
+	}
+	if (c == EOF)
+		return (0);
+	return (c);
+}
+
+struct file *
+pushfile(const char *name, int secret)
+{
+	struct file	*nfile;
+
+	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
+		log_warn("%s", __func__);
+		return (NULL);
+	}
+	if ((nfile->name = strdup(name)) == NULL) {
+		log_warn("%s", __func__);
+		free(nfile);
+		return (NULL);
+	}
+	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+		free(nfile->name);
+		free(nfile);
+		return (NULL);
+	}
+	nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
+	nfile->ungetsize = 16;
+	nfile->ungetbuf = malloc(nfile->ungetsize);
+	if (nfile->ungetbuf == NULL) {
+		log_warn("%s", __func__);
+		fclose(nfile->stream);
+		free(nfile->name);
+		free(nfile);
+		return (NULL);
+	}
+	TAILQ_INSERT_TAIL(&files, nfile, entry);
+	return (nfile);
+}
+
+int
+popfile(void)
+{
+	struct file	*prev;
+
+	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
+		prev->errors += file->errors;
+
+	TAILQ_REMOVE(&files, file, entry);
+	fclose(file->stream);
+	free(file->name);
+	free(file->ungetbuf);
+	free(file);
+	file = prev;
+	return (file ? 0 : EOF);
+}
+
+int
+parse_config(const char *filename, struct galileo *env)
+{
+	struct sym	*sym, *next;
+	size_t		 n;
+
+	conf = env;
+
+	n = strlcpy(conf->sc_conffile, filename, sizeof(conf->sc_conffile));
+	if (n >= sizeof(conf->sc_conffile)) {
+		log_warn("path too long: %s", filename);
+		return (-1);
+	}
+
+	if ((file = pushfile(filename, 0)) == NULL) {
+		log_warn("failed to open %s", filename);
+		return (-1);
+	}
+	topfile = file;
+	setservent(1);
+
+	yyparse();
+	errors = file->errors;
+	popfile();
+
+	endservent();
+
+	/* Free macros and check which have not been used. */
+	TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
+		if (!sym->used)
+			fprintf(stderr, "warning: macro `%s' not used\n",
+			    sym->nam);
+		if (!sym->persist) {
+			free(sym->nam);
+			free(sym->val);
+			TAILQ_REMOVE(&symhead, sym, entry);
+			free(sym);
+		}
+	}
+
+	if (errors)
+		return (-1);
+
+	return (0);
+}
+
+int
+symset(const char *nam, const char *val, int persist)
+{
+	struct sym	*sym;
+
+	TAILQ_FOREACH(sym, &symhead, entry) {
+		if (strcmp(nam, sym->nam) == 0)
+			break;
+	}
+
+	if (sym != NULL) {
+		if (sym->persist == 1)
+			return (0);
+		else {
+			free(sym->nam);
+			free(sym->val);
+			TAILQ_REMOVE(&symhead, sym, entry);
+			free(sym);
+		}
+	}
+	if ((sym = calloc(1, sizeof(*sym))) == NULL)
+		return (-1);
+
+	sym->nam = strdup(nam);
+	if (sym->nam == NULL) {
+		free(sym);
+		return (-1);
+	}
+	sym->val = strdup(val);
+	if (sym->val == NULL) {
+		free(sym->nam);
+		free(sym);
+		return (-1);
+	}
+	sym->used = 0;
+	sym->persist = persist;
+	TAILQ_INSERT_TAIL(&symhead, sym, entry);
+	return (0);
+}
+
+int
+cmdline_symset(char *s)
+{
+	char	*sym, *val;
+	int	ret;
+
+	if ((val = strrchr(s, '=')) == NULL)
+		return (-1);
+	sym = strndup(s, val - s);
+	if (sym == NULL)
+		fatal("%s: strndup", __func__);
+	ret = symset(sym, val + 1, 1);
+	free(sym);
+
+	return (ret);
+}
+
+char *
+symget(const char *nam)
+{
+	struct sym	*sym;
+
+	TAILQ_FOREACH(sym, &symhead, entry) {
+		if (strcmp(nam, sym->nam) == 0) {
+			sym->used = 1;
+			return (sym->val);
+		}
+	}
+	return (NULL);
+}
+
+int
+getservice(const char *n)
+{
+	struct servent	*s;
+	const char	*errstr;
+	long long	 llval;
+
+	llval = strtonum(n, 0, UINT16_MAX, &errstr);
+	if (errstr) {
+		s = getservbyname(n, "tcp");
+		if (s == NULL)
+			s = getservbyname(n, "udp");
+		if (s == NULL)
+			return (-1);
+		return (ntohs(s->s_port));
+	}
+
+	return ((unsigned short)llval);
+}
blob - /dev/null
blob + 3fc07eaa6783b32515760bb9b6f75e6b35b3d755 (mode 644)
--- /dev/null
+++ proc.c
@@ -0,0 +1,832 @@
+/*	$OpenBSD: proc.c,v 1.41 2021/12/04 06:52:58 florian Exp $	*/
+
+/*
+ * Copyright (c) 2010 - 2016 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
+ *
+ * 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 <sys/types.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <paths.h>
+#include <pwd.h>
+#include <event.h>
+#include <imsg.h>
+
+#include "log.h"
+#include "proc.h"
+
+#include "galileo.h"
+
+void	 proc_exec(struct privsep *, struct privsep_proc *, unsigned int, int,
+	    int, char **);
+void	 proc_setup(struct privsep *, struct privsep_proc *, unsigned int);
+void	 proc_open(struct privsep *, int, int);
+void	 proc_accept(struct privsep *, int, enum privsep_procid,
+	    unsigned int);
+void	 proc_close(struct privsep *);
+int	 proc_ispeer(struct privsep_proc *, unsigned int, enum privsep_procid);
+void	 proc_shutdown(struct privsep_proc *);
+void	 proc_sig_handler(int, short, void *);
+void	 proc_range(struct privsep *, enum privsep_procid, int *, int *);
+int	 proc_dispatch_null(int, struct privsep_proc *, struct imsg *);
+
+int
+proc_ispeer(struct privsep_proc *procs, unsigned int nproc,
+    enum privsep_procid type)
+{
+	unsigned int	i;
+
+	for (i = 0; i < nproc; i++)
+		if (procs[i].p_id == type)
+			return (1);
+	return (0);
+}
+
+enum privsep_procid
+proc_getid(struct privsep_proc *procs, unsigned int nproc,
+    const char *proc_name)
+{
+	struct privsep_proc	*p;
+	unsigned int		 proc;
+
+	for (proc = 0; proc < nproc; proc++) {
+		p = &procs[proc];
+		if (strcmp(p->p_title, proc_name))
+			continue;
+
+		return (p->p_id);
+	}
+
+	return (PROC_MAX);
+}
+
+void
+proc_exec(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc,
+    int debug, int argc, char **argv)
+{
+	unsigned int		 proc, nargc, i, proc_i;
+	char			**nargv;
+	struct privsep_proc	*p;
+	char			 num[32];
+	int			 fd;
+
+	/* Prepare the new process argv. */
+	nargv = calloc(argc + 5, sizeof(char *));
+	if (nargv == NULL)
+		fatal("%s: calloc", __func__);
+
+	/* Copy call argument first. */
+	nargc = 0;
+	nargv[nargc++] = argv[0];
+
+	/* Set process name argument and save the position. */
+	nargv[nargc++] = "-P";
+	proc_i = nargc;
+	nargc++;
+
+	/* Point process instance arg to stack and copy the original args. */
+	nargv[nargc++] = "-I";
+	nargv[nargc++] = num;
+	for (i = 1; i < (unsigned int) argc; i++)
+		nargv[nargc++] = argv[i];
+
+	nargv[nargc] = NULL;
+
+	for (proc = 0; proc < nproc; proc++) {
+		p = &procs[proc];
+
+		/* Update args with process title. */
+		nargv[proc_i] = (char *)(uintptr_t)p->p_title;
+
+		/* Fire children processes. */
+		for (i = 0; i < ps->ps_instances[p->p_id]; i++) {
+			/* Update the process instance number. */
+			snprintf(num, sizeof(num), "%u", i);
+
+			fd = ps->ps_pipes[p->p_id][i].pp_pipes[PROC_PARENT][0];
+			ps->ps_pipes[p->p_id][i].pp_pipes[PROC_PARENT][0] = -1;
+
+			switch (fork()) {
+			case -1:
+				fatal("%s: fork", __func__);
+				break;
+			case 0:
+				/* First create a new session */
+				if (setsid() == -1)
+					fatal("setsid");
+
+				/* Prepare parent socket. */
+				if (fd != PROC_PARENT_SOCK_FILENO) {
+					if (dup2(fd, PROC_PARENT_SOCK_FILENO)
+					    == -1)
+						fatal("dup2");
+				} else if (fcntl(fd, F_SETFD, 0) == -1)
+					fatal("fcntl");
+
+				/* Daemons detach from terminal. */
+				if (!debug && (fd =
+				    open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
+					(void)dup2(fd, STDIN_FILENO);
+					(void)dup2(fd, STDOUT_FILENO);
+					(void)dup2(fd, STDERR_FILENO);
+					if (fd > 2)
+						(void)close(fd);
+				}
+
+				execvp(argv[0], nargv);
+				fatal("%s: execvp", __func__);
+				break;
+			default:
+				/* Close child end. */
+				close(fd);
+				break;
+			}
+		}
+	}
+	free(nargv);
+}
+
+void
+proc_connect(struct privsep *ps)
+{
+	struct imsgev		*iev;
+	unsigned int		 src, dst, inst;
+
+	/* Don't distribute any sockets if we are not really going to run. */
+	if (ps->ps_noaction)
+		return;
+
+	for (dst = 0; dst < PROC_MAX; dst++) {
+		/* We don't communicate with ourselves. */
+		if (dst == PROC_PARENT)
+			continue;
+
+		for (inst = 0; inst < ps->ps_instances[dst]; inst++) {
+			iev = &ps->ps_ievs[dst][inst];
+			imsg_init(&iev->ibuf, ps->ps_pp->pp_pipes[dst][inst]);
+			event_set(&iev->ev, iev->ibuf.fd, iev->events,
+			    iev->handler, iev->data);
+			event_add(&iev->ev, NULL);
+		}
+	}
+
+	/* Distribute the socketpair()s for everyone. */
+	for (src = 0; src < PROC_MAX; src++)
+		for (dst = src; dst < PROC_MAX; dst++) {
+			/* Parent already distributed its fds. */
+			if (src == PROC_PARENT || dst == PROC_PARENT)
+				continue;
+
+			proc_open(ps, src, dst);
+		}
+}
+
+void
+proc_init(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc,
+    int debug, int argc, char **argv, enum privsep_procid proc_id)
+{
+	struct privsep_proc	*p = NULL;
+	struct privsep_pipes	*pa, *pb;
+	unsigned int		 proc;
+	unsigned int		 dst;
+	int			 fds[2];
+
+	/* Don't initiate anything if we are not really going to run. */
+	if (ps->ps_noaction)
+		return;
+
+	if (proc_id == PROC_PARENT) {
+		privsep_process = PROC_PARENT;
+		proc_setup(ps, procs, nproc);
+
+		/*
+		 * Create the children sockets so we can use them
+		 * to distribute the rest of the socketpair()s using
+		 * proc_connect() later.
+		 */
+		for (dst = 0; dst < PROC_MAX; dst++) {
+			/* Don't create socket for ourselves. */
+			if (dst == PROC_PARENT)
+				continue;
+
+			for (proc = 0; proc < ps->ps_instances[dst]; proc++) {
+				pa = &ps->ps_pipes[PROC_PARENT][0];
+				pb = &ps->ps_pipes[dst][proc];
+				if (socketpair(AF_UNIX,
+				    SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
+				    PF_UNSPEC, fds) == -1)
+					fatal("%s: socketpair", __func__);
+
+				pa->pp_pipes[dst][proc] = fds[0];
+				pb->pp_pipes[PROC_PARENT][0] = fds[1];
+			}
+		}
+
+		/* Engage! */
+		proc_exec(ps, procs, nproc, debug, argc, argv);
+		return;
+	}
+
+	/* Initialize a child */
+	for (proc = 0; proc < nproc; proc++) {
+		if (procs[proc].p_id != proc_id)
+			continue;
+		p = &procs[proc];
+		break;
+	}
+	if (p == NULL || p->p_init == NULL)
+		fatalx("%s: process %d missing process initialization",
+		    __func__, proc_id);
+
+	p->p_init(ps, p);
+
+	fatalx("failed to initiate child process");
+}
+
+void
+proc_accept(struct privsep *ps, int fd, enum privsep_procid dst,
+    unsigned int n)
+{
+	struct privsep_pipes	*pp = ps->ps_pp;
+	struct imsgev		*iev;
+
+	if (ps->ps_ievs[dst] == NULL) {
+#if DEBUG > 1
+		log_debug("%s: %s src %d %d to dst %d %d not connected",
+		    __func__, ps->ps_title[privsep_process],
+		    privsep_process, ps->ps_instance + 1,
+		    dst, n + 1);
+#endif
+		close(fd);
+		return;
+	}
+
+	if (pp->pp_pipes[dst][n] != -1) {
+		log_warnx("%s: duplicated descriptor", __func__);
+		close(fd);
+		return;
+	} else
+		pp->pp_pipes[dst][n] = fd;
+
+	iev = &ps->ps_ievs[dst][n];
+	imsg_init(&iev->ibuf, fd);
+	event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data);
+	event_add(&iev->ev, NULL);
+}
+
+void
+proc_setup(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc)
+{
+	unsigned int		 i, j, src, dst, id;
+	struct privsep_pipes	*pp;
+
+	/* Initialize parent title, ps_instances and procs. */
+	ps->ps_title[PROC_PARENT] = "parent";
+
+	for (src = 0; src < PROC_MAX; src++)
+		/* Default to 1 process instance */
+		if (ps->ps_instances[src] < 1)
+			ps->ps_instances[src] = 1;
+
+	for (src = 0; src < nproc; src++) {
+		procs[src].p_ps = ps;
+		if (procs[src].p_cb == NULL)
+			procs[src].p_cb = proc_dispatch_null;
+
+		id = procs[src].p_id;
+		ps->ps_title[id] = procs[src].p_title;
+		if ((ps->ps_ievs[id] = calloc(ps->ps_instances[id],
+		    sizeof(struct imsgev))) == NULL)
+			fatal("%s: calloc", __func__);
+
+		/* With this set up, we are ready to call imsg_init(). */
+		for (i = 0; i < ps->ps_instances[id]; i++) {
+			ps->ps_ievs[id][i].handler = proc_dispatch;
+			ps->ps_ievs[id][i].events = EV_READ;
+			ps->ps_ievs[id][i].proc = &procs[src];
+			ps->ps_ievs[id][i].data = &ps->ps_ievs[id][i];
+		}
+	}
+
+	/*
+	 * Allocate pipes for all process instances (incl. parent)
+	 *
+	 * - ps->ps_pipes: N:M mapping
+	 * N source processes connected to M destination processes:
+	 * [src][instances][dst][instances], for example
+	 * [PROC_RELAY][3][PROC_CA][3]
+	 *
+	 * - ps->ps_pp: per-process 1:M part of ps->ps_pipes
+	 * Each process instance has a destination array of socketpair fds:
+	 * [dst][instances], for example
+	 * [PROC_PARENT][0]
+	 */
+	for (src = 0; src < PROC_MAX; src++) {
+		/* Allocate destination array for each process */
+		if ((ps->ps_pipes[src] = calloc(ps->ps_instances[src],
+		    sizeof(struct privsep_pipes))) == NULL)
+			fatal("%s: calloc", __func__);
+
+		for (i = 0; i < ps->ps_instances[src]; i++) {
+			pp = &ps->ps_pipes[src][i];
+
+			for (dst = 0; dst < PROC_MAX; dst++) {
+				/* Allocate maximum fd integers */
+				if ((pp->pp_pipes[dst] =
+				    calloc(ps->ps_instances[dst],
+				    sizeof(int))) == NULL)
+					fatal("%s: calloc", __func__);
+
+				/* Mark fd as unused */
+				for (j = 0; j < ps->ps_instances[dst]; j++)
+					pp->pp_pipes[dst][j] = -1;
+			}
+		}
+	}
+
+	ps->ps_pp = &ps->ps_pipes[privsep_process][ps->ps_instance];
+}
+
+void
+proc_kill(struct privsep *ps)
+{
+	char		*cause;
+	pid_t		 pid;
+	int		 len, status;
+
+	if (privsep_process != PROC_PARENT)
+		return;
+
+	proc_close(ps);
+
+	do {
+		pid = waitpid(WAIT_ANY, &status, 0);
+		if (pid <= 0)
+			continue;
+
+		if (WIFSIGNALED(status)) {
+			len = asprintf(&cause, "terminated; signal %d",
+			    WTERMSIG(status));
+		} else if (WIFEXITED(status)) {
+			if (WEXITSTATUS(status) != 0)
+				len = asprintf(&cause, "exited abnormally");
+			else
+				len = 0;
+		} else
+			len = -1;
+
+		if (len == 0) {
+			/* child exited OK, don't print a warning message */
+		} else if (len != -1) {
+			log_warnx("lost child: pid %u %s", pid, cause);
+			free(cause);
+		} else
+			log_warnx("lost child: pid %u", pid);
+	} while (pid != -1 || errno == EINTR);
+}
+
+void
+proc_open(struct privsep *ps, int src, int dst)
+{
+	struct privsep_pipes	*pa, *pb;
+	struct privsep_fd	 pf;
+	int			 fds[2];
+	unsigned int		 i, j;
+
+	/* Exchange pipes between process. */
+	for (i = 0; i < ps->ps_instances[src]; i++) {
+		for (j = 0; j < ps->ps_instances[dst]; j++) {
+			/* Don't create sockets for ourself. */
+			if (src == dst && i == j)
+				continue;
+
+			/* Proxies don't talk to each other. */
+			if (src == PROC_PROXY && dst == PROC_PROXY)
+				continue;
+
+			pa = &ps->ps_pipes[src][i];
+			pb = &ps->ps_pipes[dst][j];
+			if (socketpair(AF_UNIX,
+			    SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
+			    PF_UNSPEC, fds) == -1)
+				fatal("%s: socketpair", __func__);
+
+			pa->pp_pipes[dst][j] = fds[0];
+			pb->pp_pipes[src][i] = fds[1];
+
+			pf.pf_procid = src;
+			pf.pf_instance = i;
+			if (proc_compose_imsg(ps, dst, j, IMSG_CTL_PROCFD,
+			    -1, pb->pp_pipes[src][i], &pf, sizeof(pf)) == -1)
+				fatal("%s: proc_compose_imsg", __func__);
+
+			pf.pf_procid = dst;
+			pf.pf_instance = j;
+			if (proc_compose_imsg(ps, src, i, IMSG_CTL_PROCFD,
+			    -1, pa->pp_pipes[dst][j], &pf, sizeof(pf)) == -1)
+				fatal("%s: proc_compose_imsg", __func__);
+
+			/*
+			 * We have to flush to send the descriptors and close
+			 * them to avoid the fd ramp on startup.
+			 */
+			if (proc_flush_imsg(ps, src, i) == -1 ||
+			    proc_flush_imsg(ps, dst, j) == -1)
+				fatal("%s: imsg_flush", __func__);
+		}
+	}
+}
+
+void
+proc_close(struct privsep *ps)
+{
+	unsigned int		 dst, n;
+	struct privsep_pipes	*pp;
+
+	if (ps == NULL)
+		return;
+
+	pp = ps->ps_pp;
+
+	for (dst = 0; dst < PROC_MAX; dst++) {
+		if (ps->ps_ievs[dst] == NULL)
+			continue;
+
+		for (n = 0; n < ps->ps_instances[dst]; n++) {
+			if (pp->pp_pipes[dst][n] == -1)
+				continue;
+
+			/* Cancel the fd, close and invalidate the fd */
+			event_del(&(ps->ps_ievs[dst][n].ev));
+			imsg_clear(&(ps->ps_ievs[dst][n].ibuf));
+			close(pp->pp_pipes[dst][n]);
+			pp->pp_pipes[dst][n] = -1;
+		}
+		free(ps->ps_ievs[dst]);
+	}
+}
+
+void
+proc_shutdown(struct privsep_proc *p)
+{
+	struct privsep	*ps = p->p_ps;
+
+	if (p->p_shutdown != NULL)
+		(*p->p_shutdown)();
+
+	proc_close(ps);
+
+	log_info("%s exiting, pid %d", p->p_title, getpid());
+
+	exit(0);
+}
+
+void
+proc_sig_handler(int sig, short event, void *arg)
+{
+	struct privsep_proc	*p = arg;
+
+	switch (sig) {
+	case SIGINT:
+	case SIGTERM:
+		proc_shutdown(p);
+		break;
+	case SIGCHLD:
+	case SIGHUP:
+		/* ignore */
+		break;
+	default:
+		fatalx("%s: unexpected signal", __func__);
+		/* NOTREACHED */
+	}
+}
+
+void
+proc_run(struct privsep *ps, struct privsep_proc *p,
+    struct privsep_proc *procs, unsigned int nproc,
+    void (*run)(struct privsep *, struct privsep_proc *, void *), void *arg)
+{
+	struct passwd		*pw;
+	const char		*root;
+
+	log_procinit(p->p_title);
+
+	/* Set the process group of the current process */
+	setpgid(0, 0);
+
+	/* Use non-standard user */
+	if (p->p_pw != NULL)
+		pw = p->p_pw;
+	else
+		pw = ps->ps_pw;
+
+	/* Change root directory */
+	if (p->p_chroot != NULL)
+		root = p->p_chroot;
+	else
+		root = pw->pw_dir;
+
+	if (chroot(root) == -1)
+		fatal("%s: chroot", __func__);
+	if (chdir("/") == -1)
+		fatal("%s: chdir(\"/\")", __func__);
+
+	privsep_process = p->p_id;
+
+	setproctitle("%s", p->p_title);
+
+	if (setgroups(1, &pw->pw_gid) ||
+	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+		fatal("%s: cannot drop privileges", __func__);
+
+	event_init();
+
+	signal(SIGPIPE, SIG_IGN);
+
+	signal_set(&ps->ps_evsigint, SIGINT, proc_sig_handler, p);
+	signal_set(&ps->ps_evsigterm, SIGTERM, proc_sig_handler, p);
+	signal_set(&ps->ps_evsigchld, SIGCHLD, proc_sig_handler, p);
+	signal_set(&ps->ps_evsighup, SIGHUP, proc_sig_handler, p);
+
+	signal_add(&ps->ps_evsigint, NULL);
+	signal_add(&ps->ps_evsigterm, NULL);
+	signal_add(&ps->ps_evsigchld, NULL);
+	signal_add(&ps->ps_evsighup, NULL);
+
+	proc_setup(ps, procs, nproc);
+	proc_accept(ps, PROC_PARENT_SOCK_FILENO, PROC_PARENT, 0);
+
+	log_debug("%s: %s %d/%d, pid %d", __func__, p->p_title,
+	    ps->ps_instance + 1, ps->ps_instances[p->p_id], getpid());
+
+	if (run != NULL)
+		run(ps, p, arg);
+
+	event_dispatch();
+
+	proc_shutdown(p);
+}
+
+void
+proc_dispatch(int fd, short event, void *arg)
+{
+	struct imsgev		*iev = arg;
+	struct privsep_proc	*p = iev->proc;
+	struct privsep		*ps = p->p_ps;
+	struct imsgbuf		*ibuf;
+	struct imsg		 imsg;
+	ssize_t			 n;
+	const char		*title;
+	struct privsep_fd	 pf;
+
+	title = ps->ps_title[privsep_process];
+	ibuf = &iev->ibuf;
+
+	if (event & EV_READ) {
+		if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+			fatal("%s: imsg_read", __func__);
+		if (n == 0) {
+			/* this pipe is dead, so remove the event handler */
+			event_del(&iev->ev);
+			event_loopexit(NULL);
+			return;
+		}
+	}
+
+	if (event & EV_WRITE) {
+		if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+			fatal("%s: msgbuf_write", __func__);
+		if (n == 0) {
+			/* this pipe is dead, so remove the event handler */
+			event_del(&iev->ev);
+			event_loopexit(NULL);
+			return;
+		}
+	}
+
+	for (;;) {
+		if ((n = imsg_get(ibuf, &imsg)) == -1)
+			fatal("%s: imsg_get", __func__);
+		if (n == 0)
+			break;
+
+#if DEBUG > 1
+		log_debug("%s: %s %d got imsg %d peerid %d from %s %d",
+		    __func__, title, ps->ps_instance + 1,
+		    imsg.hdr.type, imsg.hdr.peerid, p->p_title, imsg.hdr.pid);
+#endif
+
+		/*
+		 * Check the message with the program callback
+		 */
+		if ((p->p_cb)(fd, p, &imsg) == 0) {
+			/* Message was handled by the callback, continue */
+			imsg_free(&imsg);
+			continue;
+		}
+
+		/*
+		 * Generic message handling
+		 */
+		switch (imsg.hdr.type) {
+		case IMSG_CTL_PROCFD:
+			IMSG_SIZE_CHECK(&imsg, &pf);
+			memcpy(&pf, imsg.data, sizeof(pf));
+			proc_accept(ps, imsg.fd, pf.pf_procid,
+			    pf.pf_instance);
+			break;
+		default:
+			fatalx("%s: %s %d got invalid imsg %d peerid %d "
+			    "from %s %d",
+			    __func__, title, ps->ps_instance + 1,
+			    imsg.hdr.type, imsg.hdr.peerid,
+			    p->p_title, imsg.hdr.pid);
+		}
+		imsg_free(&imsg);
+	}
+	imsg_event_add(iev);
+}
+
+int
+proc_dispatch_null(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+	return (-1);
+}
+
+/*
+ * imsg helper functions
+ */
+
+void
+imsg_event_add(struct imsgev *iev)
+{
+	if (iev->handler == NULL) {
+		imsg_flush(&iev->ibuf);
+		return;
+	}
+
+	iev->events = EV_READ;
+	if (iev->ibuf.w.queued)
+		iev->events |= EV_WRITE;
+
+	event_del(&iev->ev);
+	event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data);
+	event_add(&iev->ev, NULL);
+}
+
+int
+imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid,
+    pid_t pid, int fd, void *data, uint16_t datalen)
+{
+	int	ret;
+
+	if ((ret = imsg_compose(&iev->ibuf, type, peerid,
+	    pid, fd, data, datalen)) == -1)
+		return (ret);
+	imsg_event_add(iev);
+	return (ret);
+}
+
+int
+imsg_composev_event(struct imsgev *iev, uint16_t type, uint32_t peerid,
+    pid_t pid, int fd, const struct iovec *iov, int iovcnt)
+{
+	int	ret;
+
+	if ((ret = imsg_composev(&iev->ibuf, type, peerid,
+	    pid, fd, iov, iovcnt)) == -1)
+		return (ret);
+	imsg_event_add(iev);
+	return (ret);
+}
+
+void
+proc_range(struct privsep *ps, enum privsep_procid id, int *n, int *m)
+{
+	if (*n == -1) {
+		/* Use a range of all target instances */
+		*n = 0;
+		*m = ps->ps_instances[id];
+	} else {
+		/* Use only a single slot of the specified peer process */
+		*m = *n + 1;
+	}
+}
+
+int
+proc_compose_imsg(struct privsep *ps, enum privsep_procid id, int n,
+    uint16_t type, uint32_t peerid, int fd, void *data, uint16_t datalen)
+{
+	int	 m;
+
+	proc_range(ps, id, &n, &m);
+	for (; n < m; n++) {
+		if (imsg_compose_event(&ps->ps_ievs[id][n],
+		    type, peerid, ps->ps_instance + 1, fd, data, datalen) == -1)
+			return (-1);
+	}
+
+	return (0);
+}
+
+int
+proc_compose(struct privsep *ps, enum privsep_procid id,
+    uint16_t type, void *data, uint16_t datalen)
+{
+	return (proc_compose_imsg(ps, id, -1, type, -1, -1, data, datalen));
+}
+
+int
+proc_composev_imsg(struct privsep *ps, enum privsep_procid id, int n,
+    uint16_t type, uint32_t peerid, int fd, const struct iovec *iov, int iovcnt)
+{
+	int	 m;
+
+	proc_range(ps, id, &n, &m);
+	for (; n < m; n++)
+		if (imsg_composev_event(&ps->ps_ievs[id][n],
+		    type, peerid, ps->ps_instance + 1, fd, iov, iovcnt) == -1)
+			return (-1);
+
+	return (0);
+}
+
+int
+proc_composev(struct privsep *ps, enum privsep_procid id,
+    uint16_t type, const struct iovec *iov, int iovcnt)
+{
+	return (proc_composev_imsg(ps, id, -1, type, -1, -1, iov, iovcnt));
+}
+
+int
+proc_forward_imsg(struct privsep *ps, struct imsg *imsg,
+    enum privsep_procid id, int n)
+{
+	return (proc_compose_imsg(ps, id, n, imsg->hdr.type,
+	    imsg->hdr.peerid, imsg->fd, imsg->data, IMSG_DATA_SIZE(imsg)));
+}
+
+struct imsgbuf *
+proc_ibuf(struct privsep *ps, enum privsep_procid id, int n)
+{
+	int	 m;
+
+	proc_range(ps, id, &n, &m);
+	return (&ps->ps_ievs[id][n].ibuf);
+}
+
+struct imsgev *
+proc_iev(struct privsep *ps, enum privsep_procid id, int n)
+{
+	int	 m;
+
+	proc_range(ps, id, &n, &m);
+	return (&ps->ps_ievs[id][n]);
+}
+
+/* This function should only be called with care as it breaks async I/O */
+int
+proc_flush_imsg(struct privsep *ps, enum privsep_procid id, int n)
+{
+	struct imsgbuf	*ibuf;
+	int		 m, ret = 0;
+
+	proc_range(ps, id, &n, &m);
+	for (; n < m; n++) {
+		if ((ibuf = proc_ibuf(ps, id, n)) == NULL)
+			return (-1);
+		do {
+			ret = imsg_flush(ibuf);
+		} while (ret == -1 && errno == EAGAIN);
+		if (ret == -1)
+			break;
+		imsg_event_add(&ps->ps_ievs[id][n]);
+	}
+
+	return (ret);
+}
blob - /dev/null
blob + 6f25e7b2f0efd297f2b6b8cdc3e4ce95bbfc0548 (mode 644)
--- /dev/null
+++ proc.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2010-2015 Reyk Floeter <reyk@openbsd.org>
+ *
+ * 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.
+ */
+
+/* imsg */
+struct imsgev {
+	struct imsgbuf		 ibuf;
+	void			(*handler)(int, short, void *);
+	struct event		 ev;
+	struct privsep_proc	*proc;
+	void			*data;
+	short			 events;
+};
+
+#define IMSG_SIZE_CHECK(imsg, p) do {					\
+	if (IMSG_DATA_SIZE(imsg) < sizeof(*p))				\
+		fatalx("bad length imsg received (%s)",	#p);		\
+} while (0)
+#define IMSG_DATA_SIZE(imsg)	((imsg)->hdr.len - IMSG_HEADER_SIZE)
+
+/* privsep */
+enum privsep_procid {
+	PROC_PARENT,
+	PROC_PROXY,
+	PROC_MAX,
+};
+
+#define CONFIG_RELOAD		0x00
+#define CONFIG_SOCKS		0x01
+#define CONFIG_ALL		0xff
+
+struct privsep_pipes {
+	int				*pp_pipes[PROC_MAX];
+};
+
+struct privsep {
+	struct privsep_pipes		*ps_pipes[PROC_MAX];
+	struct privsep_pipes		*ps_pp;
+
+	struct imsgev			*ps_ievs[PROC_MAX];
+	const char			*ps_title[PROC_MAX];
+	uint8_t				 ps_what[PROC_MAX];
+
+	struct passwd			*ps_pw;
+	int				 ps_noaction;
+
+	unsigned int			 ps_instances[PROC_MAX];
+	unsigned int			 ps_instance;
+
+	/* Event and signal handlers */
+	struct event			 ps_evsigint;
+	struct event			 ps_evsigterm;
+	struct event			 ps_evsigchld;
+	struct event			 ps_evsighup;
+
+	void				*ps_env;
+};
+
+struct privsep_proc {
+	const char		*p_title;
+	enum privsep_procid	 p_id;
+	int			(*p_cb)(int, struct privsep_proc *,
+				    struct imsg *);
+	void			(*p_init)(struct privsep *,
+				    struct privsep_proc *);
+	void			(*p_shutdown)(void);
+	const char		*p_chroot;
+	struct passwd		*p_pw;
+	struct privsep		*p_ps;
+};
+
+struct privsep_fd {
+	enum privsep_procid		 pf_procid;
+	unsigned int			 pf_instance;
+};
+
+/* proc.c */
+void	 proc_init(struct privsep *, struct privsep_proc *, unsigned int,
+	    int, int, char **, enum privsep_procid);
+void	 proc_kill(struct privsep *);
+void	 proc_connect(struct privsep *ps);
+void	 proc_dispatch(int, short event, void *);
+void	 proc_range(struct privsep *, enum privsep_procid, int *, int *);
+void	 proc_run(struct privsep *, struct privsep_proc *,
+	    struct privsep_proc *, unsigned int,
+	    void (*)(struct privsep *, struct privsep_proc *, void *), void *);
+void	 imsg_event_add(struct imsgev *);
+int	 imsg_compose_event(struct imsgev *, uint16_t, uint32_t,
+	    pid_t, int, void *, uint16_t);
+int	 imsg_composev_event(struct imsgev *, uint16_t, uint32_t,
+	    pid_t, int, const struct iovec *, int);
+int	 proc_compose_imsg(struct privsep *, enum privsep_procid, int,
+	    uint16_t, uint32_t, int, void *, uint16_t);
+int	 proc_compose(struct privsep *, enum privsep_procid,
+	    uint16_t, void *data, uint16_t);
+int	 proc_composev_imsg(struct privsep *, enum privsep_procid, int,
+	    uint16_t, uint32_t, int, const struct iovec *, int);
+int	 proc_composev(struct privsep *, enum privsep_procid,
+	    uint16_t, const struct iovec *, int);
+int	 proc_forward_imsg(struct privsep *, struct imsg *,
+	    enum privsep_procid, int);
+struct imsgbuf *
+	 proc_ibuf(struct privsep *, enum privsep_procid, int);
+struct imsgev *
+	 proc_iev(struct privsep *, enum privsep_procid, int);
+enum privsep_procid
+	 proc_getid(struct privsep_proc *, unsigned int, const char *);
+int	 proc_flush_imsg(struct privsep *, enum privsep_procid, int);
blob - /dev/null
blob + c8aeb21b92288169a1883ad7d5651ad13d5a08af (mode 644)
--- /dev/null
+++ proxy.c
@@ -0,0 +1,575 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@omarpolo.com>
+ * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org>
+ *
+ * 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 <sys/types.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <asr.h>
+#include <ctype.h>
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <imsg.h>
+#include <tls.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "proc.h"
+
+#include "galileo.h"
+
+#define MINIMUM(a, b)	((a) < (b) ? (a) : (b))
+
+/* provided by OpenBSD' base libevent but not in any header? */
+extern void	 bufferevent_read_pressure_cb(struct evbuffer *, size_t,
+		    size_t, void *);
+
+void	proxy_init(struct privsep *, struct privsep_proc *, void *);
+int	proxy_launch(struct galileo *);
+void	proxy_accept(int, short, void *);
+void	proxy_inflight_dec(const char *);
+int	proxy_dispatch_parent(int, struct privsep_proc *, struct imsg *);
+void	proxy_resolved(struct asr_result *, void *);
+void	proxy_connect(int, short, void *);
+void	proxy_read(struct bufferevent *, void *);
+void	proxy_write(struct bufferevent *, void *);
+void	proxy_error(struct bufferevent *, short, void *);
+int	proxy_bufferevent_add(struct event *, int);
+void	proxy_tls_writecb(int, short, void *);
+void	proxy_tls_readcb(int, short, void *);
+
+static struct privsep_proc procs[] = {
+	{ "parent",	PROC_PARENT, proxy_dispatch_parent },
+};
+
+volatile int proxy_clients;
+volatile int proxy_inflight;
+uint32_t proxy_fcg_id;
+
+void
+proxy(struct privsep *ps, struct privsep_proc *p)
+{
+	proc_run(ps, p, procs, nitems(procs), proxy_init, NULL);
+}
+
+void
+proxy_init(struct privsep *ps, struct privsep_proc *p, void *arg)
+{
+	if (config_init(ps->ps_env) == -1)
+		fatal("failed to initialize configuration");
+
+	/* We use a custom shutdown callback */
+	/* p->p_shutdown = proxy_shutdown */
+
+	if (pledge("stdio recvfd unix inet dns", NULL) == -1)
+		fatal("pledge");
+}
+
+int
+proxy_launch(struct galileo *env)
+{
+	event_add(&env->sc_evsock, NULL);
+	return (0);
+}
+
+void
+proxy_purge(struct server *srv)
+{
+}
+
+void
+proxy_accept(int fd, short event, void *arg)
+{
+	struct galileo	*env = arg;
+
+	log_debug("%s", __func__);
+	fcgi_accept(env);
+}
+
+void
+proxy_inflight_dec(const char *why)
+{
+	proxy_inflight--;
+	log_debug("%s: inflight decremented, now %d, %s",
+	    __func__, proxy_inflight, why);
+}
+
+int
+proxy_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+	struct privsep	*ps = p->p_ps;
+	struct galileo	*env = ps->ps_env;
+
+	switch (imsg->hdr.type) {
+	case IMSG_CFG_SRV:
+		if (config_getserver(env, imsg) == -1)
+			fatal("config_getproxy");
+		break;
+	case IMSG_CFG_SOCK:
+		/* XXX: improve */
+
+		if (env->sc_sock_fd != -1) {
+			event_del(&env->sc_evsock);
+			close(env->sc_sock_fd);
+		}
+
+		env->sc_sock_fd = config_getsock(env, imsg);
+		if (env->sc_sock_fd == -1)
+			fatal("config_getsock");
+
+		event_set(&env->sc_evsock, env->sc_sock_fd,
+		    EV_READ | EV_PERSIST, proxy_accept, env);
+		event_add(&env->sc_evsock, NULL);
+		/* evtimer_set(&env->sc_evpause, proxy_accept_paused, env); */
+		break;
+	case IMSG_CFG_DONE:
+		log_debug("config done!");
+		break;
+	case IMSG_CTL_START:
+		proxy_launch(env);
+		break;
+	default:
+		log_warnx("unknown message %d", imsg->hdr.type);
+		return (-1);
+	}
+
+	return (0);
+}
+
+static struct proxy_config *
+proxy_server_match(struct galileo *env, struct client *clt)
+{
+	struct server		*srv;
+
+	if (clt->clt_server_name == NULL)
+		return NULL;
+
+	TAILQ_FOREACH(srv, &env->sc_servers, srv_entry) {
+		if (!strcmp(clt->clt_server_name, srv->srv_conf.host))
+			return &srv->srv_conf;
+	}
+
+	return NULL;
+}
+
+void
+proxy_start_request(struct galileo *env, struct client *clt)
+{
+	struct addrinfo		 hints;
+	struct asr_query	*query;
+	char			 port[32];
+
+	if ((clt->clt_pc = proxy_server_match(env, clt)) == NULL) {
+		if (clt_printf(clt, "Status: 501\r\n\r\n") == -1)
+			return;
+		fcgi_end_request(clt, 1);
+		return;
+	}
+
+	(void)snprintf(port, sizeof(port), "%d", clt->clt_pc->proxy_port);
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+
+	query = getaddrinfo_async(clt->clt_pc->proxy_addr, port, &hints, NULL);
+	if (query == NULL) {
+		log_warn("getaddrinfo_async");
+		fcgi_abort_request(clt);
+		return;
+	}
+
+	clt->clt_evasr = event_asr_run(query, proxy_resolved, clt);
+	if (clt->clt_evasr == NULL) {
+		log_warn("event_asr_run");
+		asr_abort(query);
+		fcgi_abort_request(clt);
+		return;
+	}
+}
+
+void
+proxy_resolved(struct asr_result *res, void *d)
+{
+	struct client		*clt = d;
+	struct proxy_config	*pc = clt->clt_pc;
+
+	clt->clt_evasr = NULL;
+
+	if (res->ar_gai_errno != 0) {
+		log_warnx("failed to resolve %s:%d: %s",
+		    pc->proxy_addr, pc->proxy_port,
+		    gai_strerror(res->ar_gai_errno));
+		if (clt_printf(clt, "Status: 501\r\n") == -1)
+			return;
+		if (clt_printf(clt, "Content-Type: text/plain\r\n") == -1)
+			return;
+		if (clt_printf(clt, "Proxy error; connection failed") == -1)
+			return;
+		fcgi_end_request(clt, 1);
+		return;
+	}
+
+	clt->clt_addrinfo = res->ar_addrinfo;
+	clt->clt_p = clt->clt_addrinfo;
+	proxy_connect(-1, 0, clt);
+}
+
+void
+proxy_connect(int fd, short ev, void *d)
+{
+	struct client		*clt = d;
+	struct evbuffer		*out;
+	struct addrinfo		*p;
+	struct tls_config	*conf;
+	struct timeval		 conntv = {5, 0};
+	int			 err = 0;
+	socklen_t		 len = sizeof(err);
+
+again:
+	if (clt->clt_p == NULL)
+		goto err;
+
+	if (clt->clt_fd != -1) {
+		if (getsockopt(clt->clt_fd, SOL_SOCKET, SO_ERROR, &err, &len)
+		    == -1)
+			goto err;
+		if (err != 0) {
+			errno = err;
+			goto err;
+		}
+		goto done;
+	}
+
+	p = clt->clt_p;
+	clt->clt_fd = socket(p->ai_family, p->ai_socktype | SOCK_NONBLOCK,
+	    p->ai_protocol);
+	if (clt->clt_fd == -1) {
+		clt->clt_p = clt->clt_p->ai_next;
+		goto again;
+	}
+
+	if (connect(clt->clt_fd, p->ai_addr, p->ai_addrlen) == 0)
+		goto done;
+
+	clt->clt_evconn_live = 1;
+	event_set(&clt->clt_evconn, clt->clt_fd, EV_WRITE, proxy_connect, clt);
+	event_add(&clt->clt_evconn, &conntv);
+	return;
+
+done:
+	clt->clt_evconn_live = 0;
+	freeaddrinfo(clt->clt_addrinfo);
+	clt->clt_addrinfo = clt->clt_p = NULL;
+
+	/* initialize TLS for Gemini */
+	if ((conf = tls_config_new()) == NULL) {
+		log_warn("tls_config_new failed");
+		goto err;
+	}
+
+	tls_config_insecure_noverifycert(conf);
+
+	if ((clt->clt_ctx = tls_client()) == NULL) {
+		log_warnx("tls_client failed");
+		tls_config_free(conf);
+		goto err;
+	}
+
+	if (tls_configure(clt->clt_ctx, conf) == -1) {
+		log_warnx("tls_configure failed");
+		tls_config_free(conf);
+		goto err;
+	}
+
+	tls_config_free(conf);
+
+	if (tls_connect_socket(clt->clt_ctx, clt->clt_fd,
+	    clt->clt_pc->proxy_name) == -1) {
+		log_warnx("tls_connect_socket failed");
+		goto err;
+	}
+
+	clt->clt_bev = bufferevent_new(clt->clt_fd, proxy_read, proxy_write,
+	    proxy_error, clt);
+	if (clt->clt_bev == NULL) {
+		log_warn("bufferevent_new");
+		goto err;
+	}
+	out = EVBUFFER_OUTPUT(clt->clt_bev);
+
+	event_set(&clt->clt_bev->ev_read, clt->clt_fd, EV_READ,
+	    proxy_tls_readcb, clt->clt_bev);
+	event_set(&clt->clt_bev->ev_write, clt->clt_fd, EV_WRITE,
+	    proxy_tls_writecb, clt->clt_bev);
+
+	/* bufferevent_settimeout(); */
+	bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
+
+	/* TODO: compute the URL */
+	if (evbuffer_add_printf(out, "gemini://localhost/\r\n") == -1) {
+		log_warn("bufferevent_printf failed");
+		goto err;
+	}
+
+	return;
+
+err:
+	log_warn("failed to connect to %s:%d",
+	    clt->clt_pc->proxy_addr, clt->clt_pc->proxy_port);
+	if (clt_printf(clt, "Status: 501\r\n") == -1)
+		return;
+	if (clt_printf(clt, "Content-Type: text/plain\r\n") == -1)
+		return;
+	if (clt_printf(clt, "Proxy error; connection failed") == -1)
+		return;
+	fcgi_end_request(clt, 1);
+}
+
+void
+proxy_read(struct bufferevent *bev, void *d)
+{
+	struct client		*clt = d;
+	struct evbuffer		*src = EVBUFFER_INPUT(bev);
+	char			*hdr;
+	size_t			 len;
+	int			 code;
+
+	if (clt->clt_headersdone) {
+	copy:
+		clt_write_bufferevent(clt, bev);
+		return;
+	}
+
+	hdr = evbuffer_readln(src, &len, EVBUFFER_EOL_CRLF_STRICT);
+	if (hdr == NULL) {
+		if (EVBUFFER_LENGTH(src) >= 1026)
+			proxy_error(bev, EV_READ, clt);
+		return;
+	}
+
+	if (len < 4 ||
+	    !isdigit((unsigned char)hdr[0]) ||
+	    !isdigit((unsigned char)hdr[1]) ||
+	    hdr[2] != ' ') {
+		log_warnx("invalid ");
+		free(hdr);
+		proxy_error(bev, EV_READ, clt);
+		return;
+	}
+
+	code = (hdr[0] - '0') * 10 + (hdr[1] - '0');
+	if (code != 20) {
+		log_warnx("un-handled gemini reply status %d", code);
+		free(hdr);
+		proxy_error(bev, EV_READ, clt);
+		return;
+	}
+
+	if (clt_printf(clt, "Content-Type: %s\r\n", &hdr[4]) == -1)
+		return;
+	if (clt_printf(clt, "\r\n") == -1)
+		return;
+
+	clt->clt_headersdone = 1;
+	goto copy;
+}
+
+void
+proxy_write(struct bufferevent *bev, void *d)
+{
+	return;
+}
+
+void
+proxy_error(struct bufferevent *bev, short err, void *d)
+{
+	struct client		*clt = d;
+	int			 status = !(err & EVBUFFER_EOF);
+
+	log_debug("proxy error, shutting down the connection (err: %x)",
+	    err);
+
+	if (!clt->clt_headersdone) {
+		if (clt_printf(clt, "Status: 501\r\n") == -1)
+			return;
+		if (clt_printf(clt, "Content-Type: text/plain\r\n") == -1)
+			return;
+		if (clt_printf(clt, "Proxy error\n") == -1)
+			return;
+	}
+
+	fcgi_end_request(clt, status);
+}
+
+void
+proxy_tls_readcb(int fd, short event, void *arg)
+{
+	struct bufferevent	*bufev = arg;
+	struct client		*clt = bufev->cbarg;
+	char			 rbuf[IBUF_READ_SIZE];
+	int			 what = EVBUFFER_READ;
+	int			 howmuch = IBUF_READ_SIZE;
+	ssize_t			 ret;
+	size_t			 len;
+
+	if (event == EV_TIMEOUT) {
+		what |= EVBUFFER_TIMEOUT;
+		goto err;
+	}
+
+	if (bufev->wm_read.high != 0)
+		howmuch = MINIMUM(sizeof(rbuf), bufev->wm_read.high);
+
+	ret = tls_read(clt->clt_ctx, rbuf, howmuch);
+	if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) {
+		goto retry;
+	} else if (ret == -1) {
+		what |= EVBUFFER_ERROR;
+		goto err;
+	}
+	len = ret;
+
+	if (len == 0) {
+		what |= EVBUFFER_EOF;
+		goto err;
+	}
+
+	if (evbuffer_add(bufev->input, rbuf, len) == -1) {
+		what |= EVBUFFER_ERROR;
+		goto err;
+	}
+
+	proxy_bufferevent_add(&bufev->ev_read, bufev->timeout_read);
+
+	len = EVBUFFER_LENGTH(bufev->input);
+	if (bufev->wm_read.low != 0 && len < bufev->wm_read.low)
+		return;
+	if (bufev->wm_read.high != 0 && len > bufev->wm_read.high) {
+		struct evbuffer *buf = bufev->input;
+		event_del(&bufev->ev_read);
+		evbuffer_setcb(buf, bufferevent_read_pressure_cb, bufev);
+		return;
+	}
+
+	if (bufev->readcb != NULL)
+		(*bufev->readcb)(bufev, bufev->cbarg);
+	return;
+
+retry:
+	proxy_bufferevent_add(&bufev->ev_read, bufev->timeout_read);
+	return;
+
+err:
+	(*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+int
+proxy_bufferevent_add(struct event *ev, int timeout)
+{
+	struct timeval tv, *ptv = NULL;
+
+	if (timeout) {
+		timerclear(&tv);
+		tv.tv_sec = timeout;
+		ptv = &tv;
+	}
+
+	return (event_add(ev, ptv));
+}
+
+void
+proxy_tls_writecb(int fd, short event, void *arg)
+{
+	struct bufferevent	*bufev = arg;
+	struct client		*clt = bufev->cbarg;
+	ssize_t			 ret;
+	short			 what = EVBUFFER_WRITE;
+	size_t			 len;
+
+	if (event == EV_TIMEOUT) {
+		what |= EVBUFFER_TIMEOUT;
+		goto err;
+	}
+
+	if (EVBUFFER_LENGTH(bufev->output)) {
+		ret = tls_write(clt->clt_ctx,
+		    EVBUFFER_DATA(bufev->output),
+		    EVBUFFER_LENGTH(bufev->output));
+		if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) {
+			goto retry;
+		} else if (ret == -1) {
+			what |= EVBUFFER_ERROR;
+			goto err;
+		}
+		len = ret;
+		evbuffer_drain(bufev->output, len);
+	}
+
+	if (EVBUFFER_LENGTH(bufev->output) != 0)
+		proxy_bufferevent_add(&bufev->ev_write, bufev->timeout_write);
+
+	if (bufev->writecb != NULL &&
+	    EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low)
+		(*bufev->writecb)(bufev, bufev->cbarg);
+	return;
+
+retry:
+	proxy_bufferevent_add(&bufev->ev_write, bufev->timeout_write);
+	return;
+
+err:
+	(*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+void
+proxy_client_free(struct client *clt)
+{
+	if (clt->clt_evasr)
+		event_asr_abort(clt->clt_evasr);
+
+	if (clt->clt_addrinfo)
+		freeaddrinfo(clt->clt_addrinfo);
+
+	if (clt->clt_evconn_live)
+		event_del(&clt->clt_evconn);
+
+	if (clt->clt_fd != -1)
+		close(clt->clt_fd);
+
+	if (clt->clt_ctx)
+		tls_free(clt->clt_ctx);
+
+	if (clt->clt_bev)
+		bufferevent_free(clt->clt_bev);
+
+	free(clt->clt_server_name);
+	free(clt->clt_script_name);
+	free(clt->clt_path_info);
+	free(clt);
+}
blob - /dev/null
blob + f05ceadf51129ffe6559594eeea12fa517db5f69 (mode 644)
--- /dev/null
+++ xmalloc.c
@@ -0,0 +1,100 @@
+/* $OpenBSD: xmalloc.c,v 1.4 2019/06/28 05:44:09 deraadt Exp $ */
+/*
+ * Author: Tatu Ylonen <ylo@cs.hut.fi>
+ * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
+ *                    All rights reserved
+ * Versions of malloc and friends that check their results, and never return
+ * failure (they call fatal if they encounter an error).
+ *
+ * As far as I am concerned, the code I have written for this software
+ * can be used freely for any purpose.  Any derived versions of this
+ * software must be clearly marked as such, and if the derived work is
+ * incompatible with the protocol description in the RFC file, it must be
+ * called by a name other than "ssh" or "Secure Shell".
+ */
+
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "log.h"
+#include "xmalloc.h"
+
+void *
+xmalloc(size_t size)
+{
+	void *ptr;
+
+	if (size == 0)
+		fatal("xmalloc: zero size");
+	ptr = malloc(size);
+	if (ptr == NULL)
+		fatal("xmalloc: allocating %zu bytes", size);
+	return ptr;
+}
+
+void *
+xcalloc(size_t nmemb, size_t size)
+{
+	void *ptr;
+
+	if (size == 0 || nmemb == 0)
+		fatal("xcalloc: zero size");
+	ptr = calloc(nmemb, size);
+	if (ptr == NULL)
+		fatal("xcalloc: allocating %zu * %zu bytes", nmemb, size);
+	return ptr;
+}
+
+void *
+xreallocarray(void *ptr, size_t nmemb, size_t size)
+{
+	void *new_ptr;
+
+	new_ptr = reallocarray(ptr, nmemb, size);
+	if (new_ptr == NULL)
+		fatal("xreallocarray: allocating %zu * %zu bytes",
+		    nmemb, size);
+	return new_ptr;
+}
+
+void *
+xrecallocarray(void *ptr, size_t oldnmemb, size_t nmemb, size_t size)
+{
+	void *new_ptr;
+
+	new_ptr = recallocarray(ptr, oldnmemb, nmemb, size);
+	if (new_ptr == NULL)
+		fatal("xrecallocarray: allocating %zu * %zu bytes",
+		    nmemb, size);
+	return new_ptr;
+}
+
+char *
+xstrdup(const char *str)
+{
+	char *cp;
+
+	if ((cp = strdup(str)) == NULL)
+		fatal("xstrdup");
+	return cp;
+}
+
+int
+xasprintf(char **ret, const char *fmt, ...)
+{
+	va_list ap;
+	int i;
+
+	va_start(ap, fmt);
+	i = vasprintf(ret, fmt, ap);
+	va_end(ap);
+
+	if (i == -1)
+		fatal("xasprintf");
+
+	return i;
+}
blob - /dev/null
blob + 5e11cb4e79d681af3c5b8787d9308302336a092f (mode 644)
--- /dev/null
+++ xmalloc.h
@@ -0,0 +1,31 @@
+/* $OpenBSD: xmalloc.h,v 1.3 2015/11/17 18:25:03 tobias Exp $ */
+
+/*
+ * Author: Tatu Ylonen <ylo@cs.hut.fi>
+ * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
+ *                    All rights reserved
+ * Created: Mon Mar 20 22:09:17 1995 ylo
+ *
+ * Versions of malloc and friends that check their results, and never return
+ * failure (they call fatal if they encounter an error).
+ *
+ * As far as I am concerned, the code I have written for this software
+ * can be used freely for any purpose.  Any derived versions of this
+ * software must be clearly marked as such, and if the derived work is
+ * incompatible with the protocol description in the RFC file, it must be
+ * called by a name other than "ssh" or "Secure Shell".
+ */
+
+#ifndef XMALLOC_H
+#define XMALLOC_H
+
+void	*xmalloc(size_t);
+void	*xcalloc(size_t, size_t);
+void	*xreallocarray(void *, size_t, size_t);
+void	*xrecallocarray(void *, size_t, size_t, size_t);
+char	*xstrdup(const char *);
+int	 xasprintf(char **, const char *, ...)
+                __attribute__((__format__ (printf, 2, 3)))
+                __attribute__((__nonnull__ (2)));
+
+#endif	/* XMALLOC_H */