diff options
author | William A. Rowe Jr <wrowe@apache.org> | 2016-11-04 14:20:16 +0000 |
---|---|---|
committer | William A. Rowe Jr <wrowe@apache.org> | 2016-11-04 14:20:16 +0000 |
commit | e514eb999b0e78e1e5ee7e15db2d1eaf62afe5dd (patch) | |
tree | b2e8aa35ff7865c0874507c356c8841e10ccab9b | |
parent | fb3f938dd04df3d5893ccf77360af0fd28e74efc (diff) | |
download | httpd-e514eb999b0e78e1e5ee7e15db2d1eaf62afe5dd.tar.gz |
Add an option to enforce stricter HTTP conformance
This is a first stab, the checks will likely have to be revised.
For now, we check
* if the request line contains control characters
* if the request uri has fragment or username/password
* that the request method is standard or registered with RegisterHttpMethod
* that the request protocol is of the form HTTP/[1-9]+.[0-9]+,
or missing for 0.9
* if there is garbage in the request line after the protocol
* if any request header contains control characters
* if any request header has an empty name
* for the host name in the URL or Host header:
- if an IPv4 dotted decimal address: Reject octal or hex values, require
exactly four parts
- if a DNS host name: Reject non-alphanumeric characters besides '.' and
'-'. As a side effect, this rejects multiple Host headers.
* if any response header contains control characters
* if any response header has an empty name
* that the Location response header (if present) has a valid scheme and is
absolute
If we have a host name both from the URL and the Host header, we replace the
Host header with the value from the URL to enforce RFC conformance.
There is a log-only mode, but the loglevels of the logged messages need some
thought/work. Currently, the checks for incoming data log for 'core' and the
checks for outgoing data log for 'http'. Maybe we need a way to configure the
loglevels separately from the core/http loglevels.
change protocol number parsing in strict mode according to HTTPbis draft
- only accept single digit version components
- don't accept white-space after protocol specification
Clean up comment, fix log tags.
Submitted by: sf
Backports: r1426877, r1426879, r1426988, r1426992
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x-merge-http-strict@1768036 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r-- | CHANGES | 3 | ||||
-rw-r--r-- | include/ap_mmn.h | 2 | ||||
-rw-r--r-- | include/http_core.h | 5 | ||||
-rw-r--r-- | include/httpd.h | 9 | ||||
-rw-r--r-- | modules/http/http_filters.c | 91 | ||||
-rw-r--r-- | server/core.c | 31 | ||||
-rw-r--r-- | server/protocol.c | 117 | ||||
-rw-r--r-- | server/util.c | 10 | ||||
-rw-r--r-- | server/vhost.c | 168 |
9 files changed, 387 insertions, 49 deletions
@@ -2,6 +2,9 @@ Changes with Apache 2.4.24 + *) core, http: Extend HttpProtocol with an option to enforce stricter HTTP + conformance or to only log the found problems. [Stefan Fritsch] + *) core: Correctly parse an IPv6 literal host specification in an absolute URL in the request line. [Stefan Fritsch] diff --git a/include/ap_mmn.h b/include/ap_mmn.h index 1cd06ff4f1..6e6bef47c7 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -488,6 +488,8 @@ * 20120211.66 (2.4.24-dev) Rename ap_proxy_check_backend() to * ap_proxy_check_connection(). * 20120211.67 (2.5.0-dev) Add http09_enable to core_server_config + * Add http_conformance to core_server_config, + * add ap_has_cntrl() */ #define MODULE_MAGIC_COOKIE 0x41503234UL /* "AP24" */ diff --git a/include/http_core.h b/include/http_core.h index cbd924060a..89281cc6e6 100644 --- a/include/http_core.h +++ b/include/http_core.h @@ -731,6 +731,11 @@ typedef struct { #define AP_HTTP09_DISABLE 2 char http09_enable; +#define AP_HTTP_CONFORMANCE_UNSET 0 +#define AP_HTTP_CONFORMANCE_LIBERAL 1 +#define AP_HTTP_CONFORMANCE_STRICT 2 +#define AP_HTTP_CONFORMANCE_LOGONLY 4 + char http_conformance; } core_server_config; /* for AddOutputFiltersByType in core.c */ diff --git a/include/httpd.h b/include/httpd.h index e02bcc09fb..18cfd04c24 100644 --- a/include/httpd.h +++ b/include/httpd.h @@ -2317,6 +2317,15 @@ AP_DECLARE(char *) ap_get_exec_line(apr_pool_t *p, const char *cmd, const char * const *argv); +/** + * Check if string contains a control character + * @param str the string to check + * @param srclen length of the data + * @return 1 if yes, 0 if no control characters + */ +AP_DECLARE(int) ap_has_cntrl(const char *str) + AP_FN_ATTR_NONNULL_ALL; + #define AP_NORESTART APR_OS_START_USEERR + 1 /** diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c index 88772ecbe9..c2dd0ce092 100644 --- a/modules/http/http_filters.c +++ b/modules/http/http_filters.c @@ -668,14 +668,91 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, return APR_SUCCESS; } +struct check_header_ctx { + request_rec *r; + int error; +}; + +/* check a single header, to be used with apr_table_do() */ +static int check_header(void *arg, const char *name, const char *val) +{ + struct check_header_ctx *ctx = arg; + if (name[0] == '\0') { + ctx->error = 1; + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02428) + "Empty response header name, aborting request"); + return 0; + } + if (ap_has_cntrl(name)) { + ctx->error = 1; + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02429) + "Response header name '%s' contains control " + "characters, aborting request", + name); + return 0; + } + if (ap_has_cntrl(val)) { + ctx->error = 1; + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02430) + "Response header '%s' contains control characters, " + "aborting request: %s", + name, val); + return 0; + } + return 1; +} + +/** + * Check headers for HTTP conformance + * @return 1 if ok, 0 if bad + */ +static APR_INLINE int check_headers(request_rec *r) +{ + const char *loc; + struct check_header_ctx ctx = { r, 0 }; + + apr_table_do(check_header, &ctx, r->headers_out, NULL); + if (ctx.error) + return 0; /* problem has been logged by check_header() */ + + if ((loc = apr_table_get(r->headers_out, "Location")) != NULL) { + const char *scheme_end = ap_strchr_c(loc, ':'); + const char *s = loc; + + /* + * Check that the URI has a valid scheme and is absolute + * XXX Should we do a full uri parse here? + */ + if (scheme_end == NULL || scheme_end == loc) + goto bad; + + do { + if ((!apr_isalnum(*s) && *s != '.' && *s != '+' && *s != '-') + || !apr_isascii(*s) ) { + goto bad; + } + } while (++s < scheme_end); + + if (scheme_end[1] != '/' || scheme_end[2] != '/') + goto bad; + } + + return 1; + +bad: + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02431) + "Bad Location header in response: '%s', aborting request", + loc); + return 0; +} + typedef struct header_struct { apr_pool_t *pool; apr_bucket_brigade *bb; } header_struct; /* Send a single HTTP header field to the client. Note that this function - * is used in calls to table_do(), so their interfaces are co-dependent. - * In other words, don't change this one without checking table_do in alloc.c. + * is used in calls to apr_table_do(), so don't change its interface. * It returns true unless there was a write error of some kind. */ static int form_header_field(header_struct *h, @@ -1175,6 +1252,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, header_filter_ctx *ctx = f->ctx; const char *ctype; ap_bucket_error *eb = NULL; + core_server_config *conf; AP_DEBUG_ASSERT(!r->main); @@ -1230,6 +1308,15 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, r->headers_out); } + conf = ap_get_core_module_config(r->server->module_config); + if (conf->http_conformance & AP_HTTP_CONFORMANCE_STRICT) { + int ok = check_headers(r); + if (!ok && !(conf->http_conformance & AP_HTTP_CONFORMANCE_LOGONLY)) { + ap_die(HTTP_INTERNAL_SERVER_ERROR, r); + return AP_FILTER_ERROR; + } + } + /* * Remove the 'Vary' header field if the client can't handle it. * Since this will have nasty effects on HTTP/1.1 caches, force diff --git a/server/core.c b/server/core.c index 58b95ccf2b..c277af3bb7 100644 --- a/server/core.c +++ b/server/core.c @@ -522,6 +522,9 @@ static void *merge_core_server_configs(apr_pool_t *p, void *basev, void *virtv) if (virt->http09_enable != AP_HTTP09_UNSET) conf->http09_enable = virt->http09_enable; + if (virt->http_conformance != AP_HTTP_CONFORMANCE_UNSET) + conf->http_conformance = virt->http_conformance; + /* no action for virt->accf_map, not allowed per-vhost */ if (virt->protocol) @@ -3911,10 +3914,27 @@ static const char *set_http_protocol(cmd_parms *cmd, void *dummy, else if (strcmp(arg, "1.0") == 0) conf->http09_enable = AP_HTTP09_DISABLE; else - return "HttpProtocol min must be one of '0.9' and '1.0'"; - return NULL; + return "HttpProtocol 'min' must be one of '0.9' and '1.0'"; + return NULL; + } + + if (strcmp(arg, "strict") == 0) + conf->http_conformance = AP_HTTP_CONFORMANCE_STRICT; + else if (strcmp(arg, "strict,log-only") == 0) + conf->http_conformance = AP_HTTP_CONFORMANCE_STRICT| + AP_HTTP_CONFORMANCE_LOGONLY; + else if (strcmp(arg, "liberal") == 0) + conf->http_conformance = AP_HTTP_CONFORMANCE_LIBERAL; + else + return "HttpProtocol accepts 'min=0.9', 'min=1.0', 'liberal', " + "'strict', 'strict,log-only'"; + + if ((conf->http_conformance & AP_HTTP_CONFORMANCE_STRICT) && + (conf->http_conformance & AP_HTTP_CONFORMANCE_LIBERAL)) { + return "HttpProtocol 'strict' and 'liberal' are mutually exclusive"; } - return "HttpProtocol must be min=0.9|1.0"; + + return NULL; } static const char *set_http_method(cmd_parms *cmd, void *conf, const char *arg) @@ -4450,8 +4470,9 @@ AP_INIT_ITERATE("Protocols", set_protocols, NULL, RSRC_CONF, AP_INIT_TAKE1("ProtocolsHonorOrder", set_protocols_honor_order, NULL, RSRC_CONF, "'off' (default) or 'on' to respect given order of protocols, " "by default the client specified order determines selection"), -AP_INIT_TAKE1("HttpProtocol", set_http_protocol, NULL, RSRC_CONF, - "'min=0.9' (default) or 'min=1.0' to allow/deny HTTP/0.9"), +AP_INIT_ITERATE("HttpProtocol", set_http_protocol, NULL, RSRC_CONF, + "'min=0.9' (default) or 'min=1.0' to allow/deny HTTP/0.9; " + "'liberal', 'strict', 'strict,log-only'"), AP_INIT_ITERATE("RegisterHttpMethod", set_http_method, NULL, RSRC_CONF, "Registers non-standard HTTP methods"), { NULL } diff --git a/server/protocol.c b/server/protocol.c index 558d70f32b..a1a3845a7e 100644 --- a/server/protocol.c +++ b/server/protocol.c @@ -562,6 +562,9 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb) char http[5]; apr_size_t len; int num_blank_lines = DEFAULT_LIMIT_BLANK_LINES; + core_server_config *conf = ap_get_core_module_config(r->server->module_config); + int strict = conf->http_conformance & AP_HTTP_CONFORMANCE_STRICT; + int enforce_strict = !(conf->http_conformance & AP_HTTP_CONFORMANCE_LOGONLY); /* Read past empty lines until we get a real request line, * a read error, the connection closes (EOF), or we timeout. @@ -648,8 +651,6 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb) pro = ll; len = strlen(ll); } else { - core_server_config *conf; - conf = ap_get_core_module_config(r->server->module_config); r->assbackwards = 1; pro = "HTTP/0.9"; len = 8; @@ -674,12 +675,59 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb) && apr_isdigit(pro[7])) { r->proto_num = HTTP_VERSION(pro[5] - '0', pro[7] - '0'); } - else if (3 == sscanf(r->protocol, "%4s/%u.%u", http, &major, &minor) - && (strcasecmp("http", http) == 0) - && (minor < HTTP_VERSION(1, 0)) ) /* don't allow HTTP/0.1000 */ - r->proto_num = HTTP_VERSION(major, minor); - else - r->proto_num = HTTP_VERSION(1, 0); + else { + if (strict) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02418) + "Invalid protocol '%s'", r->protocol); + if (enforce_strict) { + r->status = HTTP_BAD_REQUEST; + return 0; + } + } + if (3 == sscanf(r->protocol, "%4s/%u.%u", http, &major, &minor) + && (strcasecmp("http", http) == 0) + && (minor < HTTP_VERSION(1, 0)) ) { /* don't allow HTTP/0.1000 */ + r->proto_num = HTTP_VERSION(major, minor); + } + else { + r->proto_num = HTTP_VERSION(1, 0); + } + } + + if (strict) { + int err = 0; + if (ap_has_cntrl(r->the_request)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02420) + "Request line must not contain control characters"); + err = HTTP_BAD_REQUEST; + } + if (r->parsed_uri.fragment) { + /* RFC3986 3.5: no fragment */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02421) + "URI must not contain a fragment"); + err = HTTP_BAD_REQUEST; + } + else if (r->parsed_uri.user || r->parsed_uri.password) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02422) + "URI must not contain a username/password"); + err = HTTP_BAD_REQUEST; + } + else if (r->method_number == M_INVALID) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02423) + "Invalid HTTP method string: %s", r->method); + err = HTTP_NOT_IMPLEMENTED; + } + else if (r->assbackwards == 0 && r->proto_num < HTTP_VERSION(1, 0)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02424) + "HTTP/0.x does not take a protocol"); + err = HTTP_BAD_REQUEST; + } + + if (err && enforce_strict) { + r->status = err; + return 0; + } + } return 1; } @@ -723,6 +771,7 @@ AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb apr_size_t len; int fields_read = 0; char *tmp_field; + core_server_config *conf = ap_get_core_module_config(r->server->module_config); /* * Read header lines until we get the empty separator line, a read error, @@ -876,6 +925,33 @@ AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb *tmp_field-- = '\0'; } + if (conf->http_conformance & AP_HTTP_CONFORMANCE_STRICT) { + int err = 0; + + if (*last_field == '\0') { + err = HTTP_BAD_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02425) + "Empty request header field name not allowed"); + } + else if (ap_has_cntrl(last_field)) { + err = HTTP_BAD_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02426) + "[HTTP strict] Request header field name contains " + "control character: %.*s", + (int)LOG_NAME_MAX_LEN, last_field); + } + else if (ap_has_cntrl(value)) { + err = HTTP_BAD_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02427) + "Request header field '%.*s' contains" + "control character", (int)LOG_NAME_MAX_LEN, + last_field); + } + if (err && !(conf->http_conformance & AP_HTTP_CONFORMANCE_LOGONLY)) { + r->status = err; + return; + } + } apr_table_addn(r->headers_in, last_field, value); /* reset the alloc_len so that we'll allocate a new @@ -925,7 +1001,7 @@ request_rec *ap_read_request(conn_rec *conn) request_rec *r; apr_pool_t *p; const char *expect; - int access_status = HTTP_OK; + int access_status; apr_bucket_brigade *tmp_bb; apr_socket_t *csd; apr_interval_time_t cur_timeout; @@ -984,9 +1060,11 @@ request_rec *ap_read_request(conn_rec *conn) /* Get the request... */ if (!read_request_line(r, tmp_bb)) { - if (r->status == HTTP_REQUEST_URI_TOO_LARGE - || r->status == HTTP_BAD_REQUEST - || r->status == HTTP_VERSION_NOT_SUPPORTED) { + switch (r->status) { + case HTTP_REQUEST_URI_TOO_LARGE: + case HTTP_BAD_REQUEST: + case HTTP_VERSION_NOT_SUPPORTED: + case HTTP_NOT_IMPLEMENTED: if (r->status == HTTP_REQUEST_URI_TOO_LARGE) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00565) "request failed: client's request-line exceeds LimitRequestLine (longer than %d)", @@ -1004,19 +1082,17 @@ request_rec *ap_read_request(conn_rec *conn) r = NULL; apr_brigade_destroy(tmp_bb); goto traceout; - } - else if (r->status == HTTP_REQUEST_TIME_OUT) { + case HTTP_REQUEST_TIME_OUT: ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, NULL); - if (!r->connection->keepalives) { + if (!r->connection->keepalives) ap_run_log_transaction(r); - } apr_brigade_destroy(tmp_bb); goto traceout; + default: + apr_brigade_destroy(tmp_bb); + r = NULL; + goto traceout; } - - apr_brigade_destroy(tmp_bb); - r = NULL; - goto traceout; } /* We may have been in keep_alive_timeout mode, so toggle back @@ -1101,6 +1177,7 @@ request_rec *ap_read_request(conn_rec *conn) * now read. may update status. */ ap_update_vhost_from_headers(r); + access_status = r->status; /* Toggle to the Host:-based vhost's timeout mode to fetch the * request body and send the response body, if needed. diff --git a/server/util.c b/server/util.c index 06da789718..2fe71a13c5 100644 --- a/server/util.c +++ b/server/util.c @@ -2189,6 +2189,16 @@ AP_DECLARE(void) ap_bin2hex(const void *src, apr_size_t srclen, char *dest) *dest = '\0'; } +AP_DECLARE(int) ap_has_cntrl(const char *str) +{ + while (*str) { + if (apr_iscntrl(*str)) + return 1; + str++; + } + return 0; +} + AP_DECLARE(int) ap_is_directory(apr_pool_t *p, const char *path) { apr_finfo_t finfo; diff --git a/server/vhost.c b/server/vhost.c index 90ed1398e0..227081c473 100644 --- a/server/vhost.c +++ b/server/vhost.c @@ -747,6 +747,59 @@ static apr_status_t fix_hostname_non_v6(request_rec *r, char *host) return APR_SUCCESS; } +/* + * If strict mode ever becomes the default, this should be folded into + * fix_hostname_non_v6() + */ +static apr_status_t strict_hostname_check(request_rec *r, char *host, + int logonly) +{ + char *ch; + int is_dotted_decimal = 1, leading_zeroes = 0, dots = 0; + + for (ch = host; *ch; ch++) { + if (!apr_isascii(*ch)) { + goto bad; + } + else if (apr_isalpha(*ch) || *ch == '-') { + is_dotted_decimal = 0; + } + else if (ch[0] == '.') { + dots++; + if (ch[1] == '0' && apr_isdigit(ch[2])) + leading_zeroes = 1; + } + else if (!apr_isdigit(*ch)) { + /* also takes care of multiple Host headers by denying commas */ + goto bad; + } + } + if (is_dotted_decimal) { + if (host[0] == '.' || (host[0] == '0' && apr_isdigit(host[1]))) + leading_zeroes = 1; + if (leading_zeroes || dots != 3) { + /* RFC 3986 7.4 */ + goto bad; + } + } + else { + /* The top-level domain must start with a letter (RFC 1123 2.1) */ + while (ch > host && *ch != '.') + ch--; + if (ch[0] == '.' && ch[1] != '\0' && !apr_isalpha(ch[1])) + goto bad; + } + return APR_SUCCESS; + +bad: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02415) + "[strict] Invalid host name '%s'%s%.6s", + host, *ch ? ", problem near: " : "", ch); + if (logonly) + return APR_SUCCESS; + return APR_EINVAL; +} + /* Lowercase and remove any trailing dot and/or :port from the hostname, * and check that it is sane. * @@ -760,22 +813,23 @@ static apr_status_t fix_hostname_non_v6(request_rec *r, char *host) * Instead we just check for filesystem metacharacters: directory * separators / and \ and sequences of more than one dot. */ -static void fix_hostname(request_rec *r, const char *host_header) +static int fix_hostname(request_rec *r, const char *host_header, + unsigned http_conformance) { const char *src; char *host, *scope_id; apr_port_t port; apr_status_t rv; const char *c; + int is_v6literal = 0; + int strict = http_conformance & AP_HTTP_CONFORMANCE_STRICT; + int strict_logonly = http_conformance & AP_HTTP_CONFORMANCE_LOGONLY; src = host_header ? host_header : r->hostname; - /* According to RFC 2616, Host header field CAN be blank. - * XXX But only 'if the requested URI does not include an Internet host - * XXX name'. Can this happen? - */ + /* According to RFC 2616, Host header field CAN be blank */ if (!*src) { - return; + return is_v6literal; } /* apr_parse_addr_port will interpret a bare integer as a port @@ -784,8 +838,16 @@ static void fix_hostname(request_rec *r, const char *host_header) for (c = src; apr_isdigit(*c); ++c); if (!*c) { /* pure integer */ + if (strict) { + /* RFC 3986 7.4 */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02416) + "[strict] purely numeric host names not allowed: %s", + src); + if (!strict_logonly) + goto bad_nolog; + } r->hostname = src; - return; + return is_v6literal; } if (host_header) { @@ -802,9 +864,7 @@ static void fix_hostname(request_rec *r, const char *host_header) r->parsed_uri.port_str = apr_itoa(r->pool, (int)port); } if (host_header[0] == '[') - rv = fix_hostname_v6_literal(r, host); - else - rv = fix_hostname_non_v6(r, host); + is_v6literal = 1; } else { /* @@ -813,24 +873,32 @@ static void fix_hostname(request_rec *r, const char *host_header) */ host = apr_pstrdup(r->pool, r->hostname); if (ap_strchr(host, ':') != NULL) - rv = fix_hostname_v6_literal(r, host); - else - rv = fix_hostname_non_v6(r, host); + is_v6literal = 1; + } + + if (is_v6literal) { + rv = fix_hostname_v6_literal(r, host); + } + else { + rv = fix_hostname_non_v6(r, host); + if (strict && rv == APR_SUCCESS) + rv = strict_hostname_check(r, host, strict_logonly); } if (rv != APR_SUCCESS) goto bad; + r->hostname = host; - return; + return is_v6literal; bad: - r->status = HTTP_BAD_REQUEST; ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00550) "Client sent malformed Host header: %s", - r->hostname); - return; + src); +bad_nolog: + r->status = HTTP_BAD_REQUEST; + return is_v6literal; } - /* return 1 if host matches ServerName or ServerAliases */ static int matches_aliases(server_rec *s, const char *host) { @@ -1040,23 +1108,79 @@ static void check_serverpath(request_rec *r) } } +static APR_INLINE const char *construct_host_header(request_rec *r, + int is_v6literal) +{ + struct iovec iov[5]; + apr_size_t nvec = 0; + /* + * We cannot use ap_get_server_name/port here, because we must + * ignore UseCanonicalName/Port. + */ + if (is_v6literal) { + iov[nvec].iov_base = "["; + iov[nvec].iov_len = 1; + nvec++; + } + iov[nvec].iov_base = (void *)r->hostname; + iov[nvec].iov_len = strlen(r->hostname); + nvec++; + if (is_v6literal) { + iov[nvec].iov_base = "]"; + iov[nvec].iov_len = 1; + nvec++; + } + if (r->parsed_uri.port_str) { + iov[nvec].iov_base = ":"; + iov[nvec].iov_len = 1; + nvec++; + iov[nvec].iov_base = r->parsed_uri.port_str; + iov[nvec].iov_len = strlen(r->parsed_uri.port_str); + nvec++; + } + return apr_pstrcatv(r->pool, iov, nvec, NULL); +} AP_DECLARE(void) ap_update_vhost_from_headers(request_rec *r) { - const char *host_header; + core_server_config *conf = ap_get_core_module_config(r->server->module_config); + const char *host_header = apr_table_get(r->headers_in, "Host"); + int is_v6literal, have_hostname_from_url = 0; if (r->hostname) { /* * If there was a host part in the Request-URI, ignore the 'Host' * header. */ - fix_hostname(r, NULL); + have_hostname_from_url = 1; + is_v6literal = fix_hostname(r, NULL, conf->http_conformance); } - else if ((host_header = apr_table_get(r->headers_in, "Host")) != NULL ) { - fix_hostname(r, host_header); + else if (host_header != NULL) { + is_v6literal = fix_hostname(r, host_header, conf->http_conformance); } if (r->status != HTTP_OK) return; + + if (conf->http_conformance & AP_HTTP_CONFORMANCE_STRICT) { + /* + * If we have both hostname from an absoluteURI and a Host header, + * we must ignore the Host header (RFC 2616 5.2). + * To enforce this, we reset the Host header to the value from the + * request line. + */ + if (have_hostname_from_url && host_header != NULL) { + const char *info = "Would replace"; + const char *new = construct_host_header(r, is_v6literal); + if (!(conf->http_conformance & AP_HTTP_CONFORMANCE_LOGONLY)) { + apr_table_set(r->headers_in, "Host", r->hostname); + info = "Replacing"; + } + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02417) + "%s Host header '%s' with host from request uri: " + "'%s'", info, host_header, new); + } + } + /* check if we tucked away a name_chain */ if (r->connection->vhost_lookup_data) { if (r->hostname) |