diff options
Diffstat (limited to 'modules')
33 files changed, 1485 insertions, 1218 deletions
diff --git a/modules/generators/cgi_common.h b/modules/generators/cgi_common.h new file mode 100644 index 0000000..66f9418 --- /dev/null +++ b/modules/generators/cgi_common.h @@ -0,0 +1,639 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "apr.h" +#include "apr_strings.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" + +#include "httpd.h" +#include "util_filter.h" +#include "util_script.h" + +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; + +/* These functions provided by mod_cgi.c/mod_cgid.c still. */ +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); +static apr_status_t include_cgi(include_ctx_t *ctx, ap_filter_t *f, + apr_bucket_brigade *bb, char *s); +static apr_status_t include_cmd(include_ctx_t *ctx, ap_filter_t *f, + apr_bucket_brigade *bb, const char *command); + +/* Read and discard all output from the brigade. Note that with the + * CGI bucket, the brigade will become empty once the script's stdout + * is closed (or on error/timeout), but the stderr output may not have + * been entirely captured at this point. */ +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); + } +} + +static int log_scripterror(request_rec *r, cgi_server_conf *conf, int ret, + apr_status_t rv, const char *logno, + const char *error) +{ + apr_file_t *f = NULL; + apr_finfo_t finfo; + char time_str[APR_CTIME_LEN]; + + /* Intentional no APLOGNO */ + /* Callee provides APLOGNO in error text */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, + "%sstderr from %s: %s", logno ? logno : "", r->filename, error); + + /* 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) { + char *prev = newline - 1; + if (prev >= argsbuffer && *prev == '\r') { + newline = prev; + } + + *newline = '\0'; + } + log_scripterror(r, conf, r->status, 0, APLOGNO(01215), argsbuffer); + } + + return rv; +} + +static apr_status_t cgi_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; +} + +/* Hook to register exec= handling with mod_include. */ +static void cgi_optfns_retrieve(void) +{ + APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *cgi_pfn_reg_with_ssi; + + 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", cgi_handle_exec); + } +} + +#ifdef WANT_CGI_BUCKET +/* 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; + apr_interval_time_t timeout; +}; + +/* 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_interval_time_t timeout, + 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); + + /* Disable APR timeout handling since we'll use poll() entirely. */ + apr_file_pipe_timeout_set(out, 0); + apr_file_pipe_timeout_set(err, 0); + + 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; + data->timeout = timeout; + 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; + + if (block != APR_NONBLOCK_READ) { + timeout = data->timeout > 0 ? data->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 /* WANT_CGI_BUCKET */ + +/* Handle the CGI response output, having set up the brigade with the + * CGI or PIPE bucket as appropriate. */ +static int cgi_handle_response(request_rec *r, int nph, apr_bucket_brigade *bb, + apr_interval_time_t timeout, cgi_server_conf *conf, + char *logdata, apr_file_t *script_err) +{ + apr_status_t rv; + + /* 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) { + /* In the case of a timeout reading script output, clear + * the brigade to avoid a second attempt to read the + * output. */ + if (ret == HTTP_GATEWAY_TIME_OUT) { + apr_brigade_cleanup(bb); + } + + ret = log_script(r, conf, ret, logdata, 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); + + if (script_err) { + 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" + */ + discard_script_output(bb); + apr_brigade_destroy(bb); + 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 (script_err && rv == APR_SUCCESS && !r->connection->aborted) { + apr_file_pipe_timeout_set(script_err, timeout); + log_script_err(r, script_err); + } + + if (script_err) apr_file_close(script_err); + + return OK; /* NOT r->status, even if it has changed. */ +} + +/* Read the request body and write it to fd 'script_out', using 'bb' + * as temporary bucket brigade. If 'logbuf' is non-NULL, the first + * logbufbytes of stdout are stored in logbuf. */ +static apr_status_t cgi_handle_request(request_rec *r, apr_file_t *script_out, + apr_bucket_brigade *bb, + char *logbuf, apr_size_t logbufbytes) +{ + int seen_eos = 0; + int child_stopped_reading = 0; + apr_status_t rv; + int 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) { + return rv; + } + + 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 */ + rv = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ); + if (rv) { + return rv; + } + + if (logbufbytes && dbpos < logbufbytes) { + int cursize; + + if ((dbpos + len) > logbufbytes) { + cursize = logbufbytes - dbpos; + } + else { + cursize = len; + } + memcpy(logbuf + 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; + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02651) + "Error writing request body to script %s", + r->filename); + } + } + apr_brigade_cleanup(bb); + } + while (!seen_eos); + + if (logbuf) { + logbuf[dbpos] = '\0'; + } + + return APR_SUCCESS; +} diff --git a/modules/generators/config5.m4 b/modules/generators/config5.m4 index bf29521..0863553 100644 --- a/modules/generators/config5.m4 +++ b/modules/generators/config5.m4 @@ -78,4 +78,15 @@ fi APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current]) +AC_ARG_ENABLE(cgid-fdpassing, + [APACHE_HELP_STRING(--enable-cgid-fdpassing,Enable experimental mod_cgid support for fd passing)], + [if test "$enableval" = "yes"; then + AC_CHECK_DECL(CMSG_DATA, + [AC_DEFINE([HAVE_CGID_FDPASSING], 1, [Enable FD passing support in mod_cgid])], + [AC_MSG_ERROR([cannot support mod_cgid fd-passing on this system])], [ +#include <sys/types.h> +#include <sys/socket.h>]) + fi +]) + APACHE_MODPATH_FINISH 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) = diff --git a/modules/generators/mod_cgid.c b/modules/generators/mod_cgid.c index 4bab59f..1d55b8d 100644 --- a/modules/generators/mod_cgid.c +++ b/modules/generators/mod_cgid.c @@ -57,7 +57,6 @@ #include "http_protocol.h" #include "http_main.h" #include "http_log.h" -#include "util_script.h" #include "ap_mpm.h" #include "mpm_common.h" #include "mod_suexec.h" @@ -80,11 +79,6 @@ module AP_MODULE_DECLARE_DATA cgid_module; static int cgid_start(apr_pool_t *p, server_rec *main_server, apr_proc_t *procnew); static int cgid_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *main_server); -static int handle_exec(include_ctx_t *ctx, ap_filter_t *f, apr_bucket_brigade *bb); - -static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *cgid_pfn_reg_with_ssi; -static APR_OPTIONAL_FN_TYPE(ap_ssi_get_tag_and_value) *cgid_pfn_gtv; -static APR_OPTIONAL_FN_TYPE(ap_ssi_parse_string) *cgid_pfn_ps; static apr_pool_t *pcgi = NULL; static pid_t daemon_pid; @@ -220,6 +214,15 @@ typedef struct { #endif } cgid_req_t; +#define cgi_server_conf cgid_server_conf +#define cgi_module cgid_module + +#ifdef HAVE_CGID_FDPASSING +/* Pull in CGI bucket implementation. */ +#define WANT_CGI_BUCKET +#endif +#include "cgi_common.h" + /* This routine is called to create the argument list to be passed * to the CGI script. When suexec is enabled, the suexec path, user, and * group are the first three arguments to be passed; if not, all three @@ -342,15 +345,19 @@ static apr_status_t close_unix_socket(void *thefd) return close(fd); } -/* deal with incomplete reads and signals - * assume you really have to read buf_size bytes - */ -static apr_status_t sock_read(int fd, void *vbuf, size_t buf_size) +/* Read from the socket dealing with incomplete messages and signals. + * Returns 0 on success or errno on failure. Stderr fd passed as + * auxiliary data from other end is written to *errfd, or else stderr + * fileno if not present. */ +static apr_status_t sock_readhdr(int fd, int *errfd, void *vbuf, size_t buf_size) { - char *buf = vbuf; int rc; +#ifndef HAVE_CGID_FDPASSING + char *buf = vbuf; size_t bytes_read = 0; + if (errfd) *errfd = 0; + do { do { rc = read(fd, buf + bytes_read, buf_size - bytes_read); @@ -365,9 +372,60 @@ static apr_status_t sock_read(int fd, void *vbuf, size_t buf_size) } } while (bytes_read < buf_size); + +#else /* with FD passing */ + struct msghdr msg = {0}; + struct iovec vec = {vbuf, buf_size}; + struct cmsghdr *cmsg; + union { /* union to ensure alignment */ + struct cmsghdr cm; + char buf[CMSG_SPACE(sizeof(int))]; + } u; + + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + + if (errfd) { + msg.msg_control = u.buf; + msg.msg_controllen = sizeof(u.buf); + *errfd = 0; + } + + /* use MSG_WAITALL to skip loop on truncated reads */ + do { + rc = recvmsg(fd, &msg, MSG_WAITALL); + } while (rc < 0 && errno == EINTR); + + if (rc == 0) { + return ECONNRESET; + } + else if (rc < 0) { + return errno; + } + else if (rc != buf_size) { + /* MSG_WAITALL should ensure the recvmsg blocks until the + * entire length is read, but let's be paranoid. */ + return APR_INCOMPLETE; + } + + if (errfd + && (cmsg = CMSG_FIRSTHDR(&msg)) != NULL + && cmsg->cmsg_len == CMSG_LEN(sizeof(*errfd)) + && cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_RIGHTS) { + *errfd = *((int *) CMSG_DATA(cmsg)); + } +#endif + return APR_SUCCESS; } +/* As sock_readhdr but without auxiliary fd passing. */ +static apr_status_t sock_read(int fd, void *vbuf, size_t buf_size) +{ + return sock_readhdr(fd, NULL, vbuf, buf_size); +} + /* deal with signals */ static apr_status_t sock_write(int fd, const void *buf, size_t buf_size) @@ -384,7 +442,7 @@ static apr_status_t sock_write(int fd, const void *buf, size_t buf_size) return APR_SUCCESS; } -static apr_status_t sock_writev(int fd, request_rec *r, int count, ...) +static apr_status_t sock_writev(int fd, int auxfd, request_rec *r, int count, ...) { va_list ap; int rc; @@ -399,9 +457,39 @@ static apr_status_t sock_writev(int fd, request_rec *r, int count, ...) } va_end(ap); +#ifndef HAVE_CGID_FDPASSING do { rc = writev(fd, vec, count); } while (rc < 0 && errno == EINTR); +#else + { + struct msghdr msg = { 0 }; + struct cmsghdr *cmsg; + union { /* union for alignment */ + char buf[CMSG_SPACE(sizeof(int))]; + struct cmsghdr align; + } u; + + msg.msg_iov = vec; + msg.msg_iovlen = count; + + if (auxfd) { + msg.msg_control = u.buf; + msg.msg_controllen = sizeof(u.buf); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + *((int *) CMSG_DATA(cmsg)) = auxfd; + } + + do { + rc = sendmsg(fd, &msg, 0); + } while (rc < 0 && errno == EINTR); + } +#endif + if (rc < 0) { return errno; } @@ -410,7 +498,7 @@ static apr_status_t sock_writev(int fd, request_rec *r, int count, ...) } static apr_status_t get_req(int fd, request_rec *r, char **argv0, char ***env, - cgid_req_t *req) + int *errfd, cgid_req_t *req) { int i; char **environ; @@ -421,7 +509,7 @@ static apr_status_t get_req(int fd, request_rec *r, char **argv0, char ***env, r->server = apr_pcalloc(r->pool, sizeof(server_rec)); /* read the request header */ - stat = sock_read(fd, req, sizeof(*req)); + stat = sock_readhdr(fd, errfd, req, sizeof(*req)); if (stat != APR_SUCCESS) { return stat; } @@ -431,6 +519,14 @@ static apr_status_t get_req(int fd, request_rec *r, char **argv0, char ***env, return APR_SUCCESS; } + /* Sanity check the structure received. */ + if (req->env_count < 0 || req->uri_len == 0 + || req->filename_len > APR_PATH_MAX || req->filename_len == 0 + || req->argv0_len > APR_PATH_MAX || req->argv0_len == 0 + || req->loglevel > APLOG_TRACE8) { + return APR_EINVAL; + } + /* handle module indexes and such */ rconf = (void **)ap_create_request_config(r->pool); @@ -479,14 +575,15 @@ static apr_status_t get_req(int fd, request_rec *r, char **argv0, char ***env, return APR_SUCCESS; } -static apr_status_t send_req(int fd, request_rec *r, char *argv0, char **env, - int req_type) +static apr_status_t send_req(int fd, apr_file_t *errpipe, request_rec *r, + const char *argv0, char **env, int req_type) { int i; cgid_req_t req = {0}; apr_status_t stat; ap_unix_identity_t * ugid = ap_run_get_suexec_identity(r); core_dir_config *core_conf = ap_get_core_module_config(r->per_dir_config); + int errfd; if (ugid == NULL) { @@ -507,16 +604,21 @@ static apr_status_t send_req(int fd, request_rec *r, char *argv0, char **env, req.args_len = r->args ? strlen(r->args) : 0; req.loglevel = r->server->log.level; + if (errpipe) + apr_os_file_get(&errfd, errpipe); + else + errfd = 0; + /* Write the request header */ if (req.args_len) { - stat = sock_writev(fd, r, 5, + stat = sock_writev(fd, errfd, r, 5, &req, sizeof(req), r->filename, req.filename_len, argv0, req.argv0_len, r->uri, req.uri_len, r->args, req.args_len); } else { - stat = sock_writev(fd, r, 4, + stat = sock_writev(fd, errfd, r, 4, &req, sizeof(req), r->filename, req.filename_len, argv0, req.argv0_len, @@ -531,7 +633,7 @@ static apr_status_t send_req(int fd, request_rec *r, char *argv0, char **env, for (i = 0; i < req.env_count; i++) { apr_size_t curlen = strlen(env[i]); - if ((stat = sock_writev(fd, r, 2, &curlen, sizeof(curlen), + if ((stat = sock_writev(fd, 0, r, 2, &curlen, sizeof(curlen), env[i], curlen)) != APR_SUCCESS) { return stat; } @@ -582,20 +684,34 @@ static void daemon_signal_handler(int sig) } } +/* Callback executed in the forked child process if exec of the CGI + * script fails. For the fd-passing case, output to stderr goes to + * the client (request handling thread) and is logged via + * ap_log_rerror there. For the non-fd-passing case, the "fake" + * request_rec passed via userdata is used to log. */ static void cgid_child_errfn(apr_pool_t *pool, apr_status_t err, const char *description) { - request_rec *r; void *vr; apr_pool_userdata_get(&vr, ERRFN_USERDATA_KEY, pool); - r = vr; - - /* sure we got r, but don't call ap_log_rerror() because we don't - * have r->headers_in and possibly other storage referenced by - * ap_log_rerror() - */ - ap_log_error(APLOG_MARK, APLOG_ERR, err, r->server, APLOGNO(01241) "%s", description); + if (vr) { + request_rec *r = vr; + + /* sure we got r, but don't call ap_log_rerror() because we don't + * have r->headers_in and possibly other storage referenced by + * ap_log_rerror() + */ + ap_log_error(APLOG_MARK, APLOG_ERR, err, r->server, APLOGNO(01241) "%s", description); + } + else { + const char *logstr; + + logstr = apr_psprintf(pool, APLOGNO(01241) "error spawning CGI child: %s (%pm)\n", + description, &err); + fputs(logstr, stderr); + fflush(stderr); + } } static int cgid_server(void *data) @@ -670,7 +786,7 @@ static int cgid_server(void *data) } while (!daemon_should_exit) { - int errfileno = STDERR_FILENO; + int errfileno; char *argv0 = NULL; char **env = NULL; const char * const *argv; @@ -710,7 +826,7 @@ static int cgid_server(void *data) r = apr_pcalloc(ptrans, sizeof(request_rec)); procnew = apr_pcalloc(ptrans, sizeof(*procnew)); r->pool = ptrans; - stat = get_req(sd2, r, &argv0, &env, &cgid_req); + stat = get_req(sd2, r, &argv0, &env, &errfileno, &cgid_req); if (stat != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, stat, main_server, APLOGNO(01248) @@ -742,6 +858,16 @@ static int cgid_server(void *data) continue; } + if (errfileno == 0) { + errfileno = STDERR_FILENO; + } + else { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, main_server, + "using passed fd %d as stderr", errfileno); + /* Limit the received fd lifetime to pool lifetime */ + apr_pool_cleanup_register(ptrans, (void *)((long)errfileno), + close_unix_socket, close_unix_socket); + } apr_os_file_put(&r->server->error_log, &errfileno, 0, r->pool); apr_os_file_put(&inout, &sd2, 0, r->pool); @@ -801,7 +927,10 @@ static int cgid_server(void *data) close(sd2); } else { - apr_pool_userdata_set(r, ERRFN_USERDATA_KEY, apr_pool_cleanup_null, ptrans); + if (errfileno == STDERR_FILENO) { + /* Used by cgid_child_errfn without fd-passing. */ + apr_pool_userdata_set(r, ERRFN_USERDATA_KEY, apr_pool_cleanup_null, ptrans); + } argv = (const char * const *)create_argv(r->pool, NULL, NULL, NULL, argv0, r->args); @@ -946,16 +1075,6 @@ static int cgid_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, if (ret != OK ) { return ret; } - cgid_pfn_reg_with_ssi = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler); - cgid_pfn_gtv = APR_RETRIEVE_OPTIONAL_FN(ap_ssi_get_tag_and_value); - cgid_pfn_ps = APR_RETRIEVE_OPTIONAL_FN(ap_ssi_parse_string); - - if ((cgid_pfn_reg_with_ssi) && (cgid_pfn_gtv) && (cgid_pfn_ps)) { - /* Required by mod_include filter. This is how mod_cgid registers - * with mod_include to provide processing of the exec directive. - */ - cgid_pfn_reg_with_ssi("exec", handle_exec); - } } return ret; } @@ -1066,41 +1185,6 @@ static const command_rec cgid_cmds[] = {NULL} }; -static int log_scripterror(request_rec *r, cgid_server_conf * conf, int ret, - apr_status_t rv, char *error) -{ - apr_file_t *f = NULL; - struct stat 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", error, r->filename); - - /* XXX Very expensive mainline case! Open, then getfileinfo! */ - if (!conf->logname || - ((stat(conf->logname, &finfo) == 0) - && (finfo.st_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 /cgid-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/cgid-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; -} - static int log_script(request_rec *r, cgid_server_conf * conf, int ret, char *dbuf, const char *sbuf, apr_bucket_brigade *bb, apr_file_t *script_err) @@ -1221,7 +1305,7 @@ static int connect_to_daemon(int *sdptr, request_rec *r, ++connect_tries; if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { return log_scripterror(r, conf, HTTP_INTERNAL_SERVER_ERROR, errno, - APLOGNO(01255) "unable to create socket to cgi daemon"); + APLOGNO(01255), "unable to create socket to cgi daemon"); } if (connect(sd, (struct sockaddr *)server_addr, server_addr_len) < 0) { /* Save errno for later */ @@ -1242,7 +1326,7 @@ static int connect_to_daemon(int *sdptr, request_rec *r, } else { close(sd); - return log_scripterror(r, conf, HTTP_SERVICE_UNAVAILABLE, errno, APLOGNO(01257) + return log_scripterror(r, conf, HTTP_SERVICE_UNAVAILABLE, errno, APLOGNO(01257), "unable to connect to cgi daemon after multiple tries"); } } @@ -1258,13 +1342,15 @@ static int connect_to_daemon(int *sdptr, request_rec *r, if (connect_errno == ENOENT && apr_time_sec(apr_time_now() - ap_scoreboard_image->global->restart_time) > DEFAULT_CONNECT_STARTUP_DELAY) { - return log_scripterror(r, conf, HTTP_SERVICE_UNAVAILABLE, connect_errno, - apr_pstrcat(r->pool, APLOGNO(02833) "ScriptSock ", sockname, " does not exist", NULL)); + return log_scripterror(r, conf, HTTP_SERVICE_UNAVAILABLE, connect_errno, + APLOGNO(02833), + apr_pstrcat(r->pool, + "ScriptSock ", sockname, " does not exist", NULL)); } /* gotta try again, but make sure the cgid daemon is still around */ if (connect_errno != ENOENT && kill(daemon_pid, 0) != 0) { - return log_scripterror(r, conf, HTTP_SERVICE_UNAVAILABLE, connect_errno, APLOGNO(01258) + return log_scripterror(r, conf, HTTP_SERVICE_UNAVAILABLE, connect_errno, APLOGNO(01258), "cgid daemon is gone; is Apache terminating?"); } } @@ -1272,23 +1358,6 @@ static int connect_to_daemon(int *sdptr, request_rec *r, return OK; } -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); - } -} - /**************************************************************** * * Actual cgid handling... @@ -1374,7 +1443,9 @@ static apr_status_t get_cgi_pid(request_rec *r, cgid_server_conf *conf, pid_t * return stat; } - if (pid == 0) { + /* Don't accept zero as a pid here, calling kill(0, SIGTERM) etc + * later is unpleasant. */ + if (*pid == 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01261) "daemon couldn't find CGI process for connection %lu", r->connection->id); @@ -1393,19 +1464,21 @@ static apr_status_t cleanup_script(void *vptr) static int cgid_handler(request_rec *r) { - int retval, nph, dbpos; + conn_rec *c = r->connection; + int retval, nph; char *argv0, *dbuf; - apr_bucket_brigade *bb; + apr_size_t dbufsize; + apr_bucket_brigade *bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); apr_bucket *b; cgid_server_conf *conf; int is_included; - int seen_eos, child_stopped_reading; int sd; char **env; - apr_file_t *tempsock; + apr_file_t *tempsock, *script_err, *errpipe_out; struct cleanup_script_info *info; apr_status_t rv; cgid_dirconf *dc; + apr_interval_time_t timeout; if (strcmp(r->handler, CGI_MAGIC_TYPE) && strcmp(r->handler, "cgi-script")) { return DECLINED; @@ -1414,7 +1487,7 @@ static int cgid_handler(request_rec *r) conf = ap_get_module_config(r->server->module_config, &cgid_module); dc = ap_get_module_config(r->per_dir_config, &cgid_module); - + timeout = dc->timeout > 0 ? dc->timeout : r->server->timeout; is_included = !strcmp(r->protocol, "INCLUDED"); if ((argv0 = strrchr(r->filename, '/')) != NULL) { @@ -1429,12 +1502,12 @@ static int cgid_handler(request_rec *r) argv0 = r->filename; if (!(ap_allow_options(r) & OPT_EXECCGI) && !is_scriptaliased(r)) { - return log_scripterror(r, conf, HTTP_FORBIDDEN, 0, APLOGNO(01262) + return log_scripterror(r, conf, HTTP_FORBIDDEN, 0, APLOGNO(01262), "Options ExecCGI is off in this directory"); } if (nph && is_included) { - return log_scripterror(r, conf, HTTP_FORBIDDEN, 0, APLOGNO(01263) + return log_scripterror(r, conf, HTTP_FORBIDDEN, 0, APLOGNO(01263), "attempt to include NPH CGI script"); } @@ -1443,12 +1516,12 @@ static int cgid_handler(request_rec *r) #error at mod_cgi.c for required code in this path. #else if (r->finfo.filetype == APR_NOFILE) { - return log_scripterror(r, conf, HTTP_NOT_FOUND, 0, APLOGNO(01264) + return log_scripterror(r, conf, HTTP_NOT_FOUND, 0, APLOGNO(01264), "script not found or unable to stat"); } #endif if (r->finfo.filetype == APR_DIR) { - return log_scripterror(r, conf, HTTP_FORBIDDEN, 0, APLOGNO(01265) + return log_scripterror(r, conf, HTTP_FORBIDDEN, 0, APLOGNO(01265), "attempt to invoke directory as script"); } @@ -1456,7 +1529,7 @@ static int cgid_handler(request_rec *r) r->path_info && *r->path_info) { /* default to accept */ - return log_scripterror(r, conf, HTTP_NOT_FOUND, 0, APLOGNO(01266) + return log_scripterror(r, conf, HTTP_NOT_FOUND, 0, APLOGNO(01266), "AcceptPathInfo off disallows user's path"); } /* @@ -1467,6 +1540,17 @@ static int cgid_handler(request_rec *r) } */ +#ifdef HAVE_CGID_FDPASSING + rv = apr_file_pipe_create(&script_err, &errpipe_out, r->pool); + if (rv) { + return log_scripterror(r, conf, HTTP_SERVICE_UNAVAILABLE, rv, APLOGNO(10176), + "could not create pipe for stderr"); + } +#else + script_err = NULL; + errpipe_out = NULL; +#endif + /* * httpd core function used to add common environment variables like * DOCUMENT_ROOT. @@ -1479,24 +1563,28 @@ static int cgid_handler(request_rec *r) return retval; } - rv = send_req(sd, r, argv0, env, CGI_REQ); + rv = send_req(sd, errpipe_out, r, argv0, env, CGI_REQ); if (rv != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01268) - "write to cgi daemon process"); + return log_scripterror(r, conf, HTTP_SERVICE_UNAVAILABLE, rv, APLOGNO(10245), + "could not send request to cgi daemon"); } + /* The write-end of the pipe is only used by the server, so close + * it here. */ + if (errpipe_out) apr_file_close(errpipe_out); + info = apr_palloc(r->pool, sizeof(struct cleanup_script_info)); info->conf = conf; info->r = r; rv = get_cgi_pid(r, conf, &(info->pid)); - if (APR_SUCCESS == rv){ + if (rv == APR_SUCCESS) { apr_pool_cleanup_register(r->pool, info, - cleanup_script, - apr_pool_cleanup_null); + cleanup_script, apr_pool_cleanup_null); } else { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, "error determining cgi PID"); + return log_scripterror(r, conf, HTTP_SERVICE_UNAVAILABLE, rv, APLOGNO(10246), + "failed reading PID from cgi daemon"); } /* We are putting the socket discriptor into an apr_file_t so that we can @@ -1506,95 +1594,25 @@ static int cgid_handler(request_rec *r) */ apr_os_pipe_put_ex(&tempsock, &sd, 1, r->pool); - if (dc->timeout > 0) { - apr_file_pipe_timeout_set(tempsock, dc->timeout); - } - else { - apr_file_pipe_timeout_set(tempsock, r->server->timeout); - } + apr_file_pipe_timeout_set(tempsock, timeout); apr_pool_cleanup_kill(r->pool, (void *)((long)sd), close_unix_socket); - /* 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); - seen_eos = 0; - child_stopped_reading = 0; - dbuf = NULL; - dbpos = 0; + /* Buffer for logging script stdout. */ if (conf->logname) { - dbuf = apr_palloc(r->pool, conf->bufbytes + 1); + 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(01270) - "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(tempsock, data, len, NULL); - - if (rv != APR_SUCCESS) { - /* silly script stopped reading, soak up remaining message */ - child_stopped_reading = 1; - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02651) - "Error writing request body to script %s", - r->filename); - - } - } - apr_brigade_cleanup(bb); + else { + dbuf = NULL; + dbufsize = 0; } - while (!seen_eos); - if (conf->logname) { - dbuf[dbpos] = '\0'; + /* Read the request body. */ + rv = cgi_handle_request(r, tempsock, bb, dbuf, dbufsize); + if (rv) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01270) + "Error reading request entity data"); + return ap_map_http_request_error(rv, HTTP_BAD_REQUEST); } /* we're done writing, or maybe we didn't write at all; @@ -1603,134 +1621,22 @@ static int cgid_handler(request_rec *r) */ shutdown(sd, 1); - /* Handle script return... */ - if (!nph) { - conn_rec *c = r->connection; - const char *location; - char sbuf[MAX_STRING_LEN]; - int ret; - - bb = apr_brigade_create(r->pool, c->bucket_alloc); - b = apr_bucket_pipe_create(tempsock, c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, b); - b = apr_bucket_eos_create(c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, b); - - 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, NULL); - - /* - * 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 && location[0] == '/' && r->status == 200) { - - /* Soak up all the script output */ - discard_script_output(bb); - apr_brigade_destroy(bb); - /* 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" - */ - discard_script_output(bb); - apr_brigade_destroy(bb); - return HTTP_MOVED_TEMPORARILY; - } - - rv = ap_pass_brigade(r->output_filters, bb); - if (rv != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, - "Failed to flush CGI output to client"); - } - } - - if (nph) { - conn_rec *c = r->connection; - 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; - - bb = apr_brigade_create(r->pool, c->bucket_alloc); - b = apr_bucket_pipe_create(tempsock, 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); - } + bb = apr_brigade_create(r->pool, c->bucket_alloc); +#ifdef HAVE_CGID_FDPASSING + b = cgi_bucket_create(r, dc->timeout, tempsock, script_err, c->bucket_alloc); + if (b == NULL) + return HTTP_INTERNAL_SERVER_ERROR; /* should call log_scripterror() w/ _UNAVAILABLE? */ +#else + b = apr_bucket_pipe_create(tempsock, c->bucket_alloc); +#endif + APR_BRIGADE_INSERT_TAIL(bb, b); + b = apr_bucket_eos_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); - return OK; /* NOT r->status, even if it has changed. */ + return cgi_handle_response(r, nph, bb, timeout, conf, dbuf, script_err); } - - - -/*============================================================================ - *============================================================================ - * This is the beginning of the cgi filter code moved from mod_include. This - * is the code required to handle the "exec" SSI directive. - *============================================================================ - *============================================================================*/ +/* Handling include= for mod_include. */ static apr_status_t include_cgi(include_ctx_t *ctx, ap_filter_t *f, apr_bucket_brigade *bb, char *s) { @@ -1815,7 +1721,7 @@ static void add_ssi_vars(request_rec *r) } static int include_cmd(include_ctx_t *ctx, ap_filter_t *f, - apr_bucket_brigade *bb, char *command) + apr_bucket_brigade *bb, const char *command) { char **env; int sd; @@ -1836,7 +1742,7 @@ static int include_cmd(include_ctx_t *ctx, ap_filter_t *f, return retval; } - send_req(sd, r, command, env, SSI_REQ); + send_req(sd, NULL, r, command, env, SSI_REQ); info = apr_palloc(r->pool, sizeof(struct cleanup_script_info)); info->conf = conf; @@ -1881,91 +1787,6 @@ static int 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(03196) - "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(01271) "exec used but not allowed " - "in %s", r->filename); - SSI_CREATE_ERROR_BUCKET(ctx, f, bb); - return APR_SUCCESS; - } - - while (1) { - cgid_pfn_gtv(ctx, &tag, &tag_val, SSI_VALUE_DECODED); - if (!tag || !tag_val) { - break; - } - - if (!strcmp(tag, "cmd")) { - apr_status_t rv; - - cgid_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(01272) - "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; - - cgid_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(01273) "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(01274) "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 void register_hook(apr_pool_t *p) { static const char * const aszPre[] = { "mod_include.c", NULL }; @@ -1973,6 +1794,7 @@ static void register_hook(apr_pool_t *p) ap_hook_pre_config(cgid_pre_config, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_post_config(cgid_init, aszPre, NULL, APR_HOOK_MIDDLE); ap_hook_handler(cgid_handler, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_optional_fn_retrieve(cgi_optfns_retrieve, NULL, NULL, APR_HOOK_MIDDLE); } AP_DECLARE_MODULE(cgid) = { diff --git a/modules/http/http_protocol.c b/modules/http/http_protocol.c index d031f24..c31e873 100644 --- a/modules/http/http_protocol.c +++ b/modules/http/http_protocol.c @@ -1097,8 +1097,14 @@ AP_DECLARE(void) ap_set_content_type(request_rec *r, const char *ct) } else if (!r->content_type || strcmp(r->content_type, ct)) { r->content_type = ct; + AP_REQUEST_SET_BNOTE(r, AP_REQUEST_TRUSTED_CT, 0); } } +AP_DECLARE(void) ap_set_content_type_ex(request_rec *r, const char *ct, int trusted) +{ + ap_set_content_type(r, ct); + AP_REQUEST_SET_BNOTE(r, AP_REQUEST_TRUSTED_CT, trusted ? AP_REQUEST_TRUSTED_CT : 0); +} AP_DECLARE(void) ap_set_accept_ranges(request_rec *r) { diff --git a/modules/http/mod_mime.c b/modules/http/mod_mime.c index 700f824..51095a0 100644 --- a/modules/http/mod_mime.c +++ b/modules/http/mod_mime.c @@ -759,7 +759,7 @@ static int find_ct(request_rec *r) int found_metadata = 0; if (r->finfo.filetype == APR_DIR) { - ap_set_content_type(r, DIR_MAGIC_TYPE); + ap_set_content_type_ex(r, DIR_MAGIC_TYPE, 1); return OK; } @@ -850,7 +850,7 @@ static int find_ct(request_rec *r) if (exinfo == NULL || !exinfo->forced_type) { if ((type = apr_hash_get(mime_type_extensions, ext, APR_HASH_KEY_STRING)) != NULL) { - ap_set_content_type(r, (char*) type); + ap_set_content_type_ex(r, (char*) type, 1); found = 1; } } @@ -859,7 +859,7 @@ static int find_ct(request_rec *r) /* empty string is treated as special case for RemoveType */ if (exinfo->forced_type && *exinfo->forced_type) { - ap_set_content_type(r, exinfo->forced_type); + ap_set_content_type_ex(r, exinfo->forced_type, 1); found = 1; } @@ -964,33 +964,33 @@ static int find_ct(request_rec *r) memcpy(tmp, ctp->subtype, ctp->subtype_len); tmp += ctp->subtype_len; *tmp = 0; - ap_set_content_type(r, base_content_type); + ap_set_content_type_ex(r, base_content_type, AP_REQUEST_IS_TRUSTED_CT(r)); while (pp != NULL) { if (charset && !strcmp(pp->attr, "charset")) { if (!override) { - ap_set_content_type(r, + ap_set_content_type_ex(r, apr_pstrcat(r->pool, r->content_type, "; charset=", charset, - NULL)); + NULL), AP_REQUEST_IS_TRUSTED_CT(r)); override = 1; } } else { - ap_set_content_type(r, + ap_set_content_type_ex(r, apr_pstrcat(r->pool, r->content_type, "; ", pp->attr, "=", pp->val, - NULL)); + NULL), AP_REQUEST_IS_TRUSTED_CT(r)); } pp = pp->next; } if (charset && !override) { - ap_set_content_type(r, apr_pstrcat(r->pool, r->content_type, + ap_set_content_type_ex(r, apr_pstrcat(r->pool, r->content_type, "; charset=", charset, - NULL)); + NULL), AP_REQUEST_IS_TRUSTED_CT(r)); } } } diff --git a/modules/http2/h2_c2.c b/modules/http2/h2_c2.c index a955200..c65a521 100644 --- a/modules/http2/h2_c2.c +++ b/modules/http2/h2_c2.c @@ -370,6 +370,13 @@ static apr_status_t h2_c2_filter_out(ap_filter_t* f, apr_bucket_brigade* bb) h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c); apr_status_t rv; + if (bb == NULL) { +#if !AP_MODULE_MAGIC_AT_LEAST(20180720, 1) + f->c->data_in_output_filters = 0; +#endif + return APR_SUCCESS; + } + ap_assert(conn_ctx); #if AP_HAS_RESPONSE_BUCKETS if (!conn_ctx->has_final_response) { diff --git a/modules/http2/mod_proxy_http2.c b/modules/http2/mod_proxy_http2.c index ebf8f61..e5cb0ba 100644 --- a/modules/http2/mod_proxy_http2.c +++ b/modules/http2/mod_proxy_http2.c @@ -317,7 +317,7 @@ static int proxy_http2_handler(request_rec *r, apr_port_t proxyport) { const char *proxy_func; - char *locurl = url, *u; + char *locurl, *u; apr_size_t slen; int is_ssl = 0; apr_status_t status; @@ -382,6 +382,7 @@ run_connect: goto cleanup; } + locurl = url; ctx->p_conn->is_ssl = ctx->is_ssl; /* Step One: Determine the URL to connect to (might be a proxy), diff --git a/modules/mappers/mod_actions.c b/modules/mappers/mod_actions.c index ac9c3b7..5e398b5 100644 --- a/modules/mappers/mod_actions.c +++ b/modules/mappers/mod_actions.c @@ -182,8 +182,10 @@ static int action_handler(request_rec *r) return DECLINED; /* Second, check for actions (which override the method scripts) */ - action = r->handler ? r->handler : - ap_field_noparam(r->pool, r->content_type); + action = r->handler; + if (!action && AP_REQUEST_IS_TRUSTED_CT(r)) { + action = ap_field_noparam(r->pool, r->content_type); + } if (action && (t = apr_table_get(conf->action_types, action))) { int virtual = (*t++ == '0' ? 0 : 1); diff --git a/modules/mappers/mod_negotiation.c b/modules/mappers/mod_negotiation.c index c056b28..a528f81 100644 --- a/modules/mappers/mod_negotiation.c +++ b/modules/mappers/mod_negotiation.c @@ -1167,7 +1167,7 @@ static int read_types_multi(negotiation_state *neg) * might be doing. */ if (sub_req->handler && !sub_req->content_type) { - ap_set_content_type(sub_req, CGI_MAGIC_TYPE); + ap_set_content_type_ex(sub_req, CGI_MAGIC_TYPE, 1); } /* @@ -3003,14 +3003,14 @@ static int handle_map_file(request_rec *r) /* set MIME type and charset as negotiated */ if (best->mime_type && *best->mime_type) { if (best->content_charset && *best->content_charset) { - ap_set_content_type(r, apr_pstrcat(r->pool, + ap_set_content_type_ex(r, apr_pstrcat(r->pool, best->mime_type, "; charset=", best->content_charset, - NULL)); + NULL), 1); } else { - ap_set_content_type(r, apr_pstrdup(r->pool, best->mime_type)); + ap_set_content_type_ex(r, apr_pstrdup(r->pool, best->mime_type), 1); } } diff --git a/modules/mappers/mod_rewrite.c b/modules/mappers/mod_rewrite.c index bbcc11b..3fc2baf 100644 --- a/modules/mappers/mod_rewrite.c +++ b/modules/mappers/mod_rewrite.c @@ -177,6 +177,8 @@ static const char* really_last_key = "rewrite_really_last"; #define RULEFLAG_QSLAST (1<<19) #define RULEFLAG_QSNONE (1<<20) /* programattic only */ #define RULEFLAG_ESCAPECTLS (1<<21) +#define RULEFLAG_UNSAFE_PREFIX_STAT (1<<22) +#define RULEFLAG_UNSAFE_ALLOW3F (1<<23) /* return code of the rewrite rule * the result may be escaped - or not @@ -184,7 +186,7 @@ static const char* really_last_key = "rewrite_really_last"; #define ACTION_NORMAL (1<<0) #define ACTION_NOESCAPE (1<<1) #define ACTION_STATUS (1<<2) - +#define ACTION_STATUS_SET (1<<3) #define MAPTYPE_TXT (1<<0) #define MAPTYPE_DBM (1<<1) @@ -208,6 +210,7 @@ static const char* really_last_key = "rewrite_really_last"; #define OPTION_IGNORE_INHERIT (1<<8) #define OPTION_IGNORE_CONTEXT_INFO (1<<9) #define OPTION_LEGACY_PREFIX_DOCROOT (1<<10) +#define OPTION_UNSAFE_PREFIX_STAT (1<<12) #ifndef RAND_MAX #define RAND_MAX 32767 @@ -301,6 +304,14 @@ typedef enum { CONDPAT_AP_EXPR } pattern_type; +typedef enum { + RULE_RC_NOMATCH = 0, /* the rule didn't match */ + RULE_RC_MATCH = 1, /* a matching rule w/ substitution */ + RULE_RC_NOSUB = 2, /* a matching rule w/ no substitution */ + RULE_RC_STATUS_SET = 3 /* a matching rule that has set an HTTP error + to be returned in r->status */ +} rule_return_type; + typedef struct { char *input; /* Input string of RewriteCond */ char *pattern; /* the RegExp pattern string */ @@ -642,6 +653,16 @@ static unsigned is_absolute_uri(char *uri, int *supportsqs) return 0; } +static int is_absolute_path(const char *path) +{ +#ifndef CASE_BLIND_FILESYSTEM + return (path[0] == '/'); +#else + return ((AP_IS_SLASH(path[0]) && path[1] == path[0]) + || (apr_isalpha(path[0]) && path[1] == ':' && AP_IS_SLASH(path[2]))); +#endif +} + static const char c2x_table[] = "0123456789abcdef"; static APR_INLINE unsigned char *c2x(unsigned what, unsigned char prefix, @@ -927,10 +948,15 @@ static void fully_qualify_uri(request_rec *r) return; } +static int startsWith(request_rec *r, const char *haystack, const char *needle) { + int rc = (ap_strstr_c(haystack, needle) == haystack); + rewritelog((r, 5, NULL, "prefix_stat startsWith(%s, %s) %d", haystack, needle, rc)); + return rc; +} /* - * stat() only the first segment of a path + * stat() only the first segment of a path, and only if it matches the output of the last matching rule */ -static int prefix_stat(const char *path, apr_pool_t *pool) +static int prefix_stat(request_rec *r, const char *path, apr_pool_t *pool, rewriterule_entry *lastsub) { const char *curpath = path; const char *root; @@ -964,10 +990,36 @@ static int prefix_stat(const char *path, apr_pool_t *pool) apr_finfo_t sb; if (apr_stat(&sb, statpath, APR_FINFO_MIN, pool) == APR_SUCCESS) { - return 1; + if (!lastsub) { + rewritelog((r, 3, NULL, "prefix_stat no lastsub subst prefix %s", statpath)); + return 1; + } + + rewritelog((r, 3, NULL, "prefix_stat compare statpath %s and lastsub output %s STATOK %d ", + statpath, lastsub->output, lastsub->flags & RULEFLAG_UNSAFE_PREFIX_STAT)); + if (lastsub->flags & RULEFLAG_UNSAFE_PREFIX_STAT) { + return 1; + } + else { + const char *docroot = ap_document_root(r); + const char *context_docroot = ap_context_document_root(r); + /* + * As an example, path (r->filename) is /var/foo/bar/baz.html + * even if the flag is not set, we can accept a rule that + * began with a literal /var (stapath), or if the entire path + * starts with the docroot or context document root + */ + if (startsWith(r, lastsub->output, statpath) || + startsWith(r, path, docroot) || + ((docroot != context_docroot) && + startsWith(r, path, context_docroot))) { + return 1; + } + } } } + /* prefix will be added */ return 0; } @@ -3072,6 +3124,9 @@ static const char *cmd_rewriteoptions(cmd_parms *cmd, else if (!strcasecmp(w, "legacyprefixdocroot")) { options |= OPTION_LEGACY_PREFIX_DOCROOT; } + else if (!strcasecmp(w, "UnsafePrefixStat")) { + options |= OPTION_UNSAFE_PREFIX_STAT; + } else { return apr_pstrcat(cmd->pool, "RewriteOptions: unknown option '", w, "'", NULL); @@ -3780,6 +3835,18 @@ static const char *cmd_rewriterule_setflag(apr_pool_t *p, void *_cfg, ++error; } break; + case 'u': + case 'U': + if (!strcasecmp(key, "nsafePrefixStat")){ + cfg->flags |= (RULEFLAG_UNSAFE_PREFIX_STAT); + } + else if(!strcasecmp(key, "nsafeAllow3F")) { + cfg->flags |= RULEFLAG_UNSAFE_ALLOW3F; + } + else { + ++error; + } + break; default: ++error; break; @@ -4138,7 +4205,8 @@ static APR_INLINE void force_type_handler(rewriterule_entry *p, /* * Apply a single RewriteRule */ -static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) +static rule_return_type apply_rewrite_rule(rewriterule_entry *p, + rewrite_ctx *ctx) { ap_regmatch_t regmatch[AP_MAX_REG_MATCH]; apr_array_header_t *rewriteconds; @@ -4189,7 +4257,7 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) rc = !ap_regexec(p->regexp, ctx->uri, AP_MAX_REG_MATCH, regmatch, 0); if (! (( rc && !(p->flags & RULEFLAG_NOTMATCH)) || (!rc && (p->flags & RULEFLAG_NOTMATCH)) ) ) { - return 0; + return RULE_RC_NOMATCH; } /* It matched, wow! Now it's time to prepare the context structure for @@ -4240,7 +4308,7 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) } } else if (!rc) { - return 0; + return RULE_RC_NOMATCH; } /* If some HTTP header was involved in the condition, remember it @@ -4260,6 +4328,15 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) newuri = do_expand(p->output, ctx, p); rewritelog((r, 2, ctx->perdir, "rewrite '%s' -> '%s'", ctx->uri, newuri)); + if (!(p->flags & RULEFLAG_UNSAFE_ALLOW3F) && + ap_strcasestr(r->unparsed_uri, "%3f") && + ap_strchr_c(newuri, '?')) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10508) + "Unsafe URL with %%3f URL rewritten without " + "UnsafeAllow3F"); + r->status = HTTP_FORBIDDEN; + return RULE_RC_STATUS_SET; + } } /* expand [E=var:val] and [CO=<cookie>] */ @@ -4277,7 +4354,35 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) r->status = p->forced_responsecode; } - return 2; + return RULE_RC_NOSUB; + } + + /* Add the previously stripped per-directory location prefix, unless + * (1) it's an absolute URL path and + * (2) it's a full qualified URL + */ + if (!is_proxyreq + && !is_absolute_path(newuri) + && !AP_IS_SLASH(*newuri) + && !is_absolute_uri(newuri, NULL)) { + if (ctx->perdir) { + rewritelog((r, 3, ctx->perdir, "add per-dir prefix: %s -> %s%s", + newuri, ctx->perdir, newuri)); + newuri = apr_pstrcat(r->pool, ctx->perdir, newuri, NULL); + } + else if (!(p->flags & (RULEFLAG_PROXY | RULEFLAG_FORCEREDIRECT))) { + /* Not an absolute URI-path and the scheme (if any) is unknown, + * and it won't be passed to fully_qualify_uri() below either, + * so add an implicit '/' prefix. This avoids potentially a common + * rule like "RewriteRule ^/some/path(.*) $1" that is given a path + * like "/some/pathscheme:..." to produce the fully qualified URL + * "scheme:..." which could be misinterpreted later. + */ + rewritelog((r, 3, ctx->perdir, "add root prefix: %s -> /%s", + newuri, newuri)); + + newuri = apr_pstrcat(r->pool, "/", newuri, NULL); + } } /* Now adjust API's knowledge about r->filename and r->args */ @@ -4289,18 +4394,6 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) splitout_queryargs(r, p->flags); - /* Add the previously stripped per-directory location prefix, unless - * (1) it's an absolute URL path and - * (2) it's a full qualified URL - */ - if ( ctx->perdir && !is_proxyreq && *r->filename != '/' - && !is_absolute_uri(r->filename, NULL)) { - rewritelog((r, 3, ctx->perdir, "add per-dir prefix: %s -> %s%s", - r->filename, ctx->perdir, r->filename)); - - r->filename = apr_pstrcat(r->pool, ctx->perdir, r->filename, NULL); - } - /* If this rule is forced for proxy throughput * (`RewriteRule ... ... [P]') then emulate mod_proxy's * URL-to-filename handler to be sure mod_proxy is triggered @@ -4329,7 +4422,7 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) r->filename)); r->filename = apr_pstrcat(r->pool, "proxy:", r->filename, NULL); - return 1; + return RULE_RC_MATCH; } /* If this rule is explicitly forced for HTTP redirection @@ -4344,7 +4437,7 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) r->filename)); r->status = p->forced_responsecode; - return 1; + return RULE_RC_MATCH; } /* Special Rewriting Feature: Self-Reduction @@ -4366,7 +4459,7 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) "with %s", p->forced_responsecode, r->filename)); r->status = p->forced_responsecode; - return 1; + return RULE_RC_MATCH; } /* Finally remember the forced mime-type */ @@ -4375,7 +4468,7 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) /* Puuhhhhhhhh... WHAT COMPLICATED STUFF ;_) * But now we're done for this particular rule. */ - return 1; + return RULE_RC_MATCH; } /* @@ -4383,13 +4476,13 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) * i.e. a list of rewrite rules */ static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules, - char *perdir) + char *perdir, rewriterule_entry **lastsub) { rewriterule_entry *entries; rewriterule_entry *p; int i; int changed; - int rc; + rule_return_type rc; int s; rewrite_ctx *ctx; int round = 1; @@ -4397,6 +4490,7 @@ static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules, ctx = apr_palloc(r->pool, sizeof(*ctx)); ctx->perdir = perdir; ctx->r = r; + *lastsub = NULL; /* * Iterate over all existing rules @@ -4424,7 +4518,12 @@ static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules, ctx->vary = NULL; rc = apply_rewrite_rule(p, ctx); - if (rc) { + if (rc != RULE_RC_NOMATCH) { + + if (!(p->flags & RULEFLAG_NOSUB)) { + rewritelog((r, 2, perdir, "setting lastsub to rule with output %s", p->output)); + *lastsub = p; + } /* Catch looping rules with pathinfo growing unbounded */ if ( strlen( r->filename ) > 2*r->server->limit_req_line ) { @@ -4444,6 +4543,12 @@ static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules, apr_table_merge(r->headers_out, "Vary", ctx->vary); } + + /* Error while evaluating rule, r->status set */ + if (RULE_RC_STATUS_SET == rc) { + return ACTION_STATUS_SET; + } + /* * The rule sets the response code (implies match-only) */ @@ -4454,7 +4559,7 @@ static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules, /* * Indicate a change if this was not a match-only rule. */ - if (rc != 2) { + if (rc != RULE_RC_NOSUB) { changed = ((p->flags & RULEFLAG_NOESCAPE) ? ACTION_NOESCAPE : ACTION_NORMAL); } @@ -4643,6 +4748,7 @@ static int hook_uri2file(request_rec *r) int rulestatus; void *skipdata; const char *oargs; + rewriterule_entry *lastsub = NULL; /* * retrieve the config structures @@ -4754,7 +4860,7 @@ static int hook_uri2file(request_rec *r) /* * now apply the rules ... */ - rulestatus = apply_rewrite_list(r, conf->rewriterules, NULL); + rulestatus = apply_rewrite_list(r, conf->rewriterules, NULL, &lastsub); apr_table_setn(r->notes, "mod_rewrite_rewritten", apr_psprintf(r->pool,"%d",rulestatus)); } @@ -4792,6 +4898,9 @@ static int hook_uri2file(request_rec *r) r->status = HTTP_OK; return n; } + else if (ACTION_STATUS_SET == rulestatus) { + return r->status; + } if (to_proxyreq) { /* it should be go on as an internal proxy request */ @@ -4911,23 +5020,29 @@ static int hook_uri2file(request_rec *r) return HTTP_BAD_REQUEST; } - /* if there is no valid prefix, we call - * the translator from the core and - * prefix the filename with document_root + /* We have r->filename as a path in a server-context rewrite without + * the PT flag. The historical behavior is to treat it as a verbatim + * filesystem path iff the first component of the path exists and is + * readable by httpd. Otherwise, it is interpreted as DocumentRoot + * relative. * * NOTICE: * We cannot leave out the prefix_stat because - * - when we always prefix with document_root - * then no absolute path can be created, e.g. via - * emulating a ScriptAlias directive, etc. - * - when we always NOT prefix with document_root + * - If we always prefix with document_root + * then no absolute path can could ever be used in + * a substitution. e.g. emulating an Alias. + * - If we never prefix with document_root * then the files under document_root have to * be references directly and document_root * gets never used and will be a dummy parameter - - * this is also bad + * this is also bad. + * - Later addition: This part is questionable. + * If we had never prefixed, users would just + * need %{DOCUMENT_ROOT} in substitutions or the + * [PT] flag. * * BUT: - * Under real Unix systems this is no problem, + * Under real Unix systems this is no perf problem, * because we only do stat() on the first directory * and this gets cached by the kernel for along time! */ @@ -4936,7 +5051,9 @@ static int hook_uri2file(request_rec *r) uri_reduced = apr_table_get(r->notes, "mod_rewrite_uri_reduced"); } - if (!prefix_stat(r->filename, r->pool) || uri_reduced != NULL) { + if (!prefix_stat(r, r->filename, r->pool, + conf->options & OPTION_UNSAFE_PREFIX_STAT ? NULL : lastsub) + || uri_reduced != NULL) { int res; char *tmp = r->uri; @@ -4981,6 +5098,7 @@ static int hook_fixup(request_rec *r) char *ofilename, *oargs; int is_proxyreq; void *skipdata; + rewriterule_entry *lastsub; dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config, &rewrite_module); @@ -5065,7 +5183,7 @@ static int hook_fixup(request_rec *r) /* * now apply the rules ... */ - rulestatus = apply_rewrite_list(r, dconf->rewriterules, dconf->directory); + rulestatus = apply_rewrite_list(r, dconf->rewriterules, dconf->directory, &lastsub); if (rulestatus) { unsigned skip_absolute = is_absolute_uri(r->filename, NULL); int to_proxyreq = 0; @@ -5094,6 +5212,9 @@ static int hook_fixup(request_rec *r) r->status = HTTP_OK; return n; } + else if (ACTION_STATUS_SET == rulestatus) { + return r->status; + } if (to_proxyreq) { /* it should go on as an internal proxy request */ @@ -5333,7 +5454,7 @@ static int hook_mimetype(request_rec *r) rewritelog((r, 1, NULL, "force filename %s to have MIME-type '%s'", r->filename, t)); - ap_set_content_type(r, t); + ap_set_content_type_ex(r, t, 1); } /* handler */ diff --git a/modules/md/md_crypt.c b/modules/md/md_crypt.c index 4b2af89..ca44fab 100644 --- a/modules/md/md_crypt.c +++ b/modules/md/md_crypt.c @@ -57,21 +57,11 @@ #include <process.h> #endif -#if defined(LIBRESSL_VERSION_NUMBER) -/* Missing from LibreSSL */ -#define MD_USE_OPENSSL_PRE_1_1_API (LIBRESSL_VERSION_NUMBER < 0x2070000f) -#else /* defined(LIBRESSL_VERSION_NUMBER) */ -#define MD_USE_OPENSSL_PRE_1_1_API (OPENSSL_VERSION_NUMBER < 0x10100000L) -#endif - -#if (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x3050000fL)) || (OPENSSL_VERSION_NUMBER < 0x10100000L) +#if !defined(OPENSSL_NO_CT) \ + && OPENSSL_VERSION_NUMBER >= 0x10100000L \ + && (!defined(LIBRESSL_VERSION_NUMBER) \ + || LIBRESSL_VERSION_NUMBER >= 0x3050000fL) /* Missing from LibreSSL < 3.5.0 and only available since OpenSSL v1.1.x */ -#ifndef OPENSSL_NO_CT -#define OPENSSL_NO_CT -#endif -#endif - -#ifndef OPENSSL_NO_CT #include <openssl/ct.h> #endif @@ -955,12 +945,9 @@ apr_status_t md_pkey_gen(md_pkey_t **ppkey, apr_pool_t *p, md_pkey_spec_t *spec) } } -#if MD_USE_OPENSSL_PRE_1_1_API || (defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER < 0x2070000f) - -#ifndef NID_tlsfeature -#define NID_tlsfeature 1020 -#endif +#if OPENSSL_VERSION_NUMBER < 0x10100000L \ + || (defined(LIBRESSL_VERSION_NUMBER) \ + && LIBRESSL_VERSION_NUMBER < 0x2070000f) static void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d) diff --git a/modules/md/md_ocsp.c b/modules/md/md_ocsp.c index 8cbf05b..8276137 100644 --- a/modules/md/md_ocsp.c +++ b/modules/md/md_ocsp.c @@ -32,13 +32,6 @@ #include <openssl/pem.h> #include <openssl/x509v3.h> -#if defined(LIBRESSL_VERSION_NUMBER) -/* Missing from LibreSSL */ -#define MD_USE_OPENSSL_PRE_1_1_API (LIBRESSL_VERSION_NUMBER < 0x2070000f) -#else /* defined(LIBRESSL_VERSION_NUMBER) */ -#define MD_USE_OPENSSL_PRE_1_1_API (OPENSSL_VERSION_NUMBER < 0x10100000L) -#endif - #include "md.h" #include "md_crypt.h" #include "md_event.h" @@ -563,7 +556,9 @@ static const char *single_resp_summary(OCSP_SINGLERESP* resp, apr_pool_t *p) ASN1_GENERALIZEDTIME *bup = NULL, *bnextup = NULL; md_timeperiod_t valid; -#if MD_USE_OPENSSL_PRE_1_1_API +#if OPENSSL_VERSION_NUMBER < 0x10100000L \ + || (defined(LIBRESSL_VERSION_NUMBER) \ + && LIBRESSL_VERSION_NUMBER < 0x2070000f) certid = resp->certId; #else certid = OCSP_SINGLERESP_get0_id(resp); @@ -683,12 +678,6 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton) md_result_log(update->result, MD_LOG_DEBUG); goto cleanup; } - if (!bnextup) { - rv = APR_EINVAL; - md_result_set(update->result, rv, "OCSP basicresponse reports not valid dates"); - md_result_log(update->result, MD_LOG_DEBUG); - goto cleanup; - } /* Coming here, we have a response for our certid and it is either GOOD * or REVOKED. Both cases we want to remember and use in stapling. */ @@ -703,7 +692,14 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton) new_der.free_data = md_openssl_free; nstat = (bstatus == V_OCSP_CERTSTATUS_GOOD)? MD_OCSP_CERT_ST_GOOD : MD_OCSP_CERT_ST_REVOKED; valid.start = bup? md_asn1_generalized_time_get(bup) : apr_time_now(); - valid.end = md_asn1_generalized_time_get(bnextup); + if (bnextup) { + valid.end = md_asn1_generalized_time_get(bnextup); + } + else { + /* nextUpdate not set; default to 12 hours. + * Refresh attempts will be started some time earlier. */ + valid.end = valid.start + apr_time_from_sec(MD_SECS_PER_DAY / 2); + } /* First, update the instance with a copy */ apr_thread_mutex_lock(ostat->reg->mutex); diff --git a/modules/md/md_reg.c b/modules/md/md_reg.c index 8bceb0e..6aa7d78 100644 --- a/modules/md/md_reg.c +++ b/modules/md/md_reg.c @@ -31,6 +31,7 @@ #include "md_json.h" #include "md_result.h" #include "md_reg.h" +#include "md_ocsp.h" #include "md_store.h" #include "md_status.h" #include "md_tailscale.h" @@ -1321,3 +1322,38 @@ md_job_t *md_reg_job_make(md_reg_t *reg, const char *mdomain, apr_pool_t *p) { return md_job_make(p, reg->store, MD_SG_STAGING, mdomain, reg->min_delay); } + +static int get_cert_count(const md_t *md) +{ + if (md->cert_files && md->cert_files->nelts) { + return md->cert_files->nelts; + } + return md_pkeys_spec_count(md->pks); +} + +int md_reg_has_revoked_certs(md_reg_t *reg, struct md_ocsp_reg_t *ocsp, + const md_t *md, apr_pool_t *p) +{ + const md_pubcert_t *pubcert; + const md_cert_t *cert; + md_timeperiod_t ocsp_valid; + md_ocsp_cert_stat_t cert_stat; + apr_status_t rv = APR_SUCCESS; + int i; + + if (!md->stapling || !ocsp) + return 0; + + for (i = 0; i < get_cert_count(md); ++i) { + if (APR_SUCCESS != md_reg_get_pubcert(&pubcert, reg, md, i, p)) + continue; + cert = APR_ARRAY_IDX(pubcert->certs, 0, const md_cert_t*); + if(!cert) + continue; + rv = md_ocsp_get_meta(&cert_stat, &ocsp_valid, ocsp, cert, p, md); + if (APR_SUCCESS == rv && cert_stat == MD_OCSP_CERT_ST_REVOKED) { + return 1; + } + } + return 0; +} diff --git a/modules/md/md_reg.h b/modules/md/md_reg.h index 58ee16a..191b026 100644 --- a/modules/md/md_reg.h +++ b/modules/md/md_reg.h @@ -23,6 +23,7 @@ struct md_pkey_t; struct md_cert_t; struct md_result_t; struct md_pkey_spec_t; +struct md_ocsp_reg_t; #include "md_store.h" @@ -310,4 +311,10 @@ apr_status_t md_reg_lock_global(md_reg_t *reg, apr_pool_t *p); */ void md_reg_unlock_global(md_reg_t *reg, apr_pool_t *p); +/** + * @return != 0 iff `md` has any certificates known to be REVOKED. + */ +int md_reg_has_revoked_certs(md_reg_t *reg, struct md_ocsp_reg_t *ocsp, + const md_t *md, apr_pool_t *p); + #endif /* mod_md_md_reg_h */ diff --git a/modules/md/md_version.h b/modules/md/md_version.h index 86a1821..cefbb8d 100644 --- a/modules/md/md_version.h +++ b/modules/md/md_version.h @@ -27,7 +27,7 @@ * @macro * Version number of the md module as c string */ -#define MOD_MD_VERSION "2.4.25" +#define MOD_MD_VERSION "2.4.26" /** * @macro @@ -35,7 +35,7 @@ * release. This is a 24 bit number with 8 bits for major number, 8 bits * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203. */ -#define MOD_MD_VERSION_NUM 0x020419 +#define MOD_MD_VERSION_NUM 0x02041a #define MD_ACME_DEF_URL "https://acme-v02.api.letsencrypt.org/directory" #define MD_TAILSCALE_DEF_URL "file://localhost/var/run/tailscale/tailscaled.sock" diff --git a/modules/md/mod_md_config.c b/modules/md/mod_md_config.c index 31d06b4..cdd1e29 100644 --- a/modules/md/mod_md_config.c +++ b/modules/md/mod_md_config.c @@ -84,6 +84,7 @@ static md_mod_conf_t defmc = { "crt.sh", /* default cert checker site name */ "https://crt.sh?q=", /* default cert checker site url */ NULL, /* CA cert file to use */ + apr_time_from_sec(MD_SECS_PER_DAY/2), /* default time between cert checks */ apr_time_from_sec(5), /* minimum delay for retries */ 13, /* retry_failover after 14 errors, with 5s delay ~ half a day */ 0, /* store locks, disabled by default */ @@ -624,6 +625,24 @@ static const char *md_config_set_base_server(cmd_parms *cmd, void *dc, const cha return set_on_off(&config->mc->manage_base_server, value, cmd->pool); } +static const char *md_config_set_check_interval(cmd_parms *cmd, void *dc, const char *value) +{ + md_srv_conf_t *config = md_config_get(cmd->server); + const char *err = md_conf_check_location(cmd, MD_LOC_NOT_MD); + apr_time_t interval; + + (void)dc; + if (err) return err; + if (md_duration_parse(&interval, value, "s") != APR_SUCCESS) { + return "unrecognized duration format"; + } + if (interval < apr_time_from_sec(1)) { + return "check interval cannot be less than one second"; + } + config->mc->check_interval = interval; + return NULL; +} + static const char *md_config_set_min_delay(cmd_parms *cmd, void *dc, const char *value) { md_srv_conf_t *config = md_config_get(cmd->server); @@ -1304,7 +1323,8 @@ const command_rec md_cmds[] = { "Configure locking of store for updates."), AP_INIT_TAKE1("MDMatchNames", md_config_set_match_mode, NULL, RSRC_CONF, "Determines how DNS names are matched to vhosts."), - + AP_INIT_TAKE1("MDCheckInterval", md_config_set_check_interval, NULL, RSRC_CONF, + "Time between certificate checks."), AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL) }; diff --git a/modules/md/mod_md_config.h b/modules/md/mod_md_config.h index 7e87440..1ce2375 100644 --- a/modules/md/mod_md_config.h +++ b/modules/md/mod_md_config.h @@ -75,6 +75,7 @@ struct md_mod_conf_t { const char *cert_check_name; /* name of the linked certificate check site */ const char *cert_check_url; /* url "template for" checking a certificate */ const char *ca_certs; /* root certificates to use for connections */ + apr_time_t check_interval; /* duration between cert renewal checks */ apr_time_t min_delay; /* minimum delay for retries */ int retry_failover; /* number of errors to trigger CA failover */ int use_store_locks; /* use locks when updating store */ diff --git a/modules/md/mod_md_drive.c b/modules/md/mod_md_drive.c index 5565f44..d2655b8 100644 --- a/modules/md/mod_md_drive.c +++ b/modules/md/mod_md_drive.c @@ -100,7 +100,7 @@ static void process_drive_job(md_renew_ctx_t *dctx, md_job_t *job, apr_pool_t *p } if (md_will_renew_cert(md)) { - /* Renew the MDs credentials in a STAGING area. Might be invoked repeatedly + /* Renew the MDs credentials in a STAGING area. Might be invoked repeatedly * without discarding previous/intermediate results. * Only returns SUCCESS when the renewal is complete, e.g. STAGING has a * complete set of new credentials. @@ -108,7 +108,12 @@ static void process_drive_job(md_renew_ctx_t *dctx, md_job_t *job, apr_pool_t *p ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10052) "md(%s): state=%d, driving", job->mdomain, md->state); - if (!md_reg_should_renew(dctx->mc->reg, md, dctx->p)) { + if (md->stapling && dctx->mc->ocsp && + md_reg_has_revoked_certs(dctx->mc->reg, dctx->mc->ocsp, md, dctx->p)) { + ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10500) + "md(%s): has revoked certificates", job->mdomain); + } + else if (!md_reg_should_renew(dctx->mc->reg, md, dctx->p)) { ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10053) "md(%s): no need to renew", job->mdomain); goto expiry; @@ -180,10 +185,13 @@ int md_will_renew_cert(const md_t *md) return 1; } -static apr_time_t next_run_default(void) +static apr_time_t next_run_default(md_renew_ctx_t *dctx) { - /* we'd like to run at least twice a day by default */ - return apr_time_now() + apr_time_from_sec(MD_SECS_PER_DAY / 2); + unsigned char c; + apr_time_t delay = dctx->mc->check_interval; + + md_rand_bytes(&c, sizeof(c), dctx->p); + return apr_time_now() + delay + (delay * (c - 128) / 256); } static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp) @@ -211,7 +219,7 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp) * and we schedule ourself at the earliest of all. A job may specify 0 * as next_run to indicate that it wants to participate in the normal * regular runs. */ - next_run = next_run_default(); + next_run = next_run_default(dctx); for (i = 0; i < dctx->jobs->nelts; ++i) { job = APR_ARRAY_IDX(dctx->jobs, i, md_job_t *); diff --git a/modules/metadata/mod_headers.c b/modules/metadata/mod_headers.c index ef812cd..4838bd6 100644 --- a/modules/metadata/mod_headers.c +++ b/modules/metadata/mod_headers.c @@ -783,14 +783,14 @@ static int do_headers_fixup(request_rec *r, apr_table_t *headers, break; case hdr_set: if (!ap_cstr_casecmp(hdr->header, "Content-Type")) { - ap_set_content_type(r, process_tags(hdr, r)); + ap_set_content_type_ex(r, process_tags(hdr, r), 1); } apr_table_setn(headers, hdr->header, process_tags(hdr, r)); break; case hdr_setifempty: if (NULL == apr_table_get(headers, hdr->header)) { if (!ap_cstr_casecmp(hdr->header, "Content-Type")) { - ap_set_content_type(r, process_tags(hdr, r)); + ap_set_content_type_ex(r, process_tags(hdr, r), 1); } apr_table_setn(headers, hdr->header, process_tags(hdr, r)); } @@ -809,7 +809,7 @@ static int do_headers_fixup(request_rec *r, apr_table_t *headers, const char *repl = process_regexp(hdr, r->content_type, r); if (repl == NULL) return 0; - ap_set_content_type(r, repl); + ap_set_content_type_ex(r, repl, 1); } if (apr_table_get(headers, hdr->header)) { edit_do ed; diff --git a/modules/metadata/mod_mime_magic.c b/modules/metadata/mod_mime_magic.c index 7dac4fd..1c96db4 100644 --- a/modules/metadata/mod_mime_magic.c +++ b/modules/metadata/mod_mime_magic.c @@ -788,7 +788,7 @@ static int magic_rsl_to_request(request_rec *r) /* XXX: this could be done at config time I'm sure... but I'm * confused by all this magic_rsl stuff. -djg */ ap_content_type_tolower(tmp); - ap_set_content_type(r, tmp); + ap_set_content_type_ex(r, tmp, 1); if (state == rsl_encoding) { tmp = rsl_strdup(r, encoding_frag, @@ -2326,7 +2326,7 @@ static int revision_suffix(request_rec *r) /* extract content type/encoding/language from sub-request */ if (sub->content_type) { - ap_set_content_type(r, apr_pstrdup(r->pool, sub->content_type)); + ap_set_content_type_ex(r, apr_pstrdup(r->pool, sub->content_type), 1); #if MIME_MAGIC_DEBUG ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01557) MODNAME ": subrequest %s got %s", diff --git a/modules/proxy/mod_proxy.c b/modules/proxy/mod_proxy.c index c9cef7c..ad0c031 100644 --- a/modules/proxy/mod_proxy.c +++ b/modules/proxy/mod_proxy.c @@ -1245,6 +1245,7 @@ static int proxy_fixup(request_rec *r) return OK; /* otherwise; we've done the best we can */ } + /* Send a redirection if the request contains a hostname which is not */ /* fully qualified, i.e. doesn't have a domain name appended. Some proxy */ /* servers like Netscape's allow this and access hosts from the local */ @@ -1298,7 +1299,7 @@ static int proxy_handler(request_rec *r) ap_get_module_config(sconf, &proxy_module); apr_array_header_t *proxies = conf->proxies; struct proxy_remote *ents = (struct proxy_remote *) proxies->elts; - int i, rc, access_status; + int rc = DECLINED, access_status, i; int direct_connect = 0; const char *str; apr_int64_t maxfwd; @@ -1313,19 +1314,28 @@ static int proxy_handler(request_rec *r) return DECLINED; } - if (!r->proxyreq) { - /* We may have forced the proxy handler via config or .htaccess */ - if (r->handler && - strncmp(r->handler, "proxy:", 6) == 0 && - strncmp(r->filename, "proxy:", 6) != 0) { - r->proxyreq = PROXYREQ_REVERSE; - r->filename = apr_pstrcat(r->pool, r->handler, r->filename, NULL); + /* We may have forced the proxy handler via config or .htaccess */ + if (!r->proxyreq && r->handler && strncmp(r->handler, "proxy:", 6) == 0) { + char *old_filename = r->filename; + + r->proxyreq = PROXYREQ_REVERSE; + r->filename = apr_pstrcat(r->pool, r->handler, r->filename, NULL); + + /* Still need to fixup/canonicalize r->filename */ + rc = ap_proxy_fixup_uds_filename(r); + if (rc <= OK) { + rc = proxy_fixup(r); } - else { - return DECLINED; + if (rc != OK) { + r->filename = old_filename; + r->proxyreq = 0; } - } else if (strncmp(r->filename, "proxy:", 6) != 0) { - return DECLINED; + } + else if (r->proxyreq && strncmp(r->filename, "proxy:", 6) == 0) { + rc = OK; + } + if (rc != OK) { + return rc; } /* handle max-forwards / OPTIONS / TRACE */ diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h index 51a55f8..59572bf 100644 --- a/modules/proxy/mod_proxy.h +++ b/modules/proxy/mod_proxy.h @@ -1003,6 +1003,14 @@ PROXY_DECLARE(proxy_balancer_shared *) ap_proxy_find_balancershm(ap_slotmem_prov proxy_balancer *balancer, unsigned int *index); +/* + * Strip the UDS part of r->filename if any, and put the UDS path in + * r->notes ("uds_path") + * @param r current request + * @return OK if fixed up, DECLINED if not UDS, or an HTTP_XXX error + */ +PROXY_DECLARE(int) ap_proxy_fixup_uds_filename(request_rec *r); + /** * Get the most suitable worker and/or balancer for the request * @param worker worker used for processing request diff --git a/modules/proxy/proxy_util.c b/modules/proxy/proxy_util.c index a54a4fa..e71cbd8 100644 --- a/modules/proxy/proxy_util.c +++ b/modules/proxy/proxy_util.c @@ -1512,8 +1512,9 @@ static void socket_cleanup(proxy_conn_rec *conn) apr_pool_clear(conn->scpool); } -static void address_cleanup(proxy_conn_rec *conn) +static void conn_cleanup(proxy_conn_rec *conn) { + socket_cleanup(conn); conn->address = NULL; conn->addr = NULL; conn->hostname = NULL; @@ -1522,9 +1523,6 @@ static void address_cleanup(proxy_conn_rec *conn) if (conn->uds_pool) { apr_pool_clear(conn->uds_pool); } - if (conn->sock) { - socket_cleanup(conn); - } } static apr_status_t conn_pool_cleanup(void *theworker) @@ -2431,7 +2429,7 @@ static int ap_proxy_retry_worker(const char *proxy_function, proxy_worker *worke * were passed a UDS url (eg: from mod_proxy) and adjust uds_path * as required. */ -static int fix_uds_filename(request_rec *r, char **url) +PROXY_DECLARE(int) ap_proxy_fixup_uds_filename(request_rec *r) { char *uds_url = r->filename + 6, *origin_url; @@ -2439,7 +2437,6 @@ static int fix_uds_filename(request_rec *r, char **url) !ap_cstr_casecmpn(uds_url, "unix:", 5) && (origin_url = ap_strchr(uds_url + 5, '|'))) { char *uds_path = NULL; - apr_size_t url_len; apr_uri_t urisock; apr_status_t rv; @@ -2454,20 +2451,20 @@ static int fix_uds_filename(request_rec *r, char **url) if (!uds_path) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10292) "Invalid proxy UDS filename (%s)", r->filename); - return 0; + return HTTP_BAD_REQUEST; } apr_table_setn(r->notes, "uds_path", uds_path); - /* Remove the UDS path from *url and r->filename */ - url_len = strlen(origin_url); - *url = apr_pstrmemdup(r->pool, origin_url, url_len); - memcpy(uds_url, *url, url_len + 1); - ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, - "*: rewrite of url due to UDS(%s): %s (%s)", - uds_path, *url, r->filename); + "*: fixup UDS from %s: %s (%s)", + r->filename, origin_url, uds_path); + + /* Overwrite the UDS part in place */ + memmove(uds_url, origin_url, strlen(origin_url) + 1); + return OK; } - return 1; + + return DECLINED; } PROXY_DECLARE(int) ap_proxy_pre_request(proxy_worker **worker, @@ -2486,9 +2483,6 @@ PROXY_DECLARE(int) ap_proxy_pre_request(proxy_worker **worker, ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "%s: found worker %s for %s", (*worker)->s->scheme, (*worker)->s->name_ex, *url); - if (!forward && !fix_uds_filename(r, url)) { - return HTTP_INTERNAL_SERVER_ERROR; - } access_status = OK; } else if (forward) { @@ -2518,9 +2512,6 @@ PROXY_DECLARE(int) ap_proxy_pre_request(proxy_worker **worker, * regarding the Connection header in the request. */ apr_table_setn(r->subprocess_env, "proxy-nokeepalive", "1"); - if (!fix_uds_filename(r, url)) { - return HTTP_INTERNAL_SERVER_ERROR; - } } } } @@ -2530,6 +2521,20 @@ PROXY_DECLARE(int) ap_proxy_pre_request(proxy_worker **worker, "all workers are busy. Unable to serve %s", *url); access_status = HTTP_SERVICE_UNAVAILABLE; } + + if (access_status == OK && r->proxyreq == PROXYREQ_REVERSE) { + int rc = ap_proxy_fixup_uds_filename(r); + if (ap_is_HTTP_ERROR(rc)) { + return rc; + } + /* If the URL has changed in r->filename, take everything after + * the "proxy:" prefix. + */ + if (rc == OK) { + *url = apr_pstrdup(r->pool, r->filename + 6); + } + } + return access_status; } @@ -2784,8 +2789,8 @@ static apr_status_t worker_address_resolve(proxy_worker *worker, apr_sockaddr_t *addr = *paddr; for (; addr; addr = addr->next) { addrs = apr_psprintf(pool, "%s%s%pI", - addrs ? ", " : "", addrs ? addrs : "", + addrs ? ", " : "", addr); } if (r) { @@ -2959,15 +2964,16 @@ PROXY_DECLARE(apr_status_t) ap_proxy_determine_address(const char *proxy_functio */ address->expiry = apr_atomic_read32(&worker->s->address_expiry); if (address->expiry <= now) { - apr_uint32_t new_expiry = address->expiry + ttl; - while (new_expiry <= now) { - new_expiry += ttl; - } - new_expiry = apr_atomic_cas32(&worker->s->address_expiry, - new_expiry, address->expiry); - /* race lost? well the expiry should grow anyway.. */ - AP_DEBUG_ASSERT(new_expiry > now); - address->expiry = new_expiry; + apr_uint32_t prev, next = (now + ttl) - (now % ttl); + do { + prev = apr_atomic_cas32(&worker->s->address_expiry, + next, address->expiry); + if (prev == address->expiry) { + address->expiry = next; + break; + } + address->expiry = prev; + } while (prev <= now); } } else { @@ -3008,13 +3014,40 @@ PROXY_DECLARE(apr_status_t) ap_proxy_determine_address(const char *proxy_functio PROXY_THREAD_UNLOCK(worker); - /* Kill any socket using the old address */ - if (conn->sock) { - if (r ? APLOGrdebug(r) : APLOGdebug(s)) { - /* XXX: this requires the old conn->addr[ess] to still - * be alive since it's not copied by apr_socket_connect() - * in ap_proxy_connect_backend(). - */ + /* Release the old conn address */ + if (conn->address) { + /* On Windows and OS/2, apr_socket_connect() called from + * ap_proxy_connect_backend() does a simple pointer copy of + * its given conn->addr[->next] into conn->sock->remote_addr. + * Thus conn->addr cannot be freed if the conn->sock should be + * kept alive (same new and old addresses) and the old address + * is still in conn->sock->remote_addr. In this case we rather + * delay the release of the old address by moving the cleanup + * to conn->scpool such that it runs when the socket is closed. + * In any other case, including other platforms, just release + * the old address now since conn->sock->remote_addr is either + * obsolete (socket forcibly closed) or a copy on conn->scpool + * already (not a dangling pointer). + */ + int keep_addr_alive = 0, + keep_conn_alive = (conn->sock && conn->addr && + proxy_addrs_equal(conn->addr, + address->addr)); + if (keep_conn_alive) { +#if defined(WIN32) || defined(OS2) + apr_sockaddr_t *remote_addr = NULL; + apr_socket_addr_get(&remote_addr, APR_REMOTE, conn->sock); + for (addr = conn->addr; addr; addr = addr->next) { + if (addr == remote_addr) { + keep_addr_alive = 1; + break; + } + } +#else + /* Nothing to do, keep_addr_alive = 0 */ +#endif + } + else if (conn->sock && (r ? APLOGrdebug(r) : APLOGdebug(s))) { apr_sockaddr_t *local_addr = NULL; apr_sockaddr_t *remote_addr = NULL; apr_socket_addr_get(&local_addr, APR_LOCAL, conn->sock); @@ -3032,18 +3065,26 @@ PROXY_DECLARE(apr_status_t) ap_proxy_determine_address(const char *proxy_functio local_addr, remote_addr); } } - socket_cleanup(conn); + if (keep_addr_alive) { + apr_pool_cleanup_kill(conn->pool, conn->address, + proxy_address_cleanup); + apr_pool_cleanup_register(conn->scpool, conn->address, + proxy_address_cleanup, + apr_pool_cleanup_null); + } + else { + apr_pool_cleanup_run(conn->pool, conn->address, + proxy_address_cleanup); + if (!keep_conn_alive) { + conn_cleanup(conn); + } + } } - /* Kill the old address (if any) and use the new one */ - if (conn->address) { - apr_pool_cleanup_run(conn->pool, conn->address, - proxy_address_cleanup); - } + /* Use the new address */ apr_pool_cleanup_register(conn->pool, address, proxy_address_cleanup, apr_pool_cleanup_null); - address_cleanup(conn); conn->address = address; conn->hostname = address->hostname; conn->port = address->hostport; @@ -3079,6 +3120,13 @@ ap_proxy_determine_connection(apr_pool_t *p, request_rec *r, apr_pstrcat(p,"URI cannot be parsed: ", *url, NULL)); } + + if (!uri->hostname) { + return ap_proxyerror(r, HTTP_BAD_REQUEST, + apr_pstrcat(p,"URI has no hostname: ", *url, + NULL)); + } + if (!uri->port) { uri->port = ap_proxy_port_of_scheme(uri->scheme); } @@ -3125,7 +3173,7 @@ ap_proxy_determine_connection(apr_pool_t *p, request_rec *r, if (!conn->uds_path || strcmp(conn->uds_path, uds_path) != 0) { apr_pool_t *pool = conn->pool; if (conn->uds_path) { - address_cleanup(conn); + conn_cleanup(conn); if (!conn->uds_pool) { apr_pool_create(&conn->uds_pool, worker->cp->dns_pool); } @@ -3226,7 +3274,7 @@ ap_proxy_determine_connection(apr_pool_t *p, request_rec *r, if (conn->hostname && (conn->port != hostport || ap_cstr_casecmp(conn->hostname, hostname) != 0)) { - address_cleanup(conn); + conn_cleanup(conn); } /* Resolve the connection address with the determined hostname/port */ @@ -4462,6 +4510,10 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p, /* Compute Host header */ if (dconf->preserve_host == 0) { + if (!uri->hostname) { + rc = HTTP_BAD_REQUEST; + goto cleanup; + } if (ap_strchr_c(uri->hostname, ':')) { /* if literal IPv6 address */ if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) { host = apr_pstrcat(r->pool, "[", uri->hostname, "]:", diff --git a/modules/ssl/ssl_engine_init.c b/modules/ssl/ssl_engine_init.c index c2ec048..443eac4 100644 --- a/modules/ssl/ssl_engine_init.c +++ b/modules/ssl/ssl_engine_init.c @@ -844,6 +844,13 @@ static apr_status_t ssl_init_ctx_protocol(server_rec *s, } #endif +#ifdef SSL_OP_NO_RENEGOTIATION + /* For server-side SSL_CTX, disable renegotiation by default.. */ + if (!mctx->pkp) { + SSL_CTX_set_options(ctx, SSL_OP_NO_RENEGOTIATION); + } +#endif + #ifdef SSL_OP_IGNORE_UNEXPECTED_EOF /* For server-side SSL_CTX, enable ignoring unexpected EOF */ /* (OpenSSL 1.1.1 behavioural compatibility).. */ @@ -872,6 +879,14 @@ static void ssl_init_ctx_session_cache(server_rec *s, } } +#ifdef SSL_OP_NO_RENEGOTIATION +/* OpenSSL-level renegotiation protection. */ +#define MODSSL_BLOCKS_RENEG (0) +#else +/* mod_ssl-level renegotiation protection. */ +#define MODSSL_BLOCKS_RENEG (1) +#endif + static void ssl_init_ctx_callbacks(server_rec *s, apr_pool_t *p, apr_pool_t *ptemp, @@ -885,7 +900,13 @@ static void ssl_init_ctx_callbacks(server_rec *s, SSL_CTX_set_tmp_dh_callback(ctx, ssl_callback_TmpDH); #endif - SSL_CTX_set_info_callback(ctx, ssl_callback_Info); + /* The info callback is used for debug-level tracing. For OpenSSL + * versions where SSL_OP_NO_RENEGOTIATION is not available, the + * callback is also used to prevent use of client-initiated + * renegotiation. Enable it in either case. */ + if (APLOGdebug(s) || MODSSL_BLOCKS_RENEG) { + SSL_CTX_set_info_callback(ctx, ssl_callback_Info); + } #ifdef HAVE_TLS_ALPN SSL_CTX_set_alpn_select_cb(ctx, ssl_callback_alpn_select, NULL); @@ -1346,6 +1367,7 @@ static apr_status_t ssl_init_server_certs(server_rec *s, const char *vhost_id = mctx->sc->vhost_id, *key_id, *certfile, *keyfile; int i; EVP_PKEY *pkey; + int custom_dh_done = 0; #ifdef HAVE_ECC EC_GROUP *ecgroup = NULL; int curve_nid = 0; @@ -1518,14 +1540,14 @@ static apr_status_t ssl_init_server_certs(server_rec *s, */ certfile = APR_ARRAY_IDX(mctx->pks->cert_files, 0, const char *); if (certfile && !modssl_is_engine_id(certfile)) { - int done = 0, num_bits = 0; + int num_bits = 0; #if OPENSSL_VERSION_NUMBER < 0x30000000L DH *dh = modssl_dh_from_file(certfile); if (dh) { num_bits = DH_bits(dh); SSL_CTX_set_tmp_dh(mctx->ssl_ctx, dh); DH_free(dh); - done = 1; + custom_dh_done = 1; } #else pkey = modssl_dh_pkey_from_file(certfile); @@ -1535,18 +1557,18 @@ static apr_status_t ssl_init_server_certs(server_rec *s, EVP_PKEY_free(pkey); } else { - done = 1; + custom_dh_done = 1; } } #endif - if (done) { + if (custom_dh_done) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02540) "Custom DH parameters (%d bits) for %s loaded from %s", num_bits, vhost_id, certfile); } } #if !MODSSL_USE_OPENSSL_PRE_1_1_API - else { + if (!custom_dh_done) { /* If no parameter is manually configured, enable auto * selection. */ SSL_CTX_set_dh_auto(mctx->ssl_ctx, 1); diff --git a/modules/ssl/ssl_engine_io.c b/modules/ssl/ssl_engine_io.c index b91f784..9c7d216 100644 --- a/modules/ssl/ssl_engine_io.c +++ b/modules/ssl/ssl_engine_io.c @@ -208,11 +208,13 @@ static int bio_filter_out_write(BIO *bio, const char *in, int inl) BIO_clear_retry_flags(bio); +#ifndef SSL_OP_NO_RENEGOTIATION /* Abort early if the client has initiated a renegotiation. */ if (outctx->filter_ctx->config->reneg_state == RENEG_ABORT) { outctx->rc = APR_ECONNABORTED; return -1; } +#endif ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, outctx->c, "bio_filter_out_write: %i bytes", inl); @@ -473,11 +475,13 @@ static int bio_filter_in_read(BIO *bio, char *in, int inlen) BIO_clear_retry_flags(bio); +#ifndef SSL_OP_NO_RENEGOTIATION /* Abort early if the client has initiated a renegotiation. */ if (inctx->filter_ctx->config->reneg_state == RENEG_ABORT) { inctx->rc = APR_ECONNABORTED; return -1; } +#endif if (!inctx->bb) { inctx->rc = APR_EOF; diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c index fe0496f..fa1b3a8 100644 --- a/modules/ssl/ssl_engine_kernel.c +++ b/modules/ssl/ssl_engine_kernel.c @@ -992,7 +992,7 @@ static int ssl_hook_Access_classic(request_rec *r, SSLSrvConfigRec *sc, SSLDirCo /* Toggle the renegotiation state to allow the new * handshake to proceed. */ - sslconn->reneg_state = RENEG_ALLOW; + modssl_set_reneg_state(sslconn, RENEG_ALLOW); SSL_renegotiate(ssl); SSL_do_handshake(ssl); @@ -1019,7 +1019,7 @@ static int ssl_hook_Access_classic(request_rec *r, SSLSrvConfigRec *sc, SSLDirCo */ SSL_peek(ssl, peekbuf, 0); - sslconn->reneg_state = RENEG_REJECT; + modssl_set_reneg_state(sslconn, RENEG_REJECT); if (!SSL_is_init_finished(ssl)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02261) @@ -1078,7 +1078,7 @@ static int ssl_hook_Access_modern(request_rec *r, SSLSrvConfigRec *sc, SSLDirCon (sc->server->auth.verify_mode != SSL_CVERIFY_UNSET)) { int vmode_inplace, vmode_needed; int change_vmode = FALSE; - int old_state, n, rc; + int n, rc; vmode_inplace = SSL_get_verify_mode(ssl); vmode_needed = SSL_VERIFY_NONE; @@ -1180,8 +1180,6 @@ static int ssl_hook_Access_modern(request_rec *r, SSLSrvConfigRec *sc, SSLDirCon return HTTP_FORBIDDEN; } - old_state = sslconn->reneg_state; - sslconn->reneg_state = RENEG_ALLOW; modssl_set_app_data2(ssl, r); SSL_do_handshake(ssl); @@ -1191,7 +1189,6 @@ static int ssl_hook_Access_modern(request_rec *r, SSLSrvConfigRec *sc, SSLDirCon */ SSL_peek(ssl, peekbuf, 0); - sslconn->reneg_state = old_state; modssl_set_app_data2(ssl, NULL); /* @@ -2263,8 +2260,8 @@ static void log_tracing_state(const SSL *ssl, conn_rec *c, /* * This callback function is executed while OpenSSL processes the SSL * handshake and does SSL record layer stuff. It's used to trap - * client-initiated renegotiations, and for dumping everything to the - * log. + * client-initiated renegotiations (where SSL_OP_NO_RENEGOTIATION is + * not available), and for dumping everything to the log. */ void ssl_callback_Info(const SSL *ssl, int where, int rc) { @@ -2276,14 +2273,12 @@ void ssl_callback_Info(const SSL *ssl, int where, int rc) return; } - /* With TLS 1.3 this callback may be called multiple times on the first - * negotiation, so the below logic to detect renegotiations can't work. - * Fortunately renegotiations are forbidden starting with TLS 1.3, and - * this is enforced by OpenSSL so there's nothing to be done here. - */ -#if SSL_HAVE_PROTOCOL_TLSV1_3 - if (SSL_version(ssl) < TLS1_3_VERSION) -#endif +#ifndef SSL_OP_NO_RENEGOTIATION + /* With OpenSSL < 1.1.1 (implying TLS v1.2 or earlier), this + * callback is used to block client-initiated renegotiation. With + * TLSv1.3 it is unnecessary since renegotiation is forbidden at + * protocol level. Otherwise (TLSv1.2 with OpenSSL >=1.1.1), + * SSL_OP_NO_RENEGOTIATION is used to block renegotiation. */ { SSLConnRec *sslconn; @@ -2308,6 +2303,7 @@ void ssl_callback_Info(const SSL *ssl, int where, int rc) sslconn->reneg_state = RENEG_REJECT; } } +#endif s = mySrvFromConn(c); if (s && APLOGdebug(s)) { diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h index 859e932..25d79ce 100644 --- a/modules/ssl/ssl_private.h +++ b/modules/ssl/ssl_private.h @@ -549,6 +549,16 @@ typedef struct { apr_time_t source_mtime; } ssl_asn1_t; +typedef enum { + RENEG_INIT = 0, /* Before initial handshake */ + RENEG_REJECT, /* After initial handshake; any client-initiated + * renegotiation should be rejected */ + RENEG_ALLOW, /* A server-initiated renegotiation is taking + * place (as dictated by configuration) */ + RENEG_ABORT /* Renegotiation initiated by client, abort the + * connection */ +} modssl_reneg_state; + /** * Define the mod_ssl per-module configuration structure * (i.e. the global configuration for each httpd process) @@ -580,18 +590,13 @@ typedef struct { NON_SSL_SET_ERROR_MSG /* Need to set the error message */ } non_ssl_request; - /* Track the handshake/renegotiation state for the connection so - * that all client-initiated renegotiations can be rejected, as a - * partial fix for CVE-2009-3555. */ - enum { - RENEG_INIT = 0, /* Before initial handshake */ - RENEG_REJECT, /* After initial handshake; any client-initiated - * renegotiation should be rejected */ - RENEG_ALLOW, /* A server-initiated renegotiation is taking - * place (as dictated by configuration) */ - RENEG_ABORT /* Renegotiation initiated by client, abort the - * connection */ - } reneg_state; +#ifndef SSL_OP_NO_RENEGOTIATION + /* For OpenSSL < 1.1.1, track the handshake/renegotiation state + * for the connection to block client-initiated renegotiations. + * For OpenSSL >=1.1.1, the SSL_OP_NO_RENEGOTIATION flag is used in + * the SSL * options state with equivalent effect. */ + modssl_reneg_state reneg_state; +#endif server_rec *server; SSLDirConfigRec *dc; @@ -1198,6 +1203,9 @@ int ssl_is_challenge(conn_rec *c, const char *servername, * the configured ENGINE. */ int modssl_is_engine_id(const char *name); +/* Set the renegotation state for connection. */ +void modssl_set_reneg_state(SSLConnRec *sslconn, modssl_reneg_state state); + #endif /* SSL_PRIVATE_H */ /** @} */ diff --git a/modules/ssl/ssl_util_ssl.c b/modules/ssl/ssl_util_ssl.c index 44930b7..8bd9c8a 100644 --- a/modules/ssl/ssl_util_ssl.c +++ b/modules/ssl/ssl_util_ssl.c @@ -612,3 +612,19 @@ cleanup: } return rv; } + +void modssl_set_reneg_state(SSLConnRec *sslconn, modssl_reneg_state state) +{ +#ifdef SSL_OP_NO_RENEGOTIATION + switch (state) { + case RENEG_ALLOW: + SSL_clear_options(sslconn->ssl, SSL_OP_NO_RENEGOTIATION); + break; + default: + SSL_set_options(sslconn->ssl, SSL_OP_NO_RENEGOTIATION); + break; + } +#else + sslconn->reneg_state = state; +#endif +} diff --git a/modules/tls/tls_cert.c b/modules/tls/tls_cert.c index 624535a..ffb941c 100644 --- a/modules/tls/tls_cert.c +++ b/modules/tls/tls_cert.c @@ -331,11 +331,12 @@ const char *tls_cert_reg_get_id(tls_cert_reg_t *reg, const rustls_certified_key } apr_status_t tls_cert_load_root_store( - apr_pool_t *p, const char *store_file, rustls_root_cert_store **pstore) + apr_pool_t *p, const char *store_file, const rustls_root_cert_store **pstore) { const char *fpath; tls_data_t pem; - rustls_root_cert_store *store = NULL; + rustls_root_cert_store_builder *store_builder = NULL; + const rustls_root_cert_store *store = NULL; rustls_result rr = RUSTLS_RESULT_OK; apr_pool_t *ptemp = NULL; apr_status_t rv; @@ -353,11 +354,17 @@ apr_status_t tls_cert_load_root_store( rv = tls_util_file_load(ptemp, fpath, 0, 1024*1024, &pem); if (APR_SUCCESS != rv) goto cleanup; - store = rustls_root_cert_store_new(); - rr = rustls_root_cert_store_add_pem(store, pem.data, pem.len, 1); + store_builder = rustls_root_cert_store_builder_new(); + rr = rustls_root_cert_store_builder_add_pem(store_builder, pem.data, pem.len, 1); + if (RUSTLS_RESULT_OK != rr) goto cleanup; + + rr = rustls_root_cert_store_builder_build(store_builder, &store); if (RUSTLS_RESULT_OK != rr) goto cleanup; cleanup: + if (store_builder != NULL) { + rustls_root_cert_store_builder_free(store_builder); + } if (RUSTLS_RESULT_OK != rr) { const char *err_descr; rv = tls_util_rustls_error(p, rr, &err_descr); @@ -378,7 +385,7 @@ cleanup: typedef struct { const char *id; - rustls_root_cert_store *store; + const rustls_root_cert_store *store; } tls_cert_root_stores_entry_t; static int stores_entry_cleanup(void *ctx, const void *key, apr_ssize_t klen, const void *val) @@ -421,14 +428,14 @@ void tls_cert_root_stores_clear(tls_cert_root_stores_t *stores) apr_status_t tls_cert_root_stores_get( tls_cert_root_stores_t *stores, const char *store_file, - rustls_root_cert_store **pstore) + const rustls_root_cert_store **pstore) { apr_status_t rv = APR_SUCCESS; tls_cert_root_stores_entry_t *entry; entry = apr_hash_get(stores->file2store, store_file, APR_HASH_KEY_STRING); if (!entry) { - rustls_root_cert_store *store; + const rustls_root_cert_store *store; rv = tls_cert_load_root_store(stores->pool, store_file, &store); if (APR_SUCCESS != rv) goto cleanup; entry = apr_pcalloc(stores->pool, sizeof(*entry)); @@ -449,8 +456,8 @@ cleanup: typedef struct { const char *id; - const rustls_client_cert_verifier *client_verifier; - const rustls_client_cert_verifier_optional *client_verifier_opt; + rustls_client_cert_verifier *client_verifier; + rustls_client_cert_verifier *client_verifier_opt; } tls_cert_verifiers_entry_t; static int verifiers_entry_cleanup(void *ctx, const void *key, apr_ssize_t klen, const void *val) @@ -462,7 +469,7 @@ static int verifiers_entry_cleanup(void *ctx, const void *key, apr_ssize_t klen, entry->client_verifier = NULL; } if (entry->client_verifier_opt) { - rustls_client_cert_verifier_optional_free(entry->client_verifier_opt); + rustls_client_cert_verifier_free(entry->client_verifier_opt); entry->client_verifier_opt = NULL; } return 1; @@ -511,23 +518,44 @@ static tls_cert_verifiers_entry_t * verifiers_get_or_make_entry( return entry; } -apr_status_t tls_cert_client_verifiers_get( - tls_cert_verifiers_t *verifiers, - const char *store_file, - const rustls_client_cert_verifier **pverifier) +static apr_status_t tls_cert_client_verifiers_get_internal( + tls_cert_verifiers_t *verifiers, + const char *store_file, + const rustls_client_cert_verifier **pverifier, + bool allow_unauthenticated) { apr_status_t rv = APR_SUCCESS; tls_cert_verifiers_entry_t *entry; + rustls_result rr = RUSTLS_RESULT_OK; + struct rustls_web_pki_client_cert_verifier_builder *verifier_builder = NULL; entry = verifiers_get_or_make_entry(verifiers, store_file); if (!entry->client_verifier) { - rustls_root_cert_store *store; + const rustls_root_cert_store *store; rv = tls_cert_root_stores_get(verifiers->stores, store_file, &store); if (APR_SUCCESS != rv) goto cleanup; - entry->client_verifier = rustls_client_cert_verifier_new(store); + verifier_builder = rustls_web_pki_client_cert_verifier_builder_new(store); + + if (allow_unauthenticated) { + rr = rustls_web_pki_client_cert_verifier_builder_allow_unauthenticated(verifier_builder); + if (rr != RUSTLS_RESULT_OK) { + goto cleanup; + } + } + + rr = rustls_web_pki_client_cert_verifier_builder_build(verifier_builder, &entry->client_verifier); + if (rr != RUSTLS_RESULT_OK) { + goto cleanup; + } } cleanup: + if (verifier_builder != NULL) { + rustls_web_pki_client_cert_verifier_builder_free(verifier_builder); + } + if (rr != RUSTLS_RESULT_OK) { + rv = tls_util_rustls_error(verifiers->pool, rr, NULL); + } if (APR_SUCCESS == rv) { *pverifier = entry->client_verifier; } @@ -537,28 +565,19 @@ cleanup: return rv; } -apr_status_t tls_cert_client_verifiers_get_optional( + +apr_status_t tls_cert_client_verifiers_get( tls_cert_verifiers_t *verifiers, const char *store_file, - const rustls_client_cert_verifier_optional **pverifier) + const rustls_client_cert_verifier **pverifier) { - apr_status_t rv = APR_SUCCESS; - tls_cert_verifiers_entry_t *entry; - - entry = verifiers_get_or_make_entry(verifiers, store_file); - if (!entry->client_verifier_opt) { - rustls_root_cert_store *store; - rv = tls_cert_root_stores_get(verifiers->stores, store_file, &store); - if (APR_SUCCESS != rv) goto cleanup; - entry->client_verifier_opt = rustls_client_cert_verifier_optional_new(store); - } + return tls_cert_client_verifiers_get_internal(verifiers, store_file, pverifier, false); +} -cleanup: - if (APR_SUCCESS == rv) { - *pverifier = entry->client_verifier_opt; - } - else { - *pverifier = NULL; - } - return rv; +apr_status_t tls_cert_client_verifiers_get_optional( + tls_cert_verifiers_t *verifiers, + const char *store_file, + const rustls_client_cert_verifier **pverifier) +{ + return tls_cert_client_verifiers_get_internal(verifiers, store_file, pverifier, true); } diff --git a/modules/tls/tls_cert.h b/modules/tls/tls_cert.h index 6ab3f48..3326f0e 100644 --- a/modules/tls/tls_cert.h +++ b/modules/tls/tls_cert.h @@ -128,7 +128,7 @@ const char *tls_cert_reg_get_id(tls_cert_reg_t *reg, const rustls_certified_key * @param pstore the loaded root store on success */ apr_status_t tls_cert_load_root_store( - apr_pool_t *p, const char *store_file, rustls_root_cert_store **pstore); + apr_pool_t *p, const char *store_file, const rustls_root_cert_store **pstore); typedef struct tls_cert_root_stores_t tls_cert_root_stores_t; struct tls_cert_root_stores_t { @@ -157,7 +157,7 @@ void tls_cert_root_stores_clear(tls_cert_root_stores_t *stores); apr_status_t tls_cert_root_stores_get( tls_cert_root_stores_t *stores, const char *store_file, - rustls_root_cert_store **pstore); + const rustls_root_cert_store **pstore); typedef struct tls_cert_verifiers_t tls_cert_verifiers_t; struct tls_cert_verifiers_t { @@ -206,6 +206,6 @@ apr_status_t tls_cert_client_verifiers_get( apr_status_t tls_cert_client_verifiers_get_optional( tls_cert_verifiers_t *verifiers, const char *store_file, - const rustls_client_cert_verifier_optional **pverifier); + const rustls_client_cert_verifier **pverifier); -#endif /* tls_cert_h */
\ No newline at end of file +#endif /* tls_cert_h */ diff --git a/modules/tls/tls_core.c b/modules/tls/tls_core.c index 2547939..1cef254 100644 --- a/modules/tls/tls_core.c +++ b/modules/tls/tls_core.c @@ -764,8 +764,10 @@ static apr_status_t init_outgoing_connection(conn_rec *c) tls_conf_proxy_t *pc; const apr_array_header_t *ciphersuites = NULL; apr_array_header_t *tls_versions = NULL; + rustls_web_pki_server_cert_verifier_builder *verifier_builder = NULL; + struct rustls_server_cert_verifier *verifier = NULL; rustls_client_config_builder *builder = NULL; - rustls_root_cert_store *ca_store = NULL; + const rustls_root_cert_store *ca_store = NULL; const char *hostname = NULL, *alpn_note = NULL; rustls_result rr = RUSTLS_RESULT_OK; apr_status_t rv = APR_SUCCESS; @@ -809,7 +811,10 @@ static apr_status_t init_outgoing_connection(conn_rec *c) if (pc->proxy_ca && strcasecmp(pc->proxy_ca, "default")) { rv = tls_cert_root_stores_get(pc->global->stores, pc->proxy_ca, &ca_store); if (APR_SUCCESS != rv) goto cleanup; - rustls_client_config_builder_use_roots(builder, ca_store); + verifier_builder = rustls_web_pki_server_cert_verifier_builder_new(ca_store); + rr = rustls_web_pki_server_cert_verifier_builder_build(verifier_builder, &verifier); + if (RUSTLS_RESULT_OK != rr) goto cleanup; + rustls_client_config_builder_set_server_verifier(builder, verifier); } #if TLS_MACHINE_CERTS @@ -881,6 +886,7 @@ static apr_status_t init_outgoing_connection(conn_rec *c) rustls_connection_set_userdata(cc->rustls_connection, c); cleanup: + if (verifier_builder != NULL) rustls_web_pki_server_cert_verifier_builder_free(verifier_builder); if (builder != NULL) rustls_client_config_builder_free(builder); if (RUSTLS_RESULT_OK != rr) { const char *err_descr = NULL; @@ -1125,10 +1131,10 @@ static apr_status_t build_server_connection(rustls_connection **pconnection, rustls_server_config_builder_set_client_verifier(builder, verifier); } else { - const rustls_client_cert_verifier_optional *verifier; + const rustls_client_cert_verifier *verifier; rv = tls_cert_client_verifiers_get_optional(sc->global->verifiers, sc->client_ca, &verifier); if (APR_SUCCESS != rv) goto cleanup; - rustls_server_config_builder_set_client_verifier_optional(builder, verifier); + rustls_server_config_builder_set_client_verifier(builder, verifier); } } diff --git a/modules/tls/tls_version.h b/modules/tls/tls_version.h index 811d6f1..bc9fb0b 100644 --- a/modules/tls/tls_version.h +++ b/modules/tls/tls_version.h @@ -26,7 +26,7 @@ * @macro * Version number of the md module as c string */ -#define MOD_TLS_VERSION "0.8.3" +#define MOD_TLS_VERSION "0.9.0" /** * @macro @@ -34,6 +34,6 @@ * release. This is a 24 bit number with 8 bits for major number, 8 bits * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203. */ -#define MOD_TLS_VERSION_NUM 0x000802 +#define MOD_TLS_VERSION_NUM 0x000900 #endif /* mod_md_md_version_h */ |