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