commit b27be572b3b9afbcc52cb800bfacbd20218d4177 from: Stefan Sperling date: Mon Apr 14 20:08:52 2025 UTC split gotwebd into a server process and a gotweb process As before, the server process accepts incoming FCGI requests and runs in the web server's chroot. The gotweb process runs outside chroot. This allows for browsing repositories anywhere on the file system, saving disk space on servers and avoiding a repository eynchronization delay. The gotweb process runs libexec helpers installed in /usr/local. Installing static binaries in the chroot is no longer needed for gotwebd to function. This helps -portable. gotwebd now defaults to a new user account, _gotwebd, though the www user can still be used if desired. The gotwebd.conf repos_path directive is no longer relative to the chroot directory but is now an absolute path. Existing configs may need to be adjusted accordingly. ok op@ commit - 371384deec49fde240400ddd0867d8488ba0eed4 commit + b27be572b3b9afbcc52cb800bfacbd20218d4177 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) { @@ -1296,4 +1397,273 @@ gotweb_render_age(struct template *tp, time_t committe return -1; } 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 + f66b9f38c71db3ea73f08f317e7446e2d3929e59 --- 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,39 +87,64 @@ imsg_compose_event(struct imsgev *iev, uint16_t type, return (ret); } +static int +send_imsg(struct imsgev *iev, uint32_t type, int fd, const void *data, + uint16_t len) +{ + int ret, d = -1; + + 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; + /* Flush imsg to prevent fd exhaustion. 'd' will be closed. */ + if (imsgbuf_flush(&iev->ibuf) == -1) + goto err; + imsg_event_add(iev); + } + + return 0; +err: + 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, d; + size_t i; + int ret = 0; for (i = 0; i < env->nserver; ++i) { - d = -1; - if (fd != -1 && (d = dup(fd)) == -1) - goto err; + ret = send_imsg(&env->iev_server[i], type, fd, data, len); + if (ret) + break; + } - ret = imsg_compose_event(&env->iev_server[i], type, 0, -1, - d, data, len); - if (ret == -1) - goto err; + return ret; +} - /* prevent fd exhaustion */ - if (d != -1) { - if (imsgbuf_flush(&env->iev_server[i].ibuf) == -1) - goto err; - imsg_event_add(&env->iev_server[i]); - } +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; } - if (fd != -1) - close(fd); - return 0; - -err: - if (fd != -1) - close(fd); - return -1; + return ret; } int @@ -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,12 @@ 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, const char *username, + void (*handler)(int, short, void *)) { - const char *argv[8]; + const char *argv[9]; int argc = 0; int p[2]; pid_t pid; @@ -221,21 +299,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++] = username; + } else if (proc_type == GOTWEBD_PROC_GOTWEB) { + argv[argc++] = "-G"; + argv[argc++] = username; + } if (strcmp(env->gotwebd_conffile, GOTWEBD_CONF) != 0) { argv[argc++] = "-f"; argv[argc++] = env->gotwebd_conffile; @@ -276,9 +359,11 @@ main(int argc, char **argv) struct passwd *pw; 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 *www_username = GOTWEBD_WWW_USER; + gid_t www_gid; const char *argv0; if ((argv0 = argv[0]) == NULL) @@ -292,7 +377,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 +387,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 +398,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 +417,39 @@ 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; + if (env->www_user) + www_username = env->www_user; } + pw = getpwnam(www_username); + if (pw == NULL) + fatalx("unknown user %s", www_username); + www_gid = pw->pw_gid; + + pw = getpwnam(gotwebd_username); + if (pw == 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,6 +457,7 @@ 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) @@ -364,6 +465,19 @@ main(int argc, char **argv) sockets(env, GOTWEBD_SOCK_FILENO); return 1; + case GOTWEBD_PROC_GOTWEB: + setproctitle("gotweb"); + log_procinit("gotweb"); + + 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) + fatal("failed to drop privileges"); + + gotweb(env, GOTWEBD_SOCK_FILENO); + return 1; + default: + break; } if (!env->gotwebd_debug && daemon(1, 0) == -1) @@ -375,12 +489,18 @@ 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_username, + gotwebd_dispatch_server); + spawn_process(env, argv0, &env->iev_gotweb[i], + GOTWEBD_PROC_GOTWEB, gotwebd_username, + gotwebd_dispatch_gotweb); } - if (chdir("/") == -1) fatal("chdir /"); @@ -400,7 +520,7 @@ main(int argc, char **argv) signal_add(&sigpipe, NULL); signal_add(&sigusr1, NULL); - if (gotwebd_configure(env) == -1) + if (gotwebd_configure(env, pw->pw_uid, www_gid) == -1) fatalx("configuration failed"); if (setgroups(1, &pw->pw_gid) == -1 || @@ -432,24 +552,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 +607,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 +619,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 +651,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 + 562d13bf30399506f8f4f2c9eca0d103cf6e5cdd --- 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 @@ -87,6 +88,14 @@ runs 3 server processes by default. Set the .Ar user which will run +.Xr gotwebd 8 . +If not specified, the user _gotwebd will be used. +.It Ic www user Ar user +Set the +.Ar user +which runs +.Xr httpd 8 . +Needed to ensure that the web server can access UNIX-domain sockets created by .Xr gotwebd 8 . If not specified, the user www will be used. .El @@ -123,10 +132,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 +156,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 @@ -221,6 +244,8 @@ listening socket. .Sh EXAMPLES A sample configuration: .Bd -literal -offset indent +www user "www" # www username needs quotes since www is a keyword + server "localhost" { site_name "my public repos" site_owner "Flan Hacker" @@ -231,13 +256,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 + 27a53cfbf3baff7f391f1445c503ba9712923fc6 --- gotwebd/gotwebd.h +++ gotwebd/gotwebd.h @@ -32,9 +32,13 @@ #define GOTWEBD_CONF "/etc/gotwebd.conf" #ifndef GOTWEBD_DEFAULT_USER -#define GOTWEBD_DEFAULT_USER "www" +#define GOTWEBD_DEFAULT_USER "_gotwebd" #endif +#ifndef GOTWEBD_WWW_USER +#define GOTWEBD_WWW_USER "www" +#endif + #define GOTWEBD_MAXDESCRSZ 1024 #define GOTWEBD_MAXCLONEURLSZ 1024 #define GOTWEBD_CACHESIZE 1024 @@ -117,12 +121,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 +242,7 @@ enum socket_priv_fds { struct template; struct request { + TAILQ_ENTRY(request) entry; struct socket *sock; struct server *srv; struct transport *t; @@ -239,12 +253,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 +271,7 @@ struct request { uint8_t request_started; }; +TAILQ_HEAD(requestlist, request); struct fcgi_begin_request_body { uint16_t role; @@ -342,6 +361,7 @@ struct gotwebd { int priv_fd[PRIV_FDS__MAX]; char *user; + char *www_user; const char *gotwebd_conffile; int gotwebd_debug; @@ -349,12 +369,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 +450,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 +479,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 +532,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 - ce616554e436816c2be0cd52555746f30ab943f9 blob + 28cc307a5e9e04f189781f0f3c7c189a87700a17 --- gotwebd/parse.y +++ gotwebd/parse.y @@ -110,7 +110,7 @@ typedef struct { %} -%token LISTEN WWW_PATH SITE_NAME SITE_OWNER SITE_LINK LOGO +%token LISTEN WWW SITE_NAME SITE_OWNER SITE_LINK LOGO %token LOGO_URL SHOW_REPO_OWNER SHOW_REPO_AGE SHOW_REPO_DESCRIPTION %token MAX_REPOS_DISPLAY REPOS_PATH MAX_COMMITS_DISPLAY ON ERROR %token SHOW_SITE_OWNER SHOW_REPO_CLONEURL PORT PREFORK RESPECT_EXPORTOK @@ -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 { @@ -241,6 +247,12 @@ main : PREFORK NUMBER { free(gotwebd->user); gotwebd->user = $2; } + | WWW USER STRING { + if (gotwebd->www_user != NULL) + yyerror("www user already specified"); + free(gotwebd->www_user); + gotwebd->www_user = $3; + } ; server : SERVER STRING { @@ -467,6 +479,7 @@ lookup(char *s) { "summary_commits_display", SUMMARY_COMMITS_DISPLAY }, { "summary_tags_display", SUMMARY_TAGS_DISPLAY }, { "user", USER }, + { "www", WWW }, }; const struct keywords *p; @@ -832,7 +845,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 +882,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;