Blob


1 /*
2 * Copyright (c) 2021, 2022, 2023 Omar Polo <op@omarpolo.com>
3 *
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.
7 *
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.
15 */
17 #include "gmid.h"
19 #include <assert.h>
20 #include <ctype.h>
21 #include <errno.h>
22 #include <string.h>
24 #include "log.h"
26 struct fcgi_header {
27 unsigned char version;
28 unsigned char type;
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;
35 };
37 /*
38 * number of bytes in a FCGI_HEADER. Future version of the protocol
39 * will not reduce this number.
40 */
41 #define FCGI_HEADER_LEN 8
43 /*
44 * values for the version component
45 */
46 #define FCGI_VERSION_1 1
48 /*
49 * values for the type component
50 */
51 #define FCGI_BEGIN_REQUEST 1
52 #define FCGI_ABORT_REQUEST 2
53 #define FCGI_END_REQUEST 3
54 #define FCGI_PARAMS 4
55 #define FCGI_STDIN 5
56 #define FCGI_STDOUT 6
57 #define FCGI_STDERR 7
58 #define FCGI_DATA 8
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 {
65 unsigned char role1;
66 unsigned char role0;
67 unsigned char flags;
68 unsigned char reserved[5];
69 };
71 struct fcgi_begin_req_record {
72 struct fcgi_header header;
73 struct fcgi_begin_req body;
74 };
76 /*
77 * mask for flags;
78 */
79 #define FCGI_KEEP_CONN 1
81 /*
82 * values for the role
83 */
84 #define FCGI_RESPONDER 1
85 #define FCGI_AUTHORIZER 2
86 #define FCGI_FILTER 3
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];
95 };
97 /*
98 * values for proto_status
99 */
100 #define FCGI_REQUEST_COMPLETE 0
101 #define FCGI_CANT_MPX_CONN 1
102 #define FCGI_OVERLOADED 2
103 #define FCGI_UNKNOWN_ROLE 3
105 /*
106 * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT
107 * records.
108 */
109 #define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
110 #define FCGI_MAX_REQS "FCGI_MAX_REQS"
111 #define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
113 static int
114 prepare_header(struct fcgi_header *h, int type, size_t size,
115 size_t padding)
117 int id = 1;
119 memset(h, 0, sizeof(*h));
121 h->version = FCGI_VERSION_1;
122 h->type = type;
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;
129 return 0;
132 static int
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);
141 r.body.role1 = 0;
142 r.body.role0 = FCGI_RESPONDER;
143 r.body.flags = FCGI_KEEP_CONN;
145 if (bufferevent_write(bev, &r, sizeof(r)) == -1)
146 return -1;
147 return 0;
150 static int
151 fcgi_send_param(struct bufferevent *bev, const char *name,
152 const char *value)
154 struct fcgi_header h;
155 uint32_t namlen, vallen, padlen;
156 uint8_t s[8];
157 size_t size;
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)
182 return -1;
184 return 0;
187 static int
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)
194 return -1;
196 prepare_header(&h, FCGI_STDIN, 0, 0);
197 if (bufferevent_write(bev, &h, sizeof(h)) == -1)
198 return -1;
200 return 0;
203 static inline int
204 recid(struct fcgi_header *h)
206 return h->req_id0 + (h->req_id1 << 8);
209 static inline int
210 reclen(struct fcgi_header *h)
212 return h->content_len0 + (h->content_len1 << 8);
215 static void
216 fcgi_handle_stdout(struct client *c, struct evbuffer *src, size_t len)
218 struct bufferevent *bev = c->cgibev;
219 char *t;
220 size_t l;
221 int code;
223 if (c->code == 0) {
224 l = len;
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);
229 c->soff += l;
230 evbuffer_drain(src, l);
231 len -= 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);
240 /* wait a bit */
241 return;
243 *t = '\0';
244 t += 2; /* skip CRLF */
246 if (!isdigit((unsigned char)c->sbuf[0]) ||
247 !isdigit((unsigned char)c->sbuf[1]) ||
248 c->sbuf[2] != ' ') {
249 fcgi_error(bev, EVBUFFER_ERROR, c);
250 return;
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);
258 return;
261 if (start_reply(c, code, c->sbuf + 3) == -1 ||
262 c->code < 20 || c->code > 29) {
263 fcgi_error(bev, EVBUFFER_EOF, c);
264 return;
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);
274 void
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;
281 size_t len;
283 while (c->type != REQUEST_DONE) {
284 if (EVBUFFER_LENGTH(src) < sizeof(hdr))
285 return;
287 memcpy(&hdr, EVBUFFER_DATA(src), sizeof(hdr));
289 if (recid(&hdr) != 1) {
290 log_warnx("got invalid client id %d from fcgi backend",
291 recid(&hdr));
292 goto err;
295 len = reclen(&hdr);
297 if (EVBUFFER_LENGTH(src) < sizeof(hdr) + len + hdr.padding)
298 return;
300 evbuffer_drain(src, sizeof(hdr));
302 switch (hdr.type) {
303 case FCGI_END_REQUEST:
304 if (len != sizeof(end)) {
305 log_warnx("got invalid end request"
306 " record size");
307 goto err;
309 bufferevent_read(bev, &end, sizeof(end));
311 /* TODO: do something with the status? */
312 c->type = REQUEST_DONE;
313 break;
315 case FCGI_STDERR:
316 /* discard stderr (for now) */
317 evbuffer_drain(src, len);
318 break;
320 case FCGI_STDOUT:
321 fcgi_handle_stdout(c, src, len);
322 break;
324 default:
325 log_warnx("got invalid fcgi record (type=%d)",
326 hdr.type);
327 goto err;
330 evbuffer_drain(src, hdr.padding);
333 err:
334 fcgi_error(bev, EVBUFFER_ERROR, c);
335 client_write(c->bev, c);
338 void
339 fcgi_write(struct bufferevent *bev, void *d)
341 /*
342 * There's no much use for the write callback.
343 */
344 return;
347 void
348 fcgi_error(struct bufferevent *bev, short err, void *d)
350 struct client *c = d;
352 /*
353 * If we're here it means that some kind of non-recoverable
354 * error happened.
356 * Don't free bev as we might be called by a function that
357 * still uses it.
358 */
360 bufferevent_disable(bev, EVBUFFER_READ);
362 close(c->pfd);
363 c->pfd = -1;
365 /* EOF and no header */
366 if (c->code == 0) {
367 start_reply(c, CGI_ERROR, "CGI error");
368 return;
371 c->type = REQUEST_DONE;
374 static void
375 path_translate(const char *path, struct location *loc, struct location *rloc,
376 char *buf, size_t len)
378 const char *root, *sufx;
380 buf[0] = '\0';
382 if (*loc->dir != '\0')
383 root = loc->dir;
384 else if (*rloc->dir != '\0')
385 root = rloc->dir;
386 else
387 return;
389 sufx = "";
390 if (*root != '\0')
391 sufx = root[strlen(root) - 1] == '/' ? "" : "/";
393 while (*path == '/')
394 path++;
396 snprintf(buf, len, "%s%s%s", root, sufx, path);
399 void
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;
405 size_t l;
406 time_t tim;
407 struct tm tminfo;
408 struct envlist *p;
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);
415 else
416 strlcpy(path, stripped, sizeof(path));
418 port = c->iri.host;
419 if (port == NULL || *port == '\0')
420 port = "1965";
422 scriptname = "";
423 TAILQ_FOREACH(p, &loc->params, envs) {
424 if (!strcmp(p->name, "SCRIPT_NAME")) {
425 scriptname = p->value;
426 break;
430 l = strlen(scriptname);
431 while (l > 0 && scriptname[l - 1] == '/')
432 l--;
433 if (!strncmp(scriptname, path, l) && (path[l] == '/' ||
434 path[l] == '\0')) {
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));
438 path[l] = '\0';
439 fcgi_send_param(c->cgibev, "SCRIPT_NAME", path);
440 } else {
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) {
463 pct_decode_str(qs);
464 fcgi_send_param(c->cgibev, "GEMINI_SEARCH_STRING", qs);
465 free(qs);
468 TAILQ_FOREACH(p, &loc->params, envs) {
469 if (!strcmp(p->name, "SCRIPT_NAME"))
470 continue;
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);
501 } else
502 fcgi_send_param(c->cgibev, "AUTH_TYPE", "");
504 if (fcgi_end_param(c->cgibev) == -1)
505 fcgi_error(c->cgibev, EVBUFFER_ERROR, c);