commit 8ad1c570242cd93f0802931621b49b2510b338e7 from: Omar Polo date: Sun May 09 18:23:36 2021 UTC fastcgi: a first implementation Not production-ready yet, but it's a start. This adds a third ``backend'' for gmid: until now there it served local files or CGI scripts, now FastCGI applications too. FastCGI is meant to be an improvement over CGI: instead of exec'ing a script for every request, it allows to open a single connection to an ``application'' and send the requests/receive the responses over that socket using a simple binary protocol. At the moment gmid supports three different methods of opening a fastcgi connection: - local unix sockets, with: fastcgi "/path/to/sock" - network sockets, with: fastcgi tcp "host" [port] port defaults to 9000 and can be either a string or a number - subprocess, with: fastcgi spawn "/path/to/program" the fastcgi protocol is done over the executed program stdin of these, the last is only for testing and may be removed in the future. P.S.: the fastcgi rule is per-location of course :) commit - 50310aff335912edde625a5cde3729e34783fd7c commit + 8ad1c570242cd93f0802931621b49b2510b338e7 blob - 16f3bbc22db226c20300675bf27b572ef3799d15 blob + 521c5b2b4b0603cebeffbf3e66f11a72e61a93ce --- Makefile +++ Makefile @@ -14,7 +14,7 @@ y.tab.c: parse.y ${YACC} -b y -d parse.y SRCS = gmid.c iri.c utf8.c ex.c server.c sandbox.c mime.c puny.c \ - utils.c log.c dirs.c + utils.c log.c dirs.c fcgi.c OBJS = ${SRCS:.c=.o} lex.yy.o y.tab.o ${COMPAT} gmid: ${OBJS} blob - 6a5effe3c8d324d6ae4d3ef0af562482f5c53a95 blob + 0dcad61216f0b57fcabd1836f1eeda0d377d0537 --- ex.c +++ ex.c @@ -16,6 +16,8 @@ #include "gmid.h" +#include + #include #include @@ -28,10 +30,12 @@ #include static void handle_imsg_cgi_req(struct imsgbuf*, struct imsg*, size_t); +static void handle_imsg_fcgi_req(struct imsgbuf*, struct imsg*, size_t); static void handle_imsg_quit(struct imsgbuf*, struct imsg*, size_t); static void handle_dispatch_imsg(int, short, void*); static imsg_handlerfn *handlers[] = { + [IMSG_FCGI_REQ] = handle_imsg_fcgi_req, [IMSG_CGI_REQ] = handle_imsg_cgi_req, [IMSG_QUIT] = handle_imsg_quit, }; @@ -292,9 +296,122 @@ handle_imsg_cgi_req(struct imsgbuf *ibuf, struct imsg fd = launch_cgi(&iri, &req, h, l); imsg_compose(ibuf, IMSG_CGI_RES, imsg->hdr.peerid, 0, fd, NULL, 0); imsg_flush(ibuf); +} + +static int +fcgi_open_prog(struct fcgi *f) +{ + int s[2]; + pid_t p; + + /* XXX! */ + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNIX, s) == -1) + err(1, "socketpair"); + + switch (p = fork()) { + case -1: + err(1, "fork"); + case 0: + close(s[0]); + if (dup2(s[1], 0) == -1) + err(1, "dup2"); + execl(f->prog, f->prog, NULL); + err(1, "execl %s", f->prog); + default: + close(s[1]); + return s[0]; + } } +static int +fcgi_open_sock(struct fcgi *f) +{ + struct sockaddr_un addr; + int fd; + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + log_err(NULL, "socket: %s", strerror(errno)); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strlcpy(addr.sun_path, f->path, sizeof(addr.sun_path)); + + if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { + log_warn(NULL, "failed to connect to %s: %s", f->path, + strerror(errno)); + close(fd); + return -1; + } + + return fd; +} + +static int +fcgi_open_conn(struct fcgi *f) +{ + struct addrinfo hints, *servinfo, *p; + int r, sock; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG; + + if ((r = getaddrinfo(f->path, f->port, &hints, &servinfo)) != 0) { + log_warn(NULL, "getaddrinfo %s:%s: %s", f->path, f->port, + gai_strerror(r)); + return -1; + } + + for (p = servinfo; p != NULL; p = p->ai_next) { + sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (sock == -1) + continue; + if (connect(sock, p->ai_addr, p->ai_addrlen) == -1) { + close(sock); + continue; + } + break; + } + + if (p == NULL) { + log_warn(NULL, "couldn't connect to %s:%s", f->path, f->port); + sock = -1; + } + + freeaddrinfo(servinfo); + return sock; +} + static void +handle_imsg_fcgi_req(struct imsgbuf *ibuf, struct imsg *imsg, size_t datalen) +{ + struct fcgi *f; + int id, fd; + + if (datalen != sizeof(id)) + abort(); + memcpy(&id, imsg->data, datalen); + + if (id > FCGI_MAX || (fcgi[id].path == NULL && fcgi[id].prog == NULL)) + abort(); + + f = &fcgi[id]; + if (f->prog != NULL) + fd = fcgi_open_prog(f); + else if (f->port != NULL) + fd = fcgi_open_conn(f); + else + fd = fcgi_open_sock(f); + + imsg_compose(ibuf, IMSG_FCGI_FD, id, 0, fd, NULL, 0); + imsg_flush(ibuf); +} + +static void handle_imsg_quit(struct imsgbuf *ibuf, struct imsg *imsg, size_t datalen) { int i; blob - /dev/null blob + fe4db2fd0a1889a164573f3ccff93276c73fc460 (mode 644) --- /dev/null +++ fcgi.c @@ -0,0 +1,469 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "gmid.h" + +#include +#include +#include + +/* + * Sometimes it can be useful to inspect the fastcgi traffic as + * received by gmid. + * + * This will make gmid connect to a `debug.sock' socket (that must + * exists) in the current directory and send there a copy of what gets + * read. The socket can be created and monitored e.g. with + * + * rm -f debug.sock ; nc -Ulk ./debug.sock | hexdump -C + * + * NB: the sandbox must be disabled for this to work. + */ +#define DEBUG_FCGI 0 + +#ifdef DEBUG_FCGI +# include +static int debug_socket = -1; +#endif + +struct fcgi_header { + unsigned char version; + unsigned char type; + unsigned char req_id1; + unsigned char req_id0; + unsigned char content_len1; + unsigned char content_len0; + unsigned char padding; + unsigned char reserved; +}; + +/* + * number of bytes in a FCGI_HEADER. Future version of the protocol + * will not reduce this number. + */ +#define FCGI_HEADER_LEN 8 + +/* + * values for the version component + */ +#define FCGI_VERSION_1 1 + +/* + * values for the type component + */ +#define FCGI_BEGIN_REQUEST 1 +#define FCGI_ABORT_REQUEST 2 +#define FCGI_END_REQUEST 3 +#define FCGI_PARAMS 4 +#define FCGI_STDIN 5 +#define FCGI_STDOUT 6 +#define FCGI_STDERR 7 +#define FCGI_DATA 8 +#define FCGI_GET_VALUES 9 +#define FCGI_GET_VALUES_RESULT 10 +#define FCGI_UNKNOWN_TYPE 11 +#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE) + +struct fcgi_begin_req { + unsigned char role1; + unsigned char role0; + unsigned char flags; + unsigned char reserved[5]; +}; + +struct fcgi_begin_req_record { + struct fcgi_header header; + struct fcgi_begin_req body; +}; + +/* + * mask for flags; + */ +#define FCGI_KEEP_CONN 1 + +/* + * values for the role + */ +#define FCGI_RESPONDER 1 +#define FCGI_AUTHORIZER 2 +#define FCGI_FILTER 3 + +struct fcgi_end_req_body { + unsigned char app_status3; + unsigned char app_status2; + unsigned char app_status1; + unsigned char app_status0; + unsigned char proto_status; + unsigned char reserved[3]; +}; + +/* + * values for proto_status + */ +#define FCGI_REQUEST_COMPLETE 0 +#define FCGI_CANT_MPX_CONN 1 +#define FCGI_OVERLOADED 2 +#define FCGI_UNKNOWN_ROLE 3 + +/* + * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT + * records. + */ +#define FCGI_MAX_CONNS "FCGI_MAX_CONNS" +#define FCGI_MAX_REQS "FCGI_MAX_REQS" +#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS" + +static int +prepare_header(struct fcgi_header *h, int type, int id, size_t size, + size_t padding) +{ + memset(h, 0, sizeof(*h)); + + /* + * id=0 is reserved for status messages. + */ + id++; + + h->version = FCGI_VERSION_1; + h->type = type; + h->req_id1 = (id >> 8); + h->req_id0 = (id & 0xFF); + h->content_len1 = (size >> 8); + h->content_len0 = (size & 0xFF); + h->padding = padding; + + return 0; +} + +static int +fcgi_begin_request(int sock, int id) +{ + struct fcgi_begin_req_record r; + + if (id > UINT16_MAX) + return -1; + + memset(&r, 0, sizeof(r)); + prepare_header(&r.header, FCGI_BEGIN_REQUEST, id, + sizeof(r.body), 0); + assert(sizeof(r.body) == FCGI_HEADER_LEN); + + r.body.role1 = 0; + r.body.role0 = FCGI_RESPONDER; + r.body.flags = FCGI_KEEP_CONN; + + if (write(sock, &r, sizeof(r)) != sizeof(r)) + return -1; + return 0; +} + +static int +fcgi_send_param(int sock, int id, const char *name, const char *value) +{ + struct fcgi_header h; + uint32_t namlen, vallen, padlen; + uint8_t s[8]; + size_t size; + char padding[8] = { 0 }; + + namlen = strlen(name); + vallen = strlen(value); + size = namlen + vallen + 8; /* 4 for the sizes */ + padlen = (8 - (size & 0x7)) & 0x7; + + s[0] = ( namlen >> 24) | 0x80; + s[1] = ((namlen >> 16) & 0xFF); + s[2] = ((namlen >> 8) & 0xFF); + s[3] = ( namlen & 0xFF); + + s[4] = ( vallen >> 24) | 0x80; + s[5] = ((vallen >> 16) & 0xFF); + s[6] = ((vallen >> 8) & 0xFF); + s[7] = ( vallen & 0xFF); + + prepare_header(&h, FCGI_PARAMS, id, size, padlen); + + if (write(sock, &h, sizeof(h)) != sizeof(h) || + write(sock, s, sizeof(s)) != sizeof(s) || + write(sock, name, namlen) != namlen || + write(sock, value, vallen) != vallen || + write(sock, padding, padlen) != padlen) + return -1; + + return 0; +} + +static int +fcgi_end_param(int sock, int id) +{ + struct fcgi_header h; + + prepare_header(&h, FCGI_PARAMS, id, 0, 0); + if (write(sock, &h, sizeof(h)) != sizeof(h)) + return -1; + + prepare_header(&h, FCGI_STDIN, id, 0, 0); + if (write(sock, &h, sizeof(h)) != sizeof(h)) + return -1; + + return 0; +} + +static int +fcgi_abort_request(int sock, int id) +{ + struct fcgi_header h; + + prepare_header(&h, FCGI_ABORT_REQUEST, id, 0, 0); + if (write(sock, &h, sizeof(h)) != sizeof(h)) + return -1; + + return 0; +} + +static int +must_read(int sock, char *d, size_t len) +{ + ssize_t r; + +#if DEBUG_FCGI + if (debug_socket == -1) { + struct sockaddr_un addr; + + if ((debug_socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + err(1, "socket"); + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strlcpy(addr.sun_path, "./debug.sock", sizeof(addr.sun_path)); + if (connect(debug_socket, (struct sockaddr*)&addr, sizeof(addr)) + == -1) + err(1, "connect"); + } +#endif + + for (;;) { + switch (r = read(sock, d, len)) { + case -1: + case 0: + return -1; + default: +#if DEBUG_FCGI + write(debug_socket, d, r); +#endif + + if (r == (ssize_t)len) + return 0; + len -= r; + d += r; + } + } +} + +static int +fcgi_read_header(int sock, struct fcgi_header *h) +{ + if (must_read(sock, (char*)h, sizeof(*h)) == -1) + return -1; + if (h->version != FCGI_VERSION_1) { + errno = EINVAL; + return -1; + } + return 0; +} + +static inline int +recid(struct fcgi_header *h) +{ + return h->req_id0 + (h->req_id1 << 8) - 1; +} + +static inline int +reclen(struct fcgi_header *h) +{ + return h->content_len0 + (h->content_len1 << 8); +} + +static void +copy_mbuf(int fd, short ev, void *d) +{ + struct client *c = d; + struct mbuf *mbuf; + size_t len; + ssize_t r; + char *data; + + for (;;) { + mbuf = TAILQ_FIRST(&c->mbufhead); + if (mbuf == NULL) + break; + + len = mbuf->len - mbuf->off; + data = mbuf->data + mbuf->off; + switch (r = tls_write(c->ctx, data, len)) { + case -1: + /* + * Can't close_conn here. The application + * needs to be informed first, otherwise it + * can interfere with future connections. + * Check also that we're not doing recursion + * (copy_mbuf -> handle_fcgi -> copy_mbuf ...) + */ + if (c->next != NULL) + goto end; + fcgi_abort_request(0, c->id); + return; + case TLS_WANT_POLLIN: + event_once(c->fd, EV_READ, ©_mbuf, c, NULL); + return; + case TLS_WANT_POLLOUT: + event_once(c->fd, EV_WRITE, ©_mbuf, c, NULL); + return; + } + mbuf->off += r; + + if (mbuf->off == mbuf->len) { + TAILQ_REMOVE(&c->mbufhead, mbuf, mbufs); + free(mbuf); + } + } + +end: + if (c->next != NULL) + c->next(0, 0, c); +} + +static int +consume(int fd, size_t len) +{ + size_t l; + char buf[64]; + + while (len != 0) { + if ((l = len) > sizeof(buf)) + l = sizeof(buf); + if (must_read(fd, buf, l) == -1) + return 0; + len -= l; + } + + return 1; +} + +static void +close_all(struct fcgi *f) +{ + size_t i; + struct client *c; + + for (i = 0; i < MAX_USERS; i++) { + c = &clients[i]; + + if (c->fcgi != f->id) + continue; + + if (c->code != 0) + close_conn(0, 0, c); + else + start_reply(c, CGI_ERROR, "CGI error"); + } + + event_del(&f->e); + close(f->fd); + f->fd = -1; + f->s = FCGI_OFF; +} + +void +handle_fcgi(int sock, short event, void *d) +{ + struct fcgi *f = d; + struct fcgi_header h; + struct fcgi_end_req_body end; + struct client *c; + struct mbuf *mbuf; + size_t len; + + if (fcgi_read_header(sock, &h) == -1) + goto err; + + c = try_client_by_id(recid(&h)); + if (c == NULL || c->fcgi != f->id) + goto err; + + len = reclen(&h); + + switch (h.type) { + case FCGI_END_REQUEST: + if (len != sizeof(end)) + goto err; + if (must_read(sock, (char*)&end, sizeof(end)) == -1) + goto err; + /* TODO: do something with the status? */ + c->fcgi = -1; + c->next = close_conn; + event_once(c->fd, EV_WRITE, ©_mbuf, c, NULL); + break; + + case FCGI_STDERR: + /* discard stderr (for now) */ + if (!consume(sock, len)) + goto err; + break; + + case FCGI_STDOUT: + if ((mbuf = calloc(1, sizeof(*mbuf) + len)) == NULL) + fatal("calloc"); + mbuf->len = len; + if (must_read(sock, mbuf->data, len) == -1) { + free(mbuf); + goto err; + } + + if (TAILQ_EMPTY(&c->mbufhead)) { + TAILQ_INSERT_HEAD(&c->mbufhead, mbuf, mbufs); + event_once(c->fd, EV_WRITE, ©_mbuf, c, NULL); + } else + TAILQ_INSERT_TAIL(&c->mbufhead, mbuf, mbufs); + break; + + default: + log_err(NULL, "got invalid fcgi record (type=%d)", h.type); + goto err; + } + + if (!consume(sock, h.padding)) + goto err; + return; + +err: + close_all(f); +} + +void +send_fcgi_req(struct fcgi *f, struct client *c) +{ + c->next = NULL; + + fcgi_begin_request(f->fd, c->id); + fcgi_send_param(f->fd, c->id, "QUERY_STRING", c->iri.query); + fcgi_send_param(f->fd, c->id, "GEMINI_URL_PATH", c->iri.path); + fcgi_send_param(f->fd, c->id, "SERVER_SOFTWARE", "gmid/1.7"); + /* ... */ + + if (fcgi_end_param(f->fd, c->id) == -1) + close_all(f); +} blob - bdf5bd930de38dad49d7bbf63fca5282d7b6410c blob + e04e1723b9bee94fddb8e4e6657c2ae3c8684694 --- gmid.c +++ gmid.c @@ -26,6 +26,8 @@ #include #include +struct fcgi fcgi[FCGI_MAX]; + struct vhosthead hosts; int sock4, sock6; @@ -251,7 +253,7 @@ free_config(void) struct location *l, *tl; struct envlist *e, *te; struct alist *a, *ta; - int v; + int v, i; v = conf.verbose; @@ -297,6 +299,14 @@ free_config(void) TAILQ_REMOVE(&hosts, h, vhosts); free(h); + } + + for (i = 0; i < FCGI_MAX; ++i) { + if (fcgi[i].path == NULL && fcgi[i].prog == NULL) + break; + free(fcgi[i].path); + free(fcgi[i].port); + free(fcgi[i].prog); } tls_free(ctx); blob - 752653af7cf79cd1b7e9980f5c2894d1fed12828 blob + c97fd23cdc5e9b408464369fe25de5c7fbbb40b7 --- gmid.h +++ gmid.h @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -55,8 +56,24 @@ #define DOMAIN_NAME_LEN (253+1) #define LABEL_LEN (63+1) +#define FCGI_MAX 32 #define PROC_MAX 16 +struct fcgi { + int id; + char *path; + char *port; + char *prog; + int fd; + struct event e; + +#define FCGI_OFF 0 +#define FCGI_INFLIGHT 1 +#define FCGI_READY 2 + int s; +}; +extern struct fcgi fcgi[FCGI_MAX]; + TAILQ_HEAD(lochead, location); struct location { const char *match; @@ -69,6 +86,7 @@ struct location { int strip; X509_STORE *reqca; int disable_log; + int fcgi; const char *dir; int dirfd; @@ -157,6 +175,14 @@ struct parser { const char *err; }; +struct mbuf { + size_t len; + size_t off; + TAILQ_ENTRY(mbuf) mbufs; + char data[]; +}; +TAILQ_HEAD(mbufhead, mbuf); + struct client; typedef void (imsg_handlerfn)(struct imsgbuf*, struct imsg*, size_t); @@ -171,6 +197,7 @@ typedef void (*statefn)(int, short, void*); * handle_handshake -> close_conn // on err * * handle_open_conn -> handle_cgi_reply // via open_file/dir/... + * handle_open_conn -> send_fcgi_req // via apply_fastcgi, IMSG_FCGI_FD * handle_open_conn -> handle_dirlist // ...same * handle_open_conn -> send_file // ...same * handle_open_conn -> start_reply // on error @@ -180,6 +207,10 @@ typedef void (*statefn)(int, short, void*); * * handle_cgi -> close_conn * + * send_fcgi_req -> copy_mbuf // via handle_fcgi + * handle_fcgi -> close_all // on error + * copy_mbuf -> close_conn // on success/error + * * handle_dirlist -> send_directory_listing * handle_dirlist -> close_conn // on error * @@ -193,21 +224,33 @@ struct client { char req[GEMINI_URL_LEN]; struct iri iri; char domain[DOMAIN_NAME_LEN]; + + /* + * start_reply uses this to know what function call after the + * reply. It's also used as sentinel value in fastcgi to know + * if the server has closed the request. + */ statefn next; + int code; const char *meta; int fd, pfd; struct dirent **dir; int dirlen, diroff; + int fcgi; /* big enough to store STATUS + SPACE + META + CRLF */ char sbuf[1029]; ssize_t len, off; + struct mbufhead mbufhead; + struct sockaddr_storage addr; struct vhost *host; /* host they're talking to */ }; +extern struct client clients[MAX_USERS]; + struct cgireq { char buf[GEMINI_URL_LEN]; @@ -248,6 +291,8 @@ enum { enum imsg_type { IMSG_CGI_REQ, IMSG_CGI_RES, + IMSG_FCGI_REQ, + IMSG_FCGI_FD, IMSG_LOG, IMSG_QUIT, }; @@ -298,11 +343,16 @@ const char *vhost_default_mime(struct vhost*, const ch const char *vhost_index(struct vhost*, const char*); int vhost_auto_index(struct vhost*, const char*); int vhost_block_return(struct vhost*, const char*, int*, const char**); +int vhost_fastcgi(struct vhost*, const char*); int vhost_dirfd(struct vhost*, const char*); int vhost_strip(struct vhost*, const char*); X509_STORE *vhost_require_ca(struct vhost*, const char*); int vhost_disable_log(struct vhost*, const char*); + void mark_nonblock(int); +void start_reply(struct client*, int, const char*); +void close_conn(int, short, void*); +struct client *try_client_by_id(int); void loop(struct tls*, int, int, struct imsgbuf*); /* dirs.c */ @@ -325,6 +375,10 @@ int send_fd(int, int); int recv_fd(int); int executor_main(struct imsgbuf*); +/* fcgi.c */ +void handle_fcgi(int, short, void*); +void send_fcgi_req(struct fcgi*, struct client*); + /* sandbox.c */ void sandbox_server_process(void); void sandbox_executor_process(void); blob - 1aa87f2f23f40994f19f6cf4cc5450e93a758105 blob + eadbb188679b6f05a7cac3d47d401f6520cc3f67 --- lex.l +++ lex.l @@ -62,6 +62,7 @@ client return TCLIENT; default return TDEFAULT; entrypoint return TENTRYPOINT; env return TENV; +fastcgi return TFASTCGI; index return TINDEX; ipv6 return TIPV6; key return TKEY; @@ -76,7 +77,9 @@ require return TREQUIRE; return return TRETURN; root return TROOT; server return TSERVER; +spawn return TSPAWN; strip return TSTRIP; +tcp return TTCP; type return TTYPE; user return TUSER; blob - 692037aa8400cbba38e7aefbfefc5981dc97cda6 blob + e3eff65b82b6d0c29ddad82ec37d1e9540549bd4 --- parse.y +++ parse.y @@ -47,6 +47,8 @@ int check_strip_no(int); int check_prefork_num(int); void advance_loc(void); void only_once(const void*, const char*); +void only_oncei(int, const char*); +int fastcgi_conf(char *, char *, char *); %} @@ -60,7 +62,8 @@ void only_once(const void*, const char*); %token TIPV6 TPORT TPROTOCOLS TMIME TDEFAULT TTYPE TCHROOT TUSER TSERVER %token TPREFORK TLOCATION TCERT TKEY TROOT TCGI TENV TLANG TLOG TINDEX TAUTO -%token TSTRIP TBLOCK TRETURN TENTRYPOINT TREQUIRE TCLIENT TCA TALIAS +%token TSTRIP TBLOCK TRETURN TENTRYPOINT TREQUIRE TCLIENT TCA TALIAS TTCP +%token TFASTCGI TSPAWN %token TERR @@ -203,6 +206,29 @@ locopt : TAUTO TINDEX TBOOL { loc->auto_index = $3 ? only_once(loc->default_mime, "default type"); loc->default_mime = $3; } + | TFASTCGI TSPAWN TSTRING { + only_oncei(loc->fcgi, "fastcgi"); + loc->fcgi = fastcgi_conf(NULL, NULL, $3); + } + | TFASTCGI TSTRING { + only_oncei(loc->fcgi, "fastcgi"); + loc->fcgi = fastcgi_conf($2, NULL, NULL); + } + | TFASTCGI TTCP TSTRING TNUM { + char *c; + if (asprintf(&c, "%d", $4) == -1) + err(1, "asprintf"); + only_oncei(loc->fcgi, "fastcgi"); + loc->fcgi = fastcgi_conf($3, c, NULL); + } + | TFASTCGI TTCP TSTRING { + only_oncei(loc->fcgi, "fastcgi"); + loc->fcgi = fastcgi_conf($3, xstrdup("9000"), NULL); + } + | TFASTCGI TTCP TSTRING TSTRING { + only_oncei(loc->fcgi, "fastcgi"); + loc->fcgi = fastcgi_conf($3, $4, NULL); + } | TINDEX TSTRING { only_once(loc->index, "index"); loc->index = $2; @@ -241,6 +267,7 @@ new_location(void) l = xcalloc(1, sizeof(*l)); l->dirfd = -1; + l->fcgi = -1; return l; } @@ -354,3 +381,41 @@ only_once(const void *ptr, const char *name) if (ptr != NULL) yyerror("`%s' specified more than once", name); } + +void +only_oncei(int i, const char *name) +{ + if (i != -1) + yyerror("`%s' specified more than once", name); +} + +int +fastcgi_conf(char *path, char *port, char *prog) +{ + struct fcgi *f; + int i; + + for (i = 0; i < FCGI_MAX; ++i) { + f = &fcgi[i]; + + if (f->path == NULL) { + f->id = i; + f->path = path; + f->port = port; + f->prog = prog; + return i; + } + + /* XXX: what to do with prog? */ + if (!strcmp(f->path, path) && + ((port == NULL && f->port == NULL) || + !strcmp(f->port, port))) { + free(path); + free(port); + return i; + } + } + + yyerror("too much `fastcgi' rules defined."); + return -1; +} blob - 4e107392d2ac379331c4a93820e852aa076e3d64 blob + d2236d7ff967a489d0306a956a1e79dddedc95f5 --- sandbox.c +++ sandbox.c @@ -304,6 +304,8 @@ sandbox_executor_process(void) { struct vhost *h; struct location *l; + struct fcgi *f; + size_t i; TAILQ_FOREACH(h, &hosts, vhosts) { TAILQ_FOREACH(l, &h->locations, locations) { @@ -317,8 +319,25 @@ sandbox_executor_process(void) } } - /* rpath to chdir into the correct directory */ - if (pledge("stdio rpath sendfd proc exec", NULL)) + for (i = 0; i < FCGI_MAX; i++) { + f = &fcgi[i]; + if (f->path != NULL) { + if (unveil(f->path, "rw") == -1) + fatal("unveil %s", f->path); + } + + if (f->prog != NULL) { + if (unveil(f->prog, "rx") == -1) + fatal("unveil %s", f->prog); + } + } + + /* + * rpath: to chdir into the correct directory + * proc exec: CGI + * dns inet unix: FastCGI + */ + if (pledge("stdio rpath sendfd proc exec dns inet unix", NULL)) err(1, "pledge"); } blob - 79c7d9cd40b139087bcbc8fd064ab1f6aaa77a7f blob + 7bc783bb29a65885f7b95c82d0826c919527bef3 --- server.c +++ server.c @@ -26,7 +26,8 @@ #include #include -static struct client clients[MAX_USERS]; +struct client clients[MAX_USERS]; + static struct tls *ctx; static struct event e4, e6, imsgev, siginfo, sigusr2; @@ -48,7 +49,6 @@ static void fmt_sbuf(const char*, struct client*, con static int apply_block_return(struct client*); static int apply_require_ca(struct client*); static void handle_open_conn(int, short, void*); -static void start_reply(struct client*, int, const char*); static void handle_start_reply(int, short, void*); static size_t host_nth(struct vhost*); static void start_cgi(const char*, const char*, struct client*); @@ -60,16 +60,16 @@ static int read_next_dir_entry(struct client*); static void send_directory_listing(int, short, void*); static void handle_cgi_reply(int, short, void*); static void handle_copy(int, short, void*); -static void close_conn(int, short, void*); static void do_accept(int, short, void*); -struct client *client_by_id(int); static void handle_imsg_cgi_res(struct imsgbuf*, struct imsg*, size_t); +static void handle_imsg_fcgi_fd(struct imsgbuf*, struct imsg*, size_t); static void handle_imsg_quit(struct imsgbuf*, struct imsg*, size_t); static void handle_siginfo(int, short, void*); static imsg_handlerfn *handlers[] = { [IMSG_QUIT] = handle_imsg_quit, [IMSG_CGI_RES] = handle_imsg_cgi_res, + [IMSG_FCGI_FD] = handle_imsg_fcgi_fd, }; static inline int @@ -203,6 +203,25 @@ vhost_block_return(struct vhost *v, const char *path, } int +vhost_fastcgi(struct vhost *v, const char *path) +{ + struct location *loc; + + if (v == NULL || path == NULL) + return -1; + + loc = TAILQ_FIRST(&v->locations); + while ((loc = TAILQ_NEXT(loc, locations)) != NULL) { + if (loc->fcgi != -1) + if (matches(loc->match, path)) + return loc->fcgi; + } + + loc = TAILQ_FIRST(&v->locations); + return loc->fcgi; +} + +int vhost_dirfd(struct vhost *v, const char *path) { struct location *loc; @@ -553,6 +572,37 @@ apply_block_return(struct client *c) fmt_sbuf(fmt, c, path); start_reply(c, code, c->sbuf); + return 1; +} + +/* 1 if matching `fcgi' (and apply it), 0 otherwise */ +static int +apply_fastcgi(struct client *c) +{ + int id; + struct fcgi *f; + + if ((id = vhost_fastcgi(c->host, c->iri.path)) == -1) + return 0; + + switch ((f = &fcgi[id])->s) { + case FCGI_OFF: + f->s = FCGI_INFLIGHT; + log_info(c, "opening fastcgi connection for (%s,%s,%s)", + f->path, f->port, f->prog); + imsg_compose(&exibuf, IMSG_FCGI_REQ, 0, 0, -1, + &id, sizeof(id)); + imsg_flush(&exibuf); + /* fallthrough */ + case FCGI_INFLIGHT: + c->fcgi = id; + break; + case FCGI_READY: + c->fcgi = id; + send_fcgi_req(f, c); + break; + } + return 1; } @@ -627,6 +677,9 @@ handle_open_conn(int fd, short ev, void *d) if (apply_block_return(c)) return; + if (apply_fastcgi(c)) + return; + if (c->host->entrypoint != NULL) { start_cgi(c->host->entrypoint, c->iri.path, c); return; @@ -635,7 +688,7 @@ handle_open_conn(int fd, short ev, void *d) open_file(c); } -static void +void start_reply(struct client *c, int code, const char *meta) { c->code = code; @@ -1039,10 +1092,11 @@ end: close_conn(c->fd, ev, d); } -static void +void close_conn(int fd, short ev, void *d) { - struct client *c = d; + struct client *c = d; + struct mbuf *mbuf; switch (tls_close(c->ctx)) { case TLS_WANT_POLLIN: @@ -1055,6 +1109,11 @@ close_conn(int fd, short ev, void *d) connected_clients--; + while ((mbuf = TAILQ_FIRST(&c->mbufhead)) != NULL) { + TAILQ_REMOVE(&c->mbufhead, mbuf, mbufs); + free(mbuf); + } + tls_free(c->ctx); c->ctx = NULL; @@ -1101,6 +1160,7 @@ do_accept(int sock, short et, void *d) c->pfd = -1; c->dir = NULL; c->addr = addr; + c->fcgi = -1; yield_read(fd, c, &handle_handshake); connected_clients++; @@ -1111,7 +1171,7 @@ do_accept(int sock, short et, void *d) close(fd); } -struct client * +static struct client * client_by_id(int id) { if ((size_t)id > sizeof(clients)/sizeof(clients[0])) @@ -1119,6 +1179,14 @@ client_by_id(int id) return &clients[id]; } +struct client * +try_client_by_id(int id) +{ + if ((size_t)id > sizeof(clients)/sizeof(clients[0])) + return NULL; + return &clients[id]; +} + static void handle_imsg_cgi_res(struct imsgbuf *ibuf, struct imsg *imsg, size_t len) { @@ -1130,6 +1198,39 @@ handle_imsg_cgi_res(struct imsgbuf *ibuf, struct imsg start_reply(c, TEMP_FAILURE, "internal server error"); else yield_read(c->pfd, c, &handle_cgi_reply); +} + +static void +handle_imsg_fcgi_fd(struct imsgbuf *ibuf, struct imsg *imsg, size_t len) +{ + struct client *c; + struct fcgi *f; + int i, id; + + id = imsg->hdr.peerid; + f = &fcgi[id]; + + if ((f->fd = imsg->fd) != -1) { + event_set(&f->e, imsg->fd, EV_READ | EV_PERSIST, &handle_fcgi, + &fcgi[id]); + event_add(&f->e, NULL); + } else { + f->s = FCGI_OFF; + } + + for (i = 0; i < MAX_USERS; ++i) { + c = &clients[i]; + if (c->fd == -1) + continue; + if (c->fcgi != id) + continue; + + if (f->fd == -1) { + c->fcgi = -1; + start_reply(c, TEMP_FAILURE, "internal server error"); + } else + send_fcgi_req(f, c); + } } static void