diff options
Diffstat (limited to 'modules/generators/mod_cgi.c')
-rw-r--r-- | modules/generators/mod_cgi.c | 586 |
1 files changed, 24 insertions, 562 deletions
diff --git a/modules/generators/mod_cgi.c b/modules/generators/mod_cgi.c index 1f77786..3799b06 100644 --- a/modules/generators/mod_cgi.c +++ b/modules/generators/mod_cgi.c @@ -48,7 +48,6 @@ #include "http_protocol.h" #include "http_main.h" #include "http_log.h" -#include "util_script.h" #include "ap_mpm.h" #include "mod_core.h" #include "mod_cgi.h" @@ -61,9 +60,6 @@ module AP_MODULE_DECLARE_DATA cgi_module; -static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *cgi_pfn_reg_with_ssi; -static APR_OPTIONAL_FN_TYPE(ap_ssi_get_tag_and_value) *cgi_pfn_gtv; -static APR_OPTIONAL_FN_TYPE(ap_ssi_parse_string) *cgi_pfn_ps; static APR_OPTIONAL_FN_TYPE(ap_cgi_build_command) *cgi_build_command; /* Read and discard the data in the brigade produced by a CGI script */ @@ -96,6 +92,11 @@ typedef struct { apr_interval_time_t timeout; } cgi_dirconf; +#if APR_FILES_AS_SOCKETS +#define WANT_CGI_BUCKET +#endif +#include "cgi_common.h" + static void *create_cgi_config(apr_pool_t *p, server_rec *s) { cgi_server_conf *c = @@ -185,64 +186,6 @@ AP_INIT_TAKE1("CGIScriptTimeout", set_script_timeout, NULL, RSRC_CONF | ACCESS_C {NULL} }; -static int log_scripterror(request_rec *r, cgi_server_conf * conf, int ret, - apr_status_t rv, char *logno, char *error) -{ - apr_file_t *f = NULL; - apr_finfo_t finfo; - char time_str[APR_CTIME_LEN]; - int log_flags = rv ? APLOG_ERR : APLOG_ERR; - - /* Intentional no APLOGNO */ - /* Callee provides APLOGNO in error text */ - ap_log_rerror(APLOG_MARK, log_flags, rv, r, - "%s%s: %s", logno ? logno : "", error, r->filename); - - /* XXX Very expensive mainline case! Open, then getfileinfo! */ - if (!conf->logname || - ((apr_stat(&finfo, conf->logname, - APR_FINFO_SIZE, r->pool) == APR_SUCCESS) && - (finfo.size > conf->logbytes)) || - (apr_file_open(&f, conf->logname, - APR_APPEND|APR_WRITE|APR_CREATE, APR_OS_DEFAULT, - r->pool) != APR_SUCCESS)) { - return ret; - } - - /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */ - apr_ctime(time_str, apr_time_now()); - apr_file_printf(f, "%%%% [%s] %s %s%s%s %s\n", time_str, r->method, r->uri, - r->args ? "?" : "", r->args ? r->args : "", r->protocol); - /* "%% 500 /usr/local/apache/cgi-bin */ - apr_file_printf(f, "%%%% %d %s\n", ret, r->filename); - - apr_file_printf(f, "%%error\n%s\n", error); - - apr_file_close(f); - return ret; -} - -/* Soak up stderr from a script and redirect it to the error log. - */ -static apr_status_t log_script_err(request_rec *r, apr_file_t *script_err) -{ - char argsbuffer[HUGE_STRING_LEN]; - char *newline; - apr_status_t rv; - cgi_server_conf *conf = ap_get_module_config(r->server->module_config, &cgi_module); - - while ((rv = apr_file_gets(argsbuffer, HUGE_STRING_LEN, - script_err)) == APR_SUCCESS) { - newline = strchr(argsbuffer, '\n'); - if (newline) { - *newline = '\0'; - } - log_scripterror(r, conf, r->status, 0, APLOGNO(01215), argsbuffer); - } - - return rv; -} - static int log_script(request_rec *r, cgi_server_conf * conf, int ret, char *dbuf, const char *sbuf, apr_bucket_brigade *bb, apr_file_t *script_err) @@ -563,230 +506,23 @@ static apr_status_t default_build_command(const char **cmd, const char ***argv, return APR_SUCCESS; } -static void discard_script_output(apr_bucket_brigade *bb) -{ - apr_bucket *e; - const char *buf; - apr_size_t len; - - for (e = APR_BRIGADE_FIRST(bb); - e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e); - e = APR_BRIGADE_FIRST(bb)) - { - if (apr_bucket_read(e, &buf, &len, APR_BLOCK_READ)) { - break; - } - apr_bucket_delete(e); - } -} - -#if APR_FILES_AS_SOCKETS - -/* A CGI bucket type is needed to catch any output to stderr from the - * script; see PR 22030. */ -static const apr_bucket_type_t bucket_type_cgi; - -struct cgi_bucket_data { - apr_pollset_t *pollset; - request_rec *r; -}; - -/* Create a CGI bucket using pipes from script stdout 'out' - * and stderr 'err', for request 'r'. */ -static apr_bucket *cgi_bucket_create(request_rec *r, - apr_file_t *out, apr_file_t *err, - apr_bucket_alloc_t *list) -{ - apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); - apr_status_t rv; - apr_pollfd_t fd; - struct cgi_bucket_data *data = apr_palloc(r->pool, sizeof *data); - - APR_BUCKET_INIT(b); - b->free = apr_bucket_free; - b->list = list; - b->type = &bucket_type_cgi; - b->length = (apr_size_t)(-1); - b->start = -1; - - /* Create the pollset */ - rv = apr_pollset_create(&data->pollset, 2, r->pool, 0); - if (rv != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01217) - "apr_pollset_create(); check system or user limits"); - return NULL; - } - - fd.desc_type = APR_POLL_FILE; - fd.reqevents = APR_POLLIN; - fd.p = r->pool; - fd.desc.f = out; /* script's stdout */ - fd.client_data = (void *)1; - rv = apr_pollset_add(data->pollset, &fd); - if (rv != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01218) - "apr_pollset_add(); check system or user limits"); - return NULL; - } - - fd.desc.f = err; /* script's stderr */ - fd.client_data = (void *)2; - rv = apr_pollset_add(data->pollset, &fd); - if (rv != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01219) - "apr_pollset_add(); check system or user limits"); - return NULL; - } - - data->r = r; - b->data = data; - return b; -} - -/* Create a duplicate CGI bucket using given bucket data */ -static apr_bucket *cgi_bucket_dup(struct cgi_bucket_data *data, - apr_bucket_alloc_t *list) -{ - apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); - APR_BUCKET_INIT(b); - b->free = apr_bucket_free; - b->list = list; - b->type = &bucket_type_cgi; - b->length = (apr_size_t)(-1); - b->start = -1; - b->data = data; - return b; -} - -/* Handle stdout from CGI child. Duplicate of logic from the _read - * method of the real APR pipe bucket implementation. */ -static apr_status_t cgi_read_stdout(apr_bucket *a, apr_file_t *out, - const char **str, apr_size_t *len) -{ - char *buf; - apr_status_t rv; - - *str = NULL; - *len = APR_BUCKET_BUFF_SIZE; - buf = apr_bucket_alloc(*len, a->list); /* XXX: check for failure? */ - - rv = apr_file_read(out, buf, len); - - if (rv != APR_SUCCESS && rv != APR_EOF) { - apr_bucket_free(buf); - return rv; - } - - if (*len > 0) { - struct cgi_bucket_data *data = a->data; - apr_bucket_heap *h; - - /* Change the current bucket to refer to what we read */ - a = apr_bucket_heap_make(a, buf, *len, apr_bucket_free); - h = a->data; - h->alloc_len = APR_BUCKET_BUFF_SIZE; /* note the real buffer size */ - *str = buf; - APR_BUCKET_INSERT_AFTER(a, cgi_bucket_dup(data, a->list)); - } - else { - apr_bucket_free(buf); - a = apr_bucket_immortal_make(a, "", 0); - *str = a->data; - } - return rv; -} - -/* Read method of CGI bucket: polls on stderr and stdout of the child, - * sending any stderr output immediately away to the error log. */ -static apr_status_t cgi_bucket_read(apr_bucket *b, const char **str, - apr_size_t *len, apr_read_type_e block) -{ - struct cgi_bucket_data *data = b->data; - apr_interval_time_t timeout = 0; - apr_status_t rv; - int gotdata = 0; - cgi_dirconf *dc = ap_get_module_config(data->r->per_dir_config, &cgi_module); - - if (block != APR_NONBLOCK_READ) { - timeout = dc->timeout > 0 ? dc->timeout : data->r->server->timeout; - } - - do { - const apr_pollfd_t *results; - apr_int32_t num; - - rv = apr_pollset_poll(data->pollset, timeout, &num, &results); - if (APR_STATUS_IS_TIMEUP(rv)) { - if (timeout) { - ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, data->r, APLOGNO(01220) - "Timeout waiting for output from CGI script %s", - data->r->filename); - return rv; - } - else { - return APR_EAGAIN; - } - } - else if (APR_STATUS_IS_EINTR(rv)) { - continue; - } - else if (rv != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, data->r, APLOGNO(01221) - "poll failed waiting for CGI child"); - return rv; - } - - for (; num; num--, results++) { - if (results[0].client_data == (void *)1) { - /* stdout */ - rv = cgi_read_stdout(b, results[0].desc.f, str, len); - if (APR_STATUS_IS_EOF(rv)) { - rv = APR_SUCCESS; - } - gotdata = 1; - } else { - /* stderr */ - apr_status_t rv2 = log_script_err(data->r, results[0].desc.f); - if (APR_STATUS_IS_EOF(rv2)) { - apr_pollset_remove(data->pollset, &results[0]); - } - } - } - - } while (!gotdata); - - return rv; -} - -static const apr_bucket_type_t bucket_type_cgi = { - "CGI", 5, APR_BUCKET_DATA, - apr_bucket_destroy_noop, - cgi_bucket_read, - apr_bucket_setaside_notimpl, - apr_bucket_split_notimpl, - apr_bucket_copy_notimpl -}; - -#endif - static int cgi_handler(request_rec *r) { int nph; - apr_size_t dbpos = 0; + apr_size_t dbufsize; 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; + conn_rec *c = r->connection; + apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc); 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; cgi_dirconf *dc = ap_get_module_config(r->per_dir_config, &cgi_module); apr_interval_time_t timeout = dc->timeout > 0 ? dc->timeout : r->server->timeout; @@ -794,8 +530,6 @@ static int cgi_handler(request_rec *r) return DECLINED; } - c = r->connection; - is_included = !strcmp(r->protocol, "INCLUDED"); p = r->main ? r->main->pool : r->pool; @@ -864,83 +598,24 @@ static int cgi_handler(request_rec *r) 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; + /* Buffer for logging script stdout. */ if (conf->logname) { - dbuf = apr_palloc(r->pool, conf->bufbytes + 1); - dbpos = 0; + dbufsize = conf->bufbytes; + dbuf = apr_palloc(r->pool, dbufsize + 1); } - 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) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01225) - "Error reading request entity data"); - return ap_map_http_request_error(rv, HTTP_BAD_REQUEST); - } - - 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); + else { + dbufsize = 0; + dbuf = NULL; } - while (!seen_eos); - if (conf->logname) { - dbuf[dbpos] = '\0'; + /* Read the request body. */ + rv = cgi_handle_request(r, script_out, bb, dbuf, dbufsize); + if (rv) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01225) + "Error reading request entity data"); + return ap_map_http_request_error(rv, HTTP_BAD_REQUEST); } + /* Is this flush really needed? */ apr_file_flush(script_out); apr_file_close(script_out); @@ -948,10 +623,7 @@ static int cgi_handler(request_rec *r) AP_DEBUG_ASSERT(script_in != NULL); #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); + b = cgi_bucket_create(r, dc->timeout, script_in, script_err, c->bucket_alloc); if (b == NULL) return HTTP_INTERNAL_SERVER_ERROR; #else @@ -961,120 +633,7 @@ static int cgi_handler(request_rec *r) 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; - - ret = ap_scan_script_header_err_brigade_ex(r, bb, sbuf, - APLOG_MODULE_INDEX); - - /* xCGI has its own body framing mechanism which we don't - * match against any provided Content-Length, so let the - * core determine C-L vs T-E based on what's actually sent. - */ - if (!apr_table_get(r->subprocess_env, AP_TRUST_CGILIKE_CL_ENVVAR)) - apr_table_unset(r->headers_out, "Content-Length"); - apr_table_unset(r->headers_out, "Transfer-Encoding"); - - if (ret != OK) { - 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, 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, timeout); - log_script_err(r, script_err); - } - - apr_file_close(script_err); - - return OK; /* NOT r->status, even if it has changed. */ + return cgi_handle_response(r, nph, bb, timeout, conf, dbuf, script_err); } /*============================================================================ @@ -1188,107 +747,9 @@ static apr_status_t include_cmd(include_ctx_t *ctx, ap_filter_t *f, return APR_SUCCESS; } -static apr_status_t handle_exec(include_ctx_t *ctx, ap_filter_t *f, - apr_bucket_brigade *bb) -{ - char *tag = NULL; - char *tag_val = NULL; - request_rec *r = f->r; - char *file = r->filename; - char parsed_string[MAX_STRING_LEN]; - - if (!ctx->argc) { - ap_log_rerror(APLOG_MARK, - (ctx->flags & SSI_FLAG_PRINTING) - ? APLOG_ERR : APLOG_WARNING, - 0, r, APLOGNO(03195) - "missing argument for exec element in %s", r->filename); - } - - if (!(ctx->flags & SSI_FLAG_PRINTING)) { - return APR_SUCCESS; - } - - if (!ctx->argc) { - SSI_CREATE_ERROR_BUCKET(ctx, f, bb); - return APR_SUCCESS; - } - - if (ctx->flags & SSI_FLAG_NO_EXEC) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01228) "exec used but not allowed " - "in %s", r->filename); - SSI_CREATE_ERROR_BUCKET(ctx, f, bb); - return APR_SUCCESS; - } - - while (1) { - cgi_pfn_gtv(ctx, &tag, &tag_val, SSI_VALUE_DECODED); - if (!tag || !tag_val) { - break; - } - - if (!strcmp(tag, "cmd")) { - apr_status_t rv; - - cgi_pfn_ps(ctx, tag_val, parsed_string, sizeof(parsed_string), - SSI_EXPAND_LEAVE_NAME); - - rv = include_cmd(ctx, f, bb, parsed_string); - if (rv != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01229) "execution failure " - "for parameter \"%s\" to tag exec in file %s", - tag, r->filename); - SSI_CREATE_ERROR_BUCKET(ctx, f, bb); - break; - } - } - else if (!strcmp(tag, "cgi")) { - apr_status_t rv; - - cgi_pfn_ps(ctx, tag_val, parsed_string, sizeof(parsed_string), - SSI_EXPAND_DROP_NAME); - - rv = include_cgi(ctx, f, bb, parsed_string); - if (rv != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01230) "invalid CGI ref " - "\"%s\" in %s", tag_val, file); - SSI_CREATE_ERROR_BUCKET(ctx, f, bb); - break; - } - } - else { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01231) "unknown parameter " - "\"%s\" to tag exec in %s", tag, file); - SSI_CREATE_ERROR_BUCKET(ctx, f, bb); - break; - } - } - - return APR_SUCCESS; -} - - -/*============================================================================ - *============================================================================ - * This is the end of the cgi filter code moved from mod_include. - *============================================================================ - *============================================================================*/ - - static int cgi_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { - cgi_pfn_reg_with_ssi = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler); - cgi_pfn_gtv = APR_RETRIEVE_OPTIONAL_FN(ap_ssi_get_tag_and_value); - cgi_pfn_ps = APR_RETRIEVE_OPTIONAL_FN(ap_ssi_parse_string); - - if ((cgi_pfn_reg_with_ssi) && (cgi_pfn_gtv) && (cgi_pfn_ps)) { - /* Required by mod_include filter. This is how mod_cgi registers - * with mod_include to provide processing of the exec directive. - */ - cgi_pfn_reg_with_ssi("exec", handle_exec); - } - /* This is the means by which unusual (non-unix) os's may find alternate * means to run a given command (e.g. shebang/registry parsing on Win32) */ @@ -1304,6 +765,7 @@ static void register_hooks(apr_pool_t *p) static const char * const aszPre[] = { "mod_include.c", NULL }; ap_hook_handler(cgi_handler, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_post_config(cgi_post_config, aszPre, NULL, APR_HOOK_REALLY_FIRST); + ap_hook_optional_fn_retrieve(cgi_optfns_retrieve, NULL, NULL, APR_HOOK_MIDDLE); } AP_DECLARE_MODULE(cgi) = |