You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by co...@apache.org on 2017/01/05 01:57:54 UTC

svn commit: r1777405 [2/2] - in /httpd/httpd/branches/2.2.x: ./ docs/manual/mod/ include/ modules/http/ server/

Modified: httpd/httpd/branches/2.2.x/server/vhost.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.2.x/server/vhost.c?rev=1777405&r1=1777404&r2=1777405&view=diff
==============================================================================
--- httpd/httpd/branches/2.2.x/server/vhost.c (original)
+++ httpd/httpd/branches/2.2.x/server/vhost.c Thu Jan  5 01:57:54 2017
@@ -687,6 +687,113 @@ AP_DECLARE(void) ap_fini_vhost_config(ap
  * 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_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.
  *
@@ -700,78 +807,90 @@ AP_DECLARE(void) ap_fini_vhost_config(ap
  * 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 int fix_hostname(request_rec *r, const char *host_header,
+                        unsigned http_conformance)
 {
+    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);
 
-    /* According to RFC 2616, Host header field CAN be blank. */
-    if (!*r->hostname) {
-        return;
+    src = host_header ? host_header : r->hostname;
+
+    /* According to RFC 2616, Host header field CAN be blank */
+    if (!*src) {
+        return is_v6literal;
     }
 
     /* 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 */
-        return;
-    }
-
-    rv = apr_parse_addr_port(&host, &scope_id, &port, r->hostname, r->pool);
-    if (rv != APR_SUCCESS || scope_id) {
-        goto bad;
+    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;
+    }
+
+    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;
     }
-
-    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);
+        host = apr_pstrdup(r->pool, r->hostname);
+        if (ap_strchr(host, ':') != NULL)
+            is_v6literal = 1;
     }
 
-    /* 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 (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 (rv != APR_SUCCESS)
+        goto bad;
+
     r->hostname = host;
-    return;
+    return is_v6literal;
 
 bad:
+    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                  "Client sent malformed Host header: %s",
+                  src);
+bad_nolog:
     r->status = HTTP_BAD_REQUEST;
-    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                  "Client sent malformed Host header");
-    return;
+    return is_v6literal;
 }
 
-
 /* return 1 if host matches ServerName or ServerAliases */
 static int matches_aliases(server_rec *s, const char *host)
 {
@@ -968,15 +1087,78 @@ static void check_serverpath(request_rec
     }
 }
 
+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)
 {
-    /* 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;
+    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);
+        }
     }
+
     /* check if we tucked away a name_chain */
     if (r->connection->vhost_lookup_data) {
         if (r->hostname)