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