1 #include "first.h" 2 3 #include <sys/types.h> 4 #include <stdlib.h> 5 #include <string.h> 6 7 #include "gw_backend.h" 8 typedef gw_plugin_config plugin_config; 9 typedef gw_plugin_data plugin_data; 10 typedef gw_handler_ctx handler_ctx; 11 12 #include "base.h" 13 #include "buffer.h" 14 #include "fdevent.h" 15 #include "http_chunk.h" 16 #include "log.h" 17 #include "status_counter.h" 18 19 #ifdef HAVE_FASTCGI_FASTCGI_H 20 # include <fastcgi/fastcgi.h> 21 #else 22 # ifdef HAVE_FASTCGI_H 23 # include <fastcgi.h> 24 # else 25 # include "fastcgi.h" 26 # endif 27 #endif /* HAVE_FASTCGI_FASTCGI_H */ 28 29 #if GW_RESPONDER != FCGI_RESPONDER 30 #error "mismatched defines: (GW_RESPONDER != FCGI_RESPONDER)" 31 #endif 32 #if GW_AUTHORIZER != FCGI_AUTHORIZER 33 #error "mismatched defines: (GW_AUTHORIZER != FCGI_AUTHORIZER)" 34 #endif 35 #if GW_FILTER != FCGI_FILTER 36 #error "mismatched defines: (GW_FILTER != FCGI_FILTER)" 37 #endif 38 39 static void mod_fastcgi_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { 40 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 41 case 0: /* fastcgi.server */ 42 if (cpv->vtype == T_CONFIG_LOCAL) { 43 gw_plugin_config * const gw = cpv->v.v; 44 pconf->exts = gw->exts; 45 pconf->exts_auth = gw->exts_auth; 46 pconf->exts_resp = gw->exts_resp; 47 } 48 break; 49 case 1: /* fastcgi.balance */ 50 /*if (cpv->vtype == T_CONFIG_LOCAL)*//*always true here for this param*/ 51 pconf->balance = (int)cpv->v.u; 52 break; 53 case 2: /* fastcgi.debug */ 54 pconf->debug = (int)cpv->v.u; 55 break; 56 case 3: /* fastcgi.map-extensions */ 57 pconf->ext_mapping = cpv->v.a; 58 break; 59 default:/* should not happen */ 60 return; 61 } 62 } 63 64 static void mod_fastcgi_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { 65 do { 66 mod_fastcgi_merge_config_cpv(pconf, cpv); 67 } while ((++cpv)->k_id != -1); 68 } 69 70 static void mod_fastcgi_patch_config(request_st * const r, plugin_data * const p) { 71 memcpy(&p->conf, &p->defaults, sizeof(plugin_config)); 72 for (int i = 1, used = p->nconfig; i < used; ++i) { 73 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 74 mod_fastcgi_merge_config(&p->conf,p->cvlist + p->cvlist[i].v.u2[0]); 75 } 76 } 77 78 SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { 79 static const config_plugin_keys_t cpk[] = { 80 { CONST_STR_LEN("fastcgi.server"), 81 T_CONFIG_ARRAY_KVARRAY, 82 T_CONFIG_SCOPE_CONNECTION } 83 ,{ CONST_STR_LEN("fastcgi.balance"), 84 T_CONFIG_STRING, 85 T_CONFIG_SCOPE_CONNECTION } 86 ,{ CONST_STR_LEN("fastcgi.debug"), 87 T_CONFIG_INT, 88 T_CONFIG_SCOPE_CONNECTION } 89 ,{ CONST_STR_LEN("fastcgi.map-extensions"), 90 T_CONFIG_ARRAY_KVSTRING, 91 T_CONFIG_SCOPE_CONNECTION } 92 ,{ NULL, 0, 93 T_CONFIG_UNSET, 94 T_CONFIG_SCOPE_UNSET } 95 }; 96 97 plugin_data * const p = p_d; 98 if (!config_plugin_values_init(srv, p, cpk, "mod_fastcgi")) 99 return HANDLER_ERROR; 100 101 /* process and validate config directives 102 * (init i to 0 if global context; to 1 to skip empty global context) */ 103 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 104 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 105 for (; -1 != cpv->k_id; ++cpv) { 106 switch (cpv->k_id) { 107 case 0:{/* fastcgi.server */ 108 gw_plugin_config *gw = calloc(1, sizeof(gw_plugin_config)); 109 force_assert(gw); 110 if (!gw_set_defaults_backend(srv, p, cpv->v.a, gw, 0, 111 cpk[cpv->k_id].k)) { 112 gw_plugin_config_free(gw); 113 return HANDLER_ERROR; 114 } 115 cpv->v.v = gw; 116 cpv->vtype = T_CONFIG_LOCAL; 117 break; 118 } 119 case 1: /* fastcgi.balance */ 120 cpv->v.u = (unsigned int)gw_get_defaults_balance(srv, cpv->v.b); 121 break; 122 case 2: /* fastcgi.debug */ 123 case 3: /* fastcgi.map-extensions */ 124 break; 125 default:/* should not happen */ 126 break; 127 } 128 } 129 } 130 131 /* default is 0 */ 132 /*p->defaults.balance = (unsigned int)gw_get_defaults_balance(srv, NULL);*/ 133 134 /* initialize p->defaults from global config context */ 135 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 136 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 137 if (-1 != cpv->k_id) 138 mod_fastcgi_merge_config(&p->defaults, cpv); 139 } 140 141 return HANDLER_GO_ON; 142 } 143 144 145 static int fcgi_env_add(void *venv, const char *key, size_t key_len, const char *val, size_t val_len) { 146 buffer *env = venv; 147 size_t len; 148 char len_enc[8]; 149 size_t len_enc_len = 0; 150 char *dst; 151 152 if (!key || !val) return -1; 153 154 len = key_len + val_len; 155 156 len += key_len > 127 ? 4 : 1; 157 len += val_len > 127 ? 4 : 1; 158 159 if (buffer_string_length(env) + len >= FCGI_MAX_LENGTH + sizeof(FCGI_BeginRequestRecord) + sizeof(FCGI_Header)) { 160 /** 161 * we can't append more headers, ignore it 162 */ 163 return -1; 164 } 165 166 /** 167 * field length can be 31bit max 168 * 169 * HINT: this can't happen as FCGI_MAX_LENGTH is only 16bit 170 */ 171 force_assert(key_len < 0x7fffffffu); 172 force_assert(val_len < 0x7fffffffu); 173 174 if (buffer_string_space(env) < len) { 175 size_t extend = env->size * 2 - buffer_string_length(env); 176 extend = extend > len ? extend : len + 4095; 177 buffer_string_prepare_append(env, extend); 178 } 179 180 if (key_len > 127) { 181 len_enc[len_enc_len++] = ((key_len >> 24) & 0xff) | 0x80; 182 len_enc[len_enc_len++] = (key_len >> 16) & 0xff; 183 len_enc[len_enc_len++] = (key_len >> 8) & 0xff; 184 len_enc[len_enc_len++] = (key_len >> 0) & 0xff; 185 } else { 186 len_enc[len_enc_len++] = (key_len >> 0) & 0xff; 187 } 188 189 if (val_len > 127) { 190 len_enc[len_enc_len++] = ((val_len >> 24) & 0xff) | 0x80; 191 len_enc[len_enc_len++] = (val_len >> 16) & 0xff; 192 len_enc[len_enc_len++] = (val_len >> 8) & 0xff; 193 len_enc[len_enc_len++] = (val_len >> 0) & 0xff; 194 } else { 195 len_enc[len_enc_len++] = (val_len >> 0) & 0xff; 196 } 197 198 dst = buffer_string_prepare_append(env, len); 199 memcpy(dst, len_enc, len_enc_len); 200 memcpy(dst + len_enc_len, key, key_len); 201 memcpy(dst + len_enc_len + key_len, val, val_len); 202 buffer_commit(env, len); 203 204 return 0; 205 } 206 207 static void fcgi_header(FCGI_Header * header, unsigned char type, int request_id, int contentLength, unsigned char paddingLength) { 208 force_assert(contentLength <= FCGI_MAX_LENGTH); 209 210 header->version = FCGI_VERSION_1; 211 header->type = type; 212 header->requestIdB0 = request_id & 0xff; 213 header->requestIdB1 = (request_id >> 8) & 0xff; 214 header->contentLengthB0 = contentLength & 0xff; 215 header->contentLengthB1 = (contentLength >> 8) & 0xff; 216 header->paddingLength = paddingLength; 217 header->reserved = 0; 218 } 219 220 static handler_t fcgi_stdin_append(handler_ctx *hctx) { 221 FCGI_Header header; 222 chunkqueue * const req_cq = hctx->r->reqbody_queue; 223 off_t offset, weWant; 224 const off_t req_cqlen = chunkqueue_length(req_cq); 225 int request_id = hctx->request_id; 226 227 /* something to send ? */ 228 for (offset = 0; offset != req_cqlen; offset += weWant) { 229 weWant = req_cqlen - offset > FCGI_MAX_LENGTH ? FCGI_MAX_LENGTH : req_cqlen - offset; 230 231 if (-1 != hctx->wb_reqlen) { 232 if (hctx->wb_reqlen >= 0) { 233 hctx->wb_reqlen += sizeof(header); 234 } else { 235 hctx->wb_reqlen -= sizeof(header); 236 } 237 } 238 239 fcgi_header(&(header), FCGI_STDIN, request_id, weWant, 0); 240 (chunkqueue_is_empty(hctx->wb) || hctx->wb->first->type == MEM_CHUNK) /* else FILE_CHUNK for temp file */ 241 ? chunkqueue_append_mem(hctx->wb, (const char *)&header, sizeof(header)) 242 : chunkqueue_append_mem_min(hctx->wb, (const char *)&header, sizeof(header)); 243 chunkqueue_steal(hctx->wb, req_cq, weWant); 244 /*(hctx->wb_reqlen already includes reqbody_length)*/ 245 } 246 247 if (hctx->wb->bytes_in == hctx->wb_reqlen) { 248 /* terminate STDIN */ 249 /* (future: must defer ending FCGI_STDIN 250 * if might later upgrade protocols 251 * and then have more data to send) */ 252 fcgi_header(&(header), FCGI_STDIN, request_id, 0, 0); 253 chunkqueue_append_mem(hctx->wb, (const char *)&header, sizeof(header)); 254 hctx->wb_reqlen += (int)sizeof(header); 255 } 256 257 return HANDLER_GO_ON; 258 } 259 260 static handler_t fcgi_create_env(handler_ctx *hctx) { 261 FCGI_BeginRequestRecord beginRecord; 262 FCGI_Header header; 263 int request_id; 264 265 gw_host *host = hctx->host; 266 request_st * const r = hctx->r; 267 268 http_cgi_opts opts = { 269 (hctx->gw_mode == FCGI_AUTHORIZER), 270 host->break_scriptfilename_for_php, 271 host->docroot, 272 host->strip_request_uri 273 }; 274 275 size_t rsz = (size_t)(r->read_queue->bytes_out - hctx->wb->bytes_in); 276 buffer * const b = chunkqueue_prepend_buffer_open_sz(hctx->wb, rsz < 65536 ? rsz : r->rqst_header_len); 277 278 /* send FCGI_BEGIN_REQUEST */ 279 280 if (hctx->request_id == 0) { 281 hctx->request_id = 1; /* always use id 1 as we don't use multiplexing */ 282 } else { 283 log_error(r->conf.errh, __FILE__, __LINE__, 284 "fcgi-request is already in use: %d", hctx->request_id); 285 } 286 request_id = hctx->request_id; 287 288 fcgi_header(&(beginRecord.header), FCGI_BEGIN_REQUEST, request_id, sizeof(beginRecord.body), 0); 289 beginRecord.body.roleB0 = hctx->gw_mode; 290 beginRecord.body.roleB1 = 0; 291 beginRecord.body.flags = 0; 292 memset(beginRecord.body.reserved, 0, sizeof(beginRecord.body.reserved)); 293 294 buffer_copy_string_len(b, (const char *)&beginRecord, sizeof(beginRecord)); 295 fcgi_header(&header, FCGI_PARAMS, request_id, 0, 0); /*(set aside space to fill in later)*/ 296 buffer_append_string_len(b, (const char *)&header, sizeof(header)); 297 298 /* send FCGI_PARAMS */ 299 300 if (0 != http_cgi_headers(r, &opts, fcgi_env_add, b)) { 301 r->http_status = 400; 302 r->handler_module = NULL; 303 buffer_clear(b); 304 chunkqueue_remove_finished_chunks(hctx->wb); 305 return HANDLER_FINISHED; 306 } else { 307 fcgi_header(&(header), FCGI_PARAMS, request_id, 308 buffer_string_length(b) - sizeof(FCGI_BeginRequestRecord) - sizeof(FCGI_Header), 0); 309 memcpy(b->ptr+sizeof(FCGI_BeginRequestRecord), (const char *)&header, sizeof(header)); 310 311 fcgi_header(&(header), FCGI_PARAMS, request_id, 0, 0); 312 buffer_append_string_len(b, (const char *)&header, sizeof(header)); 313 314 hctx->wb_reqlen = buffer_string_length(b); 315 chunkqueue_prepend_buffer_commit(hctx->wb); 316 } 317 318 if (r->reqbody_length) { 319 /*chunkqueue_append_chunkqueue(hctx->wb, r->reqbody_queue);*/ 320 if (r->reqbody_length > 0) 321 hctx->wb_reqlen += r->reqbody_length;/* (eventual) (minimal) total request size, not necessarily including all fcgi_headers around content length yet */ 322 else /* as-yet-unknown total request size (Transfer-Encoding: chunked)*/ 323 hctx->wb_reqlen = -hctx->wb_reqlen; 324 } 325 fcgi_stdin_append(hctx); 326 327 status_counter_inc(CONST_STR_LEN("fastcgi.requests")); 328 return HANDLER_GO_ON; 329 } 330 331 typedef struct { 332 unsigned int len; 333 int type; 334 int padding; 335 int request_id; 336 } fastcgi_response_packet; 337 338 static int fastcgi_get_packet(handler_ctx *hctx, fastcgi_response_packet *packet) { 339 FCGI_Header header; 340 size_t toread = sizeof(FCGI_Header), flen = 0; 341 off_t rblen = chunkqueue_length(hctx->rb); 342 if (rblen < (off_t)sizeof(FCGI_Header)) { 343 /* no header */ 344 if (hctx->conf.debug && 0 != rblen) { 345 log_error(hctx->r->conf.errh, __FILE__, __LINE__, 346 "FastCGI: header too small: %lld bytes < %zu bytes, " 347 "waiting for more data", (long long)rblen, sizeof(FCGI_Header)); 348 } 349 return -1; 350 } 351 #ifdef __clang_analyzer__ 352 /*(unnecessary (length checked above); init to quiet scan-build)*/ 353 memset(&header, 0, sizeof(FCGI_Header)); 354 #endif 355 356 /* get at least the FastCGI header */ 357 for (chunk *c = hctx->rb->first; c; c = c->next) { 358 size_t weHave = buffer_string_length(c->mem) - c->offset; 359 if (weHave >= toread) { 360 memcpy((char *)&header + flen, c->mem->ptr + c->offset, toread); 361 break; 362 } 363 364 memcpy((char *)&header + flen, c->mem->ptr + c->offset, weHave); 365 flen += weHave; 366 toread -= weHave; 367 } 368 369 /* we have at least a header, now check how much we have to fetch */ 370 packet->len = (header.contentLengthB0 | (header.contentLengthB1 << 8)) + header.paddingLength; 371 packet->request_id = (header.requestIdB0 | (header.requestIdB1 << 8)); 372 packet->type = header.type; 373 packet->padding = header.paddingLength; 374 375 if (packet->len > (unsigned int)rblen-sizeof(FCGI_Header)) { 376 return -1; /* we didn't get the full packet */ 377 } 378 379 chunkqueue_mark_written(hctx->rb, sizeof(FCGI_Header)); 380 return 0; 381 } 382 383 static void fastcgi_get_packet_body(buffer *b, handler_ctx *hctx, fastcgi_response_packet *packet) { 384 /* copy content; hctx->rb must contain at least packet->len content */ 385 size_t toread = packet->len - packet->padding; 386 buffer_string_prepare_append(b, toread); 387 for (chunk *c = hctx->rb->first; c; c = c->next) { 388 size_t weHave = buffer_string_length(c->mem) - c->offset; 389 if (weHave >= toread) { 390 buffer_append_string_len(b, c->mem->ptr + c->offset, toread); 391 break; 392 } 393 394 buffer_append_string_len(b, c->mem->ptr + c->offset, weHave); 395 toread -= weHave; 396 } 397 chunkqueue_mark_written(hctx->rb, packet->len); 398 } 399 400 static int 401 mod_fastcgi_chunk_decode_transfer_cqlen (request_st * const r, chunkqueue * const src, const unsigned int len) 402 { 403 if (!r->resp_decode_chunked) 404 return http_chunk_transfer_cqlen(r, src, len); 405 406 if (0 == len) return 0; 407 408 /* specialized for mod_fastcgi to decode chunked encoding; 409 * FastCGI packet data is all type MEM_CHUNK 410 * entire src cq is processed, minus packet.padding at end 411 * (This extra work can be avoided if FastCGI backend does not send 412 * Transfer-Encoding: chunked, which FastCGI is not supposed to do) */ 413 uint32_t remain = len, wr; 414 for (const chunk *c = src->first; c && remain; c = c->next, remain -= wr) { 415 /*assert(c->type == MEM_CHUNK);*/ 416 wr = buffer_string_length(c->mem) - c->offset; 417 if (wr > remain) wr = remain; 418 if (0 != http_chunk_decode_append_mem(r, c->mem->ptr+c->offset, wr)) 419 return -1; 420 } 421 chunkqueue_mark_written(src, len); 422 return 0; 423 } 424 425 static handler_t fcgi_recv_parse(request_st * const r, struct http_response_opts_t *opts, buffer *b, size_t n) { 426 handler_ctx *hctx = (handler_ctx *)opts->pdata; 427 int fin = 0; 428 429 if (0 == n) { 430 if (-1 == hctx->request_id) return HANDLER_FINISHED; /*(flag request ended)*/ 431 if (!(fdevent_fdnode_interest(hctx->fdn) & FDEVENT_IN) 432 && !(r->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_POLLRDHUP)) 433 return HANDLER_GO_ON; 434 log_error(r->conf.errh, __FILE__, __LINE__, 435 "unexpected end-of-file (perhaps the fastcgi process died):" 436 "pid: %d socket: %s", 437 hctx->proc->pid, hctx->proc->connection_name->ptr); 438 439 return HANDLER_ERROR; 440 } 441 442 chunkqueue_append_buffer(hctx->rb, b); 443 444 /* 445 * parse the fastcgi packets and forward the content to the write-queue 446 * 447 */ 448 while (fin == 0) { 449 fastcgi_response_packet packet; 450 451 /* check if we have at least one packet */ 452 if (0 != fastcgi_get_packet(hctx, &packet)) { 453 /* no full packet */ 454 break; 455 } 456 457 switch(packet.type) { 458 case FCGI_STDOUT: 459 if (packet.len == 0) break; 460 461 /* is the header already finished */ 462 if (0 == r->resp_body_started) { 463 /* split header from body */ 464 buffer *hdrs = hctx->response; 465 if (NULL == hdrs) { 466 hdrs = r->tmp_buf; 467 buffer_clear(hdrs); 468 } 469 fastcgi_get_packet_body(hdrs, hctx, &packet); 470 if (HANDLER_GO_ON != http_response_parse_headers(r, &hctx->opts, hdrs)) { 471 hctx->send_content_body = 0; 472 fin = 1; 473 break; 474 } 475 if (0 == r->resp_body_started) { 476 if (!hctx->response) { 477 hctx->response = chunk_buffer_acquire(); 478 buffer_copy_buffer(hctx->response, hdrs); 479 } 480 } 481 else if (hctx->gw_mode == GW_AUTHORIZER && 482 (r->http_status == 0 || r->http_status == 200)) { 483 /* authorizer approved request; ignore the content here */ 484 hctx->send_content_body = 0; 485 } 486 } else if (hctx->send_content_body) { 487 if (0 != mod_fastcgi_chunk_decode_transfer_cqlen(r, hctx->rb, packet.len - packet.padding)) { 488 /* error writing to tempfile; 489 * truncate response or send 500 if nothing sent yet */ 490 fin = 1; 491 } 492 if (packet.padding) chunkqueue_mark_written(hctx->rb, packet.padding); 493 } else { 494 chunkqueue_mark_written(hctx->rb, packet.len); 495 } 496 break; 497 case FCGI_STDERR: 498 if (packet.len) { 499 buffer * const tb = r->tmp_buf; 500 buffer_clear(tb); 501 fastcgi_get_packet_body(tb, hctx, &packet); 502 log_error_multiline_buffer(r->conf.errh, __FILE__, __LINE__, tb, 503 "FastCGI-stderr:"); 504 } 505 break; 506 case FCGI_END_REQUEST: 507 hctx->request_id = -1; /*(flag request ended)*/ 508 fin = 1; 509 break; 510 default: 511 log_error(r->conf.errh, __FILE__, __LINE__, 512 "FastCGI: header.type not handled: %d", packet.type); 513 chunkqueue_mark_written(hctx->rb, packet.len); 514 break; 515 } 516 } 517 518 return 0 == fin ? HANDLER_GO_ON : HANDLER_FINISHED; 519 } 520 521 static handler_t fcgi_check_extension(request_st * const r, void *p_d, int uri_path_handler) { 522 plugin_data *p = p_d; 523 handler_t rc; 524 525 if (NULL != r->handler_module) return HANDLER_GO_ON; 526 527 mod_fastcgi_patch_config(r, p); 528 if (NULL == p->conf.exts) return HANDLER_GO_ON; 529 530 rc = gw_check_extension(r, p, uri_path_handler, 0); 531 if (HANDLER_GO_ON != rc) return rc; 532 533 if (r->handler_module == p->self) { 534 handler_ctx *hctx = r->plugin_ctx[p->id]; 535 hctx->opts.backend = BACKEND_FASTCGI; 536 hctx->opts.parse = fcgi_recv_parse; 537 hctx->opts.pdata = hctx; 538 hctx->stdin_append = fcgi_stdin_append; 539 hctx->create_env = fcgi_create_env; 540 if (!hctx->rb) { 541 hctx->rb = chunkqueue_init(); 542 } 543 else { 544 chunkqueue_reset(hctx->rb); 545 } 546 } 547 548 return HANDLER_GO_ON; 549 } 550 551 /* uri-path handler */ 552 static handler_t fcgi_check_extension_1(request_st * const r, void *p_d) { 553 return fcgi_check_extension(r, p_d, 1); 554 } 555 556 /* start request handler */ 557 static handler_t fcgi_check_extension_2(request_st * const r, void *p_d) { 558 return fcgi_check_extension(r, p_d, 0); 559 } 560 561 562 int mod_fastcgi_plugin_init(plugin *p); 563 int mod_fastcgi_plugin_init(plugin *p) { 564 p->version = LIGHTTPD_VERSION_ID; 565 p->name = "fastcgi"; 566 567 p->init = gw_init; 568 p->cleanup = gw_free; 569 p->set_defaults = mod_fastcgi_set_defaults; 570 p->handle_request_reset = gw_handle_request_reset; 571 p->handle_uri_clean = fcgi_check_extension_1; 572 p->handle_subrequest_start = fcgi_check_extension_2; 573 p->handle_subrequest = gw_handle_subrequest; 574 p->handle_trigger = gw_handle_trigger; 575 p->handle_waitpid = gw_handle_waitpid_cb; 576 577 return 0; 578 } 579