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