From: William A. Rowe Jr Date: Fri, 23 Dec 2016 05:22:13 +0000 (+0000) Subject: I really just did that on my test-merge branch??? fueque... reverting r1775787 X-Git-Tag: 2.2.32~60 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a57fd899535da1d742dcd3aa6d209aa609d67b31;p=thirdparty%2Fapache%2Fhttpd.git I really just did that on my test-merge branch??? fueque... reverting r1775787 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.2.x@1775788 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index d430aa81b0c..3f81842f044 100644 --- a/CHANGES +++ b/CHANGES @@ -1,46 +1,17 @@ -*- coding: utf-8 -*- Changes with Apache 2.2.32 - *) SECURITY: CVE-2016-8743 (cve.mitre.org) - Enforce HTTP request grammar corresponding to RFC7230 for request lines - and request headers, to prevent response splitting and cache pollution by - malicious clients or downstream proxies. [William Rowe, Stefan Fritsch] - - *) CVE-2016-5387: core: Mitigate [f]cgi "httpoxy" issues. + *) core: CVE-2016-5387: Mitigate [f]cgi "httpoxy" issues. [Dominic Scheirlinck , Yann Ylavic] - *) Validate HTTP response header grammar defined by RFC7230, resulting - in a 500 error in the event that invalid response header contents are - detected when serving the response, to avoid response splitting and cache - pollution by malicious clients, upstream servers or faulty modules. - [Stefan Fritsch, Eric Covener, Yann Ylavic] - - *) core: Enforce LimitRequestFieldSize after multiple headers with the same - name have been merged. [Stefan Fritsch] - - *) core: Drop Content-Length header and message-body from HTTP 204 responses. - PR 51350 [Luca Toscano] - - *) core: Permit unencoded ';' characters to appear in proxy requests and - Location: response headers. Corresponds to modern browser behavior. - [William Rowe] - - *) core: ap_rgetline_core now pulls from r->proto_input_filters. - - *) core: Correctly parse an IPv6 literal host specification in an absolute - URL in the request line. [Stefan Fritsch] - - *) core: New directive RegisterHttpMethod for registering non-standard - HTTP methods. [Stefan Fritsch] - *) core: Limit to ten the number of tolerated empty lines between request. [Yann Ylavic] - *) core: reject NULLs in request line or request headers. + *) Core: reject NULLs in request line or request headers. PR 43039 [Nick Kew] - *) core: Avoid a possible truncation of the faulty header included in the - HTML response when LimitRequestFieldSize is reached. [Yann Ylavic] + *) mod_ssl: Fix a possible memory leak on restart for custom [EC]DH params. + [Jan Kaluza, Yann Ylavic] *) mod_proxy: Fix a regression with 2.2.31 that caused inherited workers to use a different scoreboard slot then the original one. PR 58267. @@ -71,12 +42,6 @@ Changes with Apache 2.2.32 failures under Visual Studio 2015 and other mismatched MSVCRT flavors. PR59630 [Jan Ehrhardt ] - *) mod_ssl: Fix a possible memory leak on restart for custom [EC]DH params. - [Jan Kaluza, Yann Ylavic] - - *) core: Support custom ErrorDocuments for HTTP 501 and 414 status codes. - PR 57167 [Edward Lu ] - Changes with Apache 2.2.31 *) Correct win32 build issues for mod_proxy exports, OpenSSL 1.0.x headers. diff --git a/STATUS b/STATUS index 1d86a94089b..ad111c78a35 100644 --- a/STATUS +++ b/STATUS @@ -99,32 +99,6 @@ CURRENT RELEASE NOTES: RELEASE SHOWSTOPPERS: - *) Rather than odds-and-ends applied out of order, proposing we revert - r1757240, r1757256, r1757295, r1758671, r1758672, r1775232, all of - which is now recorded in the 2.2.x-merge-http-strict branch, and - bring that branch back into 2.2.x for 2.4.32 release. - Merges; - -c-1775232 . - -c-1757672 . - -c-1757671 . - -c-1757295 . - -c-1757256 . - -c-1757240 . - [here we are back at 2.2.32-dev bump] - -r1775685:1775780 https://svn.apache.org/repos/asf/httpd/httpd/branches/2.2.x-merge-http-strict/ - Roll-up patch of the above (not recommended for casual reading, these - would be committed individually as noted above... but for only for sanity - testing the end result. Due to intervening CHANGES/ap_mmn changes, there - is small delta after reverting the above...) - https://raw.githubusercontent.com/wrowe/patches/master/httpd-2.2-HEAD-http-protocol-strict.patch - This patch above does *NOT* apply to the 2.2.31 release, c.f. the delta - of the 2.2.x-merge-http-strict branch for that information. This is for - folks who are testing rollbacks plus 2.4.x activity against 2.2.x HEAD! - Sorry to start from scratch, but yann's correct observation was correct, - that nothing will apply out-of-order, and everything on 2.2 branch had - already become disordered. - +1: wrowe - PATCHES ACCEPTED TO BACKPORT FROM TRUNK: [ start all new proposals below, under PATCHES PROPOSED. ] @@ -178,6 +152,44 @@ PATCHES PROPOSED TO BACKPORT FROM TRUNK: http://home.apache.org/~ylavic/patches/httpd-2.2.x-r1753592.patch +1: ylavic + *) Enforce LimitRequestFieldSize after multiple headers with the same + name have been merged, Ensure LimitRequestFieldSize is always logged. + Downgrade some more log messages indicating client errors from level error + to info. Add log messages for various reasons to return HTTP_BAD_REQUEST. + Correctly return a 400 (Bad request) in case of a HTTP/0.9 request like + "GET @example.org/foo". + Add some trace logging to core (using AP_DEBUG_THE_REQUEST macro, because + the TRACE5 facilities aren't in 2.2.x branch). + Improve error message (PR 54384). + Submitted by: sf, rpluem, jailletc36 + [Note: everything in this patch is modifying logging and brings in the + LimitRequestFieldSize logic used for the lifespan of 2.4.x] + Trunk version of patch + http://svn.apache.org/r951900 (server/protocol.c alone) + http://svn.apache.org/r1178566 + http://svn.apache.org/r1185385 + http://svn.apache.org/r1188745 + http://svn.apache.org/r1352911 + http://svn.apache.org/r1433613 + Backport: (Adjustments dodging 2.4'isms such as APLOGNO's) + https://raw.githubusercontent.com/wrowe/patches/master/backport-2.2.x-r951900-r1178566-r1185385-r1188745-r1352911-r1433613.patch + +1: wrowe, covener + ylavic: the patch does not apply cleanly? (I tried both w/ and w/o + backport-2.2.x-r892678.patch first, conflicts in protocol.c) + + *) core: ErrorDocument now works for requests without a Host header. + Support custom ErrorDocuments for HTTP 501 and 414 status codes. + PR: 48357, 57167 + Submitted by: trawick, [Edward Lu ] + Trunk version of patch + http://svn.apache.org/r1392347 + http://svn.apache.org/r1635762 + Backport: + https://raw.githubusercontent.com/wrowe/patches/master/backport-2.2.x-r1392347-r1635762.patch + +1: wrowe, covener + ylavic: same here (hunk + access_status = HTTP_BAD_REQUEST;) + depends on the previous one? + *) core: potential rejection of valid MaxMemFree and ThreadStackSize directives trunk patch: https://svn.apache.org/r1542338 2.4.x patch: https://svn.apache.org/r1542549 diff --git a/docs/manual/mod/core.xml b/docs/manual/mod/core.xml index 326ce7e26e1..da401dba8ef 100644 --- a/docs/manual/mod/core.xml +++ b/docs/manual/mod/core.xml @@ -1440,82 +1440,6 @@ MIME content-type - -HttpProtocolOptions -Modify restrictions on HTTP Request Messages -HttpProtocolOptions [Strict|Unsafe] [RegisteredMethods|LenientMethods] - [Allow0.9|Require1.0] -HttpProtocolOptions Strict LenientMethods Allow0.9 -server config -virtual host -2.2.32 or 2.4.24 and later - - -

This directive changes the rules applied to the HTTP Request Line - (RFC 7230 §3.1.1) and the HTTP Request Header Fields - (RFC 7230 §3.2), which are now applied by default or using - the Strict option. Due to legacy modules, applications or - custom user-agents which must be deperecated the Unsafe - option has been added to revert to the legacy behaviors. These rules - are applied prior to request processing, so must be configured at the - global or default (first) matching virtual host section, by IP/port - interface (and not by name) to be honored.

- -

Prior to the introduction of this directive, the Apache HTTP Server - request message parsers were tolerant of a number of forms of input - which did not conform to the protocol. - RFC 7230 §9.4 Request Splitting and - §9.5 Response Smuggling call out only two of the potential - risks of accepting non-conformant request messages, while - RFC 7230 §3.5 "Message Parsing Robustness" identify the - risks of accepting obscure whitespace and request message formatting. - As of the introduction of this directive, all grammer rules of the - specification are enforced in the default Strict operating - mode, and the strict whitespace suggested by section 3.5 is enforced - and cannot be relaxed.

- -

Users are strongly cautioned against toggling the Unsafe - mode of operation, particularly on outward-facing, publicly accessible - server deployments. If an interface is required for faulty monitoring - or other custom service consumers running on an intranet, users should - toggle the Unsafe option only on a specific virtual host configured - to service their internal private network.

- -

Reviewing the messages logged to the ErrorLog, - configured with LogLevel debug level, - can help identify such faulty requests along with their origin. - Users should pay particular attention to the 400 responses in the access - log for invalid requests which were unexpectedly rejected.

- -

RFC 7231 §4.1 "Request Methods" "Overview" requires that - origin servers shall respond with an error when an unsupported method - is encountered in the request line. This already happens when the - LenientMethods option is used, but administrators may wish - to toggle the RegisteredMethods option and register any - non-standard methods using the RegisterHttpMethod - directive, particularly if the Unsafe option has been toggled. - The RegisteredMethods option should not - be toggled for forward proxy hosts, as the methods supported by the - origin servers are unknown to the proxy server.

- -

RFC 2616 §19.6 "Compatibility With Previous Versions" had - encouraged HTTP servers to support legacy HTTP/0.9 requests. RFC 7230 - superceeds this with "The expectation to support HTTP/0.9 requests has - been removed" and offers additional comments in - RFC 7230 Appendix A. The Require1.0 option allows - the user to remove support of the default Allow0.9 option's - behavior.

-
-
- IfDefine Encloses directives that will be processed only @@ -3757,19 +3681,5 @@ hostname or IP address - -RegisterHttpMethod -Register non-standard HTTP methods -RegisterHttpMethod method [method [...]] -server config - -

HTTP Methods that are not conforming to the relvant RFCs are normally -rejected by request processing in Apache HTTPD. To avoid this, modules -can register non-standard HTTP methods they support. -The RegisterHttpMethod allows to register such -methods manually. This can be useful for if such methods are forwared -for external processing, e.g. to a CGI script.

-
-
diff --git a/include/ap_mmn.h b/include/ap_mmn.h index c459b915dce..29227d53f4c 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -160,13 +160,6 @@ * 20051115.40 (2.2.30) Add ap_map_http_request_error() * 20051115.41 (2.2.32) Add s member to proxy_server_conf struct and server * member to proxy_worker struct. - * 20151115.42 (2.2.32) Add http09_enable, http_conformance, and - * http_methods to core_server_config - * Add ap_scan_http_field_token(), - * ap_scan_http_field_content(), - * and ap_scan_vchar_obstext() - * Replaced fold boolean with with multiple bit flags - * to ap_[r]getline() */ #define MODULE_MAGIC_COOKIE 0x41503232UL /* "AP22" */ @@ -174,7 +167,7 @@ #ifndef MODULE_MAGIC_NUMBER_MAJOR #define MODULE_MAGIC_NUMBER_MAJOR 20051115 #endif -#define MODULE_MAGIC_NUMBER_MINOR 42 /* 0...n */ +#define MODULE_MAGIC_NUMBER_MINOR 41 /* 0...n */ /** * Determine if the server's current MODULE_MAGIC_NUMBER is at least a diff --git a/include/http_core.h b/include/http_core.h index 4a85db67323..c397962bd42 100644 --- a/include/http_core.h +++ b/include/http_core.h @@ -627,21 +627,6 @@ typedef struct { #define AP_MERGE_TRAILERS_DISABLE 2 int merge_trailers; -#define AP_HTTP09_UNSET 0 -#define AP_HTTP09_ENABLE 1 -#define AP_HTTP09_DISABLE 2 - char http09_enable; - -#define AP_HTTP_CONFORMANCE_UNSET 0 -#define AP_HTTP_CONFORMANCE_UNSAFE 1 -#define AP_HTTP_CONFORMANCE_STRICT 2 - char http_conformance; - -#define AP_HTTP_METHODS_UNSET 0 -#define AP_HTTP_METHODS_LENIENT 1 -#define AP_HTTP_METHODS_REGISTERED 2 - char http_methods; - } core_server_config; /* for AddOutputFiltersByType in core.c */ diff --git a/include/http_protocol.h b/include/http_protocol.h index 1fed3b58d4e..185d62cc985 100644 --- a/include/http_protocol.h +++ b/include/http_protocol.h @@ -510,22 +510,17 @@ AP_DECLARE(int) ap_get_basic_auth_pw(request_rec *r, const char **pw); */ AP_CORE_DECLARE(void) ap_parse_uri(request_rec *r, const char *uri); -#define AP_GETLINE_FOLD 1 /* Whether to merge continuation lines */ -#define AP_GETLINE_CRLF 2 /*Whether line ends must be in the form CR LF */ - /** * Get the next line of input for the request * @param s The buffer into which to read the line * @param n The size of the buffer * @param r The request - * @param flags Bit flag of multiple parsing options - * AP_GETLINE_FOLD Whether to merge continuation lines - * AP_GETLINE_CRLF Whether line ends must be in the form CR LF + * @param fold Whether to merge continuation lines * @return The length of the line, if successful * n, if the line is too big to fit in the buffer * -1 for miscellaneous errors */ -AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int flags); +AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int fold); /** * Get the next line of input for the request @@ -543,9 +538,7 @@ AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int flags); * @param n The size of the buffer * @param read The length of the line. * @param r The request - * @param flags Bit flag of multiple parsing options - * AP_GETLINE_FOLD Whether to merge continuation lines - * AP_GETLINE_CRLF Whether line ends must be in the form CR LF + * @param fold Whether to merge continuation lines * @param bb Working brigade to use when reading buckets * @return APR_SUCCESS, if successful * APR_ENOSPC, if the line is too big to fit in the buffer @@ -554,7 +547,7 @@ AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int flags); #if APR_CHARSET_EBCDIC AP_DECLARE(apr_status_t) ap_rgetline(char **s, apr_size_t n, apr_size_t *read, - request_rec *r, int flags, + request_rec *r, int fold, apr_bucket_brigade *bb); #else /* ASCII box */ #define ap_rgetline(s, n, read, r, fold, bb) \ @@ -564,7 +557,7 @@ AP_DECLARE(apr_status_t) ap_rgetline(char **s, apr_size_t n, /** @see ap_rgetline */ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, apr_size_t *read, - request_rec *r, int flags, + request_rec *r, int fold, apr_bucket_brigade *bb); /** diff --git a/include/httpd.h b/include/httpd.h index 55da87a0971..6f4c3d4538e 100644 --- a/include/httpd.h +++ b/include/httpd.h @@ -1414,28 +1414,6 @@ AP_DECLARE(char *) ap_get_list_item(apr_pool_t *p, const char **field); */ AP_DECLARE(int) ap_find_list_item(apr_pool_t *p, const char *line, const char *tok); -/* Scan a string for field content chars, as defined by RFC7230 section 3.2 - * including VCHAR/obs-text, as well as HT and SP - * @param ptr The string to scan - * @return A pointer to the first (non-HT) ASCII ctrl character. - * @note lws and trailing whitespace are scanned, the caller is responsible - * for trimming leading and trailing whitespace - */ -AP_DECLARE(const char *) ap_scan_http_field_content(const char *ptr); - -/* Scan a string for token characters, as defined by RFC7230 section 3.2.6 - * @param ptr The string to scan - * @return A pointer to the first non-token character. - */ -AP_DECLARE(const char *) ap_scan_http_token(const char *ptr); - -/* Scan a string for visible ASCII (0x21-0x7E) or obstext (0x80+) - * and return a pointer to the first SP/CTL/NUL character encountered. - * @param ptr The string to scan - * @return A pointer to the first SP/CTL character. - */ -AP_DECLARE(const char *) ap_scan_vchar_obstext(const char *ptr); - /** * Retrieve a token, spacing over it and adjusting the pointer to * the first non-white byte afterwards. Note that these tokens diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c index 8e95494f1fc..006af7ef09d 100644 --- a/modules/http/http_filters.c +++ b/modules/http/http_filters.c @@ -125,15 +125,14 @@ static apr_status_t bail_out_on_error(http_ctx_t *ctx, /** * Parse a chunk line with optional extension, detect overflow. - * There are several error cases: - * 1) If the chunk link is misformatted, APR_EINVAL is returned. - * 2) If the conversion would require too many bits, APR_EGENERAL is returned. - * 3) If the conversion used the correct number of bits, but an overflow + * There are two error cases: + * 1) If the conversion would require too many bits, APR_EGENERAL is returned. + * 2) If the conversion used the correct number of bits, but an overflow * caused only the sign bit to flip, then APR_ENOSPC is returned. - * A negative chunk length always indicates an overflow error. + * In general, any negative number can be considered an overflow error. */ static apr_status_t parse_chunk_size(http_ctx_t *ctx, const char *buffer, - apr_size_t len, int linelimit, int strict) + apr_size_t len, int linelimit) { apr_size_t i = 0; @@ -146,12 +145,6 @@ static apr_status_t parse_chunk_size(http_ctx_t *ctx, const char *buffer, if (ctx->state == BODY_CHUNK_END || ctx->state == BODY_CHUNK_END_LF) { if (c == LF) { - if (strict && (ctx->state != BODY_CHUNK_END_LF)) { - /* - * CR missing before LF. - */ - return APR_EINVAL; - } ctx->state = BODY_CHUNK; } else if (c == CR && ctx->state == BODY_CHUNK_END) { @@ -159,7 +152,7 @@ static apr_status_t parse_chunk_size(http_ctx_t *ctx, const char *buffer, } else { /* - * CRLF expected. + * LF expected. */ return APR_EINVAL; } @@ -186,12 +179,6 @@ static apr_status_t parse_chunk_size(http_ctx_t *ctx, const char *buffer, } if (c == LF) { - if (strict && (ctx->state != BODY_CHUNK_LF)) { - /* - * CR missing before LF. - */ - return APR_EINVAL; - } if (ctx->remaining) { ctx->state = BODY_CHUNK_DATA; } @@ -213,17 +200,14 @@ static apr_status_t parse_chunk_size(http_ctx_t *ctx, const char *buffer, } else if (ctx->state == BODY_CHUNK_EXT) { /* - * Control chars (excluding tabs) are invalid. - * TODO: more precisely limit input + * Control chars (but tabs) are invalid. */ if (c != '\t' && apr_iscntrl(c)) { return APR_EINVAL; } } else if (c == ' ' || c == '\t') { - /* Be lenient up to 10 implied *LWS, a legacy of RFC 2616, - * and noted as errata to RFC7230; - * https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4667 + /* Be lenient up to 10 BWS (term from rfc7230 - 3.2.3). */ ctx->state = BODY_CHUNK_CR; if (++ctx->chunk_bws > 10) { @@ -339,10 +323,7 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) { - core_server_config *conf = - (core_server_config *)ap_get_module_config(f->r->server->module_config, - &core_module); - int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); + core_server_config *conf; apr_bucket *e; http_ctx_t *ctx = f->ctx; apr_status_t rv; @@ -350,6 +331,9 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, apr_bucket_brigade *bb; int again; + conf = (core_server_config *) + ap_get_module_config(f->r->server->module_config, &core_module); + /* just get out of the way of things we don't want. */ if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) { return ap_get_brigade(f->next, b, mode, block, readbytes); @@ -541,7 +525,7 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, if (rv == APR_SUCCESS) { parsing = 1; rv = parse_chunk_size(ctx, buffer, len, - f->r->server->limit_req_fieldsize, strict); + f->r->server->limit_req_fieldsize); } if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_INFO, rv, f->r, @@ -683,83 +667,14 @@ 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 strict; -}; - -/* 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; - const char *test; - - if (name[0] == '\0') { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, - "Empty response header name, aborting request"); - return 0; - } - - if (ctx->strict) { - test = ap_scan_http_token(name); - } - else { - test = ap_scan_vchar_obstext(name); - } - if (*test) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, - "Response header name '%s' contains invalid " - "characters, aborting request", - name); - return 0; - } - - test = ap_scan_http_field_content(val); - if (*test) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, - "Response header '%s' value of '%s' contains invalid " - "characters, aborting request", - 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) -{ - struct check_header_ctx ctx; - core_server_config *conf = - (core_server_config *)ap_get_module_config(r->server->module_config, - &core_module); - - ctx.r = r; - ctx.strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); - return apr_table_do(check_header, &ctx, r->headers_out, NULL) && - apr_table_do(check_header, &ctx, r->err_headers_out, NULL); -} - -static int check_headers_recursion(request_rec *r) -{ - void *check = NULL; - apr_pool_userdata_get(&check, "check_headers_recursion", r->pool); - if (check) { - return 1; - } - apr_pool_userdata_setn("true", "check_headers_recursion", NULL, r->pool); - 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 apr_table_do(), so don't change its interface. + * 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. * It returns true unless there was a write error of some kind. */ static int form_header_field(header_struct *h, @@ -1231,7 +1146,6 @@ AP_DECLARE_NONSTD(int) ap_send_http_trace(request_rec *r) typedef struct header_filter_ctx { int headers_sent; - int headers_error; } header_filter_ctx; AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, @@ -1247,23 +1161,19 @@ 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; - apr_bucket *eos = NULL; AP_DEBUG_ASSERT(!r->main); - if (!ctx) { - ctx = f->ctx = apr_pcalloc(r->pool, sizeof(header_filter_ctx)); - } - if (ctx->headers_sent) { - /* Eat body if response must not have one. */ - if (r->header_only || r->status == HTTP_NO_CONTENT) { + if (r->header_only) { + if (!ctx) { + ctx = f->ctx = apr_pcalloc(r->pool, sizeof(header_filter_ctx)); + } + else if (ctx->headers_sent) { apr_brigade_cleanup(b); - return APR_SUCCESS; + return OK; } } - else if (!ctx->headers_error && !check_headers(r)) { - ctx->headers_error = 1; - } + for (e = APR_BRIGADE_FIRST(b); e != APR_BRIGADE_SENTINEL(b); e = APR_BUCKET_NEXT(e)) @@ -1280,44 +1190,10 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, ap_remove_output_filter(f); return ap_pass_brigade(f->next, b); } - if (ctx->headers_error && APR_BUCKET_IS_EOS(e)) { - eos = e; - } - } - if (ctx->headers_error) { - if (!eos) { - /* Eat body until EOS */ - apr_brigade_cleanup(b); - return APR_SUCCESS; - } - - /* We may come back here from ap_die() below, - * so clear anything from this response. - */ - ctx->headers_error = 0; - apr_table_clear(r->headers_out); - apr_table_clear(r->err_headers_out); - - /* Don't recall ap_die() if we come back here (from its own internal - * redirect or error response), otherwise we can end up in infinite - * recursion; better fall through with 500, minimal headers and an - * empty body (EOS only). - */ - if (!check_headers_recursion(r)) { - apr_brigade_cleanup(b); - ap_die(HTTP_INTERNAL_SERVER_ERROR, r); - return AP_FILTER_ERROR; - } - APR_BUCKET_REMOVE(eos); - apr_brigade_cleanup(b); - APR_BRIGADE_INSERT_TAIL(b, eos); - r->status = HTTP_INTERNAL_SERVER_ERROR; - r->content_type = r->content_encoding = NULL; - r->content_languages = NULL; - ap_set_content_length(r, 0); } - else if (eb) { + if (eb) { int status; + status = eb->status; apr_brigade_cleanup(b); ap_die(status, r); @@ -1374,10 +1250,6 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, apr_table_unset(r->headers_out, "Content-Length"); } - if (r->status == HTTP_NO_CONTENT) { - apr_table_unset(r->headers_out, "Content-Length"); - } - ctype = ap_make_content_type(r, r->content_type); if (strcasecmp(ctype, NO_CONTENT_TYPE)) { apr_table_setn(r->headers_out, "Content-Type", ctype); @@ -1466,11 +1338,11 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, terminate_header(b2); ap_pass_brigade(f->next, b2); - ctx->headers_sent = 1; - if (r->header_only || r->status == HTTP_NO_CONTENT) { + if (r->header_only) { apr_brigade_cleanup(b); - return APR_SUCCESS; + ctx->headers_sent = 1; + return OK; } r->sent_bodyct = 1; /* Whatever follows is real body stuff... */ diff --git a/server/core.c b/server/core.c index b401d93a637..ddcfa6004b6 100644 --- a/server/core.c +++ b/server/core.c @@ -546,15 +546,6 @@ static void *merge_core_server_configs(apr_pool_t *p, void *basev, void *virtv) ? virt->merge_trailers : base->merge_trailers; - 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; - - if (virt->http_methods != AP_HTTP_METHODS_UNSET) - conf->http_methods = virt->http_methods; - return conf; } @@ -3250,57 +3241,6 @@ static const char *add_ct_output_filters(cmd_parms *cmd, void *conf_, return NULL; } - -static const char *set_http_protocol_options(cmd_parms *cmd, void *dummy, - const char *arg) -{ - core_server_config *conf = ap_get_module_config(cmd->server->module_config, - &core_module); - if (strcasecmp(arg, "allow0.9") == 0) - conf->http09_enable |= AP_HTTP09_ENABLE; - else if (strcasecmp(arg, "require1.0") == 0) - conf->http09_enable |= AP_HTTP09_DISABLE; - else if (strcasecmp(arg, "strict") == 0) - conf->http_conformance |= AP_HTTP_CONFORMANCE_STRICT; - else if (strcasecmp(arg, "unsafe") == 0) - conf->http_conformance |= AP_HTTP_CONFORMANCE_UNSAFE; - else if (strcasecmp(arg, "registeredmethods") == 0) - conf->http_methods |= AP_HTTP_METHODS_REGISTERED; - else if (strcasecmp(arg, "lenientmethods") == 0) - conf->http_methods |= AP_HTTP_METHODS_LENIENT; - else - return "HttpProtocolOptions accepts " - "'Unsafe' or 'Strict' (default), " - "'RegisteredMethods' or 'LenientMethods' (default), and " - "'Require1.0' or 'Allow0.9' (default)"; - - if ((conf->http09_enable & AP_HTTP09_ENABLE) - && (conf->http09_enable & AP_HTTP09_DISABLE)) - return "HttpProtocolOptions 'Allow0.9' and 'Require1.0'" - " are mutually exclusive"; - - if ((conf->http_conformance & AP_HTTP_CONFORMANCE_STRICT) - && (conf->http_conformance & AP_HTTP_CONFORMANCE_UNSAFE)) - return "HttpProtocolOptions 'Strict' and 'Unsafe'" - " are mutually exclusive"; - - if ((conf->http_methods & AP_HTTP_METHODS_REGISTERED) - && (conf->http_methods & AP_HTTP_METHODS_LENIENT)) - return "HttpProtocolOptions 'RegisteredMethods' and 'LenientMethods'" - " are mutually exclusive"; - - return NULL; -} - -static const char *set_http_method(cmd_parms *cmd, void *conf, const char *arg) -{ - const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); - if (err != NULL) - return err; - ap_method_register(cmd->pool, arg); - return NULL; -} - /* * Insert filters requested by the AddOutputFilterByType * configuration directive. We cannot add filters based @@ -3610,12 +3550,6 @@ AP_INIT_FLAG("Suexec", unixd_set_suexec, NULL, RSRC_CONF, #endif AP_INIT_FLAG("MergeTrailers", set_merge_trailers, NULL, RSRC_CONF, "merge request trailers into request headers or not"), -AP_INIT_ITERATE("HttpProtocolOptions", set_http_protocol_options, NULL, RSRC_CONF, - "'Allow0.9' or 'Require1.0' (default); " - "'RegisteredMethods' or 'LenientMethods' (default); " - "'Unsafe' or 'Strict' (default). Sets HTTP acceptance rules"), -AP_INIT_ITERATE("RegisterHttpMethod", set_http_method, NULL, RSRC_CONF, - "Registers non-standard HTTP methods"), { NULL } }; diff --git a/server/gen_test_char.c b/server/gen_test_char.c index 01f1ac57ec0..a0b55100f4e 100644 --- a/server/gen_test_char.c +++ b/server/gen_test_char.c @@ -16,11 +16,11 @@ #ifdef CROSS_COMPILE -#include #define apr_isalnum(c) (isalnum(((unsigned char)(c)))) #define apr_isalpha(c) (isalpha(((unsigned char)(c)))) #define apr_iscntrl(c) (iscntrl(((unsigned char)(c)))) #define apr_isprint(c) (isprint(((unsigned char)(c)))) +#include #define APR_HAVE_STDIO_H 1 #define APR_HAVE_STRING_H 1 @@ -51,13 +51,11 @@ #define T_HTTP_TOKEN_STOP (0x08) #define T_ESCAPE_LOGITEM (0x10) #define T_ESCAPE_FORENSIC (0x20) -#define T_HTTP_CTRLS (0x80) -#define T_VCHAR_OBSTEXT (0x100) int main(int argc, char *argv[]) { unsigned c; - unsigned short flags; + unsigned char flags; printf("/* this file is automatically generated by gen_test_char, " "do not edit */\n" @@ -67,22 +65,18 @@ int main(int argc, char *argv[]) "#define T_HTTP_TOKEN_STOP (%u)\n" "#define T_ESCAPE_LOGITEM (%u)\n" "#define T_ESCAPE_FORENSIC (%u)\n" - "#define T_HTTP_CTRLS (%u)\n" - "#define T_VCHAR_OBSTEXT (%u)\n" "\n" - "static const unsigned short test_char_table[256] = {", + "static const unsigned char test_char_table[256] = {", T_ESCAPE_SHELL_CMD, T_ESCAPE_PATH_SEGMENT, T_OS_ESCAPE_PATH, T_HTTP_TOKEN_STOP, T_ESCAPE_LOGITEM, - T_ESCAPE_FORENSIC, - T_HTTP_CTRLS, - T_VCHAR_OBSTEXT); + T_ESCAPE_FORENSIC); for (c = 0; c < 256; ++c) { flags = 0; - if (c % 8 == 0) + if (c % 20 == 0) printf("\n "); /* escape_shell_cmd */ @@ -110,36 +104,15 @@ int main(int argc, char *argv[]) flags |= T_ESCAPE_PATH_SEGMENT; } - if (!apr_isalnum(c) && !strchr("$-_.+!*'(),:;@&=/~", c)) { + if (!apr_isalnum(c) && !strchr("$-_.+!*'(),:@&=/~", c)) { flags |= T_OS_ESCAPE_PATH; } - /* Stop for any non-'token' character, including ctrls, obs-text, - * and "tspecials" (RFC2068) a.k.a. "separators" (RFC2616), which - * is easer to express as characters remaining in the ASCII token set - */ - if (!c || !(apr_isalnum(c) || strchr("!#$%&'*+-.^_`|~", c))) { + /* these are the "tspecials" (RFC2068) or "separators" (RFC2616) */ + if (c && (apr_iscntrl(c) || strchr(" \t()<>@,;:\\\"/[]?={}", c))) { flags |= T_HTTP_TOKEN_STOP; } - /* Catch CTRLs other than VCHAR, HT and SP, and obs-text (RFC7230 3.2) - * This includes only the C0 plane, not C1 (which is obs-text itself.) - * XXX: We should verify that all ASCII C0 ctrls/DEL corresponding to - * the current EBCDIC translation are captured, and ASCII C1 ctrls - * corresponding are all permitted (as they fall under obs-text rule) - */ - if (!c || (apr_iscntrl(c) && c != '\t')) { - flags |= T_HTTP_CTRLS; - } - - /* From RFC3986, the specific sets of gen-delims, sub-delims (2.2), - * and unreserved (2.3) that are possible somewhere within a URI. - * Spec requires all others to be %XX encoded, including obs-text. - */ - if (c && !apr_iscntrl(c) && c != ' ') { - flags |= T_VCHAR_OBSTEXT; - } - /* For logging, escape all control characters, * double quotes (because they delimit the request in the log file) * backslashes (because we use backslash for escaping) @@ -157,7 +130,7 @@ int main(int argc, char *argv[]) flags |= T_ESCAPE_FORENSIC; } - printf("0x%03x%c", flags, (c < 255) ? ',' : ' '); + printf("%u%c", flags, (c < 255) ? ',' : ' '); } printf("\n};\n"); diff --git a/server/protocol.c b/server/protocol.c index 4a22b766f77..6ce98cc1655 100644 --- a/server/protocol.c +++ b/server/protocol.c @@ -183,13 +183,12 @@ AP_DECLARE(apr_time_t) ap_rationalize_mtime(request_rec *r, apr_time_t mtime) return (mtime > now) ? now : mtime; } +/* Min # of bytes to allocate when reading a request line */ +#define MIN_LINE_ALLOC 80 + /* Get a line of protocol input, including any continuation lines * caused by MIME folding (or broken clients) if fold != 0, and place it * in the buffer s, of size n bytes, without the ending newline. - * - * Pulls from r->proto_input_filters instead of r->input_filters for - * stricter protocol adherence and better input filter behavior during - * chunked trailer processing (for http). * * If s is NULL, ap_rgetline_core will allocate necessary memory from r->pool. * @@ -199,7 +198,7 @@ AP_DECLARE(apr_time_t) ap_rationalize_mtime(request_rec *r, apr_time_t mtime) * APR_ENOSPC is returned if there is not enough buffer space. * Other errors may be returned on other errors. * - * The [CR]LF are *not* returned in the buffer. Therefore, a *read of 0 + * The LF is *not* returned in the buffer. Therefore, a *read of 0 * indicates that an empty line was read. * * Notes: Because the buffer uses 1 char for NUL, the most we can return is @@ -210,35 +209,32 @@ AP_DECLARE(apr_time_t) ap_rationalize_mtime(request_rec *r, apr_time_t mtime) */ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, apr_size_t *read, request_rec *r, - int flags, apr_bucket_brigade *bb) + int fold, apr_bucket_brigade *bb) { apr_status_t rv; apr_bucket *e; apr_size_t bytes_handled = 0, current_alloc = 0; char *pos, *last_char = *s; int do_alloc = (*s == NULL), saw_eos = 0; - int fold = flags & AP_GETLINE_FOLD; - int crlf = flags & AP_GETLINE_CRLF; /* * Initialize last_char as otherwise a random value will be compared * against APR_ASCII_LF at the end of the loop if bb only contains * zero-length buckets. */ - if (last_char) + if (last_char) { *last_char = '\0'; + } for (;;) { apr_brigade_cleanup(bb); - rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_GETLINE, + rv = ap_get_brigade(r->input_filters, bb, AP_MODE_GETLINE, APR_BLOCK_READ, 0); if (rv != APR_SUCCESS) { return rv; } - /* Something horribly wrong happened. Someone didn't block! - * (this also happens at the end of each keepalive connection) - */ + /* Something horribly wrong happened. Someone didn't block! */ if (APR_BRIGADE_EMPTY(bb)) { return APR_EGENERAL; } @@ -289,6 +285,9 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, /* We'll assume the common case where one bucket is enough. */ if (!*s) { current_alloc = len; + if (current_alloc < MIN_LINE_ALLOC) { + current_alloc = MIN_LINE_ALLOC; + } *s = apr_palloc(r->pool, current_alloc); } else if (bytes_handled + len > current_alloc) { @@ -324,13 +323,6 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, } } - if (crlf && (last_char <= *s || last_char[-1] != APR_ASCII_CR)) { - *last_char = '\0'; - bytes_handled = last_char - *s; - *read = bytes_handled; - return APR_EINVAL; - } - /* Now NUL-terminate the string at the end of the line; * if the last-but-one character is a CR, terminate there */ if (last_char > *s && last_char[-1] == APR_ASCII_CR) { @@ -353,7 +345,7 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, apr_brigade_cleanup(bb); /* We only care about the first byte. */ - rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_SPECULATIVE, + rv = ap_get_brigade(r->input_filters, bb, AP_MODE_SPECULATIVE, APR_BLOCK_READ, 1); if (rv != APR_SUCCESS) { return rv; @@ -404,8 +396,7 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, */ if (do_alloc) { tmp = NULL; - } - else { + } else { /* We're null terminated. */ tmp = last_char; } @@ -475,7 +466,7 @@ AP_DECLARE(apr_status_t) ap_rgetline(char **s, apr_size_t n, } #endif -AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int flags) +AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int fold) { char *tmp_s = s; apr_status_t rv; @@ -483,7 +474,7 @@ AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int flags) apr_bucket_brigade *tmp_bb; tmp_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); - rv = ap_rgetline(&tmp_s, n, &len, r, flags, tmp_bb); + rv = ap_rgetline(&tmp_s, n, &len, r, fold, tmp_bb); apr_brigade_destroy(tmp_bb); /* Map the out-of-space condition to the old API. */ @@ -563,31 +554,16 @@ AP_CORE_DECLARE(void) ap_parse_uri(request_rec *r, const char *uri) } } -/* get the length of the field name for logging, but no more than 80 bytes */ -#define LOG_NAME_MAX_LEN 80 -static int field_name_len(const char *field) -{ - const char *end = ap_strchr_c(field, ':'); - if (end == NULL || end - field > LOG_NAME_MAX_LEN) - return LOG_NAME_MAX_LEN; - return end - field; -} - static int read_request_line(request_rec *r, apr_bucket_brigade *bb) { - enum { - rrl_none, rrl_badmethod, rrl_badwhitespace, rrl_excesswhitespace, - rrl_missinguri, rrl_baduri, rrl_badprotocol, rrl_trailingtext, - rrl_badmethod09, rrl_reject09 - } deferred_error = rrl_none; - char *ll; - char *uri; + const char *ll; + const char *uri; + const char *pro; + + unsigned int major = 1, minor = 0; /* Assume HTTP/1.0 if non-"HTTP" protocol */ + char http[5]; apr_size_t len; int num_blank_lines = DEFAULT_LIMIT_BLANK_LINES; - core_server_config *conf = - (core_server_config *)ap_get_module_config(r->server->module_config, - &core_module); - int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); /* Read past empty lines until we get a real request line, * a read error, the connection closes (EOF), or we timeout. @@ -612,7 +588,7 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb) */ r->the_request = NULL; rv = ap_rgetline(&(r->the_request), (apr_size_t)(r->server->limit_req_line + 2), - &len, r, strict ? AP_GETLINE_CRLF : 0, bb); + &len, r, 0, bb); if (rv != APR_SUCCESS) { r->request_time = apr_time_now(); @@ -622,7 +598,7 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb) * happen if it exceeds the configured limit for a request-line. */ if (APR_STATUS_IS_ENOSPC(rv)) { - r->status = HTTP_REQUEST_URI_TOO_LARGE; + r->status = HTTP_REQUEST_URI_TOO_LARGE; } else if (APR_STATUS_IS_TIMEUP(rv)) { r->status = HTTP_REQUEST_TIME_OUT; @@ -637,282 +613,68 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb) } while ((len <= 0) && (--num_blank_lines >= 0)); r->request_time = apr_time_now(); + ll = r->the_request; + r->method = ap_getword_white(r->pool, &ll); - r->method = r->the_request; - - /* If there is whitespace before a method, skip it and mark in error */ - if (apr_isspace(*r->method)) { - deferred_error = rrl_badwhitespace; - for ( ; apr_isspace(*r->method); ++r->method) - ; - } - - /* Scan the method up to the next whitespace, ensure it contains only - * valid http-token characters, otherwise mark in error - */ - if (strict) { - ll = (char*) ap_scan_http_token(r->method); - } - else { - ll = (char*) ap_scan_vchar_obstext(r->method); - } - - if (((ll == r->method) || (*ll && !apr_isspace(*ll))) - && deferred_error == rrl_none) { - deferred_error = rrl_badmethod; - ll = strpbrk(ll, "\t\n\v\f\r "); - } + uri = ap_getword_white(r->pool, &ll); - /* Verify method terminated with a single SP, or mark as specific error */ - if (!ll) { - if (deferred_error == rrl_none) - deferred_error = rrl_missinguri; - r->protocol = uri = ""; - len = 0; - goto rrl_done; - } - else if (strict && ll[0] && apr_isspace(ll[1]) - && deferred_error == rrl_none) { - deferred_error = rrl_excesswhitespace; - } - - /* Advance uri pointer over leading whitespace, NUL terminate the method - * If non-SP whitespace is encountered, mark as specific error - */ - for (uri = ll; apr_isspace(*uri); ++uri) - if (*uri != ' ' && deferred_error == rrl_none) - deferred_error = rrl_badwhitespace; - *ll = '\0'; - - if (!*uri && deferred_error == rrl_none) - deferred_error = rrl_missinguri; - - /* Scan the URI up to the next whitespace, ensure it contains no raw - * control characters, otherwise mark in error - */ - ll = (char*) ap_scan_vchar_obstext(uri); - if (ll == uri || (*ll && !apr_isspace(*ll))) { - deferred_error = rrl_baduri; - ll = strpbrk(ll, "\t\n\v\f\r "); - } - - /* Verify URI terminated with a single SP, or mark as specific error */ - if (!ll) { - r->protocol = ""; - len = 0; - goto rrl_done; - } - else if (strict && ll[0] && apr_isspace(ll[1]) - && deferred_error == rrl_none) { - deferred_error = rrl_excesswhitespace; - } - - /* Advance protocol pointer over leading whitespace, NUL terminate the uri - * If non-SP whitespace is encountered, mark as specific error - */ - for (r->protocol = ll; apr_isspace(*r->protocol); ++r->protocol) - if (*r->protocol != ' ' && deferred_error == rrl_none) - deferred_error = rrl_badwhitespace; - *ll = '\0'; - - /* Scan the protocol up to the next whitespace, validation comes later */ - if (!(ll = (char*) ap_scan_vchar_obstext(r->protocol))) { - len = strlen(r->protocol); - goto rrl_done; - } - len = ll - r->protocol; - - /* Advance over trailing whitespace, if found mark in error, - * determine if trailing text is found, unconditionally mark in error, - * finally NUL terminate the protocol string - */ - if (*ll && !apr_isspace(*ll)) { - deferred_error = rrl_badprotocol; - } - else if (strict && *ll) { - deferred_error = rrl_excesswhitespace; - } - else { - for ( ; apr_isspace(*ll); ++ll) - if (*ll != ' ' && deferred_error == rrl_none) - deferred_error = rrl_badwhitespace; - if (*ll && deferred_error == rrl_none) - deferred_error = rrl_trailingtext; + if (!*r->method || !*uri) { + r->status = HTTP_BAD_REQUEST; + r->proto_num = HTTP_VERSION(1,0); + r->protocol = apr_pstrdup(r->pool, "HTTP/1.0"); + return 0; } - *((char *)r->protocol + len) = '\0'; -rrl_done: - /* For internal integrety and palloc efficiency, reconstruct the_request - * in one palloc, using only single SP characters, per spec. - */ - r->the_request = apr_pstrcat(r->pool, r->method, *uri ? " " : NULL, uri, - *r->protocol ? " " : NULL, r->protocol, NULL); + /* Provide quick information about the request method as soon as known */ - if (len == 8 - && r->protocol[0] == 'H' && r->protocol[1] == 'T' - && r->protocol[2] == 'T' && r->protocol[3] == 'P' - && r->protocol[4] == '/' && apr_isdigit(r->protocol[5]) - && r->protocol[6] == '.' && apr_isdigit(r->protocol[7]) - && r->protocol[5] != '0') { - r->assbackwards = 0; - r->proto_num = HTTP_VERSION(r->protocol[5] - '0', r->protocol[7] - '0'); - } - else if (len == 8 - && (r->protocol[0] == 'H' || r->protocol[0] == 'h') - && (r->protocol[1] == 'T' || r->protocol[1] == 't') - && (r->protocol[2] == 'T' || r->protocol[2] == 't') - && (r->protocol[3] == 'P' || r->protocol[3] == 'p') - && r->protocol[4] == '/' && apr_isdigit(r->protocol[5]) - && r->protocol[6] == '.' && apr_isdigit(r->protocol[7]) - && r->protocol[5] != '0') { - r->assbackwards = 0; - r->proto_num = HTTP_VERSION(r->protocol[5] - '0', r->protocol[7] - '0'); - if (strict && deferred_error == rrl_none) - deferred_error = rrl_badprotocol; - else - memcpy((char*)r->protocol, "HTTP", 4); - } - else if (r->protocol[0]) { - r->proto_num = HTTP_VERSION(0, 9); - /* Defer setting the r->protocol string till error msg is composed */ - if (deferred_error == rrl_none) - deferred_error = rrl_badprotocol; - } - else { - r->assbackwards = 1; - r->protocol = apr_pstrdup(r->pool, "HTTP/0.9"); - r->proto_num = HTTP_VERSION(0, 9); - } - - /* Determine the method_number and parse the uri prior to invoking error - * handling, such that these fields are available for subsitution - */ r->method_number = ap_method_number_of(r->method); - if (r->method_number == M_GET && r->method[0] == 'H') + if (r->method_number == M_GET && r->method[0] == 'H') { r->header_only = 1; - - ap_parse_uri(r, uri); - - /* With the request understood, we can consider HTTP/0.9 specific errors */ - if (r->proto_num == HTTP_VERSION(0, 9) && deferred_error == rrl_none) { - if (conf->http09_enable == AP_HTTP09_DISABLE) - deferred_error = rrl_reject09; - else if (strict && (r->method_number != M_GET || r->header_only)) - deferred_error = rrl_badmethod09; - } - - /* Now that the method, uri and protocol are all processed, - * we can safely resume any deferred error reporting - */ - if (deferred_error != rrl_none) { - if (deferred_error == rrl_badmethod) - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "HTTP Request Line; Invalid method token: '%.*s'", - field_name_len(r->method), r->method); - else if (deferred_error == rrl_badmethod09) - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "HTTP Request Line; Invalid method token: '%.*s'" - " (only GET is allowed for HTTP/0.9 requests)", - field_name_len(r->method), r->method); - else if (deferred_error == rrl_missinguri) - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "HTTP Request Line; Missing URI"); - else if (deferred_error == rrl_baduri) - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "HTTP Request Line; URI incorrectly encoded: '%.*s'", - field_name_len(r->uri), r->uri); - else if (deferred_error == rrl_badwhitespace) - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "HTTP Request Line; Invalid whitespace"); - else if (deferred_error == rrl_excesswhitespace) - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "HTTP Request Line; Excess whitespace " - "(disallowed by HttpProtocolOptions Strict"); - else if (deferred_error == rrl_trailingtext) - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "HTTP Request Line; Extraneous text found '%.*s' " - "(perhaps whitespace was injected?)", - field_name_len(ll), ll); - else if (deferred_error == rrl_reject09) - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "HTTP Request Line; Rejected HTTP/0.9 request"); - else if (deferred_error == rrl_badprotocol) - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "HTTP Request Line; Unrecognized protocol '%.*s' " - "(perhaps whitespace was injected?)", - field_name_len(r->protocol), r->protocol); - r->status = HTTP_BAD_REQUEST; - goto rrl_failed; } - if (conf->http_methods == AP_HTTP_METHODS_REGISTERED - && r->method_number == M_INVALID) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "HTTP Request Line; Unrecognized HTTP method: '%.*s' " - "(disallowed by RegisteredMethods)", - field_name_len(r->method), r->method); - r->status = HTTP_NOT_IMPLEMENTED; - /* This can't happen in an HTTP/0.9 request, we verified GET above */ + ap_parse_uri(r, uri); + if (r->status != HTTP_OK) { + r->proto_num = HTTP_VERSION(1,0); + r->protocol = apr_pstrdup(r->pool, "HTTP/1.0"); return 0; } - if (r->status != HTTP_OK) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "HTTP Request Line; Unable to parse URI: '%.*s'", - field_name_len(r->uri), r->uri); - goto rrl_failed; + if (ll[0]) { + r->assbackwards = 0; + pro = ll; + len = strlen(ll); + } else { + r->assbackwards = 1; + pro = "HTTP/0.9"; + len = 8; } + r->protocol = apr_pstrmemdup(r->pool, pro, len); - if (strict) { - if (r->parsed_uri.fragment) { - /* RFC3986 3.5: no fragment */ - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "HTTP Request Line; URI must not contain a fragment"); - r->status = HTTP_BAD_REQUEST; - goto rrl_failed; - } - if (r->parsed_uri.user || r->parsed_uri.password) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "HTTP Request Line; URI must not contain a " - "username/password"); - r->status = HTTP_BAD_REQUEST; - goto rrl_failed; - } - } + /* Avoid sscanf in the common case */ + if (len == 8 + && pro[0] == 'H' && pro[1] == 'T' && pro[2] == 'T' && pro[3] == 'P' + && pro[4] == '/' && apr_isdigit(pro[5]) && pro[6] == '.' + && 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); return 1; - -rrl_failed: - if (r->proto_num == HTTP_VERSION(0, 9)) { - /* Send all parsing and protocol error response with 1.x behavior, - * and reserve 505 errors for actual HTTP protocols presented. - * As called out in RFC7230 3.5, any errors parsing the protocol - * from the request line are nearly always misencoded HTTP/1.x - * requests. Only a valid 0.9 request with no parsing errors - * at all may be treated as a simple request, if allowed. - */ - r->assbackwards = 0; - r->connection->keepalive = AP_CONN_CLOSE; - r->proto_num = HTTP_VERSION(1, 0); - r->protocol = apr_pstrdup(r->pool, "HTTP/1.0"); - } - return 0; } -static int table_do_fn_check_lengths(void *r_, const char *key, - const char *value) +/* get the length of the field name for logging, but no more than 80 bytes */ +#define LOG_NAME_MAX_LEN 80 +static int field_name_len(const char *field) { - request_rec *r = r_; - if (value == NULL || r->server->limit_req_fieldsize >= strlen(value) ) - return 1; - - r->status = HTTP_BAD_REQUEST; - apr_table_setn(r->notes, "error-notes", - "Size of a request header field exceeds server limit."); - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Request " - "header exceeds LimitRequestFieldSize after merging: %.*s", - field_name_len(key), key); - return 0; + const char *end = ap_strchr_c(field, ':'); + if (end == NULL || end - field > LOG_NAME_MAX_LEN) + return LOG_NAME_MAX_LEN; + return end - field; } AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb) @@ -925,10 +687,6 @@ 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 = - (core_server_config *)ap_get_module_config(r->server->module_config, - &core_module); - int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); /* * Read header lines until we get the empty separator line, a read error, @@ -936,10 +694,11 @@ AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb */ while(1) { apr_status_t rv; + int folded = 0; field = NULL; rv = ap_rgetline(&field, r->server->limit_req_fieldsize + 2, - &len, r, strict ? AP_GETLINE_CRLF : 0, bb); + &len, r, 0, bb); if (rv != APR_SUCCESS) { if (APR_STATUS_IS_TIMEUP(rv)) { @@ -955,228 +714,148 @@ AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb * finding the end-of-line. This is only going to happen if it * exceeds the configured limit for a field size. */ - if (rv == APR_ENOSPC) { + if (rv == APR_ENOSPC && field) { + /* ensure ap_escape_html will terminate correctly */ + field[len - 1] = '\0'; apr_table_setn(r->notes, "error-notes", - "Size of a request header field " - "exceeds server limit."); - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, - "Request header exceeds LimitRequestFieldSize%s" - "%.*s", - (field && *field) ? ": " : "", - (field) ? field_name_len(field) : 0, - (field) ? field : ""); + apr_psprintf(r->pool, + "Size of a request header field " + "exceeds server limit.
\n" + "
\n%.*s\n
/n", + field_name_len(field), + ap_escape_html(r->pool, field))); + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, + "Request header exceeds LimitRequestFieldSize: " + "%.*s", field_name_len(field), field); } return; } - /* For all header values, and all obs-fold lines, the presence of - * additional whitespace is a no-op, so collapse trailing whitespace - * to save buffer allocation and optimize copy operations. - * Do not remove the last single whitespace under any condition. - */ - while (len > 1 && (field[len-1] == '\t' || field[len-1] == ' ')) { - field[--len] = '\0'; - } - - if (*field == '\t' || *field == ' ') { - - /* Append any newly-read obs-fold line onto the preceding - * last_field line we are processing - */ - apr_size_t fold_len; - - if (last_field == NULL) { - r->status = HTTP_BAD_REQUEST; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "Line folding encountered before first" - " header line"); - return; - } - - if (field[1] == '\0') { - r->status = HTTP_BAD_REQUEST; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "Empty folded line encountered"); - return; - } - - /* Leading whitespace on an obs-fold line can be - * similarly discarded */ - while (field[1] == '\t' || field[1] == ' ') { - ++field; --len; - } - - /* This line is a continuation of the preceding line(s), - * so append it to the line that we've set aside. - * Note: this uses a power-of-two allocator to avoid - * doing O(n) allocs and using O(n^2) space for - * continuations that span many many lines. - */ - fold_len = last_len + len + 1; /* trailing null */ - - if (fold_len >= (apr_size_t)(r->server->limit_req_fieldsize)) { - r->status = HTTP_BAD_REQUEST; - /* report what we have accumulated so far before the - * overflow (last_field) as the field with the problem + if (last_field != NULL) { + if ((len > 0) && ((*field == '\t') || *field == ' ')) { + /* This line is a continuation of the preceding line(s), + * so append it to the line that we've set aside. + * Note: this uses a power-of-two allocator to avoid + * doing O(n) allocs and using O(n^2) space for + * continuations that span many many lines. */ - apr_table_setn(r->notes, "error-notes", - "Size of a request header field " - "exceeds server limit."); - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, - "Request header exceeds LimitRequestFieldSize " - "after folding: %.*s", - field_name_len(last_field), last_field); - return; - } + apr_size_t fold_len = last_len + len + 1; /* trailing null */ + + if (fold_len >= (apr_size_t)(r->server->limit_req_fieldsize)) { + r->status = HTTP_BAD_REQUEST; + /* report what we have accumulated so far before the + * overflow (last_field) as the field with the problem + */ + apr_table_setn(r->notes, "error-notes", + apr_psprintf(r->pool, + "Size of a request header field " + "after folding " + "exceeds server limit.
\n" + "
\n%.*s\n
\n", + field_name_len(last_field), + ap_escape_html(r->pool, last_field))); + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, + "Request header exceeds LimitRequestFieldSize " + "after folding: %.*s", + field_name_len(last_field), last_field); + return; + } - if (fold_len > alloc_len) { - char *fold_buf; - alloc_len += alloc_len; if (fold_len > alloc_len) { - alloc_len = fold_len; + char *fold_buf; + alloc_len += alloc_len; + if (fold_len > alloc_len) { + alloc_len = fold_len; + } + fold_buf = (char *)apr_palloc(r->pool, alloc_len); + memcpy(fold_buf, last_field, last_len); + last_field = fold_buf; } - fold_buf = (char *)apr_palloc(r->pool, alloc_len); - memcpy(fold_buf, last_field, last_len); - last_field = fold_buf; + memcpy(last_field + last_len, field, len +1); /* +1 for nul */ + last_len += len; + folded = 1; } - memcpy(last_field + last_len, field, len +1); /* +1 for nul */ - /* Replace obs-fold w/ SP per RFC 7230 3.2.4 */ - last_field[last_len] = ' '; - last_len += len; - - /* We've appended this obs-fold line to last_len, proceed to - * read the next input line - */ - continue; - } - else if (last_field != NULL) { + else /* not a continuation line */ { - /* Process the previous last_field header line with all obs-folded - * segments already concatinated (this is not operating on the - * most recently read input line). - */ - - if (r->server->limit_req_fields + if (r->server->limit_req_fields && (++fields_read > r->server->limit_req_fields)) { - r->status = HTTP_BAD_REQUEST; - apr_table_setn(r->notes, "error-notes", - "The number of request header fields " - "exceeds this server's limit."); - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, - "Number of request headers exceeds " - "LimitRequestFields"); - return; - } - - if (!strict) - { - /* Not Strict ('Unsafe' mode), using the legacy parser */ + r->status = HTTP_BAD_REQUEST; + apr_table_setn(r->notes, "error-notes", + "The number of request header fields " + "exceeds this server's limit."); + return; + } - if (!(value = strchr(last_field, ':'))) { /* Find ':' or */ - r->status = HTTP_BAD_REQUEST; /* abort bad request */ + if (!(value = strchr(last_field, ':'))) { /* Find ':' or */ + r->status = HTTP_BAD_REQUEST; /* abort bad request */ + apr_table_setn(r->notes, "error-notes", + apr_psprintf(r->pool, + "Request header field is " + "missing ':' separator.
\n" + "
\n%.*s
\n", + (int)LOG_NAME_MAX_LEN, + ap_escape_html(r->pool, + last_field))); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Request header field is missing ':' " "separator: %.*s", (int)LOG_NAME_MAX_LEN, last_field); + return; } - /* last character of field-name */ - tmp_field = value - (value > last_field ? 1 : 0); + tmp_field = value - 1; /* last character of field-name */ *value++ = '\0'; /* NUL-terminate at colon */ - if (strpbrk(last_field, "\t\n\v\f\r ")) { - r->status = HTTP_BAD_REQUEST; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "Request header field name presented" - " invalid whitespace"); - return; - } - while (*value == ' ' || *value == '\t') { - ++value; /* Skip to start of value */ - } - - if (strpbrk(value, "\n\v\f\r")) { - r->status = HTTP_BAD_REQUEST; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "Request header field value presented" - " bad whitespace"); - return; + ++value; /* Skip to start of value */ } - if (tmp_field == last_field) { - r->status = HTTP_BAD_REQUEST; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "Request header field name was empty"); - return; + /* Strip LWS after field-name: */ + while (tmp_field > last_field + && (*tmp_field == ' ' || *tmp_field == '\t')) { + *tmp_field-- = '\0'; } - } - else /* Using strict RFC7230 parsing */ - { - /* Ensure valid token chars before ':' per RFC 7230 3.2.4 */ - value = (char *)ap_scan_http_token(last_field); - if ((value == last_field) || *value != ':') { - r->status = HTTP_BAD_REQUEST; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "Request header field name is malformed: " - "%.*s", (int)LOG_NAME_MAX_LEN, last_field); - return; - } - - *value++ = '\0'; /* NUL-terminate last_field name at ':' */ - while (*value == ' ' || *value == '\t') { - ++value; /* Skip LWS of value */ + /* Strip LWS after field-value: */ + tmp_field = last_field + last_len - 1; + while (tmp_field > value + && (*tmp_field == ' ' || *tmp_field == '\t')) { + *tmp_field-- = '\0'; } - /* Find invalid, non-HT ctrl char, or the trailing NULL */ - tmp_field = (char *)ap_scan_http_field_content(value); + apr_table_addn(r->headers_in, last_field, value); - /* Reject value for all garbage input (CTRLs excluding HT) - * e.g. only VCHAR / SP / HT / obs-text are allowed per - * RFC7230 3.2.6 - leave all more explicit rule enforcement - * for specific header handler logic later in the cycle + /* reset the alloc_len so that we'll allocate a new + * buffer if we have to do any more folding: we can't + * use the previous buffer because its contents are + * now part of r->headers_in */ - if (*tmp_field != '\0') { - r->status = HTTP_BAD_REQUEST; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "Request header value is malformed: " - "%.*s", (int)LOG_NAME_MAX_LEN, value); - return; - } - } - - apr_table_addn(r->headers_in, last_field, value); + alloc_len = 0; - /* This last_field header is now stored in headers_in, - * resume processing of the current input line. - */ + } /* end if current line is not a continuation starting with tab */ } - /* Found the terminating empty end-of-headers line, stop. */ + /* Found a blank line, stop. */ if (len == 0) { break; } - /* Keep track of this new header line so that we can extend it across - * any obs-fold or parse it on the next loop iteration. We referenced - * our previously allocated buffer in r->headers_in, - * so allocate a fresh buffer if required. + /* Keep track of this line so that we can parse it on + * the next loop iteration. (In the folded case, last_field + * has been updated already.) */ - alloc_len = 0; - last_field = field; - last_len = len; + if (!folded) { + last_field = field; + last_len = len; + } } /* Combine multiple message-header fields with the same * field-name, following RFC 2616, 4.2. */ apr_table_compress(r->headers_in, APR_OVERLAP_TABLES_MERGE); - - /* enforce LimitRequestFieldSize for merged headers */ - apr_table_do(table_do_fn_check_lengths, r, r->headers_in, NULL); } AP_DECLARE(void) ap_get_mime_headers(request_rec *r) @@ -1244,38 +923,33 @@ request_rec *ap_read_request(conn_rec *conn) /* Get the request... */ if (!read_request_line(r, tmp_bb)) { - 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, - "request failed: client's request-line exceeds LimitRequestLine (longer than %d)", - r->server->limit_req_line); + if (r->status == HTTP_REQUEST_URI_TOO_LARGE + || r->status == HTTP_BAD_REQUEST) { + if (r->status == HTTP_BAD_REQUEST) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "request failed: invalid characters in URI"); } - else if (r->method == NULL) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "request failed: malformed request line"); + else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "request failed: URI too long (longer than %d)", r->server->limit_req_line); } - access_status = r->status; - r->status = HTTP_OK; - ap_die(access_status, r); + ap_send_error_response(r, 0); ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); ap_run_log_transaction(r); - r = NULL; apr_brigade_destroy(tmp_bb); return r; - case HTTP_REQUEST_TIME_OUT: + } + else if (r->status == HTTP_REQUEST_TIME_OUT) { ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); - if (!r->connection->keepalives) + if (!r->connection->keepalives) { ap_run_log_transaction(r); - apr_brigade_destroy(tmp_bb); - return r; - default: + } apr_brigade_destroy(tmp_bb); return r; } + + apr_brigade_destroy(tmp_bb); + return NULL; } /* We may have been in keep_alive_timeout mode, so toggle back @@ -1294,7 +968,7 @@ request_rec *ap_read_request(conn_rec *conn) ap_get_mime_headers_core(r, tmp_bb); if (r->status != HTTP_OK) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "request failed: error reading the headers"); ap_send_error_response(r, 0); ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); @@ -1313,7 +987,7 @@ request_rec *ap_read_request(conn_rec *conn) */ if (!(strcasecmp(tenc, "chunked") == 0 /* fast path */ || ap_find_last_token(r->pool, tenc, "chunked"))) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "client sent unknown Transfer-Encoding " "(%s): %s", tenc, r->uri); r->status = HTTP_BAD_REQUEST; @@ -1334,6 +1008,25 @@ request_rec *ap_read_request(conn_rec *conn) apr_table_unset(r->headers_in, "Content-Length"); } } + else { + if (r->header_only) { + /* + * Client asked for headers only with HTTP/0.9, which doesn't send + * headers! Have to dink things just to make sure the error message + * comes through... + */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "client sent invalid HTTP/0.9 request: HEAD %s", + r->uri); + r->header_only = 0; + r->status = HTTP_BAD_REQUEST; + ap_send_error_response(r, 0); + ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); + ap_run_log_transaction(r); + apr_brigade_destroy(tmp_bb); + return r; + } + } apr_brigade_destroy(tmp_bb); @@ -1341,7 +1034,6 @@ 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. @@ -1364,8 +1056,8 @@ request_rec *ap_read_request(conn_rec *conn) * HTTP/1.1 mentions twice (S9, S14.23) that a request MUST contain * a Host: header, and the server MUST respond with 400 if it doesn't. */ - access_status = HTTP_BAD_REQUEST; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + r->status = HTTP_BAD_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "client sent HTTP/1.1 request without hostname " "(see RFC2616 section 14.23): %s", r->uri); } @@ -1380,8 +1072,14 @@ request_rec *ap_read_request(conn_rec *conn) ap_add_input_filter_handle(ap_http_input_filter_handle, NULL, r, r->connection); - if (access_status != HTTP_OK - || (access_status = ap_run_post_read_request(r))) { + if (r->status != HTTP_OK) { + ap_send_error_response(r, 0); + ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); + ap_run_log_transaction(r); + return r; + } + + if ((access_status = ap_run_post_read_request(r))) { ap_die(access_status, r); ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); ap_run_log_transaction(r); @@ -1581,7 +1279,7 @@ AP_DECLARE(int) ap_get_basic_auth_pw(request_rec *r, const char **pw) if (strcasecmp(ap_getword(r->pool, &auth_line, ' '), "Basic")) { /* Client tried to authenticate using wrong auth scheme */ - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "client used wrong authentication scheme: %s", r->uri); ap_note_basic_auth_failure(r); return HTTP_UNAUTHORIZED; diff --git a/server/util.c b/server/util.c index 054cc1760d5..5dee87fd229 100644 --- a/server/util.c +++ b/server/util.c @@ -71,7 +71,7 @@ * char in here and get it to work, because if char is signed then it * will first be sign extended. */ -#define TEST_CHAR(c, f) (test_char_table[(unsigned char)(c)] & (f)) +#define TEST_CHAR(c, f) (test_char_table[(unsigned)(c)] & (f)) /* Win32/NetWare/OS2 need to check for both forward and back slashes * in ap_getparents() and ap_escape_url. @@ -1425,36 +1425,6 @@ AP_DECLARE(int) ap_find_list_item(apr_pool_t *p, const char *line, return good; } -/* Scan a string for HTTP VCHAR/obs-text characters including HT and SP - * (as used in header values, for example, in RFC 7230 section 3.2) - * returning the pointer to the first non-HT ASCII ctrl character. - */ -AP_DECLARE(const char *) ap_scan_http_field_content(const char *ptr) -{ - for ( ; !TEST_CHAR(*ptr, T_HTTP_CTRLS); ++ptr) ; - - return ptr; -} - -/* Scan a string for HTTP token characters, returning the pointer to - * the first non-token character. - */ -AP_DECLARE(const char *) ap_scan_http_token(const char *ptr) -{ - for ( ; !TEST_CHAR(*ptr, T_HTTP_TOKEN_STOP); ++ptr) ; - - return ptr; -} - -/* Scan a string for visible ASCII (0x21-0x7E) or obstext (0x80+) - * and return a pointer to the first ctrl/space character encountered. - */ -AP_DECLARE(const char *) ap_scan_vchar_obstext(const char *ptr) -{ - for ( ; TEST_CHAR(*ptr, T_VCHAR_OBSTEXT); ++ptr) ; - - return ptr; -} /* Retrieve a token, spacing over it and returning a pointer to * the first non-white byte afterwards. Note that these tokens diff --git a/server/vhost.c b/server/vhost.c index c38b9368eb1..b8e9ca7594c 100644 --- a/server/vhost.c +++ b/server/vhost.c @@ -687,116 +687,6 @@ AP_DECLARE(void) ap_fini_vhost_config(apr_pool_t *p, server_rec *main_s) * run-time vhost matching functions */ -static apr_status_t fix_hostname_v6_literal(request_rec *r, char *host) -{ - char *dst; - int double_colon = 0; - - for (dst = host; *dst; dst++) { - if (apr_isxdigit(*dst)) { - if (apr_isupper(*dst)) { - *dst = apr_tolower(*dst); - } - } - else if (*dst == ':') { - if (*(dst + 1) == ':') { - if (double_colon) - return APR_EINVAL; - double_colon = 1; - } - else if (*(dst + 1) == '.') { - return APR_EINVAL; - } - } - else if (*dst == '.') { - /* For IPv4-mapped IPv6 addresses like ::FFFF:129.144.52.38 */ - if (*(dst + 1) == ':' || *(dst + 1) == '.') - return APR_EINVAL; - } - else { - return APR_EINVAL; - } - } - return APR_SUCCESS; -} - -static apr_status_t fix_hostname_non_v6(request_rec *r, char *host) -{ - char *dst; - - for (dst = host; *dst; dst++) { - if (apr_islower(*dst)) { - /* leave char unchanged */ - } - else if (*dst == '.') { - if (*(dst + 1) == '.') { - return APR_EINVAL; - } - } - else if (apr_isupper(*dst)) { - *dst = apr_tolower(*dst); - } - else if (*dst == '/' || *dst == '\\') { - return APR_EINVAL; - } - } - /* strip trailing gubbins */ - if (dst > host && dst[-1] == '.') { - dst[-1] = '\0'; - } - 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) -{ - 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, - "[strict] Invalid host name '%s'%s%.6s", - host, *ch ? ", problem near: " : "", ch); - return APR_EINVAL; -} - /* Lowercase and remove any trailing dot and/or :port from the hostname, * and check that it is sane. * @@ -810,90 +700,78 @@ bad: * Instead we just check for filesystem metacharacters: directory * separators / and \ and sequences of more than one dot. */ -static int fix_hostname(request_rec *r, const char *host_header, - unsigned http_conformance) +static void fix_hostname(request_rec *r) { - const char *src; char *host, *scope_id; + char *dst; apr_port_t port; apr_status_t rv; const char *c; - int is_v6literal = 0; - int strict = (http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); - - src = host_header ? host_header : r->hostname; - /* According to RFC 2616, Host header field CAN be blank */ - if (!*src) { - return is_v6literal; + /* According to RFC 2616, Host header field CAN be blank. */ + if (!*r->hostname) { + return; } /* apr_parse_addr_port will interpret a bare integer as a port * which is incorrect in this context. So treat it separately. */ - 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, - "[strict] purely numeric host names not allowed: %s", - src); - goto bad_nolog; - } - r->hostname = src; - return is_v6literal; + for (c = r->hostname; apr_isdigit(*c); ++c); + if (!*c) { /* pure integer */ + return; } - if (host_header) { - rv = apr_parse_addr_port(&host, &scope_id, &port, src, r->pool); - if (rv != APR_SUCCESS || scope_id) - goto bad; - if (port) { - /* Don't throw the Host: header's port number away: - save it in parsed_uri -- ap_get_server_port() needs it! */ - /* @@@ XXX there should be a better way to pass the port. - * Like r->hostname, there should be a r->portno - */ - r->parsed_uri.port = port; - r->parsed_uri.port_str = apr_itoa(r->pool, (int)port); - } - if (host_header[0] == '[') - is_v6literal = 1; + rv = apr_parse_addr_port(&host, &scope_id, &port, r->hostname, r->pool); + if (rv != APR_SUCCESS || scope_id) { + goto bad; } - else { - /* - * Already parsed, surrounding [ ] (if IPv6 literal) and :port have - * already been removed. + + if (port) { + /* Don't throw the Host: header's port number away: + save it in parsed_uri -- ap_get_server_port() needs it! */ + /* @@@ XXX there should be a better way to pass the port. + * Like r->hostname, there should be a r->portno */ - host = apr_pstrdup(r->pool, r->hostname); - if (ap_strchr(host, ':') != NULL) - is_v6literal = 1; + r->parsed_uri.port = port; + r->parsed_uri.port_str = apr_itoa(r->pool, (int)port); } - 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); + /* if the hostname is an IPv6 numeric address string, it was validated + * already; otherwise, further validation is needed + */ + if (r->hostname[0] != '[') { + for (dst = host; *dst; dst++) { + if (apr_islower(*dst)) { + /* leave char unchanged */ + } + else if (*dst == '.') { + if (*(dst + 1) == '.') { + goto bad; + } + } + else if (apr_isupper(*dst)) { + *dst = apr_tolower(*dst); + } + else if (*dst == '/' || *dst == '\\') { + goto bad; + } + } + /* strip trailing gubbins */ + if (dst > host && dst[-1] == '.') { + dst[-1] = '\0'; + } } - if (rv != APR_SUCCESS) - goto bad; - r->hostname = host; - return is_v6literal; + return; bad: - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "Client sent malformed Host header: %s", - src); -bad_nolog: r->status = HTTP_BAD_REQUEST; - return is_v6literal; + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Client sent malformed Host header"); + return; } + /* return 1 if host matches ServerName or ServerAliases */ static int matches_aliases(server_rec *s, const char *host) { @@ -1090,78 +968,15 @@ 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) { - core_server_config *conf = - (core_server_config *)ap_get_module_config(r->server->module_config, - &core_module); - const char *host_header = apr_table_get(r->headers_in, "Host"); - int is_v6literal = 0; - int have_hostname_from_url = 0; - - if (r->hostname) { - /* - * If there was a host part in the Request-URI, ignore the 'Host' - * header. - */ - have_hostname_from_url = 1; - is_v6literal = fix_hostname(r, NULL, conf->http_conformance); - } - 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_UNSAFE) { - /* - * 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 *repl = construct_host_header(r, is_v6literal); - apr_table_set(r->headers_in, "Host", repl); - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "Replacing host header '%s' with host '%s' given " - "in the request uri", host_header, repl); - } + /* must set this for HTTP/1.1 support */ + if (r->hostname || (r->hostname = apr_table_get(r->headers_in, "Host"))) { + fix_hostname(r); + if (r->status != HTTP_OK) + return; } - /* check if we tucked away a name_chain */ if (r->connection->vhost_lookup_data) { if (r->hostname)