From: Stefan Sperling Subject: Re: gotwebd fcgi/gotweb process split To: gameoftrees@openbsd.org Date: Fri, 11 Apr 2025 16:43:40 +0200 On Thu, Apr 10, 2025 at 10:52:59AM +0200, Stefan Sperling wrote: > On Wed, Apr 09, 2025 at 06:31:42PM +0200, Stefan Sperling wrote: > > This patch is still work-in-progress. It lacks documentation updates, > > and one TODO is mentioned in the code. > > updated patch which resolves remaining TODOs and updates docs, too. Another update which fixes the two issues found by me so far: 1) We need to do polling writes in fcgi_forward to avoid: fcgi_forward_response: wrote -1 of 1024 bytes: \ Resource temporarily unavailable 2) fix hang during '/etc/rc.d/gotwebd restart', causing rc to kill gotwebd M gotwebd/Makefile | 0+ 2- M gotwebd/Makefile.inc | 1+ 1- M gotwebd/config.c | 9+ 15- M gotwebd/fcgi.c | 104+ 7- M gotwebd/gotweb.c | 384+ 14- M gotwebd/gotwebd.8 | 8+ 10- M gotwebd/gotwebd.c | 242+ 80- M gotwebd/gotwebd.conf.5 | 29+ 13- M gotwebd/gotwebd.h | 30+ 10- D gotwebd/libexec/Makefile | 0+ 4- D gotwebd/libexec/Makefile.inc | 0+ 11- D gotwebd/libexec/got-read-blob/Makefile | 0+ 15- D gotwebd/libexec/got-read-commit/Makefile | 0+ 15- D gotwebd/libexec/got-read-gitconfig/Makefile | 0+ 16- D gotwebd/libexec/got-read-gotconfig/Makefile | 0+ 19- D gotwebd/libexec/got-read-object/Makefile | 0+ 15- D gotwebd/libexec/got-read-pack/Makefile | 0+ 16- D gotwebd/libexec/got-read-tag/Makefile | 0+ 15- D gotwebd/libexec/got-read-tree/Makefile | 0+ 15- M gotwebd/parse.y | 24+ 3- M gotwebd/sockets.c | 223+ 40- M regress/gotwebd/Makefile | 1+ 7- 22 files changed, 1055 insertions(+), 343 deletions(-) commit - 1fefee5fcecc39a1b50ab7c21b82b751ba1e7d30 commit + 4774f2e2fe6130bd3e9d719dd399e5a25170904c blob - 6b6e9e1815bb7b6d738165292b522056eee96e22 blob + 545a13d2e5aba6dc9df486f7300af0acf23c4df7 --- gotwebd/Makefile +++ gotwebd/Makefile @@ -1,8 +1,6 @@ .PATH:${.CURDIR}/../lib .PATH:${.CURDIR}/../template -SUBDIR = libexec - .include "../got-version.mk" .include "Makefile.inc" blob - 8140d7d3e8b25972b9b347173294bd3b671a0da1 blob + 22466be67cc8ab44489af620bed8558a24ed17c9 --- gotwebd/Makefile.inc +++ gotwebd/Makefile.inc @@ -1,9 +1,9 @@ LDADD += -lz -lutil PREFIX ?= /usr/local BINDIR ?= ${PREFIX}/sbin +LIBEXECDIR ?= ${PREFIX}/libexec CHROOT_DIR ?= /var/www GOTWEB_DIR = /bin/gotwebd -LIBEXECDIR = ${GOTWEB_DIR}/libexec HTTPD_DIR = ${CHROOT_DIR}/htdocs PROG_DIR = ${HTTPD_DIR}/${PROG} WWWUSR ?= www blob - 980ecf25520da4497a36f585459ebecbf7b1d3f5 blob + 90eb143afce8e50c7ba53d141294ca8616d7a074 --- gotwebd/config.c +++ gotwebd/config.c @@ -74,15 +74,6 @@ config_getcfg(struct gotwebd *env, struct imsg *imsg) } int -config_setserver(struct gotwebd *env, struct server *srv) -{ - if (main_compose_sockets(env, GOTWEBD_IMSG_CFG_SRV, - -1, srv, sizeof(*srv)) == -1) - fatal("main_compose_sockets GOTWEBD_IMSG_CFG_SRV"); - return 0; -} - -int config_getserver(struct gotwebd *env, struct imsg *imsg) { struct server *srv; @@ -106,17 +97,20 @@ config_getserver(struct gotwebd *env, struct imsg *ims } int -config_setsock(struct gotwebd *env, struct socket *sock) +config_setsock(struct gotwebd *env, struct socket *sock, uid_t uid, gid_t gid) { /* open listening sockets */ - if (sockets_privinit(env, sock) == -1) + if (sockets_privinit(env, sock, uid, gid) == -1) return -1; if (main_compose_sockets(env, GOTWEBD_IMSG_CFG_SOCK, sock->fd, &sock->conf, sizeof(sock->conf)) == -1) fatal("main_compose_sockets GOTWEBD_IMSG_CFG_SOCK"); - sock->fd = -1; + if (main_compose_gotweb(env, GOTWEBD_IMSG_CFG_SOCK, sock->fd, + &sock->conf, sizeof(sock->conf)) == -1) + fatal("main_compose_gotweb GOTWEBD_IMSG_CFG_SOCK"); + return 0; } @@ -172,13 +166,13 @@ config_setfd(struct gotwebd *env) fd = got_opentempfd(); if (fd == -1) fatal("got_opentemp"); - if (imsg_compose_event(&env->iev_server[j], + if (imsg_compose_event(&env->iev_gotweb[j], GOTWEBD_IMSG_CFG_FD, 0, -1, fd, NULL, 0) == -1) fatal("imsg_compose_event GOTWEBD_IMSG_CFG_FD"); - if (imsgbuf_flush(&env->iev_server[j].ibuf) == -1) + if (imsgbuf_flush(&env->iev_gotweb[j].ibuf) == -1) fatal("imsgbuf_flush"); - imsg_event_add(&env->iev_server[j]); + imsg_event_add(&env->iev_gotweb[j]); } } blob - fe58b1f12ab25d401bc5d3c10aaecf29d138f7bd blob + 3ec4aee067d2084604a4b3ca1fa315b83811ccc3 --- gotwebd/fcgi.c +++ gotwebd/fcgi.c @@ -35,6 +35,8 @@ #include "got_error.h" #include "got_reference.h" +#include "got_lib_poll.h" + #include "gotwebd.h" #include "log.h" #include "tmpl.h" @@ -53,7 +55,7 @@ void dump_fcgi_end_request_body(const char *, struct fcgi_end_request_body *); extern int cgi_inflight; -extern volatile int client_cnt; +extern struct requestlist requests; void fcgi_request(int fd, short events, void *arg) @@ -142,6 +144,7 @@ fcgi_parse_record(uint8_t *buf, size_t n, struct reque ntohs(h->content_len), c, ntohs(h->id)); break; case FCGI_STDIN: + return 0; case FCGI_ABORT_REQUEST: fcgi_create_end_record(c); fcgi_cleanup_request(c); @@ -175,6 +178,89 @@ fcgi_parse_begin_request(uint8_t *buf, uint16_t n, c->id = id; } +static void +fcgi_forward_response(int fd, short event, void *arg) +{ + const struct got_error *err = NULL; + struct request *c = arg; + uint8_t outbuf[GOTWEBD_CACHESIZE]; + ssize_t r; + + if ((event & EV_READ) == 0) + return; + + r = read(fd, outbuf, sizeof(outbuf)); + if (r == 0) + return; + + if (r == -1) { + log_warn("read response"); + } else { + err = got_poll_write_full(c->fd, outbuf, r); + if (err) { + log_warnx("forward response: %s", err->msg); + fcgi_cleanup_request(c); + return; + } + } + + event_add(c->resp_event, NULL); +} + +static void +process_request(struct request *c) +{ + struct gotwebd *env = gotwebd_env; + int ret, i, pipe[2]; + struct request ic; + struct event *resp_event = NULL; + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe) == -1) { + log_warn("socketpair"); + return; + } + + memcpy(&ic, c, sizeof(ic)); + + /* Don't leak pointers from our address space to another process. */ + ic.sock = NULL; + ic.srv = NULL; + ic.t = NULL; + ic.tp = NULL; + ic.buf = NULL; + ic.outbuf = NULL; + + /* Other process will use its own set of temp files. */ + for (i = 0; i < nitems(c->priv_fd); i++) + ic.priv_fd[i] = -1; + ic.fd = -1; + ic.resp_fd = -1; + + resp_event = calloc(1, sizeof(*resp_event)); + if (resp_event == NULL) { + log_warn("calloc"); + close(pipe[0]); + close(pipe[1]); + return; + } + + ret = imsg_compose_event(env->iev_gotweb, GOTWEBD_IMSG_REQ_PROCESS, + GOTWEBD_PROC_SERVER, getpid(), pipe[0], &ic, sizeof(ic)); + if (ret == -1) { + log_warn("imsg_compose_event"); + close(pipe[0]); + close(pipe[1]); + free(resp_event); + return; + } + + event_set(resp_event, pipe[1], EV_READ, fcgi_forward_response, c); + event_add(resp_event, NULL); + + c->resp_fd = pipe[1]; + c->resp_event = resp_event; +} + void fcgi_parse_params(uint8_t *buf, uint16_t n, struct request *c, uint16_t id) { @@ -192,8 +278,7 @@ fcgi_parse_params(uint8_t *buf, uint16_t n, struct req } if (n == 0) { - gotweb_process_request(c); - template_flush(c->tp); + process_request(c); return; } @@ -399,16 +484,28 @@ void fcgi_cleanup_request(struct request *c) { cgi_inflight--; - client_cnt--; - evtimer_del(&c->tmo); + sockets_del_request(c); + + if (evtimer_initialized(&c->tmo)) + evtimer_del(&c->tmo); if (event_initialized(&c->ev)) event_del(&c->ev); - close(c->fd); - template_free(c->tp); + if (c->fd != -1) + close(c->fd); + if (c->resp_fd != -1) + close(c->resp_fd); + if (c->tp != NULL) + template_free(c->tp); if (c->t != NULL) gotweb_free_transport(c->t); + if (c->resp_event) { + event_del(c->resp_event); + free(c->resp_event); + } + free(c->buf); + free(c->outbuf); free(c); } blob - 1a1a8770b26108d09399eb43a781fa0e4d63c1b9 blob + 91799176b5f93d034ae4014bd3a97128c31aeee4 --- gotwebd/gotweb.c +++ gotwebd/gotweb.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -142,11 +143,124 @@ gotweb_reply_file(struct request *c, const char *ctype return gotweb_reply(c, 200, ctype, NULL); } +static void +free_request(struct request *c) +{ + if (c->fd != -1) + close(c->fd); + if (c->tp != NULL) + template_free(c->tp); + if (c->t != NULL) + gotweb_free_transport(c->t); + free(c->buf); + free(c->outbuf); + free(c); +} + +static struct socket * +gotweb_get_socket(int sock_id) +{ + struct socket *sock; + + TAILQ_FOREACH(sock, &gotwebd_env->sockets, entry) { + if (sock->conf.id == sock_id) + return sock; + } + + return NULL; +} + +static struct request * +recv_request(struct imsg *imsg) +{ + const struct got_error *error; + struct request *c; + struct server *srv; + size_t datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + int fd = -1; + uint8_t *outbuf = NULL; + + if (datalen != sizeof(*c)) { + log_warnx("bad request size received over imsg"); + return NULL; + } + + fd = imsg_get_fd(imsg); + if (fd == -1) { + log_warnx("no client file descriptor"); + return NULL; + } + + c = calloc(1, sizeof(*c)); + if (c == NULL) { + log_warn("calloc"); + return NULL; + } + + outbuf = calloc(1, GOTWEBD_CACHESIZE); + if (outbuf == NULL) { + log_warn("calloc"); + free(c); + return NULL; + } + + memcpy(c, imsg->data, sizeof(*c)); + + /* Non-NULL pointers, if any, are not from our address space. */ + c->sock = NULL; + c->srv = NULL; + c->t = NULL; + c->tp = NULL; + c->buf = NULL; + c->outbuf = outbuf; + + memset(&c->ev, 0, sizeof(c->ev)); + memset(&c->tmo, 0, sizeof(c->tmo)); + + /* Use our own temporary file descriptors. */ + memcpy(c->priv_fd, gotwebd_env->priv_fd, sizeof(c->priv_fd)); + + c->fd = fd; + + c->tp = template(c, fcgi_write, c->outbuf, GOTWEBD_CACHESIZE); + if (c->tp == NULL) { + log_warn("gotweb init template"); + free_request(c); + return NULL; + } + + c->sock = gotweb_get_socket(c->sock_id); + if (c->sock == NULL) { + log_warn("socket id '%d' not found", c->sock_id); + free_request(c); + return NULL; + } + + /* init the transport */ + error = gotweb_init_transport(&c->t); + if (error) { + log_warnx("gotweb init transport: %s", error->msg); + free_request(c); + return NULL; + } + + /* get the gotwebd server */ + srv = gotweb_get_server(c->server_name); + if (srv == NULL) { + log_warnx("server '%s' not found", c->server_name); + free_request(c); + return NULL; + } + c->srv = srv; + + return c; +} + void gotweb_process_request(struct request *c) { const struct got_error *error = NULL; - struct server *srv = NULL; + struct server *srv = c->srv;; struct querystring *qs = NULL; struct repo_dir *repo_dir = NULL; struct repo_commit *commit; @@ -155,19 +269,6 @@ gotweb_process_request(struct request *c) size_t len; int r, binary = 0; - /* init the transport */ - error = gotweb_init_transport(&c->t); - if (error) { - log_warnx("%s: %s", __func__, error->msg); - return; - } - /* get the gotwebd server */ - srv = gotweb_get_server(c->server_name); - if (srv == NULL) { - log_warnx("%s: error server is NULL", __func__); - goto err; - } - c->srv = srv; /* parse our querystring */ error = gotweb_init_querystring(&qs); if (error) { @@ -1297,3 +1398,272 @@ gotweb_render_age(struct template *tp, time_t committe } return 0; } + +static void +gotweb_shutdown(void) +{ + imsgbuf_clear(&gotwebd_env->iev_parent->ibuf); + free(gotwebd_env->iev_parent); + free(gotwebd_env); + + exit(0); +} + +static void +gotweb_sighdlr(int sig, short event, void *arg) +{ + switch (sig) { + case SIGHUP: + log_info("%s: ignoring SIGHUP", __func__); + break; + case SIGPIPE: + log_info("%s: ignoring SIGPIPE", __func__); + break; + case SIGUSR1: + log_info("%s: ignoring SIGUSR1", __func__); + break; + case SIGCHLD: + break; + case SIGINT: + case SIGTERM: + gotweb_shutdown(); + break; + default: + log_warn("unhandled signal %d", sig); + } +} + +static void +gotweb_launch(struct gotwebd *env) +{ + struct server *srv; + const struct got_error *error; + + if (env->iev_server == NULL) + fatal("server process not connected"); + +#ifndef PROFILE + if (pledge("stdio rpath recvfd sendfd proc exec unveil", NULL) == -1) + fatal("pledge"); +#endif + + TAILQ_FOREACH(srv, &gotwebd_env->servers, entry) { + if (unveil(srv->repos_path, "r") == -1) + fatal("unveil %s", srv->repos_path); + } + + error = got_privsep_unveil_exec_helpers(); + if (error) + fatalx("%s", error->msg); + + if (unveil(NULL, NULL) == -1) + fatal("unveil"); + + event_add(&env->iev_server->ev, NULL); +} + +static void +send_request_done(struct imsgev *iev, int request_id) +{ + struct gotwebd *env = gotwebd_env; + + if (imsg_compose_event(env->iev_server, GOTWEBD_IMSG_REQ_DONE, + GOTWEBD_PROC_GOTWEB, getpid(), -1, + &request_id, sizeof(request_id)) == -1) + log_warn("imsg_compose_event"); +} + +static void +gotweb_dispatch_server(int fd, short event, void *arg) +{ + struct imsgev *iev = arg; + struct imsgbuf *ibuf; + struct imsg imsg; + struct request *c; + ssize_t n; + int shut = 0; + + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsgbuf_read(ibuf)) == -1) + fatal("imsgbuf_read error"); + if (n == 0) /* Connection closed */ + shut = 1; + } + if (event & EV_WRITE) { + if (imsgbuf_write(ibuf) == -1) + fatal("imsgbuf_write"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("imsg_get"); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case GOTWEBD_IMSG_REQ_PROCESS: + c = recv_request(&imsg); + if (c) { + int request_id = c->request_id; + gotweb_process_request(c); + template_flush(c->tp); + free_request(c); + send_request_done(iev, request_id); + } + break; + default: + fatalx("%s: unknown imsg type %d", __func__, + imsg.hdr.type); + } + + imsg_free(&imsg); + } + + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +static void +recv_server_pipe(struct gotwebd *env, struct imsg *imsg) +{ + struct imsgev *iev; + int fd; + + if (env->iev_server != NULL) { + log_warn("server pipe already received"); + return; + } + + fd = imsg_get_fd(imsg); + if (fd == -1) + fatalx("invalid server pipe fd"); + + iev = calloc(1, sizeof(*iev)); + if (iev == NULL) + fatal("calloc"); + + if (imsgbuf_init(&iev->ibuf, fd) == -1) + fatal("imsgbuf_init"); + imsgbuf_allow_fdpass(&iev->ibuf); + + iev->handler = gotweb_dispatch_server; + iev->data = iev; + event_set(&iev->ev, fd, EV_READ, gotweb_dispatch_server, iev); + + env->iev_server = iev; +} + +static void +gotweb_dispatch_main(int fd, short event, void *arg) +{ + struct imsgev *iev = arg; + struct imsgbuf *ibuf; + struct imsg imsg; + struct gotwebd *env = gotwebd_env; + ssize_t n; + int shut = 0; + + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsgbuf_read(ibuf)) == -1) + fatal("imsgbuf_read error"); + if (n == 0) /* Connection closed */ + shut = 1; + } + if (event & EV_WRITE) { + if (imsgbuf_write(ibuf) == -1) + fatal("imsgbuf_write"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("imsg_get"); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case GOTWEBD_IMSG_CFG_SRV: + config_getserver(env, &imsg); + break; + case GOTWEBD_IMSG_CFG_FD: + config_getfd(env, &imsg); + break; + case GOTWEBD_IMSG_CFG_SOCK: + config_getsock(env, &imsg); + break; + case GOTWEBD_IMSG_CFG_DONE: + config_getcfg(env, &imsg); + break; + case GOTWEBD_IMSG_CTL_PIPE: + recv_server_pipe(env, &imsg); + break; + case GOTWEBD_IMSG_CTL_START: + gotweb_launch(env); + break; + default: + fatalx("%s: unknown imsg type %d", __func__, + imsg.hdr.type); + } + + imsg_free(&imsg); + } + + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +void +gotweb(struct gotwebd *env, int fd) +{ + struct event sighup, sigint, sigusr1, sigchld, sigterm; + struct event_base *evb; + + evb = event_init(); + + sockets_rlimit(-1); + + if ((env->iev_parent = malloc(sizeof(*env->iev_parent))) == NULL) + fatal("malloc"); + if (imsgbuf_init(&env->iev_parent->ibuf, fd) == -1) + fatal("imsgbuf_init"); + imsgbuf_allow_fdpass(&env->iev_parent->ibuf); + env->iev_parent->handler = gotweb_dispatch_main; + env->iev_parent->data = env->iev_parent; + event_set(&env->iev_parent->ev, fd, EV_READ, gotweb_dispatch_main, + env->iev_parent); + event_add(&env->iev_parent->ev, NULL); + + signal(SIGPIPE, SIG_IGN); + + signal_set(&sighup, SIGHUP, gotweb_sighdlr, env); + signal_add(&sighup, NULL); + signal_set(&sigint, SIGINT, gotweb_sighdlr, env); + signal_add(&sigint, NULL); + signal_set(&sigusr1, SIGUSR1, gotweb_sighdlr, env); + signal_add(&sigusr1, NULL); + signal_set(&sigchld, SIGCHLD, gotweb_sighdlr, env); + signal_add(&sigchld, NULL); + signal_set(&sigterm, SIGTERM, gotweb_sighdlr, env); + signal_add(&sigterm, NULL); + +#ifndef PROFILE + if (pledge("stdio rpath recvfd sendfd proc exec unveil", NULL) == -1) + fatal("pledge"); +#endif + event_dispatch(); + event_base_free(evb); + gotweb_shutdown(); +} blob - 8413e78f48a48cfc92e794fff34a97a8765976e7 blob + f6353254aaee1f21a9dd7e56f1aff9a4d95f47af --- gotwebd/gotwebd.8 +++ gotwebd/gotwebd.8 @@ -85,24 +85,22 @@ can be configured via the .Xr gotwebd.conf 5 configuration file. .It -Git repositories must be created at a suitable location inside the -web server's -.Xr chroot 2 -environment. -These repositories should +Git repositories must be created. +These repositories may reside anywhere in the filesystem and must +be readable, but should .Em not -be writable by the user ID shared between +be writable, by the user .Nm -and -.Xr httpd 8 . +runs as. The default location for repositories published by .Nm is .Pa /var/www/got/public . .It -Git repositories served by +If the Git repositories served by .Nm -should be kept up-to-date with a mechanism such as +do not receive changes from committers directly, they need to be kept +up-to-date with a mechanism such as .Cm got fetch , .Xr git-fetch 1 , or blob - 61a08a5b8aa69b071a02be23f9794bab66c345fc blob + d738c5a52b29d013687fb03ff5f2e55f4a9a512d --- gotwebd/gotwebd.c +++ gotwebd/gotwebd.c @@ -48,11 +48,12 @@ __dead void usage(void); int main(int, char **); -int gotwebd_configure(struct gotwebd *); +int gotwebd_configure(struct gotwebd *, uid_t, gid_t); void gotwebd_configure_done(struct gotwebd *); void gotwebd_sighdlr(int sig, short event, void *arg); void gotwebd_shutdown(void); -void gotwebd_dispatch_sockets(int, short, void *); +void gotwebd_dispatch_server(int, short, void *); +void gotwebd_dispatch_gotweb(int, short, void *); struct gotwebd *gotwebd_env; @@ -75,7 +76,7 @@ imsg_event_add(struct imsgev *iev) int imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid, - pid_t pid, int fd, const void *data, uint16_t datalen) + pid_t pid, int fd, const void *data, size_t datalen) { int ret; @@ -86,42 +87,67 @@ imsg_compose_event(struct imsgev *iev, uint16_t type, return (ret); } -int -main_compose_sockets(struct gotwebd *env, uint32_t type, int fd, - const void *data, uint16_t len) +static int +send_imsg(struct imsgev *iev, uint32_t type, int fd, const void *data, + uint16_t len) { - size_t i; - int ret, d; + int ret, d = -1; - for (i = 0; i < env->nserver; ++i) { + if (fd != -1 && (d = dup(fd)) == -1) + goto err; + + ret = imsg_compose_event(iev, type, 0, -1, d, data, len); + if (ret == -1) + goto err; + + if (d != -1) { d = -1; - if (fd != -1 && (d = dup(fd)) == -1) + /* Flush imsg to prevent fd exhaustion. 'd' will be closed. */ + if (imsgbuf_flush(&iev->ibuf) == -1) goto err; - - ret = imsg_compose_event(&env->iev_server[i], type, 0, -1, - d, data, len); - if (ret == -1) - goto err; - - /* prevent fd exhaustion */ - if (d != -1) { - if (imsgbuf_flush(&env->iev_server[i].ibuf) == -1) - goto err; - imsg_event_add(&env->iev_server[i]); - } + imsg_event_add(iev); } - if (fd != -1) - close(fd); return 0; - err: - if (fd != -1) - close(fd); + if (d != -1) + close(d); return -1; } int +main_compose_sockets(struct gotwebd *env, uint32_t type, int fd, + const void *data, uint16_t len) +{ + size_t i; + int ret = 0; + + for (i = 0; i < env->nserver; ++i) { + ret = send_imsg(&env->iev_server[i], type, fd, data, len); + if (ret) + break; + } + + return ret; +} + +int +main_compose_gotweb(struct gotwebd *env, uint32_t type, int fd, + const void *data, uint16_t len) +{ + size_t i; + int ret = 0; + + for (i = 0; i < env->nserver; ++i) { + ret = send_imsg(&env->iev_gotweb[i], type, fd, data, len); + if (ret) + break; + } + + return ret; +} + +int sockets_compose_main(struct gotwebd *env, uint32_t type, const void *d, uint16_t len) { @@ -129,7 +155,7 @@ sockets_compose_main(struct gotwebd *env, uint32_t typ } void -gotwebd_dispatch_sockets(int fd, short event, void *arg) +gotwebd_dispatch_server(int fd, short event, void *arg) { struct imsgev *iev = arg; struct imsgbuf *ibuf; @@ -179,6 +205,56 @@ gotwebd_dispatch_sockets(int fd, short event, void *ar } void +gotwebd_dispatch_gotweb(int fd, short event, void *arg) +{ + struct imsgev *iev = arg; + struct imsgbuf *ibuf; + struct imsg imsg; + struct gotwebd *env = gotwebd_env; + ssize_t n; + int shut = 0; + + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsgbuf_read(ibuf)) == -1) + fatal("imsgbuf_read error"); + if (n == 0) /* Connection closed */ + shut = 1; + } + if (event & EV_WRITE) { + if (imsgbuf_write(ibuf) == -1) + fatal("imsgbuf_write"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("imsg_get"); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case GOTWEBD_IMSG_CFG_DONE: + gotwebd_configure_done(env); + break; + default: + fatalx("%s: unknown imsg type %d", __func__, + imsg.hdr.type); + } + + imsg_free(&imsg); + } + + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +void gotwebd_sighdlr(int sig, short event, void *arg) { /* struct privsep *ps = arg; */ @@ -203,10 +279,11 @@ gotwebd_sighdlr(int sig, short event, void *arg) } } -static int -spawn_socket_process(struct gotwebd *env, const char *argv0, int n) +static void +spawn_process(struct gotwebd *env, const char *argv0, struct imsgev *iev, + enum gotwebd_proc_type proc_type, void (*handler)(int, short, void *)) { - const char *argv[8]; + const char *argv[9]; int argc = 0; int p[2]; pid_t pid; @@ -221,21 +298,26 @@ spawn_socket_process(struct gotwebd *env, const char * break; default: /* parent */ close(p[0]); - if (imsgbuf_init(&env->iev_server[n].ibuf, p[1]) == -1) + if (imsgbuf_init(&iev->ibuf, p[1]) == -1) fatal("imsgbuf_init"); - imsgbuf_allow_fdpass(&env->iev_server[n].ibuf); - env->iev_server[n].handler = gotwebd_dispatch_sockets; - env->iev_server[n].data = &env->iev_server[n]; - event_set(&env->iev_server[n].ev, p[1], EV_READ, - gotwebd_dispatch_sockets, &env->iev_server[n]); - event_add(&env->iev_server[n].ev, NULL); - return 0; + imsgbuf_allow_fdpass(&iev->ibuf); + iev->handler = handler; + iev->data = iev; + event_set(&iev->ev, p[1], EV_READ, handler, iev); + event_add(&iev->ev, NULL); + return; } close(p[1]); argv[argc++] = argv0; - argv[argc++] = "-S"; + if (proc_type == GOTWEBD_PROC_SERVER) { + argv[argc++] = "-S"; + argv[argc++] = env->user; + } else if (proc_type == GOTWEBD_PROC_GOTWEB) { + argv[argc++] = "-G"; + argv[argc++] = env->user; + } if (strcmp(env->gotwebd_conffile, GOTWEBD_CONF) != 0) { argv[argc++] = "-f"; argv[argc++] = env->gotwebd_conffile; @@ -273,12 +355,12 @@ main(int argc, char **argv) struct event sigint, sigterm, sighup, sigpipe, sigusr1; struct event_base *evb; struct gotwebd *env; - struct passwd *pw; + struct passwd *pw_gotwebd; int ch, i; int no_action = 0; - int server_proc = 0; + int proc_type = GOTWEBD_PROC_PARENT; const char *conffile = GOTWEBD_CONF; - const char *username = GOTWEBD_DEFAULT_USER; + const char *gotwebd_username = GOTWEBD_DEFAULT_USER; const char *argv0; if ((argv0 = argv[0]) == NULL) @@ -292,7 +374,7 @@ main(int argc, char **argv) fatal("%s: calloc", __func__); config_init(env); - while ((ch = getopt(argc, argv, "D:df:nSv")) != -1) { + while ((ch = getopt(argc, argv, "D:dG:f:nS:vW:")) != -1) { switch (ch) { case 'D': if (cmdline_symset(optarg) < 0) @@ -302,6 +384,10 @@ main(int argc, char **argv) case 'd': env->gotwebd_debug = 1; break; + case 'G': + proc_type = GOTWEBD_PROC_GOTWEB; + gotwebd_username = optarg; + break; case 'f': conffile = optarg; break; @@ -309,7 +395,8 @@ main(int argc, char **argv) no_action = 1; break; case 'S': - server_proc = 1; + proc_type = GOTWEBD_PROC_SERVER; + gotwebd_username = optarg; break; case 'v': if (env->gotwebd_verbose < 3) @@ -327,29 +414,32 @@ main(int argc, char **argv) gotwebd_env = env; env->gotwebd_conffile = conffile; - if (parse_config(env->gotwebd_conffile, env) == -1) - exit(1); + if (proc_type == GOTWEBD_PROC_PARENT) { + if (parse_config(env->gotwebd_conffile, env) == -1) + exit(1); - if (no_action) { - fprintf(stderr, "configuration OK\n"); - exit(0); + if (no_action) { + fprintf(stderr, "configuration OK\n"); + exit(0); + } + + if (env->user) + gotwebd_username = env->user; } + pw_gotwebd = getpwnam(gotwebd_username); + if (pw_gotwebd == NULL) + fatalx("unknown user %s", gotwebd_username); + /* check for root privileges */ if (geteuid()) fatalx("need root privileges"); - if (env->user) - username = env->user; - pw = getpwnam(username); - if (pw == NULL) - fatalx("unknown user %s", username); - env->pw = pw; - log_init(env->gotwebd_debug, LOG_DAEMON); log_setverbose(env->gotwebd_verbose); - if (server_proc) { + switch (proc_type) { + case GOTWEBD_PROC_SERVER: setproctitle("server"); log_procinit("server"); @@ -357,13 +447,31 @@ main(int argc, char **argv) fatal("chroot %s", env->httpd_chroot); if (chdir("/") == -1) fatal("chdir /"); - if (setgroups(1, &pw->pw_gid) == -1 || - setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 || - setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1) + + if (setgroups(1, &pw_gotwebd->pw_gid) == -1 || + setresgid(pw_gotwebd->pw_gid, pw_gotwebd->pw_gid, + pw_gotwebd->pw_gid) == -1 || + setresuid(pw_gotwebd->pw_uid, pw_gotwebd->pw_uid, + pw_gotwebd->pw_uid) == -1) fatal("failed to drop privileges"); sockets(env, GOTWEBD_SOCK_FILENO); return 1; + case GOTWEBD_PROC_GOTWEB: + setproctitle("gotweb"); + log_procinit("gotweb"); + + if (setgroups(1, &pw_gotwebd->pw_gid) == -1 || + setresgid(pw_gotwebd->pw_gid, pw_gotwebd->pw_gid, + pw_gotwebd->pw_gid) == -1 || + setresuid(pw_gotwebd->pw_uid, pw_gotwebd->pw_uid, + pw_gotwebd->pw_uid) == -1) + fatal("failed to drop privileges"); + + gotweb(env, GOTWEBD_SOCK_FILENO); + return 1; + default: + break; } if (!env->gotwebd_debug && daemon(1, 0) == -1) @@ -375,12 +483,16 @@ main(int argc, char **argv) env->iev_server = calloc(env->nserver, sizeof(*env->iev_server)); if (env->iev_server == NULL) fatal("calloc"); + env->iev_gotweb = calloc(env->nserver, sizeof(*env->iev_gotweb)); + if (env->iev_gotweb == NULL) + fatal("calloc"); for (i = 0; i < env->nserver; ++i) { - if (spawn_socket_process(env, argv0, i) == -1) - fatal("spawn_socket_process"); + spawn_process(env, argv0, &env->iev_server[i], + GOTWEBD_PROC_SERVER, gotwebd_dispatch_server); + spawn_process(env, argv0, &env->iev_gotweb[i], + GOTWEBD_PROC_GOTWEB, gotwebd_dispatch_gotweb); } - if (chdir("/") == -1) fatal("chdir /"); @@ -400,12 +512,15 @@ main(int argc, char **argv) signal_add(&sigpipe, NULL); signal_add(&sigusr1, NULL); - if (gotwebd_configure(env) == -1) + if (gotwebd_configure(env, + pw_gotwebd->pw_uid, pw_gotwebd->pw_gid) == -1) fatalx("configuration failed"); - if (setgroups(1, &pw->pw_gid) == -1 || - setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 || - setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1) + if (setgroups(1, &pw_gotwebd->pw_gid) == -1 || + setresgid(pw_gotwebd->pw_gid, pw_gotwebd->pw_gid, + pw_gotwebd->pw_gid) == -1 || + setresuid(pw_gotwebd->pw_uid, pw_gotwebd->pw_uid, + pw_gotwebd->pw_uid) == -1) fatal("failed to drop privileges"); #ifdef PROFILE @@ -432,24 +547,54 @@ main(int argc, char **argv) return (0); } +static void +connect_children(struct gotwebd *env) +{ + struct imsgev *iev1, *iev2; + int pipe[2]; + int i; + + for (i = 0; i < env->nserver; i++) { + iev1 = &env->iev_server[i]; + iev2 = &env->iev_gotweb[i]; + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe) == -1) + fatal("socketpair"); + + if (send_imsg(iev1, GOTWEBD_IMSG_CTL_PIPE, pipe[0], NULL, 0)) + fatal("send_imsg"); + + if (send_imsg(iev2, GOTWEBD_IMSG_CTL_PIPE, pipe[1], NULL, 0)) + fatal("send_imsg"); + + close(pipe[0]); + close(pipe[1]); + } +} + int -gotwebd_configure(struct gotwebd *env) +gotwebd_configure(struct gotwebd *env, uid_t uid, gid_t gid) { struct server *srv; struct socket *sock; /* gotweb need to reload its config. */ - env->gotwebd_reload = env->prefork_gotwebd; + env->servers_pending = env->prefork_gotwebd; + env->gotweb_pending = env->prefork_gotwebd; /* send our gotweb servers */ TAILQ_FOREACH(srv, &env->servers, entry) { - if (config_setserver(env, srv) == -1) - fatalx("%s: send server error", __func__); + if (main_compose_sockets(env, GOTWEBD_IMSG_CFG_SRV, + -1, srv, sizeof(*srv)) == -1) + fatal("main_compose_sockets GOTWEBD_IMSG_CFG_SRV"); + if (main_compose_gotweb(env, GOTWEBD_IMSG_CFG_SRV, + -1, srv, sizeof(*srv)) == -1) + fatal("main_compose_gotweb GOTWEBD_IMSG_CFG_SRV"); } /* send our sockets */ TAILQ_FOREACH(sock, &env->sockets, entry) { - if (config_setsock(env, sock) == -1) + if (config_setsock(env, sock, uid, gid) == -1) fatalx("%s: send socket error", __func__); } @@ -457,6 +602,9 @@ gotwebd_configure(struct gotwebd *env) if (config_setfd(env) == -1) fatalx("%s: send priv_fd error", __func__); + /* Connect servers and gotwebs. */ + connect_children(env); + if (main_compose_sockets(env, GOTWEBD_IMSG_CFG_DONE, -1, NULL, 0) == -1) fatal("main_compose_sockets GOTWEBD_IMSG_CFG_DONE"); @@ -466,15 +614,21 @@ gotwebd_configure(struct gotwebd *env) void gotwebd_configure_done(struct gotwebd *env) { - if (env->gotwebd_reload == 0) { - log_warnx("%s: configuration already finished", __func__); - return; + if (env->servers_pending > 0) { + env->servers_pending--; + if (env->servers_pending == 0 && + main_compose_sockets(env, GOTWEBD_IMSG_CTL_START, + -1, NULL, 0) == -1) + fatal("main_compose_sockets GOTWEBD_IMSG_CTL_START"); } - env->gotwebd_reload--; - if (env->gotwebd_reload == 0 && - main_compose_sockets(env, GOTWEBD_CTL_START, -1, NULL, 0) == -1) - fatal("main_compose_sockets GOTWEBD_CTL_START"); + if (env->gotweb_pending > 0) { + env->gotweb_pending--; + if (env->gotweb_pending == 0 && + main_compose_gotweb(env, GOTWEBD_IMSG_CTL_START, + -1, NULL, 0) == -1) + fatal("main_compose_sockets GOTWEBD_IMSG_CTL_START"); + } } void @@ -492,6 +646,14 @@ gotwebd_shutdown(void) } free(env->iev_server); + for (i = 0; i < env->nserver; ++i) { + event_del(&env->iev_gotweb[i].ev); + imsgbuf_clear(&env->iev_gotweb[i].ibuf); + close(env->iev_gotweb[i].ibuf.fd); + env->iev_gotweb[i].ibuf.fd = -1; + } + free(env->iev_gotweb); + do { pid = waitpid(WAIT_ANY, &status, 0); if (pid <= 0) blob - 2bbc2da98a42f54eda543a5d44e452364ab5402f blob + df671fde047c618123e126aabe674ba02a5bccae --- gotwebd/gotwebd.conf.5 +++ gotwebd/gotwebd.conf.5 @@ -44,15 +44,6 @@ For example: lan_addr = "192.168.0.1" listen on $lan_addr port 9090 .Ed -.Pp -Paths mentioned in -.Nm -must be relative to -.Pa /var/www , -the -.Xr chroot 2 -environment of -.Xr httpd 8 . .Sh GLOBAL CONFIGURATION The available global configuration directives are as follows: .Bl -tag -width Ds @@ -64,6 +55,11 @@ environment of If not specified, it defaults to .Pa /var/www , the home directory of the www user. +Setting the +.Ar path +to +.Pa / +effectively disables chroot. .It Ic listen on Ar address Ic port Ar number Configure an address and port for incoming FastCGI connections. Valid @@ -79,6 +75,11 @@ Configure a .Ux Ns -domain socket for incoming FastCGI connections. May be specified multiple times to build up a list of listening sockets. +.Pp +While the specified +.Ar path +must be absolute, it should usually point inside the web server's chroot +directory such that the web server can access the socket. .It Ic prefork Ar number Run the specified number of server processes. .Xr gotwebd 8 @@ -88,7 +89,7 @@ Set the .Ar user which will run .Xr gotwebd 8 . -If not specified, the user www will be used. +If not specified, the user _gotwebd will be used. .El .Pp If no @@ -123,10 +124,16 @@ Set the path to a custom Cascading Style Sheet (CSS) t If this option is not specified then the default style sheet .Sq gotweb.css will be used. +.Pp +This path must be valid in the web server's URL space since browsers +will attempt to fetch it. .It Ic logo Ar path Set the path to an image file containing a logo to be displayed. Defaults to .Sq got.png . +.Pp +This path must be valid in the web server's URL space since browsers +will attempt to fetch it. .It Ic logo_url Ar url Set a hyperlink for the logo. Defaults to @@ -141,9 +148,17 @@ Set to zero to show all the repositories without pagin .It Ic repos_path Ar path Set the path to the directory which contains Git repositories that the server should publish. +This path is absolute. +Repositories can be served even if they reside outside the web server's +chroot directory. +.Pp Defaults to .Pa /got/public -under the chroot. +inside the web server's chroot directory. +The +.Cm chroot +directive must be used before the server declaration in order to +take effect. .It Ic respect_exportok Ar on | off Set whether to display the repository only if it contains the magic .Pa git-daemon-export-ok @@ -231,13 +246,14 @@ server "localhost" { Another example, this time listening on a local port instead of the implicit .Ux -socket. +socket, and serving repositories located outside the web server's chroot: .Bd -literal -offset indent listen on 127.0.0.1 port 9000 listen on ::1 port 9000 server "localhost" { - site_name "my public repos" + site_name "my public repos" + repos_path "/var/git" } .Ed .Sh SEE ALSO blob - 7dd491f1a07f53e526bf9e5df13d9e0767649384 blob + 0610e995e02d69a510d070b3f7e4907b00c068e7 --- gotwebd/gotwebd.h +++ gotwebd/gotwebd.h @@ -32,7 +32,7 @@ #define GOTWEBD_CONF "/etc/gotwebd.conf" #ifndef GOTWEBD_DEFAULT_USER -#define GOTWEBD_DEFAULT_USER "www" +#define GOTWEBD_DEFAULT_USER "_gotwebd" #endif #define GOTWEBD_MAXDESCRSZ 1024 @@ -117,12 +117,21 @@ struct got_blob_object; struct got_tree_entry; struct got_reflist_head; +enum gotwebd_proc_type { + GOTWEBD_PROC_PARENT, + GOTWEBD_PROC_SERVER, + GOTWEBD_PROC_GOTWEB, +}; + enum imsg_type { GOTWEBD_IMSG_CFG_SRV, GOTWEBD_IMSG_CFG_SOCK, GOTWEBD_IMSG_CFG_FD, GOTWEBD_IMSG_CFG_DONE, - GOTWEBD_CTL_START, + GOTWEBD_IMSG_CTL_PIPE, + GOTWEBD_IMSG_CTL_START, + GOTWEBD_IMSG_REQ_PROCESS, + GOTWEBD_IMSG_REQ_DONE, }; struct imsgev { @@ -229,6 +238,7 @@ enum socket_priv_fds { struct template; struct request { + TAILQ_ENTRY(request) entry; struct socket *sock; struct server *srv; struct transport *t; @@ -239,12 +249,16 @@ struct request { uint16_t id; int fd; int priv_fd[PRIV_FDS__MAX]; + int resp_fd; + struct event *resp_event; + int sock_id; + uint32_t request_id; - uint8_t buf[FCGI_RECORD_SIZE]; + uint8_t *buf; size_t buf_pos; size_t buf_len; - uint8_t outbuf[GOTWEBD_CACHESIZE]; + uint8_t *outbuf; char querystring[MAX_QUERYSTRING]; char document_uri[MAX_DOCUMENT_URI]; @@ -253,6 +267,7 @@ struct request { uint8_t request_started; }; +TAILQ_HEAD(requestlist, request); struct fcgi_begin_request_body { uint16_t role; @@ -349,12 +364,12 @@ struct gotwebd { struct imsgev *iev_parent; struct imsgev *iev_server; + struct imsgev *iev_gotweb; size_t nserver; - struct passwd *pw; - uint16_t prefork_gotwebd; - int gotwebd_reload; + int servers_pending; + int gotweb_pending; int server_cnt; @@ -430,18 +445,22 @@ typedef int (*got_render_blame_line_cb)(struct templat /* gotwebd.c */ void imsg_event_add(struct imsgev *); int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, - pid_t, int, const void *, uint16_t); + pid_t, int, const void *, size_t); int main_compose_sockets(struct gotwebd *, uint32_t, int, const void *, uint16_t); int sockets_compose_main(struct gotwebd *, uint32_t, const void *, uint16_t); +int main_compose_gotweb(struct gotwebd *, uint32_t, int, + const void *, uint16_t); /* sockets.c */ void sockets(struct gotwebd *, int); void sockets_parse_sockets(struct gotwebd *); void sockets_socket_accept(int, short, void *); -int sockets_privinit(struct gotwebd *, struct socket *); +int sockets_privinit(struct gotwebd *, struct socket *, uid_t, gid_t); void sockets_purge(struct gotwebd *); +void sockets_rlimit(int); +void sockets_del_request(struct request *); /* gotweb.c */ void gotweb_index_navs(struct request *, struct gotweb_url *, int *, @@ -455,6 +474,7 @@ void gotweb_free_repo_commit(struct repo_commit *); void gotweb_free_repo_tag(struct repo_tag *); void gotweb_process_request(struct request *); void gotweb_free_transport(struct transport *); +void gotweb(struct gotwebd *, int); /* pages.tmpl */ int gotweb_render_page(struct template *, int (*)(struct template *)); @@ -507,7 +527,7 @@ const struct got_error *got_output_file_blame(struct r /* config.c */ int config_setserver(struct gotwebd *, struct server *); int config_getserver(struct gotwebd *, struct imsg *); -int config_setsock(struct gotwebd *, struct socket *); +int config_setsock(struct gotwebd *, struct socket *, uid_t, gid_t); int config_getsock(struct gotwebd *, struct imsg *); int config_setfd(struct gotwebd *); int config_getfd(struct gotwebd *, struct imsg *); blob - 5fd34708bd3654bc05060446ff5d55557747cfd3 (mode 644) blob + /dev/null --- gotwebd/libexec/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -SUBDIR = got-read-blob got-read-commit got-read-object got-read-tree \ - got-read-tag got-read-pack got-read-gitconfig got-read-gotconfig - -.include blob - 85bee26728643214f5d4570f003572ac1fc36d05 (mode 644) blob + /dev/null --- gotwebd/libexec/Makefile.inc +++ /dev/null @@ -1,11 +0,0 @@ -.include "../Makefile.inc" - -realinstall: - if [ ! -d ${DESTDIR}${CHROOT_DIR}${LIBEXECDIR}/. ]; then \ - ${INSTALL} -d -o root -g daemon -m 755 \ - ${DESTDIR}${CHROOT_DIR}${LIBEXECDIR}; \ - fi - ${INSTALL} ${INSTALL_COPY} -o root -g daemon -m 755 ${PROG} \ - ${DESTDIR}${CHROOT_DIR}${LIBEXECDIR}/${PROG} - -NOMAN = Yes blob - 9daacd33712bbd6a9ded56d7a03ed143ce6642d3 (mode 644) blob + /dev/null --- gotwebd/libexec/got-read-blob/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -.include "../../../got-version.mk" -.include "../Makefile.inc" - -PROG= got-read-blob -SRCS= got-read-blob.c error.c inflate.c object_parse.c \ - path.c privsep.c hash.c pollfd.c object_qid.c - -CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib -LDADD = -lutil -lz -DPADD = ${LIBZ} ${LIBUTIL} -LDSTATIC = ${STATIC} - -.PATH: ${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-blob - -.include blob - f45137c3ce56dbaadeaf0a5f0ffffd3b4204668c (mode 644) blob + /dev/null --- gotwebd/libexec/got-read-commit/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -.include "../../../got-version.mk" -.include "../Makefile.inc" - -PROG= got-read-commit -SRCS= got-read-commit.c error.c inflate.c object_parse.c \ - path.c privsep.c hash.c pollfd.c object_qid.c - -CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib -LDADD = -lutil -lz -DPADD = ${LIBZ} ${LIBUTIL} -LDSTATIC = ${STATIC} - -.PATH: ${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-commit - -.include blob - 44760613f030f1f1f62c831825d7bb85b07c9a46 (mode 644) blob + /dev/null --- gotwebd/libexec/got-read-gitconfig/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -.include "../../../got-version.mk" -.include "../Makefile.inc" - -PROG= got-read-gitconfig -SRCS= got-read-gitconfig.c error.c inflate.c object_parse.c \ - path.c privsep.c hash.c gitconfig.c pollfd.c \ - object_qid.c - -CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib -LDADD = -lutil -lz -DPADD = ${LIBZ} ${LIBUTIL} -LDSTATIC = ${STATIC} - -.PATH: ${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-gitconfig - -.include blob - 26dec69ed1be0648baa16cabea96c54dc3731c54 (mode 644) blob + /dev/null --- gotwebd/libexec/got-read-gotconfig/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -.include "../../../got-version.mk" -.include "../Makefile.inc" - -PROG= got-read-gotconfig -SRCS= got-read-gotconfig.c error.c inflate.c object_parse.c \ - path.c privsep.c hash.c parse.y pollfd.c object_qid.c - -CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib \ - -I${.CURDIR}/../../../libexec/got-read-gotconfig -YFLAGS = -LDADD = -lutil -lz -DPADD = ${LIBZ} ${LIBUTIL} -LDSTATIC = ${STATIC} - -CLEANFILES = parse.h - -.PATH: ${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-gotconfig - -.include blob - 85841212056443f99c873f1089e3fef315ab7d2a (mode 644) blob + /dev/null --- gotwebd/libexec/got-read-object/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -.include "../../../got-version.mk" -.include "../Makefile.inc" - -PROG= got-read-object -SRCS= got-read-object.c error.c inflate.c object_parse.c \ - path.c privsep.c hash.c pollfd.c object_qid.c - -CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib -LDADD = -lutil -lz -DPADD = ${LIBZ} ${LIBUTIL} -LDSTATIC = ${STATIC} - -.PATH: ${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-object - -.include blob - 6ae7cd0c813fcc65f4256be6d06040df2d2880ba (mode 644) blob + /dev/null --- gotwebd/libexec/got-read-pack/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -.include "../../../got-version.mk" -.include "../Makefile.inc" - -PROG= got-read-pack -SRCS= got-read-pack.c delta.c error.c inflate.c object_cache.c \ - object_idset.c object_parse.c opentemp.c pack.c path.c \ - privsep.c hash.c delta_cache.c pollfd.c object_qid.c - -CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib -LDADD = -lutil -lz -DPADD = ${LIBZ} ${LIBUTIL} -LDSTATIC = ${STATIC} - -.PATH: ${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-pack - -.include blob - eb7b682a069470aa6b445adfc142e1d7bdf7c4a4 (mode 644) blob + /dev/null --- gotwebd/libexec/got-read-tag/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -.include "../../../got-version.mk" -.include "../Makefile.inc" - -PROG= got-read-tag -SRCS= got-read-tag.c error.c inflate.c object_parse.c \ - path.c privsep.c hash.c pollfd.c object_qid.c - -CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib -LDADD = -lutil -lz -DPADD = ${LIBZ} ${LIBUTIL} -LDSTATIC = ${STATIC} - -.PATH: ${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-tag - -.include blob - 497d2ff1cd06d53bcc607653bb658219cd8ea982 (mode 644) blob + /dev/null --- gotwebd/libexec/got-read-tree/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -.include "../../../got-version.mk" -.include "../Makefile.inc" - -PROG= got-read-tree -SRCS= got-read-tree.c error.c inflate.c object_parse.c \ - path.c privsep.c hash.c pollfd.c object_qid.c - -CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib -LDADD = -lutil -lz -DPADD = ${LIBZ} ${LIBUTIL} -LDSTATIC = ${STATIC} - -.PATH: ${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-tree - -.include blob - ce616554e436816c2be0cd52555746f30ab943f9 blob + 5fc49003ef44e659fe3e1b31dc57eb30e8c6fe69 --- gotwebd/parse.y +++ gotwebd/parse.y @@ -197,10 +197,16 @@ main : PREFORK NUMBER { n = strlcpy(gotwebd->httpd_chroot, $2, sizeof(gotwebd->httpd_chroot)); if (n >= sizeof(gotwebd->httpd_chroot)) { - yyerror("%s: httpd_chroot truncated", __func__); + yyerror("chroot path too long: %s", $2); free($2); YYERROR; } + if (gotwebd->httpd_chroot[0] != '/') { + yyerror("chroot path must be an absolute path: " + "bad path %s", gotwebd->httpd_chroot); + free($2); + YYERROR; + } free($2); } | LISTEN ON listen_addr PORT STRING { @@ -832,7 +838,18 @@ parse_config(const char *filename, struct gotwebd *env /* add the implicit listen on socket */ if (TAILQ_EMPTY(&gotwebd->addresses)) { - const char *path = D_HTTPD_CHROOT D_UNIX_SOCKET; + char path[_POSIX_PATH_MAX]; + + if (strlcpy(path, gotwebd->httpd_chroot, sizeof(path)) + >= sizeof(path)) { + yyerror("chroot path too long: %s", + gotwebd->httpd_chroot); + } + if (strlcat(path, D_UNIX_SOCKET, sizeof(path)) + >= sizeof(path)) { + yyerror("chroot path too long: %s", + gotwebd->httpd_chroot); + } if (get_unix_addr(path) == -1) yyerror("can't listen on %s", path); } @@ -858,10 +875,14 @@ conf_new_server(const char *name) n = strlcpy(srv->name, name, sizeof(srv->name)); if (n >= sizeof(srv->name)) fatalx("%s: strlcpy", __func__); - n = strlcpy(srv->repos_path, D_GOTPATH, + n = strlcpy(srv->repos_path, gotwebd->httpd_chroot, sizeof(srv->repos_path)); if (n >= sizeof(srv->repos_path)) fatalx("%s: strlcpy", __func__); + n = strlcat(srv->repos_path, D_GOTPATH, + sizeof(srv->repos_path)); + if (n >= sizeof(srv->repos_path)) + fatalx("%s: strlcat", __func__); n = strlcpy(srv->site_name, D_SITENAME, sizeof(srv->site_name)); if (n >= sizeof(srv->site_name)) blob - 4ca6de208a73860f721177a094834ffc846d1aaf blob + fe41e8a1588f58c48515f05270570fa6735580c8 --- gotwebd/sockets.c +++ gotwebd/sockets.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -49,11 +50,7 @@ #include #include -#include "got_error.h" -#include "got_opentemp.h" #include "got_reference.h" -#include "got_repository.h" -#include "got_privsep.h" #include "gotwebd.h" #include "log.h" @@ -62,18 +59,17 @@ #define SOCKS_BACKLOG 5 #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) -volatile int client_cnt; +static volatile int client_cnt; static struct timeval timeout = { TIMEOUT_DEFAULT, 0 }; static void sockets_sighdlr(int, short, void *); static void sockets_shutdown(void); -static void sockets_launch(void); +static void sockets_launch(struct gotwebd *); static void sockets_accept_paused(int, short, void *); -static void sockets_rlimit(int); static void sockets_dispatch_main(int, short, void *); -static int sockets_unix_socket_listen(struct gotwebd *, struct socket *); +static int sockets_unix_socket_listen(struct gotwebd *, struct socket *, uid_t, gid_t); static int sockets_create_socket(struct address *); static int sockets_accept_reserve(int, struct sockaddr *, socklen_t *, int, volatile int *); @@ -83,12 +79,94 @@ static struct socket *sockets_conf_new_socket(struct g int cgi_inflight = 0; +/* Request hash table needs some spare room to avoid collisions. */ +struct requestlist requests[GOTWEBD_MAXCLIENTS * 4]; +static SIPHASH_KEY requests_hash_key; + +static void +requests_init(void) +{ + int i; + + arc4random_buf(&requests_hash_key, sizeof(requests_hash_key)); + + for (i = 0; i < nitems(requests); i++) + TAILQ_INIT(&requests[i]); +} + +static uint64_t +request_hash(uint32_t request_id) +{ + return SipHash24(&requests_hash_key, &request_id, sizeof(request_id)); +} + +static void +add_request(struct request *c) +{ + uint64_t slot = request_hash(c->request_id) % nitems(requests); + TAILQ_INSERT_HEAD(&requests[slot], c, entry); + client_cnt++; +} + void +sockets_del_request(struct request *c) +{ + uint64_t slot = request_hash(c->request_id) % nitems(requests); + TAILQ_REMOVE(&requests[slot], c, entry); + client_cnt--; +} + +static struct request * +find_request(uint32_t request_id) +{ + uint64_t slot; + struct request *c; + + slot = request_hash(request_id) % nitems(requests); + TAILQ_FOREACH(c, &requests[slot], entry) { + if (c->request_id == request_id) + return c; + } + + return NULL; +} + +static void +requests_purge(void) +{ + uint64_t slot; + struct request *c; + + for (slot = 0; slot < nitems(requests); slot++) { + while (!TAILQ_EMPTY(&requests[slot])) { + c = TAILQ_FIRST(&requests[slot]); + fcgi_cleanup_request(c); + } + } +} + +static uint32_t +get_request_id(void) +{ + int duplicate = 0; + uint32_t id; + + do { + id = arc4random(); + duplicate = (find_request(id) != NULL); + } while (duplicate || id == 0); + + return id; +} + +void sockets(struct gotwebd *env, int fd) { struct event sighup, sigint, sigusr1, sigchld, sigterm; struct event_base *evb; + requests_init(); + evb = event_init(); sockets_rlimit(-1); @@ -118,8 +196,7 @@ sockets(struct gotwebd *env, int fd) signal_add(&sigterm, NULL); #ifndef PROFILE - if (pledge("stdio rpath inet recvfd proc exec sendfd unveil", - NULL) == -1) + if (pledge("stdio inet recvfd sendfd", NULL) == -1) fatal("pledge"); #endif @@ -190,12 +267,13 @@ sockets_conf_new_socket(struct gotwebd *env, int id, s } static void -sockets_launch(void) +sockets_launch(struct gotwebd *env) { struct socket *sock; - struct server *srv; - const struct got_error *error; + if (env->iev_gotweb == NULL) + fatal("gotweb process not connected"); + TAILQ_FOREACH(sock, &gotwebd_env->sockets, entry) { log_info("%s: configuring socket %d (%d)", __func__, sock->conf.id, sock->fd); @@ -212,17 +290,12 @@ sockets_launch(void) sock->conf.id); } - TAILQ_FOREACH(srv, &gotwebd_env->servers, entry) { - if (unveil(srv->repos_path, "r") == -1) - fatal("unveil %s", srv->repos_path); - } +#ifndef PROFILE + if (pledge("stdio inet sendfd", NULL) == -1) + fatal("pledge"); +#endif + event_add(&env->iev_gotweb->ev, NULL); - error = got_privsep_unveil_exec_helpers(); - if (error) - fatal("%s", error->msg); - - if (unveil(NULL, NULL) == -1) - fatal("unveil"); } void @@ -246,6 +319,110 @@ sockets_purge(struct gotwebd *env) } static void +request_done(struct imsg *imsg) +{ + struct request *c; + uint32_t request_id; + size_t datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + + if (datalen != sizeof(request_id)) { + log_warn("IMSG_REQ_DONE with bad data length"); + return; + } + + memcpy(&request_id, imsg->data, sizeof(request_id)); + + c = find_request(request_id); + if (c == NULL) { + log_warnx("no request to clean up found for ID '%d'", + request_id); + return; + } + + fcgi_create_end_record(c); + fcgi_cleanup_request(c); +} + +static void +server_dispatch_gotweb(int fd, short event, void *arg) +{ + struct imsgev *iev = arg; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + int shut = 0; + + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsgbuf_read(ibuf)) == -1) + fatal("imsgbuf_read error"); + if (n == 0) /* Connection closed */ + shut = 1; + } + if (event & EV_WRITE) { + if (imsgbuf_write(ibuf) == -1) + fatal("imsgbuf_write"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("imsg_get"); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case GOTWEBD_IMSG_REQ_DONE: + request_done(&imsg); + break; + default: + fatalx("%s: unknown imsg type %d", __func__, + imsg.hdr.type); + } + + imsg_free(&imsg); + } + + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +static void +recv_gotweb_pipe(struct gotwebd *env, struct imsg *imsg) +{ + struct imsgev *iev; + int fd; + + if (env->iev_gotweb != NULL) { + log_warn("gotweb pipe already received"); + return; + } + + fd = imsg_get_fd(imsg); + if (fd == -1) + fatalx("invalid gotweb pipe fd"); + + iev = calloc(1, sizeof(*iev)); + if (iev == NULL) + fatal("calloc"); + + if (imsgbuf_init(&iev->ibuf, fd) == -1) + fatal("imsgbuf_init"); + imsgbuf_allow_fdpass(&iev->ibuf); + + iev->handler = server_dispatch_gotweb; + iev->data = iev; + event_set(&iev->ev, fd, EV_READ, server_dispatch_gotweb, iev); + + env->iev_gotweb = iev; +} + +static void sockets_dispatch_main(int fd, short event, void *arg) { struct imsgev *iev = arg; @@ -281,15 +458,15 @@ sockets_dispatch_main(int fd, short event, void *arg) case GOTWEBD_IMSG_CFG_SOCK: config_getsock(env, &imsg); break; - case GOTWEBD_IMSG_CFG_FD: - config_getfd(env, &imsg); - break; case GOTWEBD_IMSG_CFG_DONE: config_getcfg(env, &imsg); break; - case GOTWEBD_CTL_START: - sockets_launch(); + case GOTWEBD_IMSG_CTL_PIPE: + recv_gotweb_pipe(env, &imsg); break; + case GOTWEBD_IMSG_CTL_START: + sockets_launch(env); + break; default: fatalx("%s: unknown imsg type %d", __func__, imsg.hdr.type); @@ -354,6 +531,8 @@ sockets_shutdown(void) free(h); } + requests_purge(); + imsgbuf_clear(&gotwebd_env->iev_parent->ibuf); free(gotwebd_env->iev_parent); free(gotwebd_env); @@ -362,12 +541,12 @@ sockets_shutdown(void) } int -sockets_privinit(struct gotwebd *env, struct socket *sock) +sockets_privinit(struct gotwebd *env, struct socket *sock, uid_t uid, gid_t gid) { if (sock->conf.af_type == AF_UNIX) { log_info("%s: initializing unix socket %s", __func__, sock->conf.unix_socket_name); - sock->fd = sockets_unix_socket_listen(env, sock); + sock->fd = sockets_unix_socket_listen(env, sock, uid, gid); if (sock->fd == -1) return -1; } @@ -385,7 +564,8 @@ sockets_privinit(struct gotwebd *env, struct socket *s } static int -sockets_unix_socket_listen(struct gotwebd *env, struct socket *sock) +sockets_unix_socket_listen(struct gotwebd *env, struct socket *sock, + uid_t uid, gid_t gid) { int u_fd = -1; mode_t old_umask, mode; @@ -425,8 +605,7 @@ sockets_unix_socket_listen(struct gotwebd *env, struct return -1; } - if (chown(sock->conf.unix_socket_name, env->pw->pw_uid, - env->pw->pw_gid) == -1) { + if (chown(sock->conf.unix_socket_name, uid, gid) == -1) { log_warn("%s: chown", __func__); close(u_fd); (void)unlink(sock->conf.unix_socket_name); @@ -521,6 +700,7 @@ sockets_socket_accept(int fd, short event, void *arg) struct sockaddr_storage ss; struct timeval backoff; struct request *c = NULL; + uint8_t *buf = NULL; socklen_t len; int s; @@ -557,28 +737,32 @@ sockets_socket_accept(int fd, short event, void *arg) c = calloc(1, sizeof(struct request)); if (c == NULL) { - log_warn("%s", __func__); + log_warn("%s: calloc", __func__); close(s); cgi_inflight--; return; } - c->tp = template(c, &fcgi_write, c->outbuf, sizeof(c->outbuf)); - if (c->tp == NULL) { - log_warn("%s", __func__); + buf = calloc(1, FCGI_RECORD_SIZE); + if (buf == NULL) { + log_warn("%s: calloc", __func__); close(s); cgi_inflight--; free(c); return; } + c->buf = buf; c->fd = s; + c->resp_fd = -1; c->sock = sock; memcpy(c->priv_fd, gotwebd_env->priv_fd, sizeof(c->priv_fd)); + c->sock_id = sock->conf.id; c->buf_pos = 0; c->buf_len = 0; c->request_started = 0; c->sock->client_status = CLIENT_CONNECT; + c->request_id = get_request_id(); event_set(&c->ev, s, EV_READ|EV_PERSIST, fcgi_request, c); event_add(&c->ev, NULL); @@ -586,8 +770,7 @@ sockets_socket_accept(int fd, short event, void *arg) evtimer_set(&c->tmo, fcgi_timeout, c); evtimer_add(&c->tmo, &timeout); - client_cnt++; - + add_request(c); return; err: cgi_inflight--; @@ -596,7 +779,7 @@ err: free(c); } -static void +void sockets_rlimit(int maxfd) { struct rlimit rl; blob - 803eb4958024659ae980b5f51216764b4ebbe37b blob + 9cb243fe1e217fa2ced376a1954ea7ba7faa879b --- regress/gotwebd/Makefile +++ regress/gotwebd/Makefile @@ -59,14 +59,8 @@ ensure_root: false; \ fi -gotwebd_libexec: - @su -m ${GOTWEBD_TEST_USER} -c \ - '${MAKE} -C ${.CURDIR}/../../gotwebd/libexec' >/dev/null 2>&1 - -prepare_test_env: ensure_root gotwebd_libexec +prepare_test_env: ensure_root @mkdir -p "${GOTWEBD_TEST_CHROOT}" - @DESTDIR=${GOTWEBD_TEST_ROOT} \ - ${MAKE} -C ${.CURDIR}/../../gotwebd/libexec install >/dev/null 2>&1 @chown ${GOTWEBD_TEST_USER} "${GOTWEBD_TEST_ROOT}" \ "${GOTWEBD_TEST_CHROOT}"