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