2 * Copyright (c) 2022 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.
17 #include <sys/queue.h>
19 #include <sys/socket.h>
21 #include <netinet/in.h>
22 #include <arpa/inet.h>
38 #define MIN(a, b) ((a) < (b) ? (a) : (b))
41 unsigned char version;
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));
52 * number of bytes in a FCGI_HEADER. Future version of the protocol
53 * will not reduce this number.
55 #define FCGI_HEADER_LEN 8
58 * values for the version component
60 #define FCGI_VERSION_1 1
63 * values for the type component
65 #define FCGI_BEGIN_REQUEST 1
66 #define FCGI_ABORT_REQUEST 2
67 #define FCGI_END_REQUEST 3
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 {
82 unsigned char reserved[5];
85 struct fcgi_begin_req_record {
86 struct fcgi_header header;
87 struct fcgi_begin_req body;
93 #define FCGI_KEEP_CONN 1
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];
112 * values for proto_status
114 #define FCGI_REQUEST_COMPLETE 0
115 #define FCGI_CANT_MPX_CONN 1
116 #define FCGI_OVERLOADED 2
117 #define FCGI_UNKNOWN_ROLE 3
120 * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT
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))
134 volatile int fcgi_inflight;
137 fcgi_send_end_req(struct fcgi *fcgi, int id, int as, int ps)
139 struct bufferevent *bev = fcgi->fcg_bev;
140 struct fcgi_header hdr;
141 struct fcgi_end_req_body end;
143 memset(&hdr, 0, sizeof(hdr));
144 memset(&end, 0, sizeof(end));
146 hdr.version = FCGI_VERSION_1;
147 hdr.type = FCGI_END_REQUEST;
148 hdr.req_id0 = (id & 0xFF);
149 hdr.req_id1 = (id >> 8);
150 hdr.content_len0 = sizeof(end);
152 end.app_status0 = (unsigned char)as;
153 end.proto_status = (unsigned char)ps;
155 if (bufferevent_write(bev, &hdr, sizeof(hdr)) == -1)
157 if (bufferevent_write(bev, &end, sizeof(end)) == -1)
163 end_request(struct client *clt, int status, int proto_status)
165 struct fcgi *fcgi = clt->clt_fcgi;
168 if (clt_flush(clt) == -1)
171 r = fcgi_send_end_req(fcgi, clt->clt_id, status,
174 fcgi_error(fcgi->fcg_bev, EV_WRITE, fcgi);
178 SPLAY_REMOVE(client_tree, &fcgi->fcg_clients, clt);
179 proxy_client_free(clt);
181 if (!fcgi->fcg_keep_conn)
188 fcgi_end_request(struct client *clt, int status)
190 return (end_request(clt, status, FCGI_REQUEST_COMPLETE));
194 fcgi_abort_request(struct client *clt)
196 return (end_request(clt, 1, FCGI_OVERLOADED));
200 fcgi_inflight_dec(const char *why)
203 log_debug("%s: fcgi inflight decremented, now %d, %s",
204 __func__, fcgi_inflight, why);
208 fcgi_accept(int fd, short event, void *arg)
210 struct galileo *env = arg;
211 struct fcgi *fcgi = NULL;
213 struct sockaddr_storage ss;
216 event_add(&env->sc_evpause, NULL);
217 if ((event & EV_TIMEOUT))
221 if ((s = accept_reserve(env->sc_sock_fd, (struct sockaddr *)&ss,
222 &slen, FD_RESERVE, &fcgi_inflight)) == -1) {
224 * Pause accept if we are out of file descriptors, or
225 * libevent will haunt us here too.
227 if (errno == ENFILE || errno == EMFILE) {
228 struct timeval evtpause = { 1, 0 };
230 event_del(&env->sc_evsock);
231 evtimer_add(&env->sc_evpause, &evtpause);
232 log_debug("%s: deferring connections", __func__);
237 if ((fcgi = calloc(1, sizeof(*fcgi))) == NULL)
240 fcgi->fcg_id = ++proxy_fcg_id;
243 fcgi->fcg_want = FCGI_RECORD_HEADER;
244 fcgi->fcg_toread = sizeof(struct fcgi_header);
245 SPLAY_INIT(&fcgi->fcg_clients);
247 /* assume it's enabled until we get a FCGI_BEGIN_REQUEST */
248 fcgi->fcg_keep_conn = 1;
250 fcgi->fcg_bev = bufferevent_new(fcgi->fcg_s, fcgi_read, fcgi_write,
252 if (fcgi->fcg_bev == NULL)
255 bufferevent_enable(fcgi->fcg_bev, EV_READ | EV_WRITE);
262 fcgi_inflight_dec(__func__);
267 parse_len(struct fcgi *fcgi, struct evbuffer *src)
269 unsigned char c, x[3];
272 evbuffer_remove(src, &c, 1);
276 if (fcgi->fcg_toread < 3)
279 fcgi->fcg_toread -= 3;
280 evbuffer_remove(src, x, sizeof(x));
281 return (((c & 0x7F) << 24) | (x[0] << 16) | (x[1] << 8) | x[2]);
285 fcgi_parse_params(struct fcgi *fcgi, struct evbuffer *src, struct client *clt)
288 char server[HOST_NAME_MAX + 1];
292 while (fcgi->fcg_toread > 0) {
293 if ((nlen = parse_len(fcgi, src)) < 0 ||
294 (vlen = parse_len(fcgi, src)) < 0)
297 if (fcgi->fcg_toread < nlen + vlen)
300 if ((size_t)nlen > sizeof(pname) - 1) {
301 /* ignore this parameter */
302 fcgi->fcg_toread -= nlen - vlen;
303 evbuffer_drain(src, nlen + vlen);
307 fcgi->fcg_toread -= nlen;
308 evbuffer_remove(src, &pname, nlen);
311 if (!strcmp(pname, "SERVER_NAME") &&
312 (size_t)vlen < sizeof(server)) {
313 fcgi->fcg_toread -= vlen;
314 evbuffer_remove(src, &server, vlen);
317 if ((clt->clt_server_name = strdup(server)) == NULL)
319 log_debug("clt %d: server_name: %s", clt->clt_id,
320 clt->clt_server_name);
324 if (!strcmp(pname, "SCRIPT_NAME") &&
325 (size_t)vlen < sizeof(path)) {
326 fcgi->fcg_toread -= vlen;
327 evbuffer_remove(src, &path, vlen);
330 if ((clt->clt_script_name = strdup(path)) == NULL)
332 log_debug("clt %d: script_name: %s", clt->clt_id,
333 clt->clt_script_name);
337 if (!strcmp(pname, "PATH_INFO") &&
338 (size_t)vlen < sizeof(path)) {
339 fcgi->fcg_toread -= vlen;
340 evbuffer_remove(src, &path, vlen);
344 (void) strlcpy(path, "/", sizeof(path));
346 if ((clt->clt_path_info = strdup(path)) == NULL)
348 log_debug("clt %d: path_info: %s", clt->clt_id,
353 fcgi->fcg_toread -= vlen;
354 evbuffer_drain(src, vlen);
361 fcgi_read(struct bufferevent *bev, void *d)
363 struct fcgi *fcgi = d;
364 struct galileo *env = fcgi->fcg_env;
365 struct evbuffer *src = EVBUFFER_INPUT(bev);
366 struct fcgi_header hdr;
367 struct fcgi_begin_req breq;
368 struct client *clt, q;
371 memset(&q, 0, sizeof(q));
374 if (EVBUFFER_LENGTH(src) < (size_t)fcgi->fcg_toread)
377 if (fcgi->fcg_want == FCGI_RECORD_HEADER) {
378 fcgi->fcg_want = FCGI_RECORD_BODY;
379 bufferevent_read(bev, &hdr, sizeof(hdr));
382 log_warnx("header: v=%d t=%d id=%d len=%d p=%d",
383 hdr.version, hdr.type,
384 CAT(hdr.req_id0, hdr.req_id1),
385 CAT(hdr.content_len0, hdr.content_len1),
389 if (hdr.version != FCGI_VERSION_1) {
390 log_warnx("unknown fastcgi version: %d",
392 fcgi_error(bev, EV_READ, d);
396 fcgi->fcg_toread = CAT(hdr.content_len0,
398 if (fcgi->fcg_toread < 0) {
399 log_warnx("invalid record length: %d",
401 fcgi_error(bev, EV_READ, d);
405 fcgi->fcg_padding = hdr.padding;
406 if (fcgi->fcg_padding < 0) {
407 log_warnx("invalid padding: %d",
409 fcgi_error(bev, EV_READ, d);
413 fcgi->fcg_type = hdr.type;
414 fcgi->fcg_rec_id = CAT(hdr.req_id0, hdr.req_id1);
418 q.clt_id = fcgi->fcg_rec_id;
419 clt = SPLAY_FIND(client_tree, &fcgi->fcg_clients, &q);
421 switch (fcgi->fcg_type) {
422 case FCGI_BEGIN_REQUEST:
423 if (sizeof(breq) != fcgi->fcg_toread) {
424 log_warnx("unexpected size for "
425 "FCGI_BEGIN_REQUEST");
426 fcgi_error(bev, EV_READ, d);
430 evbuffer_remove(src, &breq, sizeof(breq));
432 role = CAT(breq.role0, breq.role1);
433 if (role != FCGI_RESPONDER) {
434 log_warnx("unknown fastcgi role: %d",
436 if (fcgi_send_end_req(fcgi, fcgi->fcg_rec_id,
437 1, FCGI_UNKNOWN_ROLE) == -1) {
438 fcgi_error(bev, EV_READ, d);
444 if (!fcgi->fcg_keep_conn) {
445 log_warnx("trying to reuse the fastcgi "
446 "socket without marking it as so.");
447 fcgi_error(bev, EV_READ, d);
450 fcgi->fcg_keep_conn = breq.flags & FCGI_KEEP_CONN;
453 log_warnx("ignoring attemp to re-use an "
454 "active request id (%d)",
459 if ((clt = calloc(1, sizeof(*clt))) == NULL) {
464 clt->clt_id = fcgi->fcg_rec_id;
466 clt->clt_fcgi = fcgi;
467 SPLAY_INSERT(client_tree, &fcgi->fcg_clients, clt);
471 log_warnx("got FCGI_PARAMS for inactive id "
472 "(%d)", fcgi->fcg_rec_id);
473 evbuffer_drain(src, fcgi->fcg_toread);
476 if (fcgi->fcg_toread == 0) {
477 evbuffer_drain(src, fcgi->fcg_toread);
478 proxy_start_request(env, clt);
481 if (fcgi_parse_params(fcgi, src, clt) == -1) {
482 log_warnx("fcgi_parse_params failed");
483 fcgi_error(bev, EV_READ, d);
489 evbuffer_drain(src, fcgi->fcg_toread);
491 case FCGI_ABORT_REQUEST:
493 log_warnx("got FCGI_ABORT_REQUEST for inactive"
494 " id (%d)", fcgi->fcg_rec_id);
495 evbuffer_drain(src, fcgi->fcg_toread);
498 if (fcgi_end_request(clt, 1) == -1) {
499 /* calls fcgi_error on failure */
504 log_warnx("unknown fastcgi record type %d",
506 evbuffer_drain(src, fcgi->fcg_toread);
510 /* Prepare for the next record. */
511 evbuffer_drain(src, fcgi->fcg_padding);
512 fcgi->fcg_want = FCGI_RECORD_HEADER;
513 fcgi->fcg_toread = sizeof(struct fcgi_header);
518 fcgi_write(struct bufferevent *bev, void *d)
520 struct fcgi *fcgi = d;
521 struct evbuffer *out = EVBUFFER_OUTPUT(bev);
523 if (fcgi->fcg_done && EVBUFFER_LENGTH(out) == 0)
524 fcgi_error(bev, EVBUFFER_EOF, fcgi);
528 fcgi_error(struct bufferevent *bev, short event, void *d)
530 struct fcgi *fcgi = d;
531 struct galileo *env = fcgi->fcg_env;
534 log_debug("fcgi failure, shutting down connection (ev: %x)",
536 fcgi_inflight_dec(__func__);
538 while ((clt = SPLAY_MIN(client_tree, &fcgi->fcg_clients)) != NULL) {
539 SPLAY_REMOVE(client_tree, &fcgi->fcg_clients, clt);
540 proxy_client_free(clt);
544 bufferevent_free(fcgi->fcg_bev);
545 SPLAY_REMOVE(fcgi_tree, &env->sc_fcgi_socks, fcgi);
552 clt_flush(struct client *clt)
554 struct fcgi *fcgi = clt->clt_fcgi;
555 struct bufferevent *bev = fcgi->fcg_bev;
556 struct fcgi_header hdr;
558 if (clt->clt_buflen == 0)
561 memset(&hdr, 0, sizeof(hdr));
562 hdr.version = FCGI_VERSION_1;
563 hdr.type = FCGI_STDOUT;
564 hdr.req_id0 = (clt->clt_id & 0xFF);
565 hdr.req_id1 = (clt->clt_id >> 8);
566 hdr.content_len0 = (clt->clt_buflen & 0xFF);
567 hdr.content_len1 = (clt->clt_buflen >> 8);
569 if (bufferevent_write(bev, &hdr, sizeof(hdr)) == -1 ||
570 bufferevent_write(bev, clt->clt_buf, clt->clt_buflen) == -1) {
571 fcgi_error(bev, EV_WRITE, fcgi);
581 clt_write(struct client *clt, const uint8_t *buf, size_t len)
586 left = sizeof(clt->clt_buf) - clt->clt_buflen;
588 if (clt_flush(clt) == -1)
590 left = sizeof(clt->clt_buf);
593 copy = MIN(left, len);
595 memcpy(&clt->clt_buf[clt->clt_buflen], buf, copy);
596 clt->clt_buflen += copy;
605 clt_putc(struct client *clt, char ch)
607 return (clt_write(clt, &ch, 1));
611 clt_puts(struct client *clt, const char *str)
613 return (clt_write(clt, str, strlen(str)));
617 clt_write_bufferevent(struct client *clt, struct bufferevent *bev)
619 struct evbuffer *src = EVBUFFER_INPUT(bev);
620 size_t len, left, copy;
622 len = EVBUFFER_LENGTH(src);
624 left = sizeof(clt->clt_buf) - clt->clt_buflen;
626 if (clt_flush(clt) == -1)
628 left = sizeof(clt->clt_buf);
631 copy = bufferevent_read(bev, &clt->clt_buf[clt->clt_buflen],
633 clt->clt_buflen += copy;
635 len = EVBUFFER_LENGTH(src);
642 clt_printf(struct client *clt, const char *fmt, ...)
644 struct fcgi *fcgi = clt->clt_fcgi;
645 struct bufferevent *bev = fcgi->fcg_bev;
651 r = vasprintf(&str, fmt, ap);
654 fcgi_error(bev, EV_WRITE, fcgi);
658 r = clt_write(clt, str, r);
664 fcgi_cmp(struct fcgi *a, struct fcgi *b)
666 return ((int)a->fcg_id - b->fcg_id);
670 fcgi_client_cmp(struct client *a, struct client *b)
672 return ((int)a->clt_id - b->clt_id);
675 SPLAY_GENERATE(fcgi_tree, fcgi, fcg_nodes, fcgi_cmp);
676 SPLAY_GENERATE(client_tree, client, clt_nodes, fcgi_client_cmp);