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