xref: /lighttpd1.4/src/mod_cgi.c (revision fa5e9b53)
1 #include "first.h"
2 
3 #include "base.h"
4 #include "stat_cache.h"
5 #include "http_kv.h"
6 #include "fdlog.h"
7 #include "log.h"
8 #include "response.h"
9 #include "http_cgi.h"
10 #include "http_chunk.h"
11 #include "http_header.h"
12 
13 #include "plugin.h"
14 
15 #include <sys/types.h>
16 #include "sys-socket.h"
17 #ifdef HAVE_SYS_WAIT_H
18 #include <sys/wait.h>
19 #endif
20 
21 #include <unistd.h>
22 #include <errno.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <fdevent.h>
26 
27 #include <fcntl.h>
28 #include <signal.h>
29 
30 typedef struct {
31 	uintptr_t *offsets;
32 	size_t osize;
33 	size_t oused;
34 	buffer *b;
35 	buffer *boffsets;
36 	buffer *ld_preload;
37 	buffer *ld_library_path;
38       #ifdef __CYGWIN__
39 	buffer *systemroot;
40       #endif
41 } env_accum;
42 
43 typedef struct {
44 	unix_time64_t read_timeout;
45 	unix_time64_t write_timeout;
46 	int signal_fin;
47 } cgi_limits;
48 
49 typedef struct {
50 	const array *cgi;
51 	const cgi_limits *limits;
52 	unsigned short execute_x_only;
53 	unsigned short local_redir;
54 	unsigned short xsendfile_allow;
55 	unsigned short upgrade;
56 	const array *xsendfile_docroot;
57 } plugin_config;
58 
59 struct cgi_pid_t;
60 
61 typedef struct {
62 	PLUGIN_DATA;
63 	plugin_config defaults;
64 	plugin_config conf;
65 	int tempfile_accum;
66 	struct cgi_pid_t *cgi_pid;
67 	env_accum env;
68 } plugin_data;
69 
70 typedef struct {
71 	struct cgi_pid_t *cgi_pid;
72 	int fd;
73 	int fdtocgi;
74 	int rd_revents;
75 	int wr_revents;
76 	fdnode *fdn;
77 	fdnode *fdntocgi;
78 
79 	request_st *r;
80 	connection *con;          /* dumb pointer */
81 	struct fdevents *ev;      /* dumb pointer */
82 	plugin_data *plugin_data; /* dumb pointer */
83 
84 	buffer *response;
85 	unix_time64_t read_ts;
86 	unix_time64_t write_ts;
87 	buffer *cgi_handler;      /* dumb pointer */
88 	http_response_opts opts;
89 	plugin_config conf;
90 } handler_ctx;
91 
92 typedef struct cgi_pid_t {
93 	pid_t pid;
94 	int signal_sent;
95 	handler_ctx *hctx;
96 	struct cgi_pid_t *next;
97 	struct cgi_pid_t *prev;
98 } cgi_pid_t;
99 
100 static handler_ctx * cgi_handler_ctx_init(void) {
101 	handler_ctx *hctx = calloc(1, sizeof(*hctx));
102 
103 	force_assert(hctx);
104 
105 	hctx->response = chunk_buffer_acquire();
106 	hctx->fd = -1;
107 	hctx->fdtocgi = -1;
108 
109 	return hctx;
110 }
111 
112 static void cgi_handler_ctx_free(handler_ctx *hctx) {
113 	chunk_buffer_release(hctx->response);
114 	free(hctx);
115 }
116 
117 INIT_FUNC(mod_cgi_init) {
118 	plugin_data *p;
119 	const char *s;
120 
121 	p = calloc(1, sizeof(*p));
122 
123 	force_assert(p);
124 
125 	/* for valgrind */
126 	s = getenv("LD_PRELOAD");
127 	if (s) buffer_copy_string((p->env.ld_preload = buffer_init()), s);
128 	s = getenv("LD_LIBRARY_PATH");
129 	if (s) buffer_copy_string((p->env.ld_library_path = buffer_init()), s);
130       #ifdef __CYGWIN__
131 	/* CYGWIN needs SYSTEMROOT */
132 	s = getenv("SYSTEMROOT");
133 	if (s) buffer_copy_string((p->env.systemroot = buffer_init()), s);
134       #endif
135 
136 	return p;
137 }
138 
139 
140 FREE_FUNC(mod_cgi_free) {
141 	plugin_data *p = p_d;
142 	buffer_free(p->env.ld_preload);
143 	buffer_free(p->env.ld_library_path);
144       #ifdef __CYGWIN__
145 	buffer_free(p->env.systemroot);
146       #endif
147 
148     for (cgi_pid_t *cgi_pid = p->cgi_pid, *next; cgi_pid; cgi_pid = next) {
149         next = cgi_pid->next;
150         free(cgi_pid);
151     }
152 
153     if (NULL == p->cvlist) return;
154     /* (init i to 0 if global context; to 1 to skip empty global context) */
155     for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
156         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
157         for (; -1 != cpv->k_id; ++cpv) {
158             if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue;
159             switch (cpv->k_id) {
160               case 6: /* cgi.limits */
161                 free(cpv->v.v);
162                 break;
163               default:
164                 break;
165             }
166         }
167     }
168 }
169 
170 static void mod_cgi_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
171     switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
172       case 0: /* cgi.assign */
173         pconf->cgi = cpv->v.a;
174         break;
175       case 1: /* cgi.execute-x-only */
176         pconf->execute_x_only = (unsigned short)cpv->v.u;
177         break;
178       case 2: /* cgi.x-sendfile */
179         pconf->xsendfile_allow = (unsigned short)cpv->v.u;
180         break;
181       case 3: /* cgi.x-sendfile-docroot */
182         pconf->xsendfile_docroot = cpv->v.a;
183         break;
184       case 4: /* cgi.local-redir */
185         pconf->local_redir = (unsigned short)cpv->v.u;
186         break;
187       case 5: /* cgi.upgrade */
188         pconf->upgrade = (unsigned short)cpv->v.u;
189         break;
190       case 6: /* cgi.limits */
191         if (cpv->vtype != T_CONFIG_LOCAL) break;
192         pconf->limits = cpv->v.v;
193         break;
194       default:/* should not happen */
195         return;
196     }
197 }
198 
199 static void mod_cgi_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
200     do {
201         mod_cgi_merge_config_cpv(pconf, cpv);
202     } while ((++cpv)->k_id != -1);
203 }
204 
205 static void mod_cgi_patch_config(request_st * const r, plugin_data * const p) {
206     p->conf = p->defaults; /* copy small struct instead of memcpy() */
207     /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
208     for (int i = 1, used = p->nconfig; i < used; ++i) {
209         if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
210             mod_cgi_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]);
211     }
212 }
213 
214 __attribute_cold__
215 __attribute_pure__
216 static int mod_cgi_str_to_signal (const char *s, int default_sig) {
217     static const struct { const char *name; int sig; } sigs[] = {
218       { "HUP",  SIGHUP  }
219      ,{ "INT",  SIGINT  }
220      ,{ "QUIT", SIGQUIT }
221      ,{ "ILL",  SIGILL  }
222      ,{ "TRAP", SIGTRAP }
223      ,{ "ABRT", SIGABRT }
224      #ifdef SIGBUS
225      ,{ "BUS",  SIGBUS  }
226      #endif
227      ,{ "FPE",  SIGFPE  }
228      ,{ "KILL", SIGKILL }
229      #ifdef SIGUSR1
230      ,{ "USR1", SIGUSR1 }
231      #endif
232      ,{ "SEGV", SIGSEGV }
233      #ifdef SIGUSR2
234      ,{ "USR2", SIGUSR2 }
235      #endif
236      ,{ "PIPE", SIGPIPE }
237      ,{ "ALRM", SIGALRM }
238      ,{ "TERM", SIGTERM }
239      #ifdef SIGCHLD
240      ,{ "CHLD", SIGCHLD }
241      #endif
242      #ifdef SIGCONT
243      ,{ "CONT", SIGCONT }
244      #endif
245      #ifdef SIGURG
246      ,{ "URG",  SIGURG  }
247      #endif
248      #ifdef SIGXCPU
249      ,{ "XCPU", SIGXCPU }
250      #endif
251      #ifdef SIGXFSZ
252      ,{ "XFSZ", SIGXFSZ }
253      #endif
254      #ifdef SIGWINCH
255      ,{ "WINCH",SIGWINCH}
256      #endif
257      #ifdef SIGPOLL
258      ,{ "POLL", SIGPOLL }
259      #endif
260      #ifdef SIGIO
261      ,{ "IO",   SIGIO   }
262      #endif
263     };
264 
265     if (s[0] == 'S' && s[1] == 'I' && s[2] == 'G') s += 3; /*("SIG" prefix)*/
266     for (uint32_t i = 0; i < sizeof(sigs)/sizeof(*sigs); ++i) {
267         if (0 == strcmp(s, sigs[i].name)) return sigs[i].sig;
268     }
269     return default_sig;
270 }
271 
272 static cgi_limits * mod_cgi_parse_limits(const array * const a, log_error_st * const errh) {
273     cgi_limits * const limits = calloc(1, sizeof(cgi_limits));
274     force_assert(limits);
275     for (uint32_t i = 0; i < a->used; ++i) {
276         const data_unset * const du = a->data[i];
277         int32_t v = config_plugin_value_to_int32(du, -1);
278         if (buffer_eq_icase_slen(&du->key, CONST_STR_LEN("read-timeout"))) {
279             limits->read_timeout = (unix_time64_t)v;
280             continue;
281         }
282         if (buffer_eq_icase_slen(&du->key, CONST_STR_LEN("write-timeout"))) {
283             limits->write_timeout = (unix_time64_t)v;
284             continue;
285         }
286         if (buffer_eq_icase_slen(&du->key, CONST_STR_LEN("tcp-fin-propagate"))) {
287             if (-1 == v) {
288                 v = SIGTERM;
289                 if (du->type == TYPE_STRING) {
290                     buffer * const vstr = &((data_string *)du)->value;
291                     buffer_to_upper(vstr);
292                     v = mod_cgi_str_to_signal(vstr->ptr, SIGTERM);
293                 }
294             }
295             limits->signal_fin = v;
296             continue;
297         }
298         log_error(errh, __FILE__, __LINE__,
299                   "unrecognized cgi.limits param: %s", du->key.ptr);
300     }
301     return limits;
302 }
303 
304 SETDEFAULTS_FUNC(mod_cgi_set_defaults) {
305     static const config_plugin_keys_t cpk[] = {
306       { CONST_STR_LEN("cgi.assign"),
307         T_CONFIG_ARRAY_KVSTRING,
308         T_CONFIG_SCOPE_CONNECTION }
309      ,{ CONST_STR_LEN("cgi.execute-x-only"),
310         T_CONFIG_BOOL,
311         T_CONFIG_SCOPE_CONNECTION }
312      ,{ CONST_STR_LEN("cgi.x-sendfile"),
313         T_CONFIG_BOOL,
314         T_CONFIG_SCOPE_CONNECTION }
315      ,{ CONST_STR_LEN("cgi.x-sendfile-docroot"),
316         T_CONFIG_ARRAY_VLIST,
317         T_CONFIG_SCOPE_CONNECTION }
318      ,{ CONST_STR_LEN("cgi.local-redir"),
319         T_CONFIG_BOOL,
320         T_CONFIG_SCOPE_CONNECTION }
321      ,{ CONST_STR_LEN("cgi.upgrade"),
322         T_CONFIG_BOOL,
323         T_CONFIG_SCOPE_CONNECTION }
324      ,{ CONST_STR_LEN("cgi.limits"),
325         T_CONFIG_ARRAY_KVANY,
326         T_CONFIG_SCOPE_CONNECTION }
327      ,{ NULL, 0,
328         T_CONFIG_UNSET,
329         T_CONFIG_SCOPE_UNSET }
330     };
331 
332     plugin_data * const p = p_d;
333     if (!config_plugin_values_init(srv, p, cpk, "mod_cgi"))
334         return HANDLER_ERROR;
335 
336     /* process and validate config directives
337      * (init i to 0 if global context; to 1 to skip empty global context) */
338     for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
339         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
340         for (; -1 != cpv->k_id; ++cpv) {
341             switch (cpv->k_id) {
342               case 0: /* cgi.assign */
343               case 1: /* cgi.execute-x-only */
344               case 2: /* cgi.x-sendfile */
345                 break;
346               case 3: /* cgi.x-sendfile-docroot */
347                 for (uint32_t j = 0; j < cpv->v.a->used; ++j) {
348                     data_string *ds = (data_string *)cpv->v.a->data[j];
349                     if (ds->value.ptr[0] != '/') {
350                         log_error(srv->errh, __FILE__, __LINE__,
351                           "%s paths must begin with '/'; invalid: \"%s\"",
352                           cpk[cpv->k_id].k, ds->value.ptr);
353                         return HANDLER_ERROR;
354                     }
355                     buffer_path_simplify(&ds->value);
356                     buffer_append_slash(&ds->value);
357                 }
358                 break;
359               case 4: /* cgi.local-redir */
360               case 5: /* cgi.upgrade */
361                 break;
362               case 6: /* cgi.limits */
363                 cpv->v.v = mod_cgi_parse_limits(cpv->v.a, srv->errh);
364                 if (NULL == cpv->v.v) return HANDLER_ERROR;
365                 cpv->vtype = T_CONFIG_LOCAL;
366                 break;
367               default:/* should not happen */
368                 break;
369             }
370         }
371     }
372 
373     /* initialize p->defaults from global config context */
374     if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
375         const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
376         if (-1 != cpv->k_id)
377             mod_cgi_merge_config(&p->defaults, cpv);
378     }
379 
380     p->tempfile_accum = config_feature_bool(srv, "cgi.tempfile-accum", 1);
381 
382     return HANDLER_GO_ON;
383 }
384 
385 
386 static cgi_pid_t * cgi_pid_add(plugin_data *p, pid_t pid, handler_ctx *hctx) {
387     cgi_pid_t *cgi_pid = malloc(sizeof(cgi_pid_t));
388     force_assert(cgi_pid);
389     cgi_pid->pid = pid;
390     cgi_pid->signal_sent = 0;
391     cgi_pid->hctx = hctx;
392     cgi_pid->prev = NULL;
393     cgi_pid->next = p->cgi_pid;
394     p->cgi_pid = cgi_pid;
395     return cgi_pid;
396 }
397 
398 static void cgi_pid_kill(cgi_pid_t *cgi_pid, int sig) {
399     cgi_pid->signal_sent = sig; /*(save last signal sent)*/
400     kill(cgi_pid->pid, sig);
401 }
402 
403 static void cgi_pid_del(plugin_data *p, cgi_pid_t *cgi_pid) {
404     if (cgi_pid->prev)
405         cgi_pid->prev->next = cgi_pid->next;
406     else
407         p->cgi_pid = cgi_pid->next;
408 
409     if (cgi_pid->next)
410         cgi_pid->next->prev = cgi_pid->prev;
411 
412     free(cgi_pid);
413 }
414 
415 
416 static void cgi_connection_close_fdtocgi(handler_ctx *hctx) {
417 	/*(closes only hctx->fdtocgi)*/
418 	if (-1 == hctx->fdtocgi) return;
419 	struct fdevents * const ev = hctx->ev;
420 	fdevent_fdnode_event_del(ev, hctx->fdntocgi);
421 	/*fdevent_unregister(ev, hctx->fdtocgi);*//*(handled below)*/
422 	fdevent_sched_close(ev, hctx->fdtocgi, 0);
423 	hctx->fdntocgi = NULL;
424 	hctx->fdtocgi = -1;
425 }
426 
427 static void cgi_connection_close(handler_ctx *hctx) {
428 	/* the connection to the browser went away, but we still have a connection
429 	 * to the CGI script
430 	 *
431 	 * close cgi-connection
432 	 */
433 
434 	if (hctx->fd != -1) {
435 		struct fdevents * const ev = hctx->ev;
436 		/* close connection to the cgi-script */
437 		fdevent_fdnode_event_del(ev, hctx->fdn);
438 		/*fdevent_unregister(ev, hctx->fd);*//*(handled below)*/
439 		fdevent_sched_close(ev, hctx->fd, 0);
440 		hctx->fdn = NULL;
441 	}
442 
443 	if (hctx->fdtocgi != -1) {
444 		cgi_connection_close_fdtocgi(hctx); /*(closes only hctx->fdtocgi)*/
445 	}
446 
447 	plugin_data * const p = hctx->plugin_data;
448 	request_st * const r = hctx->r;
449 	r->plugin_ctx[p->id] = NULL;
450 
451 	if (hctx->cgi_pid) {
452 		cgi_pid_kill(hctx->cgi_pid, SIGTERM);
453 		hctx->cgi_pid->hctx = NULL;
454 	}
455 	cgi_handler_ctx_free(hctx);
456 
457 	/* finish response (if not already r->resp_body_started, r->resp_body_finished) */
458 	if (r->handler_module == p->self) {
459 		http_response_backend_done(r);
460 	}
461 }
462 
463 static handler_t cgi_connection_close_callback(request_st * const r, void *p_d) {
464     handler_ctx *hctx = r->plugin_ctx[((plugin_data *)p_d)->id];
465     if (hctx) {
466         chunkqueue_set_tempdirs(&r->reqbody_queue, /* reset sz */
467                                 r->reqbody_queue.tempdirs, 0);
468         cgi_connection_close(hctx);
469     }
470     return HANDLER_GO_ON;
471 }
472 
473 
474 static int cgi_write_request(handler_ctx *hctx, int fd);
475 
476 
477 static handler_t cgi_handle_fdevent_send (void *ctx, int revents) {
478 	handler_ctx *hctx = ctx;
479 	hctx->wr_revents |= revents;
480 	joblist_append(hctx->con);
481 	return HANDLER_FINISHED;
482 }
483 
484 
485 static handler_t cgi_process_wr_revents (handler_ctx * const hctx, request_st * const r, int revents) {
486 	if (revents & FDEVENT_OUT) {
487 		if (0 != cgi_write_request(hctx, hctx->fdtocgi)) {
488 			cgi_connection_close(hctx);
489 			return HANDLER_ERROR;
490 		}
491 		/* more request body to be sent to CGI */
492 	}
493 
494 	if (revents & FDEVENT_HUP) {
495 		/* skip sending remaining data to CGI */
496 		if (r->reqbody_length) {
497 			chunkqueue *cq = &r->reqbody_queue;
498 			chunkqueue_mark_written(cq, chunkqueue_length(cq));
499 			if (cq->bytes_in != (off_t)r->reqbody_length) {
500 				r->keep_alive = 0;
501 			}
502 		}
503 
504 		cgi_connection_close_fdtocgi(hctx); /*(closes only hctx->fdtocgi)*/
505 	} else if (revents & FDEVENT_ERR) {
506 		/* kill all connections to the cgi process */
507 #if 1
508 		log_error(r->conf.errh, __FILE__, __LINE__, "cgi-FDEVENT_ERR");
509 #endif
510 		cgi_connection_close(hctx);
511 		return HANDLER_ERROR;
512 	}
513 
514 	return HANDLER_GO_ON;
515 }
516 
517 
518 static handler_t cgi_response_headers(request_st * const r, struct http_response_opts_t *opts) {
519     /* response headers just completed */
520     handler_ctx *hctx = (handler_ctx *)opts->pdata;
521 
522     if (light_btst(r->resp_htags, HTTP_HEADER_UPGRADE)) {
523         if (hctx->conf.upgrade && r->http_status == 101) {
524             /* 101 Switching Protocols; transition to transparent proxy */
525             if (r->h2_connect_ext) {
526                 r->http_status = 200; /* OK (response status for CONNECT) */
527                 http_header_response_unset(r, HTTP_HEADER_UPGRADE,
528                                            CONST_STR_LEN("Upgrade"));
529                 http_header_response_unset(r, HTTP_HEADER_OTHER,
530                                          CONST_STR_LEN("Sec-WebSocket-Accept"));
531             }
532             http_response_upgrade_read_body_unknown(r);
533         }
534         else {
535             light_bclr(r->resp_htags, HTTP_HEADER_UPGRADE);
536           #if 0
537             /* preserve prior questionable behavior; likely broken behavior
538              * anyway if backend thinks connection is being upgraded but client
539              * does not receive Connection: upgrade */
540             http_header_response_unset(r, HTTP_HEADER_UPGRADE,
541                                        CONST_STR_LEN("Upgrade"));
542           #endif
543         }
544     }
545     else if (__builtin_expect( (r->h2_connect_ext != 0), 0)
546              && r->http_status < 300) {
547         /*(not handling other 1xx intermediate responses here; not expected)*/
548         http_response_body_clear(r, 0);
549         r->handler_module = NULL;
550         r->http_status = 405; /* Method Not Allowed */
551         return HANDLER_FINISHED;
552     }
553 
554     if (hctx->conf.upgrade
555         && !(light_btst(r->resp_htags, HTTP_HEADER_UPGRADE)
556              || r->h2_connect_ext)) {
557         chunkqueue *cq = &r->reqbody_queue;
558         hctx->conf.upgrade = 0;
559         if (cq->bytes_out == (off_t)r->reqbody_length) {
560             cgi_connection_close_fdtocgi(hctx); /*(closes hctx->fdtocgi)*/
561         }
562     }
563 
564     return HANDLER_GO_ON;
565 }
566 
567 
568 __attribute_cold__
569 __attribute_noinline__
570 static handler_t cgi_local_redir(request_st * const r, handler_ctx * const hctx) {
571     buffer_clear(hctx->response);
572     chunk_buffer_yield(hctx->response);
573     http_response_reset(r); /*(includes r->http_status = 0)*/
574     r->con->srv->plugins_request_reset(r);
575     /*cgi_connection_close(hctx);*//*(already cleaned up and hctx is now invalid)*/
576     return HANDLER_COMEBACK;
577 }
578 
579 
580 static int cgi_recv_response(request_st * const r, handler_ctx * const hctx) {
581 		const off_t bytes_in = r->write_queue.bytes_in;
582 		switch (http_response_read(r, &hctx->opts,
583 					   hctx->response, hctx->fdn)) {
584 		default:
585 			if (r->write_queue.bytes_in > bytes_in)
586 				hctx->read_ts = log_monotonic_secs;
587 			return HANDLER_GO_ON;
588 		case HANDLER_ERROR:
589 			http_response_backend_error(r);
590 			__attribute_fallthrough__
591 		case HANDLER_FINISHED:
592 			cgi_connection_close(hctx);
593 			return HANDLER_FINISHED;
594 		case HANDLER_COMEBACK:
595 			return cgi_local_redir(r, hctx); /* HANDLER_COMEBACK */
596 		}
597 }
598 
599 
600 static handler_t cgi_handle_fdevent(void *ctx, int revents) {
601 	handler_ctx *hctx = ctx;
602 	hctx->rd_revents |= revents;
603 	joblist_append(hctx->con);
604 	return HANDLER_FINISHED;
605 }
606 
607 
608 static handler_t cgi_process_rd_revents(handler_ctx * const hctx, request_st * const r, int revents) {
609 	if (revents & FDEVENT_IN) {
610 		handler_t rc = cgi_recv_response(r, hctx); /*(might invalidate hctx)*/
611 		if (rc != HANDLER_GO_ON) return rc;         /*(unless HANDLER_GO_ON)*/
612 	}
613 
614 	/* perhaps this issue is already handled */
615 	if (revents & (FDEVENT_HUP|FDEVENT_RDHUP)) {
616 		if (r->resp_body_started) {
617 			/* drain any remaining data from kernel pipe buffers
618 			 * even if (r->conf.stream_response_body
619 			 *          & FDEVENT_STREAM_RESPONSE_BUFMIN)
620 			 * since event loop will spin on fd FDEVENT_HUP event
621 			 * until unregistered. */
622 			handler_t rc;
623 			const unsigned short flags = r->conf.stream_response_body;
624 			r->conf.stream_response_body &= ~FDEVENT_STREAM_RESPONSE_BUFMIN;
625 			r->conf.stream_response_body |= FDEVENT_STREAM_RESPONSE_POLLRDHUP;
626 			do {
627 				rc = cgi_recv_response(r,hctx); /*(might invalidate hctx)*/
628 			} while (rc == HANDLER_GO_ON);           /*(unless HANDLER_GO_ON)*/
629 			r->conf.stream_response_body = flags;
630 			return rc; /* HANDLER_FINISHED or HANDLER_COMEBACK or HANDLER_ERROR */
631 		} else if (!buffer_is_blank(hctx->response)) {
632 			/* unfinished header package which is a body in reality */
633 			r->resp_body_started = 1;
634 			if (0 != http_chunk_append_buffer(r, hctx->response)) {
635 				cgi_connection_close(hctx);
636 				return HANDLER_ERROR;
637 			}
638 			if (0 == r->http_status) r->http_status = 200; /* OK */
639 		}
640 		cgi_connection_close(hctx);
641 	} else if (revents & FDEVENT_ERR) {
642 		/* kill all connections to the cgi process */
643 		cgi_connection_close(hctx);
644 		return HANDLER_ERROR;
645 	}
646 
647 	return HANDLER_GO_ON;
648 }
649 
650 
651 __attribute_cold__
652 __attribute_noinline__
653 static void cgi_env_offset_resize(env_accum *env) {
654     chunk_buffer_prepare_append(env->boffsets, env->boffsets->size*2);
655     env->offsets = (uintptr_t *)(void *)env->boffsets->ptr;
656     env->osize = env->boffsets->size/sizeof(*env->offsets);
657 }
658 
659 static int cgi_env_add(void *venv, const char *key, size_t key_len, const char *val, size_t val_len) {
660 	env_accum *env = venv;
661 
662 	if (!key || (!val && val_len)) return -1;
663 
664 	if (__builtin_expect( (env->osize == env->oused), 0))
665 		cgi_env_offset_resize(env);
666 	env->offsets[env->oused++] = env->b->used-1;
667 
668 	char * const dst = buffer_extend(env->b, key_len + val_len + 2);
669 	memcpy(dst, key, key_len);
670 	dst[key_len] = '=';
671 	if (val_len) memcpy(dst + key_len + 1, val, val_len);
672 	dst[key_len + 1 + val_len] = '\0';
673 
674 	return 0;
675 }
676 
677 static int cgi_write_request(handler_ctx *hctx, int fd) {
678 	request_st * const r = hctx->r;
679 	chunkqueue *cq = &r->reqbody_queue;
680 	chunk *c;
681 
682 	chunkqueue_remove_finished_chunks(cq); /* unnecessary? */
683 
684 	/* old comment: windows doesn't support select() on pipes - wouldn't be easy to fix for all platforms.
685 	 */
686 
687 	for (c = cq->first; c; c = cq->first) {
688 		ssize_t wr = chunkqueue_write_chunk_to_pipe(fd, cq, r->conf.errh);
689 		if (wr > 0) {
690 			hctx->write_ts = log_monotonic_secs;
691 			chunkqueue_mark_written(cq, wr);
692 			/* continue if wrote whole chunk or wrote 16k block
693 			 * (see chunkqueue_write_chunk_file_intermed()) */
694 			if (c != cq->first || wr == 16384)
695 				continue;
696 			/*(else partial write)*/
697 		}
698 		else if (wr < 0) {
699 				switch(errno) {
700 				case EAGAIN:
701 				case EINTR:
702 					/* ignore and try again later */
703 					break;
704 				case EPIPE:
705 				case ECONNRESET:
706 					/* connection closed */
707 					log_error(r->conf.errh, __FILE__, __LINE__,
708 					  "failed to send post data to cgi, connection closed by CGI");
709 					/* skip all remaining data */
710 					chunkqueue_mark_written(cq, chunkqueue_length(cq));
711 					break;
712 				default:
713 					/* fatal error */
714 					log_perror(r->conf.errh, __FILE__, __LINE__, "write() failed");
715 					return -1;
716 				}
717 		}
718 		/*if (0 == wr) break;*/ /*(might block)*/
719 		break;
720 	}
721 
722 	if (cq->bytes_out == (off_t)r->reqbody_length && !hctx->conf.upgrade) {
723 		/* sent all request body input */
724 		/* close connection to the cgi-script */
725 		if (-1 == hctx->fdtocgi) { /*(received request body sent in initial send to pipe buffer)*/
726 			--r->con->srv->cur_fds;
727 			if (close(fd)) {
728 				log_perror(r->conf.errh, __FILE__, __LINE__, "cgi stdin close %d failed", fd);
729 			}
730 		} else {
731 			cgi_connection_close_fdtocgi(hctx); /*(closes only hctx->fdtocgi)*/
732 		}
733 	} else {
734 		off_t cqlen = chunkqueue_length(cq);
735 		if (cq->bytes_in != r->reqbody_length && cqlen < 65536 - 16384) {
736 			/*(r->conf.stream_request_body & FDEVENT_STREAM_REQUEST)*/
737 			if (!(r->conf.stream_request_body & FDEVENT_STREAM_REQUEST_POLLIN)) {
738 				r->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_POLLIN;
739 				r->con->is_readable = 1; /* trigger optimistic read from client */
740 			}
741 		}
742 		struct fdevents * const ev = hctx->ev;
743 		if (-1 == hctx->fdtocgi) { /*(not registered yet)*/
744 			hctx->fdtocgi = fd;
745 			hctx->fdntocgi = fdevent_register(ev, hctx->fdtocgi, cgi_handle_fdevent_send, hctx);
746 		}
747 		if (0 == cqlen) { /*(chunkqueue_is_empty(cq))*/
748 			if ((fdevent_fdnode_interest(hctx->fdntocgi) & FDEVENT_OUT)) {
749 				fdevent_fdnode_event_set(ev, hctx->fdntocgi, 0);
750 			}
751 		} else {
752 			/* more request body remains to be sent to CGI so register for fdevents */
753 			hctx->write_ts = log_monotonic_secs;
754 			fdevent_fdnode_event_set(ev, hctx->fdntocgi, FDEVENT_OUT);
755 		}
756 	}
757 
758 	return 0;
759 }
760 
761 static int cgi_create_env(request_st * const r, plugin_data * const p, handler_ctx * const hctx, buffer * const cgi_handler) {
762 	char *args[3];
763 	int to_cgi_fds[2];
764 	int from_cgi_fds[2];
765 	UNUSED(p);
766 
767 	if (!buffer_is_blank(cgi_handler)) {
768 		if (NULL == stat_cache_path_stat(cgi_handler)) {
769 			log_perror(r->conf.errh, __FILE__, __LINE__,
770 			  "stat for cgi-handler %s", cgi_handler->ptr);
771 			return -1;
772 		}
773 	}
774 
775 	to_cgi_fds[0] = -1;
776   #ifndef __CYGWIN__
777 	if (0 == r->reqbody_length) {
778 		/* future: might keep fd open in p->devnull for reuse
779 		 * and dup() here, or do not close() (later in this func) */
780 		to_cgi_fds[0] = fdevent_open_devnull();
781 		if (-1 == to_cgi_fds[0]) {
782 			log_perror(r->conf.errh, __FILE__, __LINE__, "open /dev/null");
783 			return -1;
784 		}
785 	}
786 	else if (!(r->conf.stream_request_body /*(if not streaming request body)*/
787 	           & (FDEVENT_STREAM_REQUEST|FDEVENT_STREAM_REQUEST_BUFMIN))) {
788 		chunkqueue * const cq = &r->reqbody_queue;
789 		chunk * const c = cq->first;
790 		if (c && c == cq->last && c->type == FILE_CHUNK && c->file.is_temp) {
791 			/* request body in single tempfile if not streaming req body */
792 			if (-1 == c->file.fd
793 			    && 0 != chunkqueue_open_file_chunk(cq, r->conf.errh))
794 				return -1;
795 		  #ifdef __COVERITY__
796 			force_assert(-1 != c->file.fd);
797 		  #endif
798 			if (-1 == lseek(c->file.fd, 0, SEEK_SET)) {
799 				log_perror(r->conf.errh, __FILE__, __LINE__,
800 				  "lseek %s", c->mem->ptr);
801 				return -1;
802 			}
803 			to_cgi_fds[0] = c->file.fd;
804 			to_cgi_fds[1] = -1;
805 		}
806 	}
807   #endif
808 
809 	unsigned int bufsz_hint = 16384;
810   #ifdef _WIN32
811 	if (r->reqbody_length <= 1048576)
812 		bufsz_hint = (unsigned int)r->reqbody_length;
813   #endif
814 	if (-1 == to_cgi_fds[0] && fdevent_pipe_cloexec(to_cgi_fds, bufsz_hint)) {
815 		log_perror(r->conf.errh, __FILE__, __LINE__, "pipe failed");
816 		return -1;
817 	}
818 	if (fdevent_pipe_cloexec(from_cgi_fds, bufsz_hint)) {
819 		if (0 == r->reqbody_length) {
820 			close(to_cgi_fds[0]);
821 		}
822 		else if (-1 != to_cgi_fds[1]) {
823 			close(to_cgi_fds[0]);
824 			close(to_cgi_fds[1]);
825 		}
826 		log_perror(r->conf.errh, __FILE__, __LINE__, "pipe failed");
827 		return -1;
828 	}
829 
830 	env_accum * const env = &p->env;
831 	env->b = chunk_buffer_acquire();
832 	env->boffsets = chunk_buffer_acquire();
833 	buffer_truncate(env->b, 0);
834 	char **envp;
835 	{
836 		size_t i = 0;
837 		http_cgi_opts opts = { 0, 0, NULL, NULL };
838 		env->offsets = (uintptr_t *)(void *)env->boffsets->ptr;
839 		env->osize = env->boffsets->size/sizeof(*env->offsets);
840 		env->oused = 0;
841 
842 		/* create environment */
843 
844 		off_t reqbody_length = r->reqbody_length;
845 		if (r->h2_connect_ext) {
846 			/*(SERVER_PROTOCOL=HTTP/1.1 instead of HTTP/2.0)*/
847 			r->http_version = HTTP_VERSION_1_1;
848 			r->http_method = HTTP_METHOD_GET;
849 			if (reqbody_length < 0)
850 				r->reqbody_length = 0;
851 		}
852 
853 		http_cgi_headers(r, &opts, cgi_env_add, env);
854 
855 		if (r->h2_connect_ext) {
856 			r->http_version = HTTP_VERSION_2;
857 			r->http_method = HTTP_METHOD_CONNECT;
858 			r->reqbody_length = reqbody_length;
859 			/* https://datatracker.ietf.org/doc/html/rfc6455#section-4.1
860 			 * 7. The request MUST include a header field with the name
861 			 *    |Sec-WebSocket-Key|.  The value of this header field MUST be a
862 			 *    nonce consisting of a randomly selected 16-byte value that has
863 			 *    been base64-encoded (see Section 4 of [RFC4648]).  The nonce
864 			 *    MUST be selected randomly for each connection.
865 			 * Note: Sec-WebSocket-Key is not used in RFC8441;
866 			 *       include Sec-WebSocket-Key for HTTP/1.1 compatibility;
867 			 *       !!not random!! base64-encoded "0000000000000000" */
868 			if (!http_header_request_get(r, HTTP_HEADER_OTHER,
869 			                             CONST_STR_LEN("Sec-WebSocket-Key")))
870 				cgi_env_add(env, CONST_STR_LEN("HTTP_SEC_WEBSOCKET_KEY"),
871 				                 CONST_STR_LEN("MDAwMDAwMDAwMDAwMDAwMAo="));
872 			/*(Upgrade and Connection should not exist for HTTP/2 request)*/
873 			cgi_env_add(env, CONST_STR_LEN("HTTP_UPGRADE"), CONST_STR_LEN("websocket"));
874 			cgi_env_add(env, CONST_STR_LEN("HTTP_CONNECTION"), CONST_STR_LEN("upgrade"));
875 		}
876 
877 		/* for valgrind */
878 		if (p->env.ld_preload) {
879 			cgi_env_add(env, CONST_STR_LEN("LD_PRELOAD"), BUF_PTR_LEN(p->env.ld_preload));
880 		}
881 		if (p->env.ld_library_path) {
882 			cgi_env_add(env, CONST_STR_LEN("LD_LIBRARY_PATH"), BUF_PTR_LEN(p->env.ld_library_path));
883 		}
884 	      #ifdef __CYGWIN__
885 		/* CYGWIN needs SYSTEMROOT */
886 		if (p->env.systemroot) {
887 			cgi_env_add(env, CONST_STR_LEN("SYSTEMROOT"), BUF_PTR_LEN(p->env.systemroot));
888 		}
889 	      #endif
890 
891 		/* adjust (uintptr_t) offsets to (char *) ptr
892 		 * (stored as offsets while accumulating in buffer,
893 		 *  in case buffer is reallocated during env creation) */
894 		if (__builtin_expect( (env->osize == env->oused), 0))
895 			cgi_env_offset_resize(env);
896 		envp = (char **)env->offsets;
897 		envp[env->oused] = NULL;
898 		const uintptr_t baseptr = (uintptr_t)env->b->ptr;
899 		for (i = 0; i < env->oused; ++i)
900 			envp[i] += baseptr;
901 
902 		/* set up args */
903 		i = 0;
904 
905 		if (!buffer_is_blank(cgi_handler)) {
906 			args[i++] = cgi_handler->ptr;
907 		}
908 		args[i++] = r->physical.path.ptr;
909 		args[i  ] = NULL;
910 	}
911 
912 	int dfd = fdevent_open_dirname(r->physical.path.ptr,r->conf.follow_symlink);
913 	if (-1 == dfd) {
914 		log_perror(r->conf.errh, __FILE__, __LINE__, "open dirname %s failed", r->physical.path.ptr);
915 	}
916 
917 	int serrh_fd = r->conf.serrh ? r->conf.serrh->fd : -1;
918 	pid_t pid = (dfd >= 0)
919 	  ? fdevent_fork_execve(args[0], args, envp,
920 	                        to_cgi_fds[0], from_cgi_fds[1], serrh_fd, dfd)
921 	  : -1;
922 
923 	chunk_buffer_release(env->boffsets);
924 	chunk_buffer_release(env->b);
925 	env->boffsets = NULL;
926 	env->b = NULL;
927 
928 	if (-1 == pid) {
929 		/* log error with errno prior to calling close() (might change errno) */
930 		log_perror(r->conf.errh, __FILE__, __LINE__, "fork failed");
931 		if (-1 != dfd) close(dfd);
932 		close(from_cgi_fds[0]);
933 		close(from_cgi_fds[1]);
934 		if (0 == r->reqbody_length) {
935 			close(to_cgi_fds[0]);
936 		}
937 		else if (-1 != to_cgi_fds[1]) {
938 			close(to_cgi_fds[0]);
939 			close(to_cgi_fds[1]);
940 		}
941 		return -1;
942 	} else {
943 		if (-1 != dfd) close(dfd);
944 		close(from_cgi_fds[1]);
945 
946 		hctx->fd = from_cgi_fds[0];
947 		hctx->cgi_pid = cgi_pid_add(p, pid, hctx);
948 
949 		if (0 == r->reqbody_length) {
950 			close(to_cgi_fds[0]);
951 		}
952 		else if (-1 == to_cgi_fds[1]) {
953 			chunkqueue * const cq = &r->reqbody_queue;
954 			chunkqueue_mark_written(cq, chunkqueue_length(cq));
955 		}
956 		else if (0 == fdevent_fcntl_set_nb(to_cgi_fds[1])
957 		         && 0 == cgi_write_request(hctx, to_cgi_fds[1])) {
958 			close(to_cgi_fds[0]);
959 			++r->con->srv->cur_fds;
960 		}
961 		else {
962 			close(to_cgi_fds[0]);
963 			close(to_cgi_fds[1]);
964 			/*(hctx->fd not yet registered with fdevent, so manually
965 			 * cleanup here; see fdevent_register() further below)*/
966 			close(hctx->fd);
967 			hctx->fd = -1;
968 			cgi_connection_close(hctx);
969 			return -1;
970 		}
971 
972 		++r->con->srv->cur_fds;
973 
974 		struct fdevents * const ev = hctx->ev;
975 		hctx->fdn = fdevent_register(ev, hctx->fd, cgi_handle_fdevent, hctx);
976 		if (-1 == fdevent_fcntl_set_nb(hctx->fd)) {
977 			log_perror(r->conf.errh, __FILE__, __LINE__, "fcntl failed");
978 			cgi_connection_close(hctx);
979 			return -1;
980 		}
981 		hctx->read_ts = log_monotonic_secs;
982 		fdevent_fdnode_event_set(ev, hctx->fdn, FDEVENT_IN | FDEVENT_RDHUP);
983 
984 		return 0;
985 	}
986 }
987 
988 URIHANDLER_FUNC(cgi_is_handled) {
989 	plugin_data *p = p_d;
990 	const stat_cache_st *st;
991 	data_string *ds;
992 
993 	if (NULL != r->handler_module) return HANDLER_GO_ON;
994 	/* r->physical.path is non-empty for handle_subrequest_start */
995 	/*if (buffer_is_blank(&r->physical.path)) return HANDLER_GO_ON;*/
996 
997 	mod_cgi_patch_config(r, p);
998 	if (NULL == p->conf.cgi) return HANDLER_GO_ON;
999 
1000 	ds = (data_string *)array_match_key_suffix(p->conf.cgi, &r->physical.path);
1001 	if (NULL == ds) return HANDLER_GO_ON;
1002 
1003 	/* r->tmp_sce is set in http_response_physical_path_check() and is valid
1004 	 * in handle_subrequest_start callback -- handle_subrequest_start callbacks
1005 	 * should not change r->physical.path (or should invalidate r->tmp_sce) */
1006 	st = r->tmp_sce && buffer_is_equal(&r->tmp_sce->name, &r->physical.path)
1007 	   ? &r->tmp_sce->st
1008 	   : stat_cache_path_stat(&r->physical.path);
1009 	if (NULL == st) return HANDLER_GO_ON;
1010 
1011 	/* (aside: CGI might be executable even if it is not readable) */
1012 	if (!S_ISREG(st->st_mode)) return HANDLER_GO_ON;
1013 	if (p->conf.execute_x_only == 1 && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) return HANDLER_GO_ON;
1014 
1015 	if (__builtin_expect( (r->h2_connect_ext != 0), 0) && !p->conf.upgrade) {
1016 		r->http_status = 405; /* Method Not Allowed */
1017 		return HANDLER_FINISHED;
1018 	}
1019 
1020 	if (r->reqbody_length
1021 	    && p->tempfile_accum
1022 	    && !(r->conf.stream_request_body /*(if not streaming request body)*/
1023 	         & (FDEVENT_STREAM_REQUEST|FDEVENT_STREAM_REQUEST_BUFMIN))) {
1024 		/* store request body in single tempfile if not streaming request body*/
1025 		r->reqbody_queue.upload_temp_file_size = INTMAX_MAX;
1026 	}
1027 
1028 	{
1029 		handler_ctx *hctx = cgi_handler_ctx_init();
1030 		hctx->ev = r->con->srv->ev;
1031 		hctx->r = r;
1032 		hctx->con = r->con;
1033 		hctx->plugin_data = p;
1034 		hctx->cgi_handler = &ds->value;
1035 		memcpy(&hctx->conf, &p->conf, sizeof(plugin_config));
1036 		if (__builtin_expect( (r->h2_connect_ext != 0), 0)) {
1037 		}
1038 		else if (!light_btst(r->rqst_htags, HTTP_HEADER_UPGRADE))
1039 			hctx->conf.upgrade = 0;
1040 		else if (!hctx->conf.upgrade || r->http_version != HTTP_VERSION_1_1) {
1041 			hctx->conf.upgrade = 0;
1042 			http_header_request_unset(r, HTTP_HEADER_UPGRADE,
1043 			                          CONST_STR_LEN("Upgrade"));
1044 		}
1045 		hctx->opts.max_per_read =
1046 		  !(r->conf.stream_response_body /*(if not streaming response body)*/
1047 		    & (FDEVENT_STREAM_RESPONSE|FDEVENT_STREAM_RESPONSE_BUFMIN))
1048 		    ? 262144
1049 		    : (r->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)
1050 		      ? 16384  /* FDEVENT_STREAM_RESPONSE_BUFMIN */
1051 		      : 65536; /* FDEVENT_STREAM_RESPONSE */
1052 		hctx->opts.fdfmt = S_IFIFO;
1053 		hctx->opts.backend = BACKEND_CGI;
1054 		hctx->opts.authorizer = 0;
1055 		hctx->opts.local_redir = hctx->conf.local_redir;
1056 		hctx->opts.xsendfile_allow = hctx->conf.xsendfile_allow;
1057 		hctx->opts.xsendfile_docroot = hctx->conf.xsendfile_docroot;
1058 		hctx->opts.pdata = hctx;
1059 		hctx->opts.headers = cgi_response_headers;
1060 		r->plugin_ctx[p->id] = hctx;
1061 		r->handler_module = p->self;
1062 	}
1063 
1064 	return HANDLER_GO_ON;
1065 }
1066 
1067 /*
1068  * - HANDLER_GO_ON : not our job
1069  * - HANDLER_FINISHED: got response
1070  * - HANDLER_WAIT_FOR_EVENT: waiting for response
1071  */
1072 SUBREQUEST_FUNC(mod_cgi_handle_subrequest) {
1073 	plugin_data * const p = p_d;
1074 	handler_ctx * const hctx = r->plugin_ctx[p->id];
1075 	if (NULL == hctx) return HANDLER_GO_ON;
1076 
1077 	if (__builtin_expect(
1078 	     (r->conf.stream_request_body & FDEVENT_STREAM_REQUEST_TCP_FIN), 0)
1079 	    && hctx->conf.limits && hctx->conf.limits->signal_fin) {
1080 		/* XXX: consider setting r->http_status = 499 if (0 == r->http_status)
1081 		 * (499 is nginx custom status to indicate client closed connection) */
1082 		if (-1 == hctx->fd) return HANDLER_ERROR; /*(CGI not yet spawned)*/
1083 		if (hctx->cgi_pid) /* send signal to notify CGI about TCP FIN */
1084 			cgi_pid_kill(hctx->cgi_pid, hctx->conf.limits->signal_fin);
1085 	}
1086 
1087 	const int rd_revents = hctx->rd_revents;
1088 	const int wr_revents = hctx->wr_revents;
1089 	if (rd_revents) {
1090 		hctx->rd_revents = 0;
1091 		handler_t rc = cgi_process_rd_revents(hctx, r, rd_revents);
1092 		if (rc != HANDLER_GO_ON) return rc; /*(might invalidate hctx)*/
1093 	}
1094 	if (wr_revents) {
1095 		hctx->wr_revents = 0;
1096 		handler_t rc = cgi_process_wr_revents(hctx, r, wr_revents);
1097 		if (rc != HANDLER_GO_ON) return rc; /*(might invalidate hctx)*/
1098 	}
1099 
1100 	if ((r->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)
1101 	    && r->resp_body_started) {
1102 		if (chunkqueue_length(&r->write_queue) > 65536 - 4096) {
1103 			fdevent_fdnode_event_clr(hctx->ev, hctx->fdn, FDEVENT_IN);
1104 		} else if (!(fdevent_fdnode_interest(hctx->fdn) & FDEVENT_IN)) {
1105 			/* optimistic read from backend */
1106 			handler_t rc = cgi_recv_response(r, hctx);  /*(might invalidate hctx)*/
1107 			if (rc != HANDLER_GO_ON) return rc;          /*(unless HANDLER_GO_ON)*/
1108 			hctx->read_ts = log_monotonic_secs;
1109 			fdevent_fdnode_event_add(hctx->ev, hctx->fdn, FDEVENT_IN);
1110 		}
1111 	}
1112 
1113 	chunkqueue * const cq = &r->reqbody_queue;
1114 
1115 	if (cq->bytes_in != (off_t)r->reqbody_length) {
1116 		/*(64k - 4k to attempt to avoid temporary files
1117 		 * in conjunction with FDEVENT_STREAM_REQUEST_BUFMIN)*/
1118 		if (chunkqueue_length(cq) > 65536 - 4096
1119 		    && (r->conf.stream_request_body & FDEVENT_STREAM_REQUEST_BUFMIN)){
1120 			r->conf.stream_request_body &= ~FDEVENT_STREAM_REQUEST_POLLIN;
1121 		} else {
1122 			handler_t rc = r->con->reqbody_read(r);
1123 			if (rc != HANDLER_GO_ON
1124 			    && !(r->h2_connect_ext && -1 == hctx->fd
1125 				    && rc == HANDLER_WAIT_FOR_EVENT))
1126 				return rc;
1127 		}
1128 	}
1129 
1130 	if (-1 == hctx->fd) {
1131 			/* CGI environment requires that Content-Length be set.
1132 			 * Send 411 Length Required if Content-Length missing.
1133 			 * (occurs here if client sends Transfer-Encoding: chunked
1134 			 *  and module is flagged to stream request body to backend) */
1135 			if (-1 == r->reqbody_length && !r->h2_connect_ext) {
1136 				return (r->conf.stream_request_body & FDEVENT_STREAM_REQUEST)
1137 				  ? http_response_reqbody_read_error(r, 411)
1138 				  : HANDLER_WAIT_FOR_EVENT;
1139 			}
1140 		if (cgi_create_env(r, p, hctx, hctx->cgi_handler)) {
1141 			r->http_status = 500;
1142 			r->handler_module = NULL;
1143 
1144 			return HANDLER_FINISHED;
1145 		}
1146 	} else if (!chunkqueue_is_empty(cq)) {
1147 		if (fdevent_fdnode_interest(hctx->fdntocgi) & FDEVENT_OUT)
1148 			return HANDLER_WAIT_FOR_EVENT;
1149 		if (0 != cgi_write_request(hctx, hctx->fdtocgi)) {
1150 			cgi_connection_close(hctx);
1151 			return HANDLER_ERROR;
1152 		}
1153 	}
1154 
1155 	/* if not done, wait for CGI to close stdout, so we read EOF on pipe */
1156 	return HANDLER_WAIT_FOR_EVENT;
1157 }
1158 
1159 
1160 __attribute_cold__
1161 __attribute_noinline__
1162 static void cgi_trigger_hctx_timeout(handler_ctx * const hctx, const char * const msg) {
1163     request_st * const r = hctx->r;
1164     joblist_append(r->con);
1165 
1166     log_error(r->conf.errh, __FILE__, __LINE__,
1167       "%s timeout on CGI: %s (pid: %lld)",
1168       msg, r->physical.path.ptr, (long long)hctx->cgi_pid->pid);
1169 
1170     if (*msg == 'w') { /* "write" */
1171         /* theoretically, response might be waiting on hctx->fdn pipe
1172          * if it arrived since we last checked for event, and if CGI
1173          * timeout out while reading (or did not read) request body */
1174         handler_t rc = cgi_recv_response(r, hctx); /*(might invalidate hctx)*/
1175         if (rc != HANDLER_GO_ON) return;            /*(unless HANDLER_GO_ON)*/
1176     }
1177 
1178     if (0 == r->http_status) r->http_status = 504; /* Gateway Timeout */
1179     cgi_connection_close(hctx);
1180 }
1181 
1182 
1183 static handler_t cgi_trigger_cb(server *srv, void *p_d) {
1184     UNUSED(srv);
1185     const unix_time64_t mono = log_monotonic_secs;
1186     plugin_data * const p = p_d;
1187     for (cgi_pid_t *cgi_pid = p->cgi_pid; cgi_pid; cgi_pid = cgi_pid->next) {
1188         /*(hctx stays in cgi_pid list until process pid is reaped,
1189          * so p->cgi_pid[] is not modified during this loop)*/
1190         handler_ctx * const hctx = cgi_pid->hctx;
1191         if (!hctx) continue; /*(already called cgi_pid_kill())*/
1192         const cgi_limits * const limits = hctx->conf.limits;
1193         if (NULL == limits) continue;
1194         if (limits->read_timeout && hctx->fdn
1195             && (fdevent_fdnode_interest(hctx->fdn) & FDEVENT_IN)
1196             && mono - hctx->read_ts > limits->read_timeout) {
1197             cgi_trigger_hctx_timeout(hctx, "read");
1198             continue;
1199         }
1200         if (limits->write_timeout && hctx->fdntocgi
1201             && (fdevent_fdnode_interest(hctx->fdntocgi) & FDEVENT_OUT)
1202             && mono - hctx->write_ts > limits->write_timeout) {
1203             cgi_trigger_hctx_timeout(hctx, "write");
1204             continue;
1205         }
1206     }
1207     return HANDLER_GO_ON;
1208 }
1209 
1210 
1211 static handler_t cgi_waitpid_cb(server *srv, void *p_d, pid_t pid, int status) {
1212     /*(XXX: if supporting a large number of CGI, might use a different algorithm
1213      * instead of linked list, e.g. splaytree indexed with pid)*/
1214     plugin_data *p = (plugin_data *)p_d;
1215     for (cgi_pid_t *cgi_pid = p->cgi_pid; cgi_pid; cgi_pid = cgi_pid->next) {
1216         if (pid != cgi_pid->pid) continue;
1217 
1218         handler_ctx * const hctx = cgi_pid->hctx;
1219         if (hctx) hctx->cgi_pid = NULL;
1220 
1221         if (WIFEXITED(status)) {
1222             /* (skip logging (non-zero) CGI exit; might be very noisy) */
1223         }
1224         else if (WIFSIGNALED(status)) {
1225             /* ignore SIGTERM if sent by cgi_connection_close() (NULL == hctx)*/
1226             if (WTERMSIG(status) != cgi_pid->signal_sent) {
1227                 log_error_st *errh = hctx ? hctx->r->conf.errh : srv->errh;
1228                 log_error(errh, __FILE__, __LINE__,
1229                   "CGI pid %d died with signal %d", pid, WTERMSIG(status));
1230             }
1231         }
1232         else {
1233             log_error_st *errh = hctx ? hctx->r->conf.errh : srv->errh;
1234             log_error(errh, __FILE__, __LINE__,
1235               "CGI pid %d ended unexpectedly", pid);
1236         }
1237 
1238         cgi_pid_del(p, cgi_pid);
1239         return HANDLER_FINISHED;
1240     }
1241 
1242     return HANDLER_GO_ON;
1243 }
1244 
1245 
1246 int mod_cgi_plugin_init(plugin *p);
1247 int mod_cgi_plugin_init(plugin *p) {
1248 	p->version     = LIGHTTPD_VERSION_ID;
1249 	p->name        = "cgi";
1250 
1251 	p->handle_request_reset = cgi_connection_close_callback;
1252 	p->handle_subrequest_start = cgi_is_handled;
1253 	p->handle_subrequest = mod_cgi_handle_subrequest;
1254 	p->handle_trigger = cgi_trigger_cb;
1255 	p->handle_waitpid = cgi_waitpid_cb;
1256 	p->init           = mod_cgi_init;
1257 	p->cleanup        = mod_cgi_free;
1258 	p->set_defaults   = mod_cgi_set_defaults;
1259 
1260 	return 0;
1261 }
1262