=================================================================== RCS file: /home/cvspublic/httpd-2.0/modules/generators/mod_cgi.c,v retrieving revision 1.148.2.7 retrieving revision 1.163 diff -u -r1.148.2.7 -r1.163 --- httpd-2.0/modules/generators/mod_cgi.c 2004/02/09 20:53:17 1.148.2.7 +++ httpd-2.0/modules/generators/mod_cgi.c 2004/05/05 15:30:53 1.163 @@ -32,8 +32,10 @@ #include "apr_optional.h" #include "apr_buckets.h" #include "apr_lib.h" +#include "apr_poll.h" #define APR_WANT_STRFUNC +#define APR_WANT_MEMFUNC #include "apr_want.h" #define CORE_PRIVATE @@ -191,13 +193,14 @@ /* Soak up stderr from a script and redirect it to the error log. */ -static void log_script_err(request_rec *r, apr_file_t *script_err) +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; - while (apr_file_gets(argsbuffer, HUGE_STRING_LEN, - script_err) == APR_SUCCESS) { + while ((rv = apr_file_gets(argsbuffer, HUGE_STRING_LEN, + script_err)) == APR_SUCCESS) { newline = strchr(argsbuffer, '\n'); if (newline) { *newline = '\0'; @@ -205,6 +208,8 @@ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", argsbuffer); } + + return rv; } static int log_script(request_rec *r, cgi_server_conf * conf, int ret, @@ -270,7 +275,10 @@ apr_file_printf(f, "%s\n", sbuf); first = 1; - APR_BRIGADE_FOREACH(e, bb) { + for (e = APR_BRIGADE_FIRST(bb); + e != APR_BRIGADE_SENTINEL(bb); + e = APR_BUCKET_NEXT(e)) + { if (APR_BUCKET_IS_EOS(e)) { break; } @@ -431,14 +439,6 @@ } else { procnew = apr_pcalloc(p, sizeof(*procnew)); - if (e_info->prog_type == RUN_AS_SSI) { - SPLIT_AND_PASS_PRETAG_BUCKETS(*(e_info->bb), e_info->ctx, - e_info->next, rc); - if (rc != APR_SUCCESS) { - return rc; - } - } - rc = ap_os_create_privileged_process(r, procnew, command, argv, env, procattr, p); @@ -446,7 +446,7 @@ /* Bad things happened. Everyone should have cleaned up. */ ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_TOCLIENT, rc, r, "couldn't create child process: %d: %s", rc, - apr_filename_of_pathname(r->filename)); + apr_filepath_name_get(r->filename)); } else { apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT); @@ -528,7 +528,11 @@ const char *buf; apr_size_t len; apr_status_t rv; - APR_BRIGADE_FOREACH(e, bb) { + + for (e = APR_BRIGADE_FIRST(bb); + e != APR_BRIGADE_SENTINEL(bb); + e = APR_BUCKET_NEXT(e)) + { if (APR_BUCKET_IS_EOS(e)) { break; } @@ -539,6 +543,172 @@ } } +#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); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + + 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); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + + fd.desc.f = err; /* script's stderr */ + fd.client_data = (void *)2; + rv = apr_pollset_add(data->pollset, &fd); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + + 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; + apr_status_t rv; + int gotdata = 0; + + timeout = block == APR_NONBLOCK_READ ? 0 : 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)) { + return timeout == 0 ? APR_EAGAIN : rv; + } + else if (APR_STATUS_IS_EINTR(rv)) { + continue; + } + else if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, data->r, + "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; @@ -556,6 +726,7 @@ cgi_server_conf *conf; apr_status_t rv; cgi_exec_info_t e_info; + conn_rec *c = r->connection; if(strcmp(r->handler, CGI_MAGIC_TYPE) && strcmp(r->handler, "cgi-script")) return DECLINED; @@ -571,7 +742,7 @@ return DECLINED; } - argv0 = apr_filename_of_pathname(r->filename); + argv0 = apr_filepath_name_get(r->filename); nph = !(strncmp(argv0, "nph-", 4)); conf = ap_get_module_config(r->server->module_config, &cgi_module); @@ -637,7 +808,7 @@ /* Transfer any put/post args, CERN style... * Note that we already ignore SIGPIPE in the core server. */ - bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + bb = apr_brigade_create(r->pool, c->bucket_alloc); seen_eos = 0; child_stopped_reading = 0; if (conf->logname) { @@ -654,7 +825,10 @@ return rv; } - APR_BRIGADE_FOREACH(bucket, bb) { + for (bucket = APR_BRIGADE_FIRST(bb); + bucket != APR_BRIGADE_SENTINEL(bb); + bucket = APR_BUCKET_NEXT(bucket)) + { const char *data; apr_size_t len; @@ -710,18 +884,28 @@ 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); +#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 (script_in && !nph) { - conn_rec *c = r->connection; + if (!nph) { const char *location; char sbuf[MAX_STRING_LEN]; int ret; - b = apr_bucket_pipe_create(script_in, c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, b); - b = apr_bucket_eos_create(c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, b); - if ((ret = ap_scan_script_header_err_brigade(r, bb, sbuf))) { return log_script(r, conf, ret, dbuf, sbuf, bb, script_err); } @@ -731,6 +915,7 @@ if (location && location[0] == '/' && r->status == 200) { discard_script_output(bb); apr_brigade_destroy(bb); + apr_file_pipe_timeout_set(script_err, r->server->timeout); log_script_err(r, script_err); /* This redirect needs to be a GET no matter what the original * method was. @@ -757,22 +942,8 @@ } 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) { - log_script_err(r, script_err); - } - - apr_file_close(script_err); } - - if (script_in && nph) { - conn_rec *c = r->connection; + else /* nph */ { struct ap_filter_t *cur; /* get rid of all filters up through protocol... since we @@ -786,14 +957,20 @@ } r->output_filters = r->proto_output_filters = cur; - bb = apr_brigade_create(r->pool, c->bucket_alloc); - b = apr_bucket_pipe_create(script_in, c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, b); - b = apr_bucket_eos_create(c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, b); - ap_pass_brigade(r->output_filters, bb); + 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. */ } @@ -803,92 +980,68 @@ * is the code required to handle the "exec" SSI directive. *============================================================================ *============================================================================*/ -static int include_cgi(char *s, request_rec *r, ap_filter_t *next, - apr_bucket *head_ptr, apr_bucket **inserted_head) +static apr_status_t include_cgi(include_ctx_t *ctx, ap_filter_t *f, + apr_bucket_brigade *bb, char *s) { - request_rec *rr = ap_sub_req_lookup_uri(s, r, next); + request_rec *r = f->r; + request_rec *rr = ap_sub_req_lookup_uri(s, r, f->next); int rr_status; - apr_bucket *tmp_buck, *tmp2_buck; if (rr->status != HTTP_OK) { ap_destroy_sub_req(rr); - return -1; + return APR_EGENERAL; } /* No hardwired path info or query allowed */ - if ((rr->path_info && rr->path_info[0]) || rr->args) { ap_destroy_sub_req(rr); - return -1; + return APR_EGENERAL; } if (rr->finfo.filetype != APR_REG) { ap_destroy_sub_req(rr); - return -1; + return APR_EGENERAL; } /* Script gets parameters of the *document*, for back compatibility */ - rr->path_info = r->path_info; /* hard to get right; see mod_cgi.c */ rr->args = r->args; /* Force sub_req to be treated as a CGI request, even if ordinary * typing rules would have called it something else. */ - ap_set_content_type(rr, CGI_MAGIC_TYPE); /* Run it. */ - rr_status = ap_run_sub_req(rr); if (ap_is_HTTP_REDIRECT(rr_status)) { - apr_size_t len_loc; const char *location = apr_table_get(rr->headers_out, "Location"); - conn_rec *c = r->connection; - location = ap_escape_html(rr->pool, location); - len_loc = strlen(location); + if (location) { + char *buffer; - /* XXX: if most of this stuff is going to get copied anyway, - * it'd be more efficient to pstrcat it into a single pool buffer - * and a single pool bucket */ - - tmp_buck = apr_bucket_immortal_create("bucket_alloc); - APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck); - tmp2_buck = apr_bucket_heap_create(location, len_loc, NULL, - c->bucket_alloc); - APR_BUCKET_INSERT_BEFORE(head_ptr, tmp2_buck); - tmp2_buck = apr_bucket_immortal_create("\">", sizeof("\">") - 1, - c->bucket_alloc); - APR_BUCKET_INSERT_BEFORE(head_ptr, tmp2_buck); - tmp2_buck = apr_bucket_heap_create(location, len_loc, NULL, - c->bucket_alloc); - APR_BUCKET_INSERT_BEFORE(head_ptr, tmp2_buck); - tmp2_buck = apr_bucket_immortal_create("", sizeof("") - 1, - c->bucket_alloc); - APR_BUCKET_INSERT_BEFORE(head_ptr, tmp2_buck); - - if (*inserted_head == NULL) { - *inserted_head = tmp_buck; + location = ap_escape_html(rr->pool, location); + buffer = apr_pstrcat(ctx->pool, "", + location, "", NULL); + + APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(buffer, + strlen(buffer), ctx->pool, + f->c->bucket_alloc)); } } ap_destroy_sub_req(rr); - return 0; + return APR_SUCCESS; } - -static int include_cmd(include_ctx_t *ctx, apr_bucket_brigade **bb, - const char *command, request_rec *r, ap_filter_t *f) +static apr_status_t include_cmd(include_ctx_t *ctx, ap_filter_t *f, + apr_bucket_brigade *bb, const char *command) { cgi_exec_info_t e_info; - const char **argv; - apr_file_t *script_out = NULL, *script_in = NULL, *script_err = NULL; - apr_bucket_brigade *bcgi; - apr_bucket *b; + const char **argv; + apr_file_t *script_out = NULL, *script_in = NULL, *script_err = NULL; apr_status_t rv; + request_rec *r = f->r; add_ssi_vars(r); @@ -899,15 +1052,16 @@ e_info.out_pipe = APR_FULL_BLOCK; e_info.err_pipe = APR_NO_PIPE; e_info.prog_type = RUN_AS_SSI; - e_info.bb = bb; + e_info.bb = &bb; e_info.ctx = ctx; e_info.next = f->next; - if ((rv = cgi_build_command(&command, &argv, r, r->pool, &e_info)) != APR_SUCCESS) { + if ((rv = cgi_build_command(&command, &argv, r, r->pool, + &e_info)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "don't know how to spawn cmd child process: %s", r->filename); - return HTTP_INTERNAL_SERVER_ERROR; + return rv; } /* run the script in its own process */ @@ -916,92 +1070,97 @@ &e_info)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "couldn't spawn child process: %s", r->filename); - return HTTP_INTERNAL_SERVER_ERROR; + return rv; } - bcgi = apr_brigade_create(r->pool, f->c->bucket_alloc); - b = apr_bucket_pipe_create(script_in, f->c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bcgi, b); - ap_pass_brigade(f->next, bcgi); + APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pipe_create(script_in, + f->c->bucket_alloc)); + ctx->flush_now = 1; /* We can't close the pipe here, because we may return before the * full CGI has been sent to the network. That's okay though, * because we can rely on the pool to close the pipe for us. */ - - return 0; + return APR_SUCCESS; } -static int handle_exec(include_ctx_t *ctx, apr_bucket_brigade **bb, - request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, - apr_bucket **inserted_head) +static apr_status_t handle_exec(include_ctx_t *ctx, ap_filter_t *f, + apr_bucket_brigade *bb) { - char *tag = NULL; + char *tag = NULL; char *tag_val = NULL; + request_rec *r = f->r; char *file = r->filename; - apr_bucket *tmp_buck; char parsed_string[MAX_STRING_LEN]; - *inserted_head = NULL; - if (ctx->flags & FLAG_PRINTING) { - if (ctx->flags & FLAG_NO_EXEC) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "exec used but not allowed in %s", r->filename); - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head); + if (!ctx->argc) { + ap_log_rerror(APLOG_MARK, + (ctx->flags & SSI_FLAG_PRINTING) + ? APLOG_ERR : APLOG_WARNING, + 0, r, "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, "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; } - else { - while (1) { - cgi_pfn_gtv(ctx, &tag, &tag_val, 1); - if (tag_val == NULL) { - if (tag == NULL) { - return 0; - } - else { - return 1; - } - } - if (!strcmp(tag, "cmd")) { - cgi_pfn_ps(r, ctx, tag_val, parsed_string, - sizeof(parsed_string), 1); - if (include_cmd(ctx, bb, parsed_string, r, f) == -1) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "execution failure for parameter \"%s\" " - "to tag exec in file %s", tag, r->filename); - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, - *inserted_head); - } - } - else if (!strcmp(tag, "cgi")) { - apr_status_t retval = APR_SUCCESS; - cgi_pfn_ps(r, ctx, tag_val, parsed_string, - sizeof(parsed_string), 0); + if (!strcmp(tag, "cmd")) { + apr_status_t rv; - SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx, f->next, retval); - if (retval != APR_SUCCESS) { - return retval; - } - - if (include_cgi(parsed_string, r, f->next, head_ptr, - inserted_head) == -1) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "invalid CGI ref \"%s\" in %s", - tag_val, file); - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, - *inserted_head); - } - } - else { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "unknown parameter \"%s\" to tag exec in %s", - tag, file); - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, - *inserted_head); - } + cgi_pfn_ps(ctx, tag_val, parsed_string, sizeof(parsed_string), + SSI_EXPAND_LEAVE_NAME); + + rv = include_cmd(ctx, f, bb, parsed_string); + if (!APR_STATUS_IS_SUCCESS(rv)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "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 (!APR_STATUS_IS_SUCCESS(rv)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "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, "unknown parameter " + "\"%s\" to tag exec in %s", tag, file); + SSI_CREATE_ERROR_BUCKET(ctx, f, bb); + break; + } } - return 0; + + return APR_SUCCESS; }