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"
36 #include "tmpl.h"
38 #define MIN(a, b) ((a) < (b) ? (a) : (b))
40 struct fcgi_header {
41 unsigned char version;
42 unsigned char type;
43 unsigned char req_id1;
44 unsigned char req_id0;
45 unsigned char content_len1;
46 unsigned char content_len0;
47 unsigned char padding;
48 unsigned char reserved;
49 } __attribute__((packed));
51 /*
52 * number of bytes in a FCGI_HEADER. Future version of the protocol
53 * will not reduce this number.
54 */
55 #define FCGI_HEADER_LEN 8
57 /*
58 * values for the version component
59 */
60 #define FCGI_VERSION_1 1
62 /*
63 * values for the type component
64 */
65 #define FCGI_BEGIN_REQUEST 1
66 #define FCGI_ABORT_REQUEST 2
67 #define FCGI_END_REQUEST 3
68 #define FCGI_PARAMS 4
69 #define FCGI_STDIN 5
70 #define FCGI_STDOUT 6
71 #define FCGI_STDERR 7
72 #define FCGI_DATA 8
73 #define FCGI_GET_VALUES 9
74 #define FCGI_GET_VALUES_RESULT 10
75 #define FCGI_UNKNOWN_TYPE 11
76 #define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
78 struct fcgi_begin_req {
79 unsigned char role1;
80 unsigned char role0;
81 unsigned char flags;
82 unsigned char reserved[5];
83 };
85 struct fcgi_begin_req_record {
86 struct fcgi_header header;
87 struct fcgi_begin_req body;
88 };
90 /*
91 * mask for flags;
92 */
93 #define FCGI_KEEP_CONN 1
95 /*
96 * values for the role
97 */
98 #define FCGI_RESPONDER 1
99 #define FCGI_AUTHORIZER 2
100 #define FCGI_FILTER 3
102 struct fcgi_end_req_body {
103 unsigned char app_status3;
104 unsigned char app_status2;
105 unsigned char app_status1;
106 unsigned char app_status0;
107 unsigned char proto_status;
108 unsigned char reserved[3];
109 };
111 /*
112 * values for proto_status
113 */
114 #define FCGI_REQUEST_COMPLETE 0
115 #define FCGI_CANT_MPX_CONN 1
116 #define FCGI_OVERLOADED 2
117 #define FCGI_UNKNOWN_ROLE 3
119 /*
120 * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT
121 * records.
122 */
123 #define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
124 #define FCGI_MAX_REQS "FCGI_MAX_REQS"
125 #define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
127 #define CAT(f0, f1) ((f0) + ((f1) << 8))
129 enum {
130 FCGI_RECORD_HEADER,
131 FCGI_RECORD_BODY,
132 };
134 volatile int fcgi_inflight;
135 int32_t fcgi_id;
137 int accept_reserve(int, struct sockaddr *, socklen_t *, int,
138 volatile int *);
140 static int
141 fcgi_send_end_req(struct fcgi *fcgi, int id, int as, int ps)
143 struct bufferevent *bev = fcgi->fcg_bev;
144 struct fcgi_header hdr;
145 struct fcgi_end_req_body end;
147 memset(&hdr, 0, sizeof(hdr));
148 memset(&end, 0, sizeof(end));
150 hdr.version = FCGI_VERSION_1;
151 hdr.type = FCGI_END_REQUEST;
152 hdr.req_id0 = (id & 0xFF);
153 hdr.req_id1 = (id >> 8);
154 hdr.content_len0 = sizeof(end);
156 end.app_status0 = (unsigned char)as;
157 end.proto_status = (unsigned char)ps;
159 if (bufferevent_write(bev, &hdr, sizeof(hdr)) == -1)
160 return (-1);
161 if (bufferevent_write(bev, &end, sizeof(end)) == -1)
162 return (-1);
163 return (0);
166 static int
167 end_request(struct client *clt, int status, int proto_status)
169 struct fcgi *fcgi = clt->clt_fcgi;
170 int r;
172 if (clt_flush(clt) == -1)
173 return (-1);
175 r = fcgi_send_end_req(fcgi, clt->clt_id, status,
176 proto_status);
177 if (r == -1) {
178 fcgi_error(fcgi->fcg_bev, EV_WRITE, fcgi);
179 return (-1);
182 SPLAY_REMOVE(client_tree, &fcgi->fcg_clients, clt);
183 server_client_free(clt);
185 if (!fcgi->fcg_keep_conn)
186 fcgi->fcg_done = 1;
188 return (0);
191 int
192 fcgi_end_request(struct client *clt, int status)
194 return (end_request(clt, status, FCGI_REQUEST_COMPLETE));
197 int
198 fcgi_abort_request(struct client *clt)
200 return (end_request(clt, 1, FCGI_OVERLOADED));
203 static void
204 fcgi_inflight_dec(const char *why)
206 fcgi_inflight--;
207 log_debug("%s: fcgi inflight decremented, now %d, %s",
208 __func__, fcgi_inflight, why);
211 void
212 fcgi_accept(int fd, short event, void *arg)
214 struct env *env = arg;
215 struct fcgi *fcgi = NULL;
216 socklen_t slen;
217 struct sockaddr_storage ss;
218 int s = -1;
220 event_add(&env->env_pausev, NULL);
221 if ((event & EV_TIMEOUT))
222 return;
224 slen = sizeof(ss);
225 if ((s = accept_reserve(env->env_sockfd, (struct sockaddr *)&ss,
226 &slen, FD_RESERVE, &fcgi_inflight)) == -1) {
227 /*
228 * Pause accept if we are out of file descriptors, or
229 * libevent will haunt us here too.
230 */
231 if (errno == ENFILE || errno == EMFILE) {
232 struct timeval evtpause = { 1, 0 };
234 event_del(&env->env_sockev);
235 evtimer_add(&env->env_pausev, &evtpause);
236 log_debug("%s: deferring connections", __func__);
238 return;
241 if ((fcgi = calloc(1, sizeof(*fcgi))) == NULL)
242 goto err;
244 fcgi->fcg_id = ++fcgi_id;
245 fcgi->fcg_s = s;
246 fcgi->fcg_env = env;
247 fcgi->fcg_want = FCGI_RECORD_HEADER;
248 fcgi->fcg_toread = sizeof(struct fcgi_header);
249 SPLAY_INIT(&fcgi->fcg_clients);
251 /* assume it's enabled until we get a FCGI_BEGIN_REQUEST */
252 fcgi->fcg_keep_conn = 1;
254 fcgi->fcg_bev = bufferevent_new(fcgi->fcg_s, fcgi_read, fcgi_write,
255 fcgi_error, fcgi);
256 if (fcgi->fcg_bev == NULL)
257 goto err;
259 bufferevent_enable(fcgi->fcg_bev, EV_READ | EV_WRITE);
260 return;
262 err:
263 if (s != -1) {
264 close(s);
265 free(fcgi);
266 fcgi_inflight_dec(__func__);
270 static int
271 parse_len(struct fcgi *fcgi, struct evbuffer *src)
273 unsigned char c, x[3];
275 fcgi->fcg_toread--;
276 evbuffer_remove(src, &c, 1);
277 if (c >> 7 == 0)
278 return (c);
280 if (fcgi->fcg_toread < 3)
281 return (-1);
283 fcgi->fcg_toread -= 3;
284 evbuffer_remove(src, x, sizeof(x));
285 return (((c & 0x7F) << 24) | (x[0] << 16) | (x[1] << 8) | x[2]);
288 static int
289 fcgi_parse_params(struct fcgi *fcgi, struct evbuffer *src, struct client *clt)
291 char pname[32];
292 char server[HOST_NAME_MAX + 1];
293 char path[PATH_MAX];
294 char query[GEMINI_MAXLEN];
295 char method[8];
296 int nlen, vlen;
298 while (fcgi->fcg_toread > 0) {
299 if ((nlen = parse_len(fcgi, src)) < 0 ||
300 (vlen = parse_len(fcgi, src)) < 0)
301 return (-1);
303 if (fcgi->fcg_toread < nlen + vlen)
304 return (-1);
306 if ((size_t)nlen > sizeof(pname) - 1) {
307 /* ignore this parameter */
308 fcgi->fcg_toread -= nlen - vlen;
309 evbuffer_drain(src, nlen + vlen);
310 continue;
313 fcgi->fcg_toread -= nlen;
314 evbuffer_remove(src, &pname, nlen);
315 pname[nlen] = '\0';
317 if (!strcmp(pname, "SERVER_NAME") &&
318 (size_t)vlen < sizeof(server)) {
319 fcgi->fcg_toread -= vlen;
320 evbuffer_remove(src, &server, vlen);
321 server[vlen] = '\0';
323 free(clt->clt_server_name);
324 if ((clt->clt_server_name = strdup(server)) == NULL)
325 return (-1);
326 DPRINTF("clt %d: server_name: %s", clt->clt_id,
327 clt->clt_server_name);
328 continue;
331 if (!strcmp(pname, "SCRIPT_NAME") &&
332 (size_t)vlen < sizeof(path)) {
333 fcgi->fcg_toread -= vlen;
334 evbuffer_remove(src, &path, vlen);
335 path[vlen] = '\0';
337 free(clt->clt_script_name);
338 clt->clt_script_name = strdup(path);
339 if (clt->clt_script_name == NULL)
340 return (-1);
342 DPRINTF("clt %d: script_name: %s", clt->clt_id,
343 clt->clt_script_name);
344 continue;
347 if (!strcmp(pname, "PATH_INFO") &&
348 (size_t)vlen < sizeof(path)) {
349 fcgi->fcg_toread -= vlen;
350 evbuffer_remove(src, &path, vlen);
351 path[vlen] = '\0';
353 free(clt->clt_path_info);
354 clt->clt_path_info = strdup(path);
355 if (clt->clt_path_info == NULL)
356 return (-1);
358 DPRINTF("clt %d: path_info: %s", clt->clt_id,
359 clt->clt_path_info);
360 continue;
363 if (!strcmp(pname, "QUERY_STRING") &&
364 (size_t)vlen < sizeof(query) &&
365 vlen > 0) {
366 fcgi->fcg_toread -= vlen;
367 evbuffer_remove(src, &query, vlen);
368 query[vlen] = '\0';
370 free(clt->clt_query);
371 if ((clt->clt_query = strdup(query)) == NULL)
372 return (-1);
374 DPRINTF("clt %d: query: %s", clt->clt_id,
375 clt->clt_query);
376 continue;
379 if (!strcmp(pname, "REQUEST_METHOD") &&
380 (size_t)vlen < sizeof(method)) {
381 fcgi->fcg_toread -= vlen;
382 evbuffer_remove(src, &method, vlen);
383 method[vlen] = '\0';
385 if (!strcasecmp(method, "GET"))
386 clt->clt_method = METHOD_GET;
387 if (!strcasecmp(method, "POST"))
388 clt->clt_method = METHOD_POST;
390 continue;
393 fcgi->fcg_toread -= vlen;
394 evbuffer_drain(src, vlen);
397 return (0);
400 void
401 fcgi_read(struct bufferevent *bev, void *d)
403 struct fcgi *fcgi = d;
404 struct env *env = fcgi->fcg_env;
405 struct evbuffer *src = EVBUFFER_INPUT(bev);
406 struct fcgi_header hdr;
407 struct fcgi_begin_req breq;
408 struct client *clt, q;
409 int role;
411 memset(&q, 0, sizeof(q));
413 for (;;) {
414 if (EVBUFFER_LENGTH(src) < (size_t)fcgi->fcg_toread)
415 return;
417 if (fcgi->fcg_want == FCGI_RECORD_HEADER) {
418 fcgi->fcg_want = FCGI_RECORD_BODY;
419 bufferevent_read(bev, &hdr, sizeof(hdr));
421 DPRINTF("header: v=%d t=%d id=%d len=%d p=%d",
422 hdr.version, hdr.type,
423 CAT(hdr.req_id0, hdr.req_id1),
424 CAT(hdr.content_len0, hdr.content_len1),
425 hdr.padding);
427 if (hdr.version != FCGI_VERSION_1) {
428 log_warnx("unknown fastcgi version: %d",
429 hdr.version);
430 fcgi_error(bev, EV_READ, d);
431 return;
434 fcgi->fcg_toread = CAT(hdr.content_len0,
435 hdr.content_len1);
436 if (fcgi->fcg_toread < 0) {
437 log_warnx("invalid record length: %d",
438 fcgi->fcg_toread);
439 fcgi_error(bev, EV_READ, d);
440 return;
443 fcgi->fcg_padding = hdr.padding;
444 if (fcgi->fcg_padding < 0) {
445 log_warnx("invalid padding: %d",
446 fcgi->fcg_padding);
447 fcgi_error(bev, EV_READ, d);
448 return;
451 fcgi->fcg_type = hdr.type;
452 fcgi->fcg_rec_id = CAT(hdr.req_id0, hdr.req_id1);
453 continue;
456 q.clt_id = fcgi->fcg_rec_id;
457 clt = SPLAY_FIND(client_tree, &fcgi->fcg_clients, &q);
459 switch (fcgi->fcg_type) {
460 case FCGI_BEGIN_REQUEST:
461 if (sizeof(breq) != fcgi->fcg_toread) {
462 log_warnx("unexpected size for "
463 "FCGI_BEGIN_REQUEST");
464 fcgi_error(bev, EV_READ, d);
465 return;
468 evbuffer_remove(src, &breq, sizeof(breq));
470 role = CAT(breq.role0, breq.role1);
471 if (role != FCGI_RESPONDER) {
472 log_warnx("unknown fastcgi role: %d",
473 role);
474 if (fcgi_send_end_req(fcgi, fcgi->fcg_rec_id,
475 1, FCGI_UNKNOWN_ROLE) == -1) {
476 fcgi_error(bev, EV_READ, d);
477 return;
479 break;
482 if (!fcgi->fcg_keep_conn) {
483 log_warnx("trying to reuse the fastcgi "
484 "socket without marking it as so.");
485 fcgi_error(bev, EV_READ, d);
486 return;
488 fcgi->fcg_keep_conn = breq.flags & FCGI_KEEP_CONN;
490 if (clt != NULL) {
491 log_warnx("ignoring attemp to re-use an "
492 "active request id (%d)",
493 fcgi->fcg_rec_id);
494 break;
497 if ((clt = calloc(1, sizeof(*clt))) == NULL) {
498 log_warnx("calloc");
499 break;
502 #if template
503 clt->clt_tp = template(clt, clt_tp_puts, clt_tp_putc);
504 if (clt->clt_tp == NULL) {
505 free(clt);
506 log_warn("template");
507 break;
509 #endif
511 clt->clt_id = fcgi->fcg_rec_id;
512 clt->clt_fd = -1;
513 clt->clt_fcgi = fcgi;
514 SPLAY_INSERT(client_tree, &fcgi->fcg_clients, clt);
515 break;
516 case FCGI_PARAMS:
517 if (clt == NULL) {
518 log_warnx("got FCGI_PARAMS for inactive id "
519 "(%d)", fcgi->fcg_rec_id);
520 evbuffer_drain(src, fcgi->fcg_toread);
521 break;
523 if (fcgi->fcg_toread == 0) {
524 evbuffer_drain(src, fcgi->fcg_toread);
525 if (server_handle(env, clt) == -1)
526 return;
527 break;
529 if (fcgi_parse_params(fcgi, src, clt) == -1) {
530 log_warnx("fcgi_parse_params failed");
531 fcgi_error(bev, EV_READ, d);
532 return;
534 break;
535 case FCGI_STDIN:
536 /* not interested in reading stdin */
537 evbuffer_drain(src, fcgi->fcg_toread);
538 break;
539 case FCGI_ABORT_REQUEST:
540 if (clt == NULL) {
541 log_warnx("got FCGI_ABORT_REQUEST for inactive"
542 " id (%d)", fcgi->fcg_rec_id);
543 evbuffer_drain(src, fcgi->fcg_toread);
544 break;
546 if (fcgi_end_request(clt, 1) == -1) {
547 /* calls fcgi_error on failure */
548 return;
550 break;
551 default:
552 log_warnx("unknown fastcgi record type %d",
553 fcgi->fcg_type);
554 evbuffer_drain(src, fcgi->fcg_toread);
555 break;
558 /* Prepare for the next record. */
559 evbuffer_drain(src, fcgi->fcg_padding);
560 fcgi->fcg_want = FCGI_RECORD_HEADER;
561 fcgi->fcg_toread = sizeof(struct fcgi_header);
565 void
566 fcgi_write(struct bufferevent *bev, void *d)
568 struct fcgi *fcgi = d;
569 struct evbuffer *out = EVBUFFER_OUTPUT(bev);
571 if (fcgi->fcg_done && EVBUFFER_LENGTH(out) == 0)
572 fcgi_error(bev, EVBUFFER_EOF, fcgi);
575 void
576 fcgi_error(struct bufferevent *bev, short event, void *d)
578 struct fcgi *fcgi = d;
579 struct env *env = fcgi->fcg_env;
580 struct client *clt;
582 log_debug("fcgi failure, shutting down connection (ev: %x)",
583 event);
584 fcgi_inflight_dec(__func__);
586 while ((clt = SPLAY_MIN(client_tree, &fcgi->fcg_clients)) != NULL) {
587 SPLAY_REMOVE(client_tree, &fcgi->fcg_clients, clt);
588 server_client_free(clt);
591 SPLAY_REMOVE(fcgi_tree, &env->env_fcgi_socks, fcgi);
592 fcgi_free(fcgi);
594 return;
597 void
598 fcgi_free(struct fcgi *fcgi)
600 close(fcgi->fcg_s);
601 bufferevent_free(fcgi->fcg_bev);
602 free(fcgi);
605 int
606 clt_flush(struct client *clt)
608 struct fcgi *fcgi = clt->clt_fcgi;
609 struct bufferevent *bev = fcgi->fcg_bev;
610 struct fcgi_header hdr;
612 if (clt->clt_buflen == 0)
613 return (0);
615 memset(&hdr, 0, sizeof(hdr));
616 hdr.version = FCGI_VERSION_1;
617 hdr.type = FCGI_STDOUT;
618 hdr.req_id0 = (clt->clt_id & 0xFF);
619 hdr.req_id1 = (clt->clt_id >> 8);
620 hdr.content_len0 = (clt->clt_buflen & 0xFF);
621 hdr.content_len1 = (clt->clt_buflen >> 8);
623 if (bufferevent_write(bev, &hdr, sizeof(hdr)) == -1 ||
624 bufferevent_write(bev, clt->clt_buf, clt->clt_buflen) == -1) {
625 fcgi_error(bev, EV_WRITE, fcgi);
626 return (-1);
629 clt->clt_buflen = 0;
631 return (0);
634 int
635 clt_write(struct client *clt, const uint8_t *buf, size_t len)
637 size_t left, copy;
639 while (len > 0) {
640 left = sizeof(clt->clt_buf) - clt->clt_buflen;
641 if (left == 0) {
642 if (clt_flush(clt) == -1)
643 return (-1);
644 left = sizeof(clt->clt_buf);
647 copy = MIN(left, len);
649 memcpy(&clt->clt_buf[clt->clt_buflen], buf, copy);
650 clt->clt_buflen += copy;
651 buf += copy;
652 len -= copy;
655 return (0);
658 int
659 clt_putc(struct client *clt, char ch)
661 return (clt_write(clt, &ch, 1));
664 int
665 clt_puts(struct client *clt, const char *str)
667 return (clt_write(clt, str, strlen(str)));
670 int
671 clt_write_bufferevent(struct client *clt, struct bufferevent *bev)
673 struct evbuffer *src = EVBUFFER_INPUT(bev);
674 size_t len, left, copy;
676 len = EVBUFFER_LENGTH(src);
677 while (len > 0) {
678 left = sizeof(clt->clt_buf) - clt->clt_buflen;
679 if (left == 0) {
680 if (clt_flush(clt) == -1)
681 return (-1);
682 left = sizeof(clt->clt_buf);
685 copy = bufferevent_read(bev, &clt->clt_buf[clt->clt_buflen],
686 MIN(left, len));
687 clt->clt_buflen += copy;
689 len = EVBUFFER_LENGTH(src);
692 return (0);
695 int
696 clt_printf(struct client *clt, const char *fmt, ...)
698 struct fcgi *fcgi = clt->clt_fcgi;
699 struct bufferevent *bev = fcgi->fcg_bev;
700 char *str;
701 va_list ap;
702 int r;
704 va_start(ap, fmt);
705 r = vasprintf(&str, fmt, ap);
706 va_end(ap);
707 if (r == -1) {
708 fcgi_error(bev, EV_WRITE, fcgi);
709 return (-1);
712 r = clt_write(clt, str, r);
713 free(str);
714 return (r);
717 #if template
718 int
719 clt_tp_puts(struct template *tp, const char *str)
721 struct client *clt = tp->tp_arg;
723 if (clt_puts(clt, str) == -1)
724 return (-1);
726 return (0);
729 int
730 clt_tp_putc(struct template *tp, int c)
732 struct client *clt = tp->tp_arg;
734 if (clt_putc(clt, c) == -1)
735 return (-1);
737 return (0);
739 #endif
741 int
742 fcgi_cmp(struct fcgi *a, struct fcgi *b)
744 return ((int)a->fcg_id - b->fcg_id);
747 int
748 fcgi_client_cmp(struct client *a, struct client *b)
750 return ((int)a->clt_id - b->clt_id);
753 int
754 accept_reserve(int sockfd, struct sockaddr *addr, socklen_t *addrlen,
755 int reserve, volatile int *counter)
757 int ret;
759 if (getdtablecount() + reserve + *counter >= getdtablesize()) {
760 errno = EMFILE;
761 return (-1);
764 if ((ret = accept4(sockfd, addr, addrlen, SOCK_NONBLOCK)) > -1) {
765 (*counter)++;
766 log_debug("%s: inflight incremented, now %d", __func__,
767 *counter);
770 return (ret);
773 SPLAY_GENERATE(fcgi_tree, fcgi, fcg_nodes, fcgi_cmp);
774 SPLAY_GENERATE(client_tree, client, clt_nodes, fcgi_client_cmp);