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)