2 * Copyright (c) 2021, 2022, 2023 Omar Polo <op@omarpolo.com>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
27 unsigned char version;
29 unsigned char req_id1;
30 unsigned char req_id0;
31 unsigned char content_len1;
32 unsigned char content_len0;
33 unsigned char padding;
34 unsigned char reserved;
38 * number of bytes in a FCGI_HEADER. Future version of the protocol
39 * will not reduce this number.
41 #define FCGI_HEADER_LEN 8
44 * values for the version component
46 #define FCGI_VERSION_1 1
49 * values for the type component
51 #define FCGI_BEGIN_REQUEST 1
52 #define FCGI_ABORT_REQUEST 2
53 #define FCGI_END_REQUEST 3
59 #define FCGI_GET_VALUES 9
60 #define FCGI_GET_VALUES_RESULT 10
61 #define FCGI_UNKNOWN_TYPE 11
62 #define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
64 struct fcgi_begin_req {
68 unsigned char reserved[5];
71 struct fcgi_begin_req_record {
72 struct fcgi_header header;
73 struct fcgi_begin_req body;
79 #define FCGI_KEEP_CONN 1
84 #define FCGI_RESPONDER 1
85 #define FCGI_AUTHORIZER 2
88 struct fcgi_end_req_body {
89 unsigned char app_status3;
90 unsigned char app_status2;
91 unsigned char app_status1;
92 unsigned char app_status0;
93 unsigned char proto_status;
94 unsigned char reserved[3];
98 * values for proto_status
100 #define FCGI_REQUEST_COMPLETE 0
101 #define FCGI_CANT_MPX_CONN 1
102 #define FCGI_OVERLOADED 2
103 #define FCGI_UNKNOWN_ROLE 3
106 * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT
109 #define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
110 #define FCGI_MAX_REQS "FCGI_MAX_REQS"
111 #define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
114 prepare_header(struct fcgi_header *h, int type, size_t size,
119 memset(h, 0, sizeof(*h));
121 h->version = FCGI_VERSION_1;
123 h->req_id1 = (id >> 8);
124 h->req_id0 = (id & 0xFF);
125 h->content_len1 = (size >> 8);
126 h->content_len0 = (size & 0xFF);
127 h->padding = padding;
133 fcgi_begin_request(struct bufferevent *bev)
135 struct fcgi_begin_req_record r;
137 memset(&r, 0, sizeof(r));
138 prepare_header(&r.header, FCGI_BEGIN_REQUEST, sizeof(r.body), 0);
139 assert(sizeof(r.body) == FCGI_HEADER_LEN);
142 r.body.role0 = FCGI_RESPONDER;
143 r.body.flags = FCGI_KEEP_CONN;
145 if (bufferevent_write(bev, &r, sizeof(r)) == -1)
151 fcgi_send_param(struct bufferevent *bev, const char *name,
154 struct fcgi_header h;
155 uint32_t namlen, vallen, padlen;
158 const char padding[8] = { 0 };
160 namlen = strlen(name);
161 vallen = strlen(value);
162 size = namlen + vallen + 8; /* 4 for the sizes */
163 padlen = (8 - (size & 0x7)) & 0x7;
165 s[0] = ( namlen >> 24) | 0x80;
166 s[1] = ((namlen >> 16) & 0xFF);
167 s[2] = ((namlen >> 8) & 0xFF);
168 s[3] = ( namlen & 0xFF);
170 s[4] = ( vallen >> 24) | 0x80;
171 s[5] = ((vallen >> 16) & 0xFF);
172 s[6] = ((vallen >> 8) & 0xFF);
173 s[7] = ( vallen & 0xFF);
175 prepare_header(&h, FCGI_PARAMS, size, padlen);
177 if (bufferevent_write(bev, &h, sizeof(h)) == -1 ||
178 bufferevent_write(bev, s, sizeof(s)) == -1 ||
179 bufferevent_write(bev, name, namlen) == -1 ||
180 bufferevent_write(bev, value, vallen) == -1 ||
181 bufferevent_write(bev, padding, padlen) == -1)
188 fcgi_end_param(struct bufferevent *bev)
190 struct fcgi_header h;
192 prepare_header(&h, FCGI_PARAMS, 0, 0);
193 if (bufferevent_write(bev, &h, sizeof(h)) == -1)
196 prepare_header(&h, FCGI_STDIN, 0, 0);
197 if (bufferevent_write(bev, &h, sizeof(h)) == -1)
204 recid(struct fcgi_header *h)
206 return h->req_id0 + (h->req_id1 << 8);
210 reclen(struct fcgi_header *h)
212 return h->content_len0 + (h->content_len1 << 8);
216 fcgi_handle_stdout(struct client *c, struct evbuffer *src, size_t len)
218 struct bufferevent *bev = c->cgibev;
225 if (l > sizeof(c->sbuf) - c->soff)
226 l = sizeof(c->sbuf) - c->soff;
228 memcpy(&c->sbuf[c->soff], EVBUFFER_DATA(src), l);
230 evbuffer_drain(src, l);
233 if ((t = memmem(c->sbuf, c->soff, "\r\n", 2)) == NULL) {
234 if (c->soff == sizeof(c->sbuf)) {
235 log_warnx("FastCGI application is trying to"
236 " send a header that's too long.");
237 fcgi_error(bev, EVBUFFER_ERROR, c);
244 t += 2; /* skip CRLF */
246 if (!isdigit((unsigned char)c->sbuf[0]) ||
247 !isdigit((unsigned char)c->sbuf[1]) ||
249 fcgi_error(bev, EVBUFFER_ERROR, c);
253 code = (c->sbuf[0] - '0') * 10 + (c->sbuf[1] - '0');
254 if (code < 10 || code >= 70) {
255 log_warnx("FastCGI application is trying to send an"
256 " invalid reply code: %d", code);
257 fcgi_error(bev, EVBUFFER_ERROR, c);
261 if (start_reply(c, code, c->sbuf + 3) == -1 ||
262 c->code < 20 || c->code > 29) {
263 fcgi_error(bev, EVBUFFER_EOF, c);
267 bufferevent_write(c->bev, t, &c->sbuf[c->soff] - t);
270 bufferevent_write(c->bev, EVBUFFER_DATA(src), len);
271 evbuffer_drain(src, len);
275 fcgi_read(struct bufferevent *bev, void *d)
277 struct client *c = d;
278 struct evbuffer *src = EVBUFFER_INPUT(bev);
279 struct fcgi_header hdr;
280 struct fcgi_end_req_body end;
283 while (c->type != REQUEST_DONE) {
284 if (EVBUFFER_LENGTH(src) < sizeof(hdr))
287 memcpy(&hdr, EVBUFFER_DATA(src), sizeof(hdr));
289 if (recid(&hdr) != 1) {
290 log_warnx("got invalid client id %d from fcgi backend",
297 if (EVBUFFER_LENGTH(src) < sizeof(hdr) + len + hdr.padding)
300 evbuffer_drain(src, sizeof(hdr));
303 case FCGI_END_REQUEST:
304 if (len != sizeof(end)) {
305 log_warnx("got invalid end request"
309 bufferevent_read(bev, &end, sizeof(end));
311 /* TODO: do something with the status? */
312 c->type = REQUEST_DONE;
316 /* discard stderr (for now) */
317 evbuffer_drain(src, len);
321 fcgi_handle_stdout(c, src, len);
325 log_warnx("got invalid fcgi record (type=%d)",
330 evbuffer_drain(src, hdr.padding);
334 fcgi_error(bev, EVBUFFER_ERROR, c);
335 client_write(c->bev, c);
339 fcgi_write(struct bufferevent *bev, void *d)
342 * There's no much use for the write callback.
348 fcgi_error(struct bufferevent *bev, short err, void *d)
350 struct client *c = d;
353 * If we're here it means that some kind of non-recoverable
356 * Don't free bev as we might be called by a function that
360 bufferevent_disable(bev, EVBUFFER_READ);
365 /* EOF and no header */
367 start_reply(c, CGI_ERROR, "CGI error");
371 c->type = REQUEST_DONE;
375 path_translate(const char *path, struct location *loc, struct location *rloc,
376 char *buf, size_t len)
378 const char *root, *sufx;
382 if (*loc->dir != '\0')
384 else if (*rloc->dir != '\0')
391 sufx = root[strlen(root) - 1] == '/' ? "" : "/";
396 snprintf(buf, len, "%s%s%s", root, sufx, path);
400 fcgi_req(struct client *c, struct location *loc)
402 char buf[22], path[GEMINI_URL_LEN], path_tr[PATH_MAX];
403 char *scriptname, *qs;
404 const char *stripped, *port;
410 fcgi_begin_request(c->cgibev);
412 stripped = strip_path(c->iri.path, loc->fcgi_strip);
413 if (*stripped != '/')
414 snprintf(path, sizeof(path), "/%s", stripped);
416 strlcpy(path, stripped, sizeof(path));
419 if (port == NULL || *port == '\0')
423 TAILQ_FOREACH(p, &loc->params, envs) {
424 if (!strcmp(p->name, "SCRIPT_NAME")) {
425 scriptname = p->value;
430 l = strlen(scriptname);
431 while (l > 0 && scriptname[l - 1] == '/')
433 if (!strncmp(scriptname, path, l) && (path[l] == '/' ||
435 fcgi_send_param(c->cgibev, "PATH_INFO", &path[l]);
436 path_translate(&path[l], loc, TAILQ_FIRST(&c->host->locations),
437 path_tr, sizeof(path_tr));
439 fcgi_send_param(c->cgibev, "SCRIPT_NAME", path);
441 path_translate(stripped, loc, TAILQ_FIRST(&c->host->locations),
442 path_tr, sizeof(path_tr));
443 fcgi_send_param(c->cgibev, "PATH_INFO", stripped);
444 fcgi_send_param(c->cgibev, "SCRIPT_NAME", scriptname);
447 fcgi_send_param(c->cgibev, "GATEWAY_INTERFACE", "CGI/1.1");
448 fcgi_send_param(c->cgibev, "PATH_TRANSLATED", path_tr);
449 fcgi_send_param(c->cgibev, "QUERY_STRING", c->iri.query);
450 fcgi_send_param(c->cgibev, "REMOTE_ADDR", c->rhost);
451 fcgi_send_param(c->cgibev, "REMOTE_HOST", c->rhost);
452 fcgi_send_param(c->cgibev, "REQUEST_METHOD", "GET");
453 fcgi_send_param(c->cgibev, "SERVER_NAME", c->iri.host);
454 fcgi_send_param(c->cgibev, "SERVER_PORT", port);
455 fcgi_send_param(c->cgibev, "SERVER_PROTOCOL", "GEMINI");
456 fcgi_send_param(c->cgibev, "SERVER_SOFTWARE", GMID_VERSION);
458 fcgi_send_param(c->cgibev, "GEMINI_URL_PATH", c->iri.path);
460 if (*c->iri.query != '\0' &&
461 strchr(c->iri.query, '=') == NULL &&
462 (qs = strdup(c->iri.query)) != NULL) {
464 fcgi_send_param(c->cgibev, "GEMINI_SEARCH_STRING", qs);
468 TAILQ_FOREACH(p, &loc->params, envs) {
469 if (!strcmp(p->name, "SCRIPT_NAME"))
471 fcgi_send_param(c->cgibev, p->name, p->value);
474 if (tls_peer_cert_provided(c->ctx)) {
475 fcgi_send_param(c->cgibev, "AUTH_TYPE", "CERTIFICATE");
476 fcgi_send_param(c->cgibev, "REMOTE_USER",
477 tls_peer_cert_subject(c->ctx));
478 fcgi_send_param(c->cgibev, "TLS_CLIENT_ISSUER",
479 tls_peer_cert_issuer(c->ctx));
480 fcgi_send_param(c->cgibev, "TLS_CLIENT_HASH",
481 tls_peer_cert_hash(c->ctx));
482 fcgi_send_param(c->cgibev, "TLS_VERSION",
483 tls_conn_version(c->ctx));
484 fcgi_send_param(c->cgibev, "TLS_CIPHER",
485 tls_conn_cipher(c->ctx));
487 snprintf(buf, sizeof(buf), "%d",
488 tls_conn_cipher_strength(c->ctx));
489 fcgi_send_param(c->cgibev, "TLS_CIPHER_STRENGTH", buf);
491 tim = tls_peer_cert_notbefore(c->ctx);
492 strftime(buf, sizeof(buf), "%FT%TZ",
493 gmtime_r(&tim, &tminfo));
494 fcgi_send_param(c->cgibev, "TLS_CLIENT_NOT_BEFORE", buf);
496 tim = tls_peer_cert_notafter(c->ctx);
497 strftime(buf, sizeof(buf), "%FT%TZ",
498 gmtime_r(&tim, &tminfo));
499 fcgi_send_param(c->cgibev, "TLS_CLIENT_NOT_AFTER", buf);
502 fcgi_send_param(c->cgibev, "AUTH_TYPE", "");
504 if (fcgi_end_param(c->cgibev) == -1)
505 fcgi_error(c->cgibev, EVBUFFER_ERROR, c);