Blob


1 /*
2 * Copyright (c) 2022 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 <sys/queue.h>
18 #include <sys/tree.h>
19 #include <sys/socket.h>
21 #include <netinet/in.h>
22 #include <arpa/inet.h>
24 #include <errno.h>
25 #include <event.h>
26 #include <limits.h>
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <stdint.h>
31 #include <string.h>
32 #include <unistd.h>
34 #include "log.h"
35 #include "pkg.h"
37 #if template
38 #include "tmpl.h"
39 #endif
41 #define MIN(a, b) ((a) < (b) ? (a) : (b))
43 struct fcgi_header {
44 unsigned char version;
45 unsigned char type;
46 unsigned char req_id1;
47 unsigned char req_id0;
48 unsigned char content_len1;
49 unsigned char content_len0;
50 unsigned char padding;
51 unsigned char reserved;
52 } __attribute__((packed));
54 /*
55 * number of bytes in a FCGI_HEADER. Future version of the protocol
56 * will not reduce this number.
57 */
58 #define FCGI_HEADER_LEN 8
60 /*
61 * values for the version component
62 */
63 #define FCGI_VERSION_1 1
65 /*
66 * values for the type component
67 */
68 #define FCGI_BEGIN_REQUEST 1
69 #define FCGI_ABORT_REQUEST 2
70 #define FCGI_END_REQUEST 3
71 #define FCGI_PARAMS 4
72 #define FCGI_STDIN 5
73 #define FCGI_STDOUT 6
74 #define FCGI_STDERR 7
75 #define FCGI_DATA 8
76 #define FCGI_GET_VALUES 9
77 #define FCGI_GET_VALUES_RESULT 10
78 #define FCGI_UNKNOWN_TYPE 11
79 #define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
81 struct fcgi_begin_req {
82 unsigned char role1;
83 unsigned char role0;
84 unsigned char flags;
85 unsigned char reserved[5];
86 };
88 struct fcgi_begin_req_record {
89 struct fcgi_header header;
90 struct fcgi_begin_req body;
91 };
93 /*
94 * mask for flags;
95 */
96 #define FCGI_KEEP_CONN 1
98 /*
99 * values for the role
100 */
101 #define FCGI_RESPONDER 1
102 #define FCGI_AUTHORIZER 2
103 #define FCGI_FILTER 3
105 struct fcgi_end_req_body {
106 unsigned char app_status3;
107 unsigned char app_status2;
108 unsigned char app_status1;
109 unsigned char app_status0;
110 unsigned char proto_status;
111 unsigned char reserved[3];
112 };
114 /*
115 * values for proto_status
116 */
117 #define FCGI_REQUEST_COMPLETE 0
118 #define FCGI_CANT_MPX_CONN 1
119 #define FCGI_OVERLOADED 2
120 #define FCGI_UNKNOWN_ROLE 3
122 /*
123 * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT
124 * records.
125 */
126 #define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
127 #define FCGI_MAX_REQS "FCGI_MAX_REQS"
128 #define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
130 #define CAT(f0, f1) ((f0) + ((f1) << 8))
132 enum {
133 FCGI_RECORD_HEADER,
134 FCGI_RECORD_BODY,
135 };
137 volatile int fcgi_inflight;
138 int32_t fcgi_id;
140 int accept_reserve(int, struct sockaddr *, socklen_t *, int,
141 volatile int *);
143 static int
144 fcgi_send_end_req(struct fcgi *fcgi, int id, int as, int ps)
146 struct bufferevent *bev = fcgi->fcg_bev;
147 struct fcgi_header hdr;
148 struct fcgi_end_req_body end;
150 memset(&hdr, 0, sizeof(hdr));
151 memset(&end, 0, sizeof(end));
153 hdr.version = FCGI_VERSION_1;
154 hdr.type = FCGI_END_REQUEST;
155 hdr.req_id0 = (id & 0xFF);
156 hdr.req_id1 = (id >> 8);
157 hdr.content_len0 = sizeof(end);
159 end.app_status0 = (unsigned char)as;
160 end.proto_status = (unsigned char)ps;
162 if (bufferevent_write(bev, &hdr, sizeof(hdr)) == -1)
163 return (-1);
164 if (bufferevent_write(bev, &end, sizeof(end)) == -1)
165 return (-1);
166 return (0);
169 static int
170 end_request(struct client *clt, int status, int proto_status)
172 struct fcgi *fcgi = clt->clt_fcgi;
173 int r;
175 if (clt_flush(clt) == -1)
176 return (-1);
178 r = fcgi_send_end_req(fcgi, clt->clt_id, status,
179 proto_status);
180 if (r == -1) {
181 fcgi_error(fcgi->fcg_bev, EV_WRITE, fcgi);
182 return (-1);
185 SPLAY_REMOVE(client_tree, &fcgi->fcg_clients, clt);
186 server_client_free(clt);
188 if (!fcgi->fcg_keep_conn)
189 fcgi->fcg_done = 1;
191 return (0);
194 int
195 fcgi_end_request(struct client *clt, int status)
197 return (end_request(clt, status, FCGI_REQUEST_COMPLETE));
200 int
201 fcgi_abort_request(struct client *clt)
203 return (end_request(clt, 1, FCGI_OVERLOADED));
206 static void
207 fcgi_inflight_dec(const char *why)
209 fcgi_inflight--;
210 log_debug("%s: fcgi inflight decremented, now %d, %s",
211 __func__, fcgi_inflight, why);
214 void
215 fcgi_accept(int fd, short event, void *arg)
217 struct env *env = arg;
218 struct fcgi *fcgi = NULL;
219 socklen_t slen;
220 struct sockaddr_storage ss;
221 int s = -1;
223 event_add(&env->env_pausev, NULL);
224 if ((event & EV_TIMEOUT))
225 return;
227 slen = sizeof(ss);
228 if ((s = accept_reserve(env->env_sockfd, (struct sockaddr *)&ss,
229 &slen, FD_RESERVE, &fcgi_inflight)) == -1) {
230 /*
231 * Pause accept if we are out of file descriptors, or
232 * libevent will haunt us here too.
233 */
234 if (errno == ENFILE || errno == EMFILE) {
235 struct timeval evtpause = { 1, 0 };
237 event_del(&env->env_sockev);
238 evtimer_add(&env->env_pausev, &evtpause);
239 log_debug("%s: deferring connections", __func__);
241 return;
244 if ((fcgi = calloc(1, sizeof(*fcgi))) == NULL)
245 goto err;
247 fcgi->fcg_id = ++fcgi_id;
248 fcgi->fcg_s = s;
249 fcgi->fcg_env = env;
250 fcgi->fcg_want = FCGI_RECORD_HEADER;
251 fcgi->fcg_toread = sizeof(struct fcgi_header);
252 SPLAY_INIT(&fcgi->fcg_clients);
254 /* assume it's enabled until we get a FCGI_BEGIN_REQUEST */
255 fcgi->fcg_keep_conn = 1;
257 fcgi->fcg_bev = bufferevent_new(fcgi->fcg_s, fcgi_read, fcgi_write,
258 fcgi_error, fcgi);
259 if (fcgi->fcg_bev == NULL)
260 goto err;
262 bufferevent_enable(fcgi->fcg_bev, EV_READ | EV_WRITE);
263 return;
265 err:
266 if (s != -1) {
267 close(s);
268 free(fcgi);
269 fcgi_inflight_dec(__func__);
273 static int
274 parse_len(struct fcgi *fcgi, struct evbuffer *src)
276 unsigned char c, x[3];
278 fcgi->fcg_toread--;
279 evbuffer_remove(src, &c, 1);
280 if (c >> 7 == 0)
281 return (c);
283 if (fcgi->fcg_toread < 3)
284 return (-1);
286 fcgi->fcg_toread -= 3;
287 evbuffer_remove(src, x, sizeof(x));
288 return (((c & 0x7F) << 24) | (x[0] << 16) | (x[1] << 8) | x[2]);
291 static int
292 fcgi_parse_params(struct fcgi *fcgi, struct evbuffer *src, struct client *clt)
294 char pname[32];
295 char server[HOST_NAME_MAX + 1];
296 char path[PATH_MAX];
297 char query[GEMINI_MAXLEN];
298 char method[8];
299 int nlen, vlen;
301 while (fcgi->fcg_toread > 0) {
302 if ((nlen = parse_len(fcgi, src)) < 0 ||
303 (vlen = parse_len(fcgi, src)) < 0)
304 return (-1);
306 if (fcgi->fcg_toread < nlen + vlen)
307 return (-1);
309 if ((size_t)nlen > sizeof(pname) - 1) {
310 /* ignore this parameter */
311 fcgi->fcg_toread -= nlen - vlen;
312 evbuffer_drain(src, nlen + vlen);
313 continue;
316 fcgi->fcg_toread -= nlen;
317 evbuffer_remove(src, &pname, nlen);
318 pname[nlen] = '\0';
320 if (!strcmp(pname, "SERVER_NAME") &&
321 (size_t)vlen < sizeof(server)) {
322 fcgi->fcg_toread -= vlen;
323 evbuffer_remove(src, &server, vlen);
324 server[vlen] = '\0';
326 free(clt->clt_server_name);
327 if ((clt->clt_server_name = strdup(server)) == NULL)
328 return (-1);
329 DPRINTF("clt %d: server_name: %s", clt->clt_id,
330 clt->clt_server_name);
331 continue;
334 if (!strcmp(pname, "SCRIPT_NAME") &&
335 (size_t)vlen < sizeof(path)) {
336 fcgi->fcg_toread -= vlen;
337 evbuffer_remove(src, &path, vlen);
338 path[vlen] = '\0';
340 free(clt->clt_script_name);
341 clt->clt_script_name = strdup(path);
342 if (clt->clt_script_name == NULL)
343 return (-1);
345 DPRINTF("clt %d: script_name: %s", clt->clt_id,
346 clt->clt_script_name);
347 continue;
350 if (!strcmp(pname, "PATH_INFO") &&
351 (size_t)vlen < sizeof(path)) {
352 fcgi->fcg_toread -= vlen;
353 evbuffer_remove(src, &path, vlen);
354 path[vlen] = '\0';
356 free(clt->clt_path_info);
357 clt->clt_path_info = strdup(path);
358 if (clt->clt_path_info == NULL)
359 return (-1);
361 DPRINTF("clt %d: path_info: %s", clt->clt_id,
362 clt->clt_path_info);
363 continue;
366 if (!strcmp(pname, "QUERY_STRING") &&
367 (size_t)vlen < sizeof(query) &&
368 vlen > 0) {
369 fcgi->fcg_toread -= vlen;
370 evbuffer_remove(src, &query, vlen);
371 query[vlen] = '\0';
373 free(clt->clt_query);
374 if ((clt->clt_query = strdup(query)) == NULL)
375 return (-1);
377 DPRINTF("clt %d: query: %s", clt->clt_id,
378 clt->clt_query);
379 continue;
382 if (!strcmp(pname, "REQUEST_METHOD") &&
383 (size_t)vlen < sizeof(method)) {
384 fcgi->fcg_toread -= vlen;
385 evbuffer_remove(src, &method, vlen);
386 method[vlen] = '\0';
388 if (!strcasecmp(method, "GET"))
389 clt->clt_method = METHOD_GET;
390 if (!strcasecmp(method, "POST"))
391 clt->clt_method = METHOD_POST;
393 continue;
396 fcgi->fcg_toread -= vlen;
397 evbuffer_drain(src, vlen);
400 return (0);
403 void
404 fcgi_read(struct bufferevent *bev, void *d)
406 struct fcgi *fcgi = d;
407 struct env *env = fcgi->fcg_env;
408 struct evbuffer *src = EVBUFFER_INPUT(bev);
409 struct fcgi_header hdr;
410 struct fcgi_begin_req breq;
411 struct client *clt, q;
412 int role;
414 memset(&q, 0, sizeof(q));
416 for (;;) {
417 if (EVBUFFER_LENGTH(src) < (size_t)fcgi->fcg_toread)
418 return;
420 if (fcgi->fcg_want == FCGI_RECORD_HEADER) {
421 fcgi->fcg_want = FCGI_RECORD_BODY;
422 bufferevent_read(bev, &hdr, sizeof(hdr));
424 DPRINTF("header: v=%d t=%d id=%d len=%d p=%d",
425 hdr.version, hdr.type,
426 CAT(hdr.req_id0, hdr.req_id1),
427 CAT(hdr.content_len0, hdr.content_len1),
428 hdr.padding);
430 if (hdr.version != FCGI_VERSION_1) {
431 log_warnx("unknown fastcgi version: %d",
432 hdr.version);
433 fcgi_error(bev, EV_READ, d);
434 return;
437 fcgi->fcg_toread = CAT(hdr.content_len0,
438 hdr.content_len1);
439 if (fcgi->fcg_toread < 0) {
440 log_warnx("invalid record length: %d",
441 fcgi->fcg_toread);
442 fcgi_error(bev, EV_READ, d);
443 return;
446 fcgi->fcg_padding = hdr.padding;
447 if (fcgi->fcg_padding < 0) {
448 log_warnx("invalid padding: %d",
449 fcgi->fcg_padding);
450 fcgi_error(bev, EV_READ, d);
451 return;
454 fcgi->fcg_type = hdr.type;
455 fcgi->fcg_rec_id = CAT(hdr.req_id0, hdr.req_id1);
456 continue;
459 q.clt_id = fcgi->fcg_rec_id;
460 clt = SPLAY_FIND(client_tree, &fcgi->fcg_clients, &q);
462 switch (fcgi->fcg_type) {
463 case FCGI_BEGIN_REQUEST:
464 if (sizeof(breq) != fcgi->fcg_toread) {
465 log_warnx("unexpected size for "
466 "FCGI_BEGIN_REQUEST");
467 fcgi_error(bev, EV_READ, d);
468 return;
471 evbuffer_remove(src, &breq, sizeof(breq));
473 role = CAT(breq.role0, breq.role1);
474 if (role != FCGI_RESPONDER) {
475 log_warnx("unknown fastcgi role: %d",
476 role);
477 if (fcgi_send_end_req(fcgi, fcgi->fcg_rec_id,
478 1, FCGI_UNKNOWN_ROLE) == -1) {
479 fcgi_error(bev, EV_READ, d);
480 return;
482 break;
485 if (!fcgi->fcg_keep_conn) {
486 log_warnx("trying to reuse the fastcgi "
487 "socket without marking it as so.");
488 fcgi_error(bev, EV_READ, d);
489 return;
491 fcgi->fcg_keep_conn = breq.flags & FCGI_KEEP_CONN;
493 if (clt != NULL) {
494 log_warnx("ignoring attemp to re-use an "
495 "active request id (%d)",
496 fcgi->fcg_rec_id);
497 break;
500 if ((clt = calloc(1, sizeof(*clt))) == NULL) {
501 log_warnx("calloc");
502 break;
505 #if template
506 clt->clt_tp = template(clt, clt_tp_puts, clt_tp_putc);
507 if (clt->clt_tp == NULL) {
508 free(clt);
509 log_warn("template");
510 break;
512 #endif
514 clt->clt_id = fcgi->fcg_rec_id;
515 clt->clt_fd = -1;
516 clt->clt_fcgi = fcgi;
517 SPLAY_INSERT(client_tree, &fcgi->fcg_clients, clt);
518 break;
519 case FCGI_PARAMS:
520 if (clt == NULL) {
521 log_warnx("got FCGI_PARAMS for inactive id "
522 "(%d)", fcgi->fcg_rec_id);
523 evbuffer_drain(src, fcgi->fcg_toread);
524 break;
526 if (fcgi->fcg_toread == 0) {
527 evbuffer_drain(src, fcgi->fcg_toread);
528 if (server_handle(env, clt) == -1)
529 return;
530 break;
532 if (fcgi_parse_params(fcgi, src, clt) == -1) {
533 log_warnx("fcgi_parse_params failed");
534 fcgi_error(bev, EV_READ, d);
535 return;
537 break;
538 case FCGI_STDIN:
539 /* not interested in reading stdin */
540 evbuffer_drain(src, fcgi->fcg_toread);
541 break;
542 case FCGI_ABORT_REQUEST:
543 if (clt == NULL) {
544 log_warnx("got FCGI_ABORT_REQUEST for inactive"
545 " id (%d)", fcgi->fcg_rec_id);
546 evbuffer_drain(src, fcgi->fcg_toread);
547 break;
549 if (fcgi_end_request(clt, 1) == -1) {
550 /* calls fcgi_error on failure */
551 return;
553 break;
554 default:
555 log_warnx("unknown fastcgi record type %d",
556 fcgi->fcg_type);
557 evbuffer_drain(src, fcgi->fcg_toread);
558 break;
561 /* Prepare for the next record. */
562 evbuffer_drain(src, fcgi->fcg_padding);
563 fcgi->fcg_want = FCGI_RECORD_HEADER;
564 fcgi->fcg_toread = sizeof(struct fcgi_header);
568 void
569 fcgi_write(struct bufferevent *bev, void *d)
571 struct fcgi *fcgi = d;
572 struct evbuffer *out = EVBUFFER_OUTPUT(bev);
574 if (fcgi->fcg_done && EVBUFFER_LENGTH(out) == 0)
575 fcgi_error(bev, EVBUFFER_EOF, fcgi);
578 void
579 fcgi_error(struct bufferevent *bev, short event, void *d)
581 struct fcgi *fcgi = d;
582 struct env *env = fcgi->fcg_env;
583 struct client *clt;
585 log_debug("fcgi failure, shutting down connection (ev: %x)",
586 event);
587 fcgi_inflight_dec(__func__);
589 while ((clt = SPLAY_MIN(client_tree, &fcgi->fcg_clients)) != NULL) {
590 SPLAY_REMOVE(client_tree, &fcgi->fcg_clients, clt);
591 server_client_free(clt);
594 SPLAY_REMOVE(fcgi_tree, &env->env_fcgi_socks, fcgi);
595 fcgi_free(fcgi);
597 return;
600 void
601 fcgi_free(struct fcgi *fcgi)
603 close(fcgi->fcg_s);
604 bufferevent_free(fcgi->fcg_bev);
605 free(fcgi);
608 int
609 clt_flush(struct client *clt)
611 struct fcgi *fcgi = clt->clt_fcgi;
612 struct bufferevent *bev = fcgi->fcg_bev;
613 struct fcgi_header hdr;
615 if (clt->clt_buflen == 0)
616 return (0);
618 memset(&hdr, 0, sizeof(hdr));
619 hdr.version = FCGI_VERSION_1;
620 hdr.type = FCGI_STDOUT;
621 hdr.req_id0 = (clt->clt_id & 0xFF);
622 hdr.req_id1 = (clt->clt_id >> 8);
623 hdr.content_len0 = (clt->clt_buflen & 0xFF);
624 hdr.content_len1 = (clt->clt_buflen >> 8);
626 if (bufferevent_write(bev, &hdr, sizeof(hdr)) == -1 ||
627 bufferevent_write(bev, clt->clt_buf, clt->clt_buflen) == -1) {
628 fcgi_error(bev, EV_WRITE, fcgi);
629 return (-1);
632 clt->clt_buflen = 0;
634 return (0);
637 int
638 clt_write(struct client *clt, const uint8_t *buf, size_t len)
640 size_t left, copy;
642 while (len > 0) {
643 left = sizeof(clt->clt_buf) - clt->clt_buflen;
644 if (left == 0) {
645 if (clt_flush(clt) == -1)
646 return (-1);
647 left = sizeof(clt->clt_buf);
650 copy = MIN(left, len);
652 memcpy(&clt->clt_buf[clt->clt_buflen], buf, copy);
653 clt->clt_buflen += copy;
654 buf += copy;
655 len -= copy;
658 return (0);
661 int
662 clt_putc(struct client *clt, char ch)
664 return (clt_write(clt, &ch, 1));
667 int
668 clt_puts(struct client *clt, const char *str)
670 return (clt_write(clt, str, strlen(str)));
673 int
674 clt_write_bufferevent(struct client *clt, struct bufferevent *bev)
676 struct evbuffer *src = EVBUFFER_INPUT(bev);
677 size_t len, left, copy;
679 len = EVBUFFER_LENGTH(src);
680 while (len > 0) {
681 left = sizeof(clt->clt_buf) - clt->clt_buflen;
682 if (left == 0) {
683 if (clt_flush(clt) == -1)
684 return (-1);
685 left = sizeof(clt->clt_buf);
688 copy = bufferevent_read(bev, &clt->clt_buf[clt->clt_buflen],
689 MIN(left, len));
690 clt->clt_buflen += copy;
692 len = EVBUFFER_LENGTH(src);
695 return (0);
698 int
699 clt_printf(struct client *clt, const char *fmt, ...)
701 struct fcgi *fcgi = clt->clt_fcgi;
702 struct bufferevent *bev = fcgi->fcg_bev;
703 char *str;
704 va_list ap;
705 int r;
707 va_start(ap, fmt);
708 r = vasprintf(&str, fmt, ap);
709 va_end(ap);
710 if (r == -1) {
711 fcgi_error(bev, EV_WRITE, fcgi);
712 return (-1);
715 r = clt_write(clt, str, r);
716 free(str);
717 return (r);
720 #if template
721 int
722 clt_tp_puts(struct template *tp, const char *str)
724 struct client *clt = tp->tp_arg;
726 if (clt_puts(clt, str) == -1)
727 return (-1);
729 return (0);
732 int
733 clt_tp_putc(struct template *tp, int c)
735 struct client *clt = tp->tp_arg;
737 if (clt_putc(clt, c) == -1)
738 return (-1);
740 return (0);
742 #endif
744 int
745 fcgi_cmp(struct fcgi *a, struct fcgi *b)
747 return ((int)a->fcg_id - b->fcg_id);
750 int
751 fcgi_client_cmp(struct client *a, struct client *b)
753 return ((int)a->clt_id - b->clt_id);
756 int
757 accept_reserve(int sockfd, struct sockaddr *addr, socklen_t *addrlen,
758 int reserve, volatile int *counter)
760 int ret;
762 if (getdtablecount() + reserve + *counter >= getdtablesize()) {
763 errno = EMFILE;
764 return (-1);
767 if ((ret = accept4(sockfd, addr, addrlen, SOCK_NONBLOCK)) > -1) {
768 (*counter)++;
769 log_debug("%s: inflight incremented, now %d", __func__,
770 *counter);
773 return (ret);
776 SPLAY_GENERATE(fcgi_tree, fcgi, fcg_nodes, fcgi_cmp);
777 SPLAY_GENERATE(client_tree, client, clt_nodes, fcgi_client_cmp);