Download raw body.
gotwebd: parse fcgi records under pledge("stdio")
This adds another gotwebd process which runs with pledge("stdio") and moves the fcgi params parsing into this process. My long-term plan is to also parse the querystring in this process such that gotweb.c and other processes which we need for supporting authentication will not have to do any parsing themselves but just deal with data in structs and fixed-sized buffers. This diff is not well tested and probably has issues. I mainly want to see if people think this is going in the right direction. Is it? This will only compile on -current. The new imsg_set_maxsize() API in -current makes it easy to pass larger structures around so we won't need to do manual chunking even if our request and querystring structs are somewhat large. (One known issue is that requests aren't terminated properly and need to time out to be removed in sockets.c, because gotweb.c does not yet notify sockets.c when a request is finished.) M gotwebd/fcgi.c | 321+ 171- M gotwebd/gotweb.c | 14+ 12- M gotwebd/gotwebd.c | 47+ 6- M gotwebd/gotwebd.h | 26+ 6- M gotwebd/pages.tmpl | 1+ 1- M gotwebd/sockets.c | 475+ 8- 6 files changed, 884 insertions(+), 204 deletions(-) commit - 0f01ac5d813fe34a941ee33dbb19dba78ddbe966 commit + c22f99fc2d8535e043a5ab37e0deb39d11575c13 blob - 0f92cc1b3d0cdafc2c5d0232698fed51772579fe blob + 3940b628da0b7fc9748f5051c23296f4a8330d61 --- gotwebd/fcgi.c +++ gotwebd/fcgi.c @@ -25,6 +25,7 @@ #include <errno.h> #include <event.h> #include <imsg.h> +#include <signal.h> #include <stdarg.h> #include <stdlib.h> #include <stdio.h> @@ -41,10 +42,14 @@ #include "log.h" #include "tmpl.h" -size_t fcgi_parse_record(uint8_t *, size_t, struct request *); -void fcgi_parse_begin_request(uint8_t *, uint16_t, struct request *, +static void fcgi_sighdlr(int, short, void *); +static void fcgi_shutdown(void); +static void fcgi_launch(struct gotwebd *); + +void fcgi_parse_record(struct gotwebd_fcgi_record *); +int fcgi_parse_begin_request(uint8_t *, uint16_t, struct request *, uint16_t); -void fcgi_parse_params(uint8_t *, uint16_t, struct request *, uint16_t); +int fcgi_parse_params(uint8_t *, uint16_t, struct gotwebd_fcgi_params *); int fcgi_send_response(struct request *, int, const void *, size_t); void dump_fcgi_request_body(const char *, struct fcgi_record_header *); @@ -54,200 +59,127 @@ void dump_fcgi_begin_request_body(const char *, void dump_fcgi_end_request_body(const char *, struct fcgi_end_request_body *); -extern int cgi_inflight; extern struct requestlist requests; -void -fcgi_request(int fd, short events, void *arg) +static void +fcgi_shutdown(void) { - struct request *c = arg; - ssize_t n; - size_t parsed = 0; + imsgbuf_clear(&gotwebd_env->iev_parent->ibuf); + free(gotwebd_env->iev_parent); + if (gotwebd_env->iev_server) { + imsgbuf_clear(&gotwebd_env->iev_server->ibuf); + free(gotwebd_env->iev_server); + } - n = read(fd, c->buf + c->buf_pos + c->buf_len, - FCGI_RECORD_SIZE - c->buf_pos - c->buf_len); + free(gotwebd_env); - switch (n) { - case -1: - switch (errno) { - case EINTR: - case EAGAIN: - event_add(&c->ev, NULL); - return; - default: - goto fail; - } + exit(0); +} + +static void +fcgi_sighdlr(int sig, short event, void *arg) +{ + switch (sig) { + case SIGHUP: + log_info("%s: ignoring SIGHUP", __func__); break; - case 0: - if (c->client_status == CLIENT_CONNECT) { - log_warnx("client %u closed connection too early", - c->request_id); - goto fail; - } - return; + 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: + fcgi_shutdown(); + break; default: + log_warn("unexpected signal %d", sig); break; } +} - c->buf_len += n; +static void +send_parsed_params(struct gotwebd_fcgi_params *params) +{ + struct gotwebd *env = gotwebd_env; - /* - * Parse the records as they are received. Per the FastCGI - * specification, the server need only receive the FastCGI - * parameter records in full; it is free to begin execution - * at that point, which is what happens here. - */ - do { - parsed = fcgi_parse_record(c->buf + c->buf_pos, c->buf_len, c); + if (imsg_compose_event(env->iev_server, GOTWEBD_IMSG_FCGI_PARAMS, + GOTWEBD_PROC_SERVER, -1, -1, params, sizeof(*params)) == -1) + log_warn("imsg_compose_event"); +} - /* - * When we start to actually process the entry, we - * send the request to the gotweb process, so we're - * done. - */ - if (c->client_status == CLIENT_REQUEST) { - fcgi_cleanup_request(c); - return; - } +static void +abort_request(uint32_t request_id) +{ + struct gotwebd *env = gotwebd_env; - if (parsed != 0) { - c->buf_pos += parsed; - c->buf_len -= parsed; - } - - /* drop the parsed record */ - if (parsed != 0 && c->buf_len > 0) { - memmove(c->buf, c->buf + c->buf_pos, c->buf_len); - c->buf_pos = 0; - } - } while (parsed > 0 && c->buf_len > 0); - - event_add(&c->ev, NULL); - return; -fail: - fcgi_cleanup_request(c); + if (imsg_compose_event(env->iev_server, GOTWEBD_IMSG_REQ_ABORT, + GOTWEBD_PROC_SERVER, -1, -1, &request_id, sizeof(request_id)) == -1) + log_warn("imsg_compose_event"); } -size_t -fcgi_parse_record(uint8_t *buf, size_t n, struct request *c) +void +fcgi_parse_record(struct gotwebd_fcgi_record *rec) { struct fcgi_record_header *h; + uint8_t *record_body; + struct gotwebd_fcgi_params params = { 0 }; - if (n < sizeof(struct fcgi_record_header)) - return 0; + if (rec->record_len < sizeof(struct fcgi_record_header) || + rec->record_len > sizeof(rec->record)) { + log_warnx("invalid fcgi record size"); + abort_request(rec->request_id); + return; + } - h = (struct fcgi_record_header*) buf; + h = (struct fcgi_record_header *)&rec->record[0]; dump_fcgi_record_header("", h); - if (n < sizeof(struct fcgi_record_header) + ntohs(h->content_len) - + h->padding_len) - return 0; + if (rec->record_len != sizeof(*h) + ntohs(h->content_len) + + h->padding_len) { + abort_request(rec->request_id); + return; + } dump_fcgi_request_body("", h); - if (h->version != 1) - log_warn("wrong version"); + if (h->version != 1) { + log_warn("wrong fcgi header version: %u", h->version); + abort_request(rec->request_id); + return; + } + record_body = &rec->record[sizeof(*h)]; switch (h->type) { - case FCGI_BEGIN_REQUEST: - fcgi_parse_begin_request(buf + - sizeof(struct fcgi_record_header), - ntohs(h->content_len), c, ntohs(h->id)); - break; case FCGI_PARAMS: - fcgi_parse_params(buf + sizeof(struct fcgi_record_header), - ntohs(h->content_len), c, ntohs(h->id)); + if (fcgi_parse_params(record_body, + ntohs(h->content_len), ¶ms) == -1) { + abort_request(rec->request_id); + break; + } + params.request_id = rec->request_id; + send_parsed_params(¶ms); break; - case FCGI_STDIN: - return 0; - case FCGI_ABORT_REQUEST: - fcgi_create_end_record(c); - fcgi_cleanup_request(c); - return 0; default: - log_warn("unimplemented type %d", h->type); + log_warn("unexpected fcgi type %d", h->type); + abort_request(rec->request_id); break; } - - return (sizeof(struct fcgi_record_header) + ntohs(h->content_len) - + h->padding_len); } -void -fcgi_parse_begin_request(uint8_t *buf, uint16_t n, - struct request *c, uint16_t id) +int +fcgi_parse_params(uint8_t *buf, uint16_t n, struct gotwebd_fcgi_params *params) { - /* XXX -- FCGI_CANT_MPX_CONN */ - if (c->request_started) { - log_warn("unexpected FCGI_BEGIN_REQUEST, ignoring"); - return; - } - - if (n != sizeof(struct fcgi_begin_request_body)) { - log_warn("wrong size %d != %lu", n, - sizeof(struct fcgi_begin_request_body)); - return; - } - - c->request_started = 1; - c->id = id; -} - -static void -process_request(struct request *c) -{ - struct gotwebd *env = gotwebd_env; - int ret, i; - struct request ic; - - 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; - - ret = imsg_compose_event(env->iev_gotweb, GOTWEBD_IMSG_REQ_PROCESS, - GOTWEBD_PROC_SERVER, -1, c->fd, &ic, sizeof(ic)); - if (ret == -1) { - log_warn("imsg_compose_event"); - close(c->fd); - } - c->fd = -1; - - c->client_status = CLIENT_REQUEST; -} - -void -fcgi_parse_params(uint8_t *buf, uint16_t n, struct request *c, uint16_t id) -{ uint32_t name_len, val_len; uint8_t *val; - if (!c->request_started) { - log_warn("FCGI_PARAMS without FCGI_BEGIN_REQUEST, ignoring"); - return; - } + if (n == 0) + return 0; - if (c->id != id) { - log_warn("unexpected id, ignoring"); - return; - } - - if (n == 0) { - process_request(c); - return; - } - while (n > 0) { if (buf[0] >> 7 == 0) { name_len = buf[0]; @@ -260,11 +192,11 @@ fcgi_parse_params(uint8_t *buf, uint16_t n, struct req n -= 4; buf += 4; } else - return; + return -1; } if (n == 0) - return; + return -1; if (buf[0] >> 7 == 0) { val_len = buf[0]; @@ -278,42 +210,45 @@ fcgi_parse_params(uint8_t *buf, uint16_t n, struct req n -= 4; buf += 4; } else - return; + return -1; } if (n < name_len + val_len) - return; + return -1; val = buf + name_len; if (val_len < MAX_QUERYSTRING && name_len == 12 && strncmp(buf, "QUERY_STRING", 12) == 0) { - memcpy(c->querystring, val, val_len); - c->querystring[val_len] = '\0'; + /* TODO: parse querystring here */ + memcpy(params->querystring, val, val_len); + params->querystring[val_len] = '\0'; } if (val_len < MAX_DOCUMENT_URI && name_len == 12 && strncmp(buf, "DOCUMENT_URI", 12) == 0) { - memcpy(c->document_uri, val, val_len); - c->document_uri[val_len] = '\0'; + memcpy(params->document_uri, val, val_len); + params->document_uri[val_len] = '\0'; } if (val_len < MAX_SERVER_NAME && name_len == 11 && strncmp(buf, "SERVER_NAME", 11) == 0) { - memcpy(c->server_name, val, val_len); - c->server_name[val_len] = '\0'; + memcpy(params->server_name, val, val_len); + params->server_name[val_len] = '\0'; } if (name_len == 5 && strncmp(buf, "HTTPS", 5) == 0) - c->https = 1; + params->https = 1; buf += name_len + val_len; n -= name_len - val_len; } + + return 0; } void @@ -453,8 +388,6 @@ fcgi_create_end_record(struct request *c) void fcgi_cleanup_request(struct request *c) { - cgi_inflight--; - if (evtimer_initialized(&c->tmo)) evtimer_del(&c->tmo); if (event_initialized(&c->ev)) @@ -510,3 +443,220 @@ dump_fcgi_end_request_body(const char *p, struct fcgi_ log_debug("%sappStatus: %d", p, ntohl(b->app_status)); log_debug("%sprotocolStatus: %d", p, b->protocol_status); } + +static void +fcgi_launch(struct gotwebd *env) +{ + if (env->iev_server == NULL) + fatalx("server process not connected"); +#ifndef PROFILE + if (pledge("stdio", NULL) == -1) + fatal("pledge"); +#endif + event_add(&env->iev_server->ev, NULL); +} + +static struct gotwebd_fcgi_record * +recv_record(struct imsg *imsg) +{ + struct gotwebd_fcgi_record *record; + + record = calloc(1, sizeof(*record)); + if (record == NULL) { + log_warn("calloc"); + return NULL; + } + + if (imsg_get_data(imsg, record, sizeof(*record)) == -1) { + log_warn("imsg_get_data"); + free(record); + return NULL; + } + + return record; +} + +static void +fcgi_dispatch_server(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_FCGI_PARSE_PARAMS: { + struct gotwebd_fcgi_record *rec; + + rec = recv_record(&imsg); + if (rec) { + fcgi_parse_record(rec); + free(rec); + } + 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); + imsgbuf_set_maxsize(&iev->ibuf, sizeof(struct gotwebd_fcgi_record)); + + iev->handler = fcgi_dispatch_server; + iev->data = iev; + event_set(&iev->ev, fd, EV_READ, fcgi_dispatch_server, iev); + imsg_event_add(iev); + + env->iev_server = iev; +} + +static void +fcgi_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_DONE: + config_getcfg(env, &imsg); + break; + case GOTWEBD_IMSG_CTL_PIPE: + recv_server_pipe(env, &imsg); + break; + case GOTWEBD_IMSG_CTL_START: + fcgi_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 +gotwebd_fcgi(struct gotwebd *env, int fd) +{ + struct event sighup, sigint, sigusr1, sigchld, sigterm; + struct event_base *evb; + + evb = event_init(); + + 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 = fcgi_dispatch_main; + env->iev_parent->data = env->iev_parent; + event_set(&env->iev_parent->ev, fd, EV_READ, fcgi_dispatch_main, + env->iev_parent); + event_add(&env->iev_parent->ev, NULL); + + signal(SIGPIPE, SIG_IGN); + + signal_set(&sighup, SIGHUP, fcgi_sighdlr, env); + signal_add(&sighup, NULL); + signal_set(&sigint, SIGINT, fcgi_sighdlr, env); + signal_add(&sigint, NULL); + signal_set(&sigusr1, SIGUSR1, fcgi_sighdlr, env); + signal_add(&sigusr1, NULL); + signal_set(&sigchld, SIGCHLD, fcgi_sighdlr, env); + signal_add(&sigchld, NULL); + signal_set(&sigterm, SIGTERM, fcgi_sighdlr, env); + signal_add(&sigterm, NULL); + +#ifndef PROFILE + if (pledge("stdio recvfd", NULL) == -1) + fatal("pledge"); +#endif + event_dispatch(); + event_base_free(evb); + fcgi_shutdown(); +} blob - 1d2b63de026c502f7440177d20c1b3e81c019ae6 blob + a62d5200b2cda89b3d6c162c9ecea8bddc684694 --- gotwebd/gotweb.c +++ gotwebd/gotweb.c @@ -231,9 +231,9 @@ recv_request(struct imsg *imsg) } /* get the gotwebd server */ - srv = gotweb_get_server(c->server_name); + srv = gotweb_get_server(c->fcgi_params.server_name); if (srv == NULL) { - log_warnx("server '%s' not found", c->server_name); + log_warnx("server '%s' not found", c->fcgi_params.server_name); fcgi_cleanup_request(c); return NULL; } @@ -262,7 +262,7 @@ gotweb_process_request(struct request *c) goto err; } c->t->qs = qs; - error = gotweb_parse_querystring(qs, c->querystring); + error = gotweb_parse_querystring(qs, c->fcgi_params.querystring); if (error) { log_warnx("%s: %s", __func__, error->msg); goto err; @@ -270,22 +270,23 @@ gotweb_process_request(struct request *c) /* Log the request. */ if (gotwebd_env->gotwebd_verbose > 0) { + struct gotwebd_fcgi_params *p = &c->fcgi_params; char *server_name = NULL; char *querystring = NULL; char *document_uri = NULL; - if (c->server_name[0] && - stravis(&server_name, c->server_name, VIS_SAFE) == -1) { + if (p->server_name[0] && + stravis(&server_name, p->server_name, VIS_SAFE) == -1) { log_warn("stravis"); server_name = NULL; } - if (c->querystring[0] && - stravis(&querystring, c->querystring, VIS_SAFE) == -1) { + if (p->querystring[0] && + stravis(&querystring, p->querystring, VIS_SAFE) == -1) { log_warn("stravis"); querystring = NULL; } - if (c->document_uri[0] && - stravis(&document_uri, c->document_uri, VIS_SAFE) == -1) { + if (p->document_uri[0] && + stravis(&document_uri, p->document_uri, VIS_SAFE) == -1) { log_warn("stravis"); document_uri = NULL; } @@ -1160,12 +1161,13 @@ int gotweb_render_absolute_url(struct request *c, struct gotweb_url *url) { struct template *tp = c->tp; - const char *proto = c->https ? "https" : "http"; + struct gotwebd_fcgi_params *p = &c->fcgi_params; + const char *proto = p->https ? "https" : "http"; if (tp_writes(tp, proto) == -1 || tp_writes(tp, "://") == -1 || - tp_htmlescape(tp, c->server_name) == -1 || - tp_htmlescape(tp, c->document_uri) == -1) + tp_htmlescape(tp, p->server_name) == -1 || + tp_htmlescape(tp, p->document_uri) == -1) return -1; return gotweb_render_url(c, url); blob - fb4dfe221b2b5e116cca94b8c78590b4759e2327 blob + 04e40320ff52b4f03be9eed163eed1a14468498a --- gotwebd/gotwebd.c +++ gotwebd/gotwebd.c @@ -315,6 +315,9 @@ spawn_process(struct gotwebd *env, const char *argv0, if (proc_type == GOTWEBD_PROC_SERVER) { argv[argc++] = "-S"; argv[argc++] = username; + } else if (proc_type == GOTWEBD_PROC_FCGI) { + argv[argc++] = "-F"; + argv[argc++] = username; } else if (proc_type == GOTWEBD_PROC_GOTWEB) { argv[argc++] = "-G"; argv[argc++] = username; @@ -378,7 +381,7 @@ main(int argc, char **argv) fatal("%s: calloc", __func__); config_init(env); - while ((ch = getopt(argc, argv, "D:dG:f:nS:vW:")) != -1) { + while ((ch = getopt(argc, argv, "D:dG:f:F:nS:vW:")) != -1) { switch (ch) { case 'D': if (cmdline_symset(optarg) < 0) @@ -395,6 +398,10 @@ main(int argc, char **argv) case 'f': conffile = optarg; break; + case 'F': + proc_type = GOTWEBD_PROC_FCGI; + gotwebd_username = optarg; + break; case 'n': no_action = 1; break; @@ -469,6 +476,22 @@ main(int argc, char **argv) sockets(env, GOTWEBD_SOCK_FILENO); return 1; + case GOTWEBD_PROC_FCGI: + setproctitle("fcgi"); + log_procinit("fcgi"); + + if (chroot(env->httpd_chroot) == -1) + fatal("chroot %s", env->httpd_chroot); + if (chdir("/") == -1) + fatal("chdir /"); + + if (setgroups(gotwebd_ngroups, gotwebd_groups) == -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"); + + gotwebd_fcgi(env, GOTWEBD_SOCK_FILENO); + return 1; case GOTWEBD_PROC_GOTWEB: setproctitle("gotweb"); log_procinit("gotweb"); @@ -493,6 +516,9 @@ main(int argc, char **argv) env->iev_server = calloc(env->nserver, sizeof(*env->iev_server)); if (env->iev_server == NULL) fatal("calloc"); + env->iev_fcgi = calloc(env->nserver, sizeof(*env->iev_fcgi)); + if (env->iev_fcgi == NULL) + fatal("calloc"); env->iev_gotweb = calloc(env->nserver, sizeof(*env->iev_gotweb)); if (env->iev_gotweb == NULL) fatal("calloc"); @@ -501,6 +527,9 @@ main(int argc, char **argv) spawn_process(env, argv0, &env->iev_server[i], GOTWEBD_PROC_SERVER, gotwebd_username, gotwebd_dispatch_server); + spawn_process(env, argv0, &env->iev_fcgi[i], + GOTWEBD_PROC_FCGI, gotwebd_username, + gotwebd_dispatch_gotweb); spawn_process(env, argv0, &env->iev_gotweb[i], GOTWEBD_PROC_GOTWEB, gotwebd_username, gotwebd_dispatch_gotweb); @@ -559,22 +588,34 @@ main(int argc, char **argv) static void connect_children(struct gotwebd *env) { - struct imsgev *iev1, *iev2; + struct imsgev *iev_server, *iev_fcgi, *iev_gotweb; int pipe[2]; int i; for (i = 0; i < env->nserver; i++) { - iev1 = &env->iev_server[i]; - iev2 = &env->iev_gotweb[i]; + iev_server = &env->iev_server[i]; + iev_fcgi = &env->iev_fcgi[i]; + iev_gotweb = &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)) + if (send_imsg(iev_server, GOTWEBD_IMSG_CTL_PIPE, + pipe[0], NULL, 0)) fatal("send_imsg"); + if (send_imsg(iev_fcgi, GOTWEBD_IMSG_CTL_PIPE, + pipe[1], NULL, 0)) + fatal("send_imsg"); - if (send_imsg(iev2, GOTWEBD_IMSG_CTL_PIPE, pipe[1], NULL, 0)) + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe) == -1) + fatal("socketpair"); + + if (send_imsg(iev_server, GOTWEBD_IMSG_CTL_PIPE, + pipe[0], NULL, 0)) fatal("send_imsg"); + if (send_imsg(iev_gotweb, GOTWEBD_IMSG_CTL_PIPE, + pipe[1], NULL, 0)) + fatal("send_imsg"); } } blob - 888f71c5b60cbc3697f03efa568436627738f7dc blob + 818dc5bf6ace697099163b2e3296de15089a872c --- gotwebd/gotwebd.h +++ gotwebd/gotwebd.h @@ -124,6 +124,7 @@ struct got_reflist_head; enum gotwebd_proc_type { GOTWEBD_PROC_PARENT, GOTWEBD_PROC_SERVER, + GOTWEBD_PROC_FCGI, GOTWEBD_PROC_GOTWEB, }; @@ -134,6 +135,9 @@ enum imsg_type { GOTWEBD_IMSG_CFG_DONE, GOTWEBD_IMSG_CTL_PIPE, GOTWEBD_IMSG_CTL_START, + GOTWEBD_IMSG_FCGI_PARSE_PARAMS, + GOTWEBD_IMSG_FCGI_PARAMS, + GOTWEBD_IMSG_REQ_ABORT, GOTWEBD_IMSG_REQ_PROCESS, }; @@ -239,6 +243,20 @@ enum socket_priv_fds { PRIV_FDS__MAX, }; +struct gotwebd_fcgi_record { + uint32_t request_id; + uint8_t record[FCGI_RECORD_SIZE]; + size_t record_len; +}; + +struct gotwebd_fcgi_params { + uint32_t request_id; + char querystring[MAX_QUERYSTRING]; + char document_uri[MAX_DOCUMENT_URI]; + char server_name[MAX_SERVER_NAME]; + int https; +}; + struct template; struct request { TAILQ_ENTRY(request) entry; @@ -257,17 +275,14 @@ struct request { uint32_t request_id; uint8_t *buf; - size_t buf_pos; size_t buf_len; uint8_t *outbuf; - char querystring[MAX_QUERYSTRING]; - char document_uri[MAX_DOCUMENT_URI]; - char server_name[MAX_SERVER_NAME]; - int https; + struct gotwebd_fcgi_params fcgi_params; + int nparams; + int nparams_parsed; - uint8_t request_started; int client_status; }; TAILQ_HEAD(requestlist, request); @@ -325,6 +340,9 @@ TAILQ_HEAD(serverlist, server); enum client_action { CLIENT_CONNECT, + CLIENT_FCGI_BEGIN, + CLIENT_FCGI_PARAMS, + CLIENT_FCGI_STDIN, CLIENT_REQUEST, CLIENT_DISCONNECT, }; @@ -367,6 +385,7 @@ struct gotwebd { struct imsgev *iev_parent; struct imsgev *iev_server; + struct imsgev *iev_fcgi; struct imsgev *iev_gotweb; size_t nserver; @@ -506,6 +525,7 @@ void fcgi_timeout(int, short, void *); void fcgi_cleanup_request(struct request *); void fcgi_create_end_record(struct request *); int fcgi_write(void *, const void *, size_t); +void gotwebd_fcgi(struct gotwebd *, int); /* got_operations.c */ const struct got_error *got_gotweb_closefile(FILE *); blob - d5af2ffcb28eb41174cdfe8a016103fbb42d50da blob + c7e1ac5b684f3f72cb60e8a5260089c8262b08a9 --- gotwebd/pages.tmpl +++ gotwebd/pages.tmpl @@ -167,7 +167,7 @@ nextsep(char *s, char **t) struct server *srv = c->srv; struct querystring *qs = c->t->qs; struct gotweb_url u_path; - const char *prfx = c->document_uri; + const char *prfx = c->fcgi_params.document_uri; const char *css = srv->custom_css; memset(&u_path, 0, sizeof(u_path)); blob - 0ddf1e382eb72e1f455b15c2ed1acf5408748f83 blob + a5c7c36b416770bf81e52f186e570e83ce9ed64d --- gotwebd/sockets.c +++ gotwebd/sockets.c @@ -79,12 +79,125 @@ 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++; +} + +static void +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; +} + +static void +cleanup_request(struct request *c) +{ + cgi_inflight--; + + del_request(c); + + if (evtimer_initialized(&c->tmo)) + evtimer_del(&c->tmo); + if (event_initialized(&c->ev)) + event_del(&c->ev); + if (c->fd != -1) + close(c->fd); + free(c->buf); + free(c); +} + +static void +request_done(struct request *c) +{ + /* + * If we have not yet handed the client off to gotweb.c we + * must send an FCGI end record ourselves. + */ + if (c->client_status > CLIENT_CONNECT && + c->client_status < CLIENT_REQUEST) + fcgi_create_end_record(c); + + cleanup_request(c); +} + 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); @@ -189,8 +302,10 @@ sockets_launch(struct gotwebd *env) { struct socket *sock; + if (env->iev_fcgi == NULL) + fatalx("fcgi process not connected"); if (env->iev_gotweb == NULL) - fatal("gotweb process not connected"); + fatalx("gotweb process not connected"); TAILQ_FOREACH(sock, &gotwebd_env->sockets, entry) { log_info("%s: configuring socket %d (%d)", __func__, @@ -212,7 +327,7 @@ sockets_launch(struct gotwebd *env) if (pledge("stdio inet sendfd", NULL) == -1) fatal("pledge"); #endif - event_add(&env->iev_gotweb->ev, NULL); + event_add(&env->iev_fcgi->ev, NULL); } @@ -284,6 +399,7 @@ recv_gotweb_pipe(struct gotwebd *env, struct imsg *ims if (imsgbuf_init(&iev->ibuf, fd) == -1) fatal("imsgbuf_init"); imsgbuf_allow_fdpass(&iev->ibuf); + imsgbuf_set_maxsize(&iev->ibuf, sizeof(struct gotwebd_fcgi_record)); iev->handler = server_dispatch_gotweb; iev->data = iev; @@ -293,7 +409,202 @@ recv_gotweb_pipe(struct gotwebd *env, struct imsg *ims env->iev_gotweb = iev; } +static int +process_request(struct request *c) +{ + struct gotwebd *env = gotwebd_env; + int ret, i; + struct request ic; + + 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; + + ret = imsg_compose_event(env->iev_gotweb, GOTWEBD_IMSG_REQ_PROCESS, + GOTWEBD_PROC_SERVER, -1, c->fd, &ic, sizeof(ic)); + if (ret == -1) { + log_warn("imsg_compose_event"); + return -1; + } + + c->fd = -1; + c->client_status = CLIENT_REQUEST; + return 0; +} + static void +recv_parsed_params(struct imsg *imsg) +{ + struct gotwebd_fcgi_params params, *p; + struct request *c; + + if (imsg_get_data(imsg, ¶ms, sizeof(params)) == -1) { + log_warn("imsg_get_data"); + return; + } + + c = find_request(params.request_id); + if (c == NULL) + return; + + if (c->client_status > CLIENT_FCGI_STDIN) + return; + + if (c->client_status < CLIENT_FCGI_PARAMS) + goto fail; + + p = &c->fcgi_params; + + if (params.querystring[0] != '\0' && + strlcpy(p->querystring, params.querystring, + sizeof(p->querystring)) >= sizeof(p->querystring)) { + log_warnx("querystring too long"); + goto fail; + } + + if (params.document_uri[0] != '\0' && + strlcpy(p->document_uri, params.document_uri, + sizeof(p->document_uri)) >= sizeof(p->document_uri)) { + log_warnx("document uri too long"); + goto fail; + } + + if (params.server_name[0] != '\0' && + strlcpy(p->server_name, params.server_name, + sizeof(p->server_name)) >= sizeof(p->server_name)) { + log_warnx("server name too long"); + goto fail; + } + + if (params.https && !p->https) + p->https = 1; + + c->nparams_parsed++; + + if (c->client_status == CLIENT_FCGI_STDIN && + c->nparams_parsed >= c->nparams) { + if (process_request(c) == -1) + goto fail; + } + + return; +fail: + request_done(c); +} + +static void +abort_request(struct imsg *imsg) +{ + struct request *c; + uint32_t request_id; + + if (imsg_get_data(imsg, &request_id, sizeof(request_id)) == -1) { + log_warn("imsg_get_data"); + return; + } + + c = find_request(request_id); + if (c == NULL) + return; + + request_done(c); +} + +static void +server_dispatch_fcgi(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_FCGI_PARAMS: + recv_parsed_params(&imsg); + break; + case GOTWEBD_IMSG_REQ_ABORT: + abort_request(&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_fcgi_pipe(struct gotwebd *env, struct imsg *imsg) +{ + struct imsgev *iev; + int fd; + + if (env->iev_fcgi != NULL) { + log_warn("fcgi 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); + imsgbuf_set_maxsize(&iev->ibuf, sizeof(struct gotwebd_fcgi_record)); + + iev->handler = server_dispatch_fcgi; + iev->data = iev; + event_set(&iev->ev, fd, EV_READ, server_dispatch_fcgi, iev); + imsg_event_add(iev); + + env->iev_fcgi = iev; +} + +static void sockets_dispatch_main(int fd, short event, void *arg) { struct imsgev *iev = arg; @@ -333,7 +644,10 @@ sockets_dispatch_main(int fd, short event, void *arg) config_getcfg(env, &imsg); break; case GOTWEBD_IMSG_CTL_PIPE: - recv_gotweb_pipe(env, &imsg); + if (env->iev_fcgi == NULL) + recv_fcgi_pipe(env, &imsg); + else + recv_gotweb_pipe(env, &imsg); break; case GOTWEBD_IMSG_CTL_START: sockets_launch(env); @@ -400,10 +714,12 @@ sockets_shutdown(void) free(h); } + requests_purge(); + imsgbuf_clear(&gotwebd_env->iev_parent->ibuf); free(gotwebd_env->iev_parent); - imsgbuf_clear(&gotwebd_env->iev_gotweb->ibuf); - free(gotwebd_env->iev_gotweb); + imsgbuf_clear(&gotwebd_env->iev_fcgi->ibuf); + free(gotwebd_env->iev_fcgi); free(gotwebd_env); exit(0); @@ -562,6 +878,157 @@ sockets_accept_paused(int fd, short events, void *arg) event_add(&sock->ev, NULL); } +static int +parse_params(struct request *c, uint8_t *record, size_t record_len) +{ + struct gotwebd *env = gotwebd_env; + struct gotwebd_fcgi_record rec; + int ret; + + memset(&rec, 0, sizeof(rec)); + + memcpy(rec.record, record, record_len); + rec.record_len = record_len; + rec.request_id = c->request_id; + + ret = imsg_compose_event(env->iev_fcgi, + GOTWEBD_IMSG_FCGI_PARSE_PARAMS, + GOTWEBD_PROC_SERVER, -1, -1, &rec, sizeof(rec)); + if (ret == -1) + log_warn("imsg_compose_event"); + + return ret; +} + +static void +read_fcgi_records(int fd, short events, void *arg) +{ + struct request *c = arg; + ssize_t n; + struct fcgi_record_header h; + size_t record_len; + + n = read(fd, c->buf + c->buf_len, FCGI_RECORD_SIZE - c->buf_len); + + switch (n) { + case -1: + switch (errno) { + case EINTR: + case EAGAIN: + goto more; + default: + goto fail; + } + break; + case 0: + if (c->client_status < CLIENT_FCGI_STDIN) { + log_warnx("client %u closed connection too early", + c->request_id); + goto fail; + } + return; + default: + break; + } + + c->buf_len += n; + + while (c->buf_len >= sizeof(h)) { + memcpy(&h, c->buf, sizeof(h)); + + record_len = sizeof(h) + ntohs(h.content_len) + h.padding_len; + if (record_len > FCGI_RECORD_SIZE) { + log_warnx("FGI record length too large"); + goto fail; + } + + if (c->buf_len < record_len) + goto more; + + switch (h.type) { + case FCGI_BEGIN_REQUEST: + if (c->client_status >= CLIENT_FCGI_BEGIN) { + log_warnx("unexpected FCGI_BEGIN_REQUEST"); + goto fail; + } + + if (ntohs(h.content_len) != + sizeof(struct fcgi_begin_request_body)) { + log_warnx("wrong begin request size %u != %zu", + ntohs(h.content_len), + sizeof(struct fcgi_begin_request_body)); + goto fail; + } + + /* XXX -- FCGI_CANT_MPX_CONN */ + c->client_status = CLIENT_FCGI_BEGIN; + c->id = ntohs(h.id); + break; + case FCGI_PARAMS: + if (c->client_status < CLIENT_FCGI_BEGIN) { + log_warnx("FCGI_PARAMS without " + "FCGI_BEGIN_REQUEST"); + goto fail; + } + if (c->client_status > CLIENT_FCGI_PARAMS) { + log_warnx("FCGI_PARAMS after FCGI_STDIN"); + goto fail; + } + + if (c->id != ntohs(h.id)) { + log_warnx("unexpected ID in FCGI header"); + goto fail; + } + + c->client_status = CLIENT_FCGI_PARAMS; + c->nparams++; + + if (parse_params(c, c->buf, record_len) == -1) + goto fail; + break; + case FCGI_ABORT_REQUEST: + log_warnx("received FCGI_ABORT_REQUEST from client"); + request_done(c); + return; + case FCGI_STDIN: + if (c->client_status < CLIENT_FCGI_BEGIN) { + log_warnx("FCGI_STDIN without " + "FCGI_BEGIN_REQUEST"); + goto fail; + } + + if (c->client_status < CLIENT_FCGI_PARAMS) { + log_warnx("FCGI_STDIN without FCGI_PARAMS"); + goto fail; + } + + if (c->id != ntohs(h.id)) { + log_warnx("unexpected ID in FCGI header"); + goto fail; + } + + c->client_status = CLIENT_FCGI_STDIN; + if (c->nparams_parsed >= c->nparams) { + if (process_request(c) == -1) + goto fail; + } + break; + default: + log_warn("unexpected FCGI type %u", h.type); + goto fail; + } + + /* drop the parsed record */ + c->buf_len -= record_len; + memmove(c->buf, c->buf + record_len, c->buf_len); + } +more: + event_add(&c->ev, NULL); + return; +fail: + request_done(c); +} + void sockets_socket_accept(int fd, short event, void *arg) { @@ -628,17 +1095,17 @@ sockets_socket_accept(int fd, short event, void *arg) 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->client_status = CLIENT_CONNECT; + c->request_id = get_request_id(); - event_set(&c->ev, s, EV_READ, fcgi_request, c); + event_set(&c->ev, s, EV_READ, read_fcgi_records, c); event_add(&c->ev, NULL); evtimer_set(&c->tmo, fcgi_timeout, c); evtimer_add(&c->tmo, &timeout); + add_request(c); return; err: cgi_inflight--;
gotwebd: parse fcgi records under pledge("stdio")