diff options
author | Stefan Fritsch <sf@apache.org> | 2012-12-29 21:39:49 +0100 |
---|---|---|
committer | Stefan Fritsch <sf@apache.org> | 2012-12-29 21:39:49 +0100 |
commit | 33a2fbf2bc4ee469cbd4be5455f157b0802515db (patch) | |
tree | 3f7c222fbb3897d9756982ba3243f02f77f3dadb | |
parent | extend description of r->hostname (diff) | |
download | apache2-33a2fbf2bc4ee469cbd4be5455f157b0802515db.tar.xz apache2-33a2fbf2bc4ee469cbd4be5455f157b0802515db.zip |
Correctly parse an IPv6 literal host specification in an absolute URL
in the request line.
- Fix handling of brackets [ ] surrounding the IPv6 address.
- Skip parsing r->hostname again if not necessary.
- Do some checks that the IPv6 address is sane. This is not done by
apr_parse_addr_port().
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1426827 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r-- | CHANGES | 3 | ||||
-rw-r--r-- | server/vhost.c | 162 |
2 files changed, 117 insertions, 48 deletions
@@ -1,6 +1,9 @@ -*- coding: utf-8 -*- Changes with Apache 2.5.0 + *) core: Correctly parse an IPv6 literal host specification in an absolute + URL in the request line. [Stefan Fritsch] + *) mod_ssl: add support for subjectAltName-based host name checking in proxy mode. PR 54030. [Kaspar Brand] diff --git a/server/vhost.c b/server/vhost.c index fd7c0ad646..697bbdcdcd 100644 --- a/server/vhost.c +++ b/server/vhost.c @@ -675,6 +675,66 @@ static int vhost_check_config(apr_pool_t *p, apr_pool_t *plog, * 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; +} + /* Lowercase and remove any trailing dot and/or :port from the hostname, * and check that it is sane. * @@ -688,67 +748,65 @@ static int vhost_check_config(apr_pool_t *p, apr_pool_t *plog, * Instead we just check for filesystem metacharacters: directory * separators / and \ and sequences of more than one dot. */ -static void fix_hostname(request_rec *r) +static void fix_hostname(request_rec *r, const char *host_header) { + const char *src; char *host, *scope_id; - char *dst; apr_port_t port; apr_status_t rv; const char *c; - /* According to RFC 2616, Host header field CAN be blank. */ - if (!*r->hostname) { + 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? + */ + if (!*src) { 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 = r->hostname; apr_isdigit(*c); ++c); - if (!*c) { /* pure integer */ + for (c = src; apr_isdigit(*c); ++c); + if (!*c) { + /* pure integer */ + r->hostname = src; return; } - rv = apr_parse_addr_port(&host, &scope_id, &port, r->hostname, r->pool); - if (rv != APR_SUCCESS || scope_id) { - goto bad; + 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] == '[') + rv = fix_hostname_v6_literal(r, host); + else + rv = fix_hostname_non_v6(r, host); } - - 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 + else { + /* + * Already parsed, surrounding [ ] (if IPv6 literal) and :port have + * already been removed. */ - r->parsed_uri.port = port; - r->parsed_uri.port_str = apr_itoa(r->pool, (int)port); - } - - /* 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'; - } + 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); } + if (rv != APR_SUCCESS) + goto bad; r->hostname = host; return; @@ -973,12 +1031,20 @@ static void check_serverpath(request_rec *r) AP_DECLARE(void) ap_update_vhost_from_headers(request_rec *r) { - /* 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; + const char *host_header; + + if (r->hostname) { + /* + * If there was a host part in the Request-URI, ignore the 'Host' + * header. + */ + fix_hostname(r, NULL); + } + else if ((host_header = apr_table_get(r->headers_in, "Host")) != NULL ) { + fix_hostname(r, host_header); } + if (r->status != HTTP_OK) + return; /* check if we tucked away a name_chain */ if (r->connection->vhost_lookup_data) { if (r->hostname) |