/* * Fetch response from backend and pass back to the front */ static int pass_response(request_rec *r, proxy_conn_rec *conn) { apr_bucket_brigade *bb; apr_bucket *b; const char *location; scgi_config *conf; socket_ex_data *sock_data; int status; sock_data = apr_palloc(r->pool, sizeof(*sock_data)); sock_data->sock = conn->sock; sock_data->counter = &conn->worker->s->read; bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); b = bucket_socket_ex_create(sock_data, r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b); b = apr_bucket_eos_create(r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b); status = ap_scan_script_header_err_brigade_ex(r, bb, NULL, APLOG_MODULE_INDEX); if (status != OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00860) "error reading response headers from %s:%u", conn->hostname, conn->port); r->status_line = NULL; apr_brigade_destroy(bb); return status; } conf = ap_get_module_config(r->per_dir_config, &proxy_scgi_module); if (conf->sendfile && conf->sendfile != scgi_sendfile_off) { short err = 1; location = apr_table_get(r->err_headers_out, conf->sendfile); if (!location) { err = 0; location = apr_table_get(r->headers_out, conf->sendfile); } if (location) { scgi_request_config *req_conf = apr_palloc(r->pool, sizeof(*req_conf)); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00861) "Found %s: %s - preparing subrequest.", conf->sendfile, location); if (err) { apr_table_unset(r->err_headers_out, conf->sendfile); } else { apr_table_unset(r->headers_out, conf->sendfile); } req_conf->location = location; req_conf->type = scgi_sendfile; ap_set_module_config(r->request_config, &proxy_scgi_module, req_conf); apr_brigade_destroy(bb); return OK; } } if (r->status == HTTP_OK && (!conf->internal_redirect /* default === On */ || conf->internal_redirect != scgi_internal_redirect_off)) { short err = 1; const char *location_header = conf->internal_redirect ? conf->internal_redirect : scgi_internal_redirect_on; location = apr_table_get(r->err_headers_out, location_header); if (!location) { err = 0; location = apr_table_get(r->headers_out, location_header); } if (location && *location == '/') { scgi_request_config *req_conf = apr_palloc(r->pool, sizeof(*req_conf)); if (strcasecmp(location_header, "Location")) { if (err) { apr_table_unset(r->err_headers_out, location_header); } else { apr_table_unset(r->headers_out, location_header); } } req_conf->location = location; req_conf->type = scgi_internal_redirect; ap_set_module_config(r->request_config, &proxy_scgi_module, req_conf); apr_brigade_destroy(bb); return OK; } } if (ap_pass_brigade(r->output_filters, bb)) { return AP_FILTER_ERROR; } return OK; }
static int cgi_handler(request_rec *r) { int nph; apr_size_t dbpos = 0; const char *argv0; const char *command; const char **argv; char *dbuf = NULL; apr_file_t *script_out = NULL, *script_in = NULL, *script_err = NULL; apr_bucket_brigade *bb; apr_bucket *b; int is_included; int seen_eos, child_stopped_reading; apr_pool_t *p; cgi_server_conf *conf; apr_status_t rv; cgi_exec_info_t e_info; conn_rec *c; if (strcmp(r->handler, CGI_MAGIC_TYPE) && strcmp(r->handler, "cgi-script")) { return DECLINED; } c = r->connection; is_included = !strcmp(r->protocol, "INCLUDED"); p = r->main ? r->main->pool : r->pool; argv0 = apr_filepath_name_get(r->filename); nph = !(strncmp(argv0, "nph-", 4)); conf = ap_get_module_config(r->server->module_config, &cgi_module); if (!(ap_allow_options(r) & OPT_EXECCGI) && !is_scriptaliased(r)) return log_scripterror(r, conf, HTTP_FORBIDDEN, 0, "Options ExecCGI is off in this directory"); if (nph && is_included) return log_scripterror(r, conf, HTTP_FORBIDDEN, 0, "attempt to include NPH CGI script"); if (r->finfo.filetype == APR_NOFILE) return log_scripterror(r, conf, HTTP_NOT_FOUND, 0, "script not found or unable to stat"); if (r->finfo.filetype == APR_DIR) return log_scripterror(r, conf, HTTP_FORBIDDEN, 0, "attempt to invoke directory as script"); if ((r->used_path_info == AP_REQ_REJECT_PATH_INFO) && r->path_info && *r->path_info) { /* default to accept */ return log_scripterror(r, conf, HTTP_NOT_FOUND, 0, "AcceptPathInfo off disallows user's path"); } /* if (!ap_suexec_enabled) { if (!ap_can_exec(&r->finfo)) return log_scripterror(r, conf, HTTP_FORBIDDEN, 0, "file permissions deny server execution"); } */ ap_add_common_vars(r); ap_add_cgi_vars(r); e_info.process_cgi = 1; e_info.cmd_type = APR_PROGRAM; e_info.detached = 0; e_info.in_pipe = APR_CHILD_BLOCK; e_info.out_pipe = APR_CHILD_BLOCK; e_info.err_pipe = APR_CHILD_BLOCK; e_info.prog_type = RUN_AS_CGI; e_info.bb = NULL; e_info.ctx = NULL; e_info.next = NULL; e_info.addrspace = 0; /* build the command line */ if ((rv = cgi_build_command(&command, &argv, r, p, &e_info)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01222) "don't know how to spawn child process: %s", r->filename); return HTTP_INTERNAL_SERVER_ERROR; } /* run the script in its own process */ if ((rv = run_cgi_child(&script_out, &script_in, &script_err, command, argv, r, p, &e_info)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01223) "couldn't spawn child process: %s", r->filename); return HTTP_INTERNAL_SERVER_ERROR; } /* Transfer any put/post args, CERN style... * Note that we already ignore SIGPIPE in the core server. */ bb = apr_brigade_create(r->pool, c->bucket_alloc); seen_eos = 0; child_stopped_reading = 0; if (conf->logname) { dbuf = apr_palloc(r->pool, conf->bufbytes + 1); dbpos = 0; } do { apr_bucket *bucket; rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES, APR_BLOCK_READ, HUGE_STRING_LEN); if (rv != APR_SUCCESS) { if (APR_STATUS_IS_TIMEUP(rv)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01224) "Timeout during reading request entity data"); return HTTP_REQUEST_TIME_OUT; } ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01225) "Error reading request entity data"); return HTTP_INTERNAL_SERVER_ERROR; } for (bucket = APR_BRIGADE_FIRST(bb); bucket != APR_BRIGADE_SENTINEL(bb); bucket = APR_BUCKET_NEXT(bucket)) { const char *data; apr_size_t len; if (APR_BUCKET_IS_EOS(bucket)) { seen_eos = 1; break; } /* We can't do much with this. */ if (APR_BUCKET_IS_FLUSH(bucket)) { continue; } /* If the child stopped, we still must read to EOS. */ if (child_stopped_reading) { continue; } /* read */ apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ); if (conf->logname && dbpos < conf->bufbytes) { int cursize; if ((dbpos + len) > conf->bufbytes) { cursize = conf->bufbytes - dbpos; } else { cursize = len; } memcpy(dbuf + dbpos, data, cursize); dbpos += cursize; } /* Keep writing data to the child until done or too much time * elapses with no progress or an error occurs. */ rv = apr_file_write_full(script_out, data, len, NULL); if (rv != APR_SUCCESS) { /* silly script stopped reading, soak up remaining message */ child_stopped_reading = 1; } } apr_brigade_cleanup(bb); } while (!seen_eos); if (conf->logname) { dbuf[dbpos] = '\0'; } /* Is this flush really needed? */ apr_file_flush(script_out); apr_file_close(script_out); AP_DEBUG_ASSERT(script_in != NULL); apr_brigade_cleanup(bb); #if APR_FILES_AS_SOCKETS apr_file_pipe_timeout_set(script_in, 0); apr_file_pipe_timeout_set(script_err, 0); b = cgi_bucket_create(r, script_in, script_err, c->bucket_alloc); if (b == NULL) return HTTP_INTERNAL_SERVER_ERROR; #else b = apr_bucket_pipe_create(script_in, c->bucket_alloc); #endif APR_BRIGADE_INSERT_TAIL(bb, b); b = apr_bucket_eos_create(c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b); /* Handle script return... */ if (!nph) { const char *location; char sbuf[MAX_STRING_LEN]; int ret; if ((ret = ap_scan_script_header_err_brigade_ex(r, bb, sbuf, APLOG_MODULE_INDEX))) { ret = log_script(r, conf, ret, dbuf, sbuf, bb, script_err); /* * ret could be HTTP_NOT_MODIFIED in the case that the CGI script * does not set an explicit status and ap_meets_conditions, which * is called by ap_scan_script_header_err_brigade, detects that * the conditions of the requests are met and the response is * not modified. * In this case set r->status and return OK in order to prevent * running through the error processing stack as this would * break with mod_cache, if the conditions had been set by * mod_cache itself to validate a stale entity. * BTW: We circumvent the error processing stack anyway if the * CGI script set an explicit status code (whatever it is) and * the only possible values for ret here are: * * HTTP_NOT_MODIFIED (set by ap_meets_conditions) * HTTP_PRECONDITION_FAILED (set by ap_meets_conditions) * HTTP_INTERNAL_SERVER_ERROR (if something went wrong during the * processing of the response of the CGI script, e.g broken headers * or a crashed CGI process). */ if (ret == HTTP_NOT_MODIFIED) { r->status = ret; return OK; } return ret; } location = apr_table_get(r->headers_out, "Location"); if (location && r->status == 200) { /* For a redirect whether internal or not, discard any * remaining stdout from the script, and log any remaining * stderr output, as normal. */ discard_script_output(bb); apr_brigade_destroy(bb); apr_file_pipe_timeout_set(script_err, r->server->timeout); log_script_err(r, script_err); } if (location && location[0] == '/' && r->status == 200) { /* This redirect needs to be a GET no matter what the original * method was. */ r->method = "GET"; r->method_number = M_GET; /* We already read the message body (if any), so don't allow * the redirected request to think it has one. We can ignore * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR. */ apr_table_unset(r->headers_in, "Content-Length"); ap_internal_redirect_handler(location, r); return OK; } else if (location && r->status == 200) { /* XXX: Note that if a script wants to produce its own Redirect * body, it now has to explicitly *say* "Status: 302" */ return HTTP_MOVED_TEMPORARILY; } rv = ap_pass_brigade(r->output_filters, bb); } else /* nph */ { struct ap_filter_t *cur; /* get rid of all filters up through protocol... since we * haven't parsed off the headers, there is no way they can * work */ cur = r->proto_output_filters; while (cur && cur->frec->ftype < AP_FTYPE_CONNECTION) { cur = cur->next; } r->output_filters = r->proto_output_filters = cur; rv = ap_pass_brigade(r->output_filters, bb); } /* don't soak up script output if errors occurred writing it * out... otherwise, we prolong the life of the script when the * connection drops or we stopped sending output for some other * reason */ if (rv == APR_SUCCESS && !r->connection->aborted) { apr_file_pipe_timeout_set(script_err, r->server->timeout); log_script_err(r, script_err); } apr_file_close(script_err); return OK; /* NOT r->status, even if it has changed. */ }
static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf, request_rec *r, apr_pool_t *setaside_pool, apr_uint16_t request_id, const char **err, int *bad_request, int *has_responded) { apr_bucket_brigade *ib, *ob; int seen_end_of_headers = 0, done = 0, ignore_body = 0; apr_status_t rv = APR_SUCCESS; int script_error_status = HTTP_OK; conn_rec *c = r->connection; struct iovec vec[2]; ap_fcgi_header header; unsigned char farray[AP_FCGI_HEADER_LEN]; apr_pollfd_t pfd; int header_state = HDR_STATE_READING_HEADERS; char stack_iobuf[AP_IOBUFSIZE]; apr_size_t iobuf_size = AP_IOBUFSIZE; char *iobuf = stack_iobuf; *err = NULL; if (conn->worker->s->io_buffer_size_set) { iobuf_size = conn->worker->s->io_buffer_size; iobuf = apr_palloc(r->pool, iobuf_size); } pfd.desc_type = APR_POLL_SOCKET; pfd.desc.s = conn->sock; pfd.p = r->pool; pfd.reqevents = APR_POLLIN | APR_POLLOUT; ib = apr_brigade_create(r->pool, c->bucket_alloc); ob = apr_brigade_create(r->pool, c->bucket_alloc); while (! done) { apr_interval_time_t timeout; apr_size_t len; int n; /* We need SOME kind of timeout here, or virtually anything will * cause timeout errors. */ apr_socket_timeout_get(conn->sock, &timeout); rv = apr_poll(&pfd, 1, &n, timeout); if (rv != APR_SUCCESS) { if (APR_STATUS_IS_EINTR(rv)) { continue; } *err = "polling"; break; } if (pfd.rtnevents & APR_POLLOUT) { apr_size_t to_send, writebuflen; int last_stdin = 0; char *iobuf_cursor; rv = ap_get_brigade(r->input_filters, ib, AP_MODE_READBYTES, APR_BLOCK_READ, iobuf_size); if (rv != APR_SUCCESS) { *err = "reading input brigade"; *bad_request = 1; break; } if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(ib))) { last_stdin = 1; } writebuflen = iobuf_size; rv = apr_brigade_flatten(ib, iobuf, &writebuflen); apr_brigade_cleanup(ib); if (rv != APR_SUCCESS) { *err = "flattening brigade"; break; } to_send = writebuflen; iobuf_cursor = iobuf; while (to_send > 0) { int nvec = 0; apr_size_t write_this_time; write_this_time = to_send < AP_FCGI_MAX_CONTENT_LEN ? to_send : AP_FCGI_MAX_CONTENT_LEN; ap_fcgi_fill_in_header(&header, AP_FCGI_STDIN, request_id, (apr_uint16_t)write_this_time, 0); ap_fcgi_header_to_array(&header, farray); vec[nvec].iov_base = (void *)farray; vec[nvec].iov_len = sizeof(farray); ++nvec; if (writebuflen) { vec[nvec].iov_base = iobuf_cursor; vec[nvec].iov_len = write_this_time; ++nvec; } rv = send_data(conn, vec, nvec, &len); if (rv != APR_SUCCESS) { *err = "sending stdin"; break; } to_send -= write_this_time; iobuf_cursor += write_this_time; } if (rv != APR_SUCCESS) { break; } if (last_stdin) { pfd.reqevents = APR_POLLIN; /* Done with input data */ /* signal EOF (empty FCGI_STDIN) */ ap_fcgi_fill_in_header(&header, AP_FCGI_STDIN, request_id, 0, 0); ap_fcgi_header_to_array(&header, farray); vec[0].iov_base = (void *)farray; vec[0].iov_len = sizeof(farray); rv = send_data(conn, vec, 1, &len); if (rv != APR_SUCCESS) { *err = "sending empty stdin"; break; } } } if (pfd.rtnevents & APR_POLLIN) { apr_size_t readbuflen; apr_uint16_t clen, rid; apr_bucket *b; unsigned char plen; unsigned char type, version; /* First, we grab the header... */ rv = get_data_full(conn, (char *) farray, AP_FCGI_HEADER_LEN); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01067) "Failed to read FastCGI header"); break; } ap_log_rdata(APLOG_MARK, APLOG_TRACE8, r, "FastCGI header", farray, AP_FCGI_HEADER_LEN, 0); ap_fcgi_header_fields_from_array(&version, &type, &rid, &clen, &plen, farray); if (version != AP_FCGI_VERSION_1) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01068) "Got bogus version %d", (int)version); rv = APR_EINVAL; break; } if (rid != request_id) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01069) "Got bogus rid %d, expected %d", rid, request_id); rv = APR_EINVAL; break; } recv_again: if (clen > iobuf_size) { readbuflen = iobuf_size; } else { readbuflen = clen; } /* Now get the actual data. Yes it sucks to do this in a second * recv call, this will eventually change when we move to real * nonblocking recv calls. */ if (readbuflen != 0) { rv = get_data(conn, iobuf, &readbuflen); if (rv != APR_SUCCESS) { *err = "reading response body"; break; } } switch (type) { case AP_FCGI_STDOUT: if (clen != 0) { b = apr_bucket_transient_create(iobuf, readbuflen, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(ob, b); if (! seen_end_of_headers) { int st = handle_headers(r, &header_state, iobuf, readbuflen); if (st == 1) { int status; seen_end_of_headers = 1; status = ap_scan_script_header_err_brigade_ex(r, ob, NULL, APLOG_MODULE_INDEX); /* suck in all the rest */ if (status != OK) { apr_bucket *tmp_b; apr_brigade_cleanup(ob); tmp_b = apr_bucket_eos_create(c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(ob, tmp_b); *has_responded = 1; r->status = status; rv = ap_pass_brigade(r->output_filters, ob); if (rv != APR_SUCCESS) { *err = "passing headers brigade to output filters"; } else if (status == HTTP_NOT_MODIFIED) { /* The 304 response MUST NOT contain * a message-body, ignore it. */ ignore_body = 1; } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01070) "Error parsing script headers"); rv = APR_EINVAL; } break; } if (conf->error_override && ap_is_HTTP_ERROR(r->status)) { /* * set script_error_status to discard * everything after the headers */ script_error_status = r->status; /* * prevent ap_die() from treating this as a * recursive error, initially: */ r->status = HTTP_OK; } if (script_error_status == HTTP_OK && !APR_BRIGADE_EMPTY(ob) && !ignore_body) { /* Send the part of the body that we read while * reading the headers. */ *has_responded = 1; rv = ap_pass_brigade(r->output_filters, ob); if (rv != APR_SUCCESS) { *err = "passing brigade to output filters"; break; } } apr_brigade_cleanup(ob); apr_pool_clear(setaside_pool); } else { /* We're still looking for the end of the * headers, so this part of the data will need * to persist. */ apr_bucket_setaside(b, setaside_pool); } } else { /* we've already passed along the headers, so now pass * through the content. we could simply continue to * setaside the content and not pass until we see the * 0 content-length (below, where we append the EOS), * but that could be a huge amount of data; so we pass * along smaller chunks */ if (script_error_status == HTTP_OK && !ignore_body) { *has_responded = 1; rv = ap_pass_brigade(r->output_filters, ob); if (rv != APR_SUCCESS) { *err = "passing brigade to output filters"; break; } } apr_brigade_cleanup(ob); } /* If we didn't read all the data, go back and get the * rest of it. */ if (clen > readbuflen) { clen -= readbuflen; goto recv_again; } } else { /* XXX what if we haven't seen end of the headers yet? */ if (script_error_status == HTTP_OK) { b = apr_bucket_eos_create(c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(ob, b); *has_responded = 1; rv = ap_pass_brigade(r->output_filters, ob); if (rv != APR_SUCCESS) { *err = "passing brigade to output filters"; break; } } /* XXX Why don't we cleanup here? (logic from AJP) */ } break; case AP_FCGI_STDERR: /* TODO: Should probably clean up this logging a bit... */ if (clen) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01071) "Got error '%.*s'", (int)readbuflen, iobuf); } if (clen > readbuflen) { clen -= readbuflen; goto recv_again; } break; case AP_FCGI_END_REQUEST: done = 1; break; default: ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01072) "Got bogus record %d", type); break; } /* Leave on above switch's inner error. */ if (rv != APR_SUCCESS) { break; } if (plen) { rv = get_data_full(conn, iobuf, plen); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02537) "Error occurred reading padding"); break; } } } } apr_brigade_destroy(ib); apr_brigade_destroy(ob); if (script_error_status != HTTP_OK) { ap_die(script_error_status, r); /* send ErrorDocument */ *has_responded = 1; } return rv; }