commit - /dev/null
commit + b0a6bcf7ac45303633103a7a64ed80fc4cf41181
blob - /dev/null
blob + 0c3fa2db9340a5f222bef82bfea91368ba5d7e8c (mode 644)
--- /dev/null
+++ Makefile
+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
+/*
+ * 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
+/*
+ * 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
+/*
+ * 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
+/*
+ * 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
+/* $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
+/* $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
+/*
+ * 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
+/* $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
+/*
+ * 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
+/*
+ * 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
+/* $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
+/* $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 */