diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-07 02:04:06 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-07 02:04:06 +0000 |
commit | 5dff2d61cc1c27747ee398e04d8e02843aabb1f8 (patch) | |
tree | a67c336b406c8227bac912beb74a1ad3cdc55100 /server/vhost.c | |
parent | Initial commit. (diff) | |
download | apache2-5dff2d61cc1c27747ee398e04d8e02843aabb1f8.tar.xz apache2-5dff2d61cc1c27747ee398e04d8e02843aabb1f8.zip |
Adding upstream version 2.4.38.upstream/2.4.38upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | server/vhost.c | 1267 |
1 files changed, 1267 insertions, 0 deletions
diff --git a/server/vhost.c b/server/vhost.c new file mode 100644 index 0000000..b23b2dd --- /dev/null +++ b/server/vhost.c @@ -0,0 +1,1267 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file vhost.c + * @brief functions pertaining to virtual host addresses + * (configuration and run-time) + */ + +#include "apr.h" +#include "apr_strings.h" +#include "apr_lib.h" + +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" +#include "http_vhost.h" +#include "http_protocol.h" +#include "http_core.h" + +#if APR_HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +/* + * After all the definitions there's an explanation of how it's all put + * together. + */ + +/* meta-list of name-vhosts. Each server_rec can be in possibly multiple + * lists of name-vhosts. + */ +typedef struct name_chain name_chain; +struct name_chain { + name_chain *next; + server_addr_rec *sar; /* the record causing it to be in + * this chain (needed for port comparisons) */ + server_rec *server; /* the server to use on a match */ +}; + +/* meta-list of ip addresses. Each server_rec can be in possibly multiple + * hash chains since it can have multiple ips. + */ +typedef struct ipaddr_chain ipaddr_chain; +struct ipaddr_chain { + ipaddr_chain *next; + server_addr_rec *sar; /* the record causing it to be in + * this chain (need for both ip addr and port + * comparisons) */ + server_rec *server; /* the server to use if this matches */ + name_chain *names; /* if non-NULL then a list of name-vhosts + * sharing this address */ + name_chain *initialnames; /* no runtime use, temporary storage of first + * NVH'es names */ +}; + +/* This defines the size of the hash table used for hashing ip addresses + * of virtual hosts. It must be a power of two. + */ +#ifndef IPHASH_TABLE_SIZE +#define IPHASH_TABLE_SIZE 256 +#endif + +/* A (n) bucket hash table, each entry has a pointer to a server rec and + * a pointer to the other entries in that bucket. Each individual address, + * even for virtualhosts with multiple addresses, has an entry in this hash + * table. There are extra buckets for _default_, and name-vhost entries. + * + * Note that after config time this is constant, so it is thread-safe. + */ +static ipaddr_chain *iphash_table[IPHASH_TABLE_SIZE]; + +/* dump out statistics about the hash function */ +/* #define IPHASH_STATISTICS */ + +/* list of the _default_ servers */ +static ipaddr_chain *default_list; + +/* whether a config error was seen */ +static int config_error = 0; + +/* config check function */ +static int vhost_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s); + +/* + * How it's used: + * + * The ip address determines which chain in iphash_table is interesting, then + * a comparison is done down that chain to find the first ipaddr_chain whose + * sar matches the address:port pair. + * + * If that ipaddr_chain has names == NULL then you're done, it's an ip-vhost. + * + * Otherwise it's a name-vhost list, and the default is the server in the + * ipaddr_chain record. We tuck away the ipaddr_chain record in the + * conn_rec field vhost_lookup_data. Later on after the headers we get a + * second chance, and we use the name_chain to figure out what name-vhost + * matches the headers. + * + * If there was no ip address match in the iphash_table then do a lookup + * in the default_list. + * + * How it's put together ... well you should be able to figure that out + * from how it's used. Or something like that. + */ + + +/* called at the beginning of the config */ +AP_DECLARE(void) ap_init_vhost_config(apr_pool_t *p) +{ + memset(iphash_table, 0, sizeof(iphash_table)); + default_list = NULL; + ap_hook_check_config(vhost_check_config, NULL, NULL, APR_HOOK_MIDDLE); +} + + +/* + * Parses a host of the form <address>[:port] + * paddr is used to create a list in the order of input + * **paddr is the ->next pointer of the last entry (or s->addrs) + * *paddr is the variable used to keep track of **paddr between calls + * port is the default port to assume + */ +static const char *get_addresses(apr_pool_t *p, const char *w_, + server_addr_rec ***paddr, + apr_port_t default_port) +{ + apr_sockaddr_t *my_addr; + server_addr_rec *sar; + char *w, *host, *scope_id; + int wild_port; + apr_size_t wlen; + apr_port_t port; + apr_status_t rv; + + if (*w_ == '\0') + return NULL; + + wlen = strlen(w_); /* wlen must be > 0 at this point */ + w = apr_pstrmemdup(p, w_, wlen); + /* apr_parse_addr_port() doesn't understand ":*" so handle that first. */ + wild_port = 0; + if (w[wlen - 1] == '*') { + if (wlen < 2) { + wild_port = 1; + } + else if (w[wlen - 2] == ':') { + w[wlen - 2] = '\0'; + wild_port = 1; + } + } + rv = apr_parse_addr_port(&host, &scope_id, &port, w, p); + /* If the string is "80", apr_parse_addr_port() will be happy and set + * host to NULL and port to 80, so watch out for that. + */ + if (rv != APR_SUCCESS) { + return "The address or port is invalid"; + } + if (!host) { + return "Missing address for VirtualHost"; + } + if (scope_id) { + return "Scope ids are not supported"; + } + if (!port && !wild_port) { + port = default_port; + } + + if (strcmp(host, "*") == 0 || strcasecmp(host, "_default_") == 0) { + rv = apr_sockaddr_info_get(&my_addr, NULL, APR_UNSPEC, port, 0, p); + if (rv) { + return "Could not determine a wildcard address ('0.0.0.0') -- " + "check resolver configuration."; + } + } + else { + rv = apr_sockaddr_info_get(&my_addr, host, APR_UNSPEC, port, 0, p); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(00547) + "Could not resolve host name %s -- ignoring!", host); + return NULL; + } + } + + /* Remember all addresses for the host */ + + do { + sar = apr_pcalloc(p, sizeof(server_addr_rec)); + **paddr = sar; + *paddr = &sar->next; + sar->host_addr = my_addr; + sar->host_port = port; + sar->virthost = host; + my_addr = my_addr->next; + } while (my_addr); + + return NULL; +} + + +/* parse the <VirtualHost> addresses */ +const char *ap_parse_vhost_addrs(apr_pool_t *p, + const char *hostname, + server_rec *s) +{ + server_addr_rec **addrs; + const char *err; + + /* start the list of addresses */ + addrs = &s->addrs; + while (hostname[0]) { + err = get_addresses(p, ap_getword_conf(p, &hostname), &addrs, s->port); + if (err) { + *addrs = NULL; + return err; + } + } + /* terminate the list */ + *addrs = NULL; + if (s->addrs) { + if (s->addrs->host_port) { + /* override the default port which is inherited from main_server */ + s->port = s->addrs->host_port; + } + } + return NULL; +} + + +AP_DECLARE_NONSTD(const char *)ap_set_name_virtual_host(cmd_parms *cmd, + void *dummy, + const char *arg) +{ + static int warnonce = 0; + if (++warnonce == 1) { + ap_log_error(APLOG_MARK, APLOG_NOTICE|APLOG_STARTUP, APR_SUCCESS, NULL, APLOGNO(00548) + "NameVirtualHost has no effect and will be removed in the " + "next release %s:%d", + cmd->directive->filename, + cmd->directive->line_num); + } + + return NULL; +} + + +/* hash table statistics, keep this in here for the beta period so + * we can find out if the hash function is ok + */ +#ifdef IPHASH_STATISTICS +static int iphash_compare(const void *a, const void *b) +{ + return (*(const int *) b - *(const int *) a); +} + + +static void dump_iphash_statistics(server_rec *main_s) +{ + unsigned count[IPHASH_TABLE_SIZE]; + int i; + ipaddr_chain *src; + unsigned total; + char buf[HUGE_STRING_LEN]; + char *p; + + total = 0; + for (i = 0; i < IPHASH_TABLE_SIZE; ++i) { + count[i] = 0; + for (src = iphash_table[i]; src; src = src->next) { + ++count[i]; + if (i < IPHASH_TABLE_SIZE) { + /* don't count the slop buckets in the total */ + ++total; + } + } + } + qsort(count, IPHASH_TABLE_SIZE, sizeof(count[0]), iphash_compare); + p = buf + apr_snprintf(buf, sizeof(buf), + APLOGNO(03235) "iphash: total hashed = %u, avg chain = %u, " + "chain lengths (count x len):", + total, total / IPHASH_TABLE_SIZE); + total = 1; + for (i = 1; i < IPHASH_TABLE_SIZE; ++i) { + if (count[i - 1] != count[i]) { + p += apr_snprintf(p, sizeof(buf) - (p - buf), " %ux%u", + total, count[i - 1]); + total = 1; + } + else { + ++total; + } + } + p += apr_snprintf(p, sizeof(buf) - (p - buf), " %ux%u", + total, count[IPHASH_TABLE_SIZE - 1]); + /* Intentional no APLOGNO */ + /* buf provides APLOGNO */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, main_s, buf); +} +#endif + + +/* This hashing function is designed to get good distribution in the cases + * where the server is handling entire "networks" of servers. i.e. a + * whack of /24s. This is probably the most common configuration for + * ISPs with large virtual servers. + * + * NOTE: This function is symmetric (i.e. collapses all 4 octets + * into one), so machine byte order (big/little endianness) does not matter. + * + * Hash function provided by David Hankins. + */ +static APR_INLINE unsigned hash_inaddr(unsigned key) +{ + key ^= (key >> 16); + return ((key >> 8) ^ key) % IPHASH_TABLE_SIZE; +} + +static APR_INLINE unsigned hash_addr(struct apr_sockaddr_t *sa) +{ + unsigned key; + + /* The key is the last four bytes of the IP address. + * For IPv4, this is the entire address, as always. + * For IPv6, this is usually part of the MAC address. + */ + key = *(unsigned *)((char *)sa->ipaddr_ptr + sa->ipaddr_len - 4); + return hash_inaddr(key); +} + +static ipaddr_chain *new_ipaddr_chain(apr_pool_t *p, + server_rec *s, server_addr_rec *sar) +{ + ipaddr_chain *new; + + new = apr_palloc(p, sizeof(*new)); + new->names = NULL; + new->initialnames = NULL; + new->server = s; + new->sar = sar; + new->next = NULL; + return new; +} + + +static name_chain *new_name_chain(apr_pool_t *p, + server_rec *s, server_addr_rec *sar) +{ + name_chain *new; + + new = apr_palloc(p, sizeof(*new)); + new->server = s; + new->sar = sar; + new->next = NULL; + return new; +} + + +static APR_INLINE ipaddr_chain *find_ipaddr(apr_sockaddr_t *sa) +{ + unsigned bucket; + ipaddr_chain *trav = NULL; + ipaddr_chain *wild_match = NULL; + + /* scan the hash table for an exact match first */ + bucket = hash_addr(sa); + for (trav = iphash_table[bucket]; trav; trav = trav->next) { + server_addr_rec *sar = trav->sar; + apr_sockaddr_t *cur = sar->host_addr; + + if (cur->port == sa->port) { + if (apr_sockaddr_equal(cur, sa)) { + return trav; + } + } + if (wild_match == NULL && (cur->port == 0 || sa->port == 0)) { + if (apr_sockaddr_equal(cur, sa)) { + /* don't break, continue looking for an exact match */ + wild_match = trav; + } + } + } + return wild_match; +} + +static ipaddr_chain *find_default_server(apr_port_t port) +{ + server_addr_rec *sar; + ipaddr_chain *trav = NULL; + ipaddr_chain *wild_match = NULL; + + for (trav = default_list; trav; trav = trav->next) { + sar = trav->sar; + if (sar->host_port == port) { + /* match! */ + return trav; + } + if (wild_match == NULL && sar->host_port == 0) { + /* don't break, continue looking for an exact match */ + wild_match = trav; + } + } + return wild_match; +} + +#if APR_HAVE_IPV6 +#define IS_IN6_ANYADDR(ad) ((ad)->family == APR_INET6 \ + && IN6_IS_ADDR_UNSPECIFIED(&(ad)->sa.sin6.sin6_addr)) +#else +#define IS_IN6_ANYADDR(ad) (0) +#endif + +static void dump_a_vhost(apr_file_t *f, ipaddr_chain *ic) +{ + name_chain *nc; + int len; + char buf[MAX_STRING_LEN]; + apr_sockaddr_t *ha = ic->sar->host_addr; + + if ((ha->family == APR_INET && ha->sa.sin.sin_addr.s_addr == INADDR_ANY) + || IS_IN6_ANYADDR(ha)) { + len = apr_snprintf(buf, sizeof(buf), "*:%u", + ic->sar->host_port); + } + else { + len = apr_snprintf(buf, sizeof(buf), "%pI", ha); + } + if (ic->sar->host_port == 0) { + buf[len-1] = '*'; + } + if (ic->names == NULL) { + apr_file_printf(f, "%-22s %s (%s:%u)\n", buf, + ic->server->server_hostname, + ic->server->defn_name, ic->server->defn_line_number); + return; + } + apr_file_printf(f, "%-22s is a NameVirtualHost\n" + "%8s default server %s (%s:%u)\n", + buf, "", ic->server->server_hostname, + ic->server->defn_name, ic->server->defn_line_number); + for (nc = ic->names; nc; nc = nc->next) { + if (nc->sar->host_port) { + apr_file_printf(f, "%8s port %u ", "", nc->sar->host_port); + } + else { + apr_file_printf(f, "%8s port * ", ""); + } + apr_file_printf(f, "namevhost %s (%s:%u)\n", + nc->server->server_hostname, + nc->server->defn_name, nc->server->defn_line_number); + if (nc->server->names) { + apr_array_header_t *names = nc->server->names; + char **name = (char **)names->elts; + int i; + for (i = 0; i < names->nelts; ++i) { + if (name[i]) { + apr_file_printf(f, "%16s alias %s\n", "", name[i]); + } + } + } + if (nc->server->wild_names) { + apr_array_header_t *names = nc->server->wild_names; + char **name = (char **)names->elts; + int i; + for (i = 0; i < names->nelts; ++i) { + if (name[i]) { + apr_file_printf(f, "%16s wild alias %s\n", "", name[i]); + } + } + } + } +} + +static void dump_vhost_config(apr_file_t *f) +{ + ipaddr_chain *ic; + int i; + + apr_file_printf(f, "VirtualHost configuration:\n"); + + /* non-wildcard servers */ + for (i = 0; i < IPHASH_TABLE_SIZE; ++i) { + for (ic = iphash_table[i]; ic; ic = ic->next) { + dump_a_vhost(f, ic); + } + } + + /* wildcard servers */ + for (ic = default_list; ic; ic = ic->next) { + dump_a_vhost(f, ic); + } +} + + +/* + * When a second or later virtual host maps to the same IP chain, + * add the relevant server names to the chain. Special care is taken + * to avoid adding ic->names until we're sure there are multiple VH'es. + */ +static void add_name_vhost_config(apr_pool_t *p, server_rec *main_s, + server_rec *s, server_addr_rec *sar, + ipaddr_chain *ic) +{ + + name_chain *nc = new_name_chain(p, s, sar); + nc->next = ic->names; + + /* iterating backwards, so each one we see becomes the current default server */ + ic->server = s; + + if (ic->names == NULL) { + if (ic->initialnames == NULL) { + /* first pass, set these names aside in case we see another VH. + * Until then, this looks like an IP-based VH to runtime. + */ + ic->initialnames = nc; + } + else { + /* second pass through this chain -- this really is an NVH, and we + * have two sets of names to link in. + */ + nc->next = ic->initialnames; + ic->names = nc; + ic->initialnames = NULL; + } + } + else { + /* 3rd or more -- just keep stacking the names */ + ic->names = nc; + } +} + +/* compile the tables and such we need to do the run-time vhost lookups */ +AP_DECLARE(void) ap_fini_vhost_config(apr_pool_t *p, server_rec *main_s) +{ + server_addr_rec *sar; + int has_default_vhost_addr; + server_rec *s; + int i; + ipaddr_chain **iphash_table_tail[IPHASH_TABLE_SIZE]; + + /* Main host first */ + s = main_s; + + if (!s->server_hostname) { + s->server_hostname = ap_get_local_host(p); + } + + /* initialize the tails */ + for (i = 0; i < IPHASH_TABLE_SIZE; ++i) { + iphash_table_tail[i] = &iphash_table[i]; + } + + /* The next things to go into the hash table are the virtual hosts + * themselves. They're listed off of main_s->next in the reverse + * order they occurred in the config file, so we insert them at + * the iphash_table_tail but don't advance the tail. + */ + + for (s = main_s->next; s; s = s->next) { + server_addr_rec *sar_prev = NULL; + has_default_vhost_addr = 0; + for (sar = s->addrs; sar; sar = sar->next) { + ipaddr_chain *ic; + char inaddr_any[16] = {0}; /* big enough to handle IPv4 or IPv6 */ + /* XXX: this treats 0.0.0.0 as a "default" server which matches no-exact-match for IPv6 */ + if (!memcmp(sar->host_addr->ipaddr_ptr, inaddr_any, sar->host_addr->ipaddr_len)) { + ic = find_default_server(sar->host_port); + + if (ic && sar->host_port == ic->sar->host_port) { /* we're a match for an existing "default server" */ + if (!sar_prev || memcmp(sar_prev->host_addr->ipaddr_ptr, inaddr_any, sar_prev->host_addr->ipaddr_len) + || sar_prev->host_port != sar->host_port) { + add_name_vhost_config(p, main_s, s, sar, ic); + } + } + else { + /* No default server, or we found a default server but + ** exactly one of us is a wildcard port, which means we want + ** two ip-based vhosts not an NVH with two names + */ + ic = new_ipaddr_chain(p, s, sar); + ic->next = default_list; + default_list = ic; + add_name_vhost_config(p, main_s, s, sar, ic); + } + has_default_vhost_addr = 1; + } + else { + /* see if it matches something we've already got */ + ic = find_ipaddr(sar->host_addr); + + if (!ic || sar->host_port != ic->sar->host_port) { + /* No matching server, or we found a matching server but + ** exactly one of us is a wildcard port, which means we want + ** two ip-based vhosts not an NVH with two names + */ + unsigned bucket = hash_addr(sar->host_addr); + ic = new_ipaddr_chain(p, s, sar); + ic->next = *iphash_table_tail[bucket]; + *iphash_table_tail[bucket] = ic; + } + add_name_vhost_config(p, main_s, s, sar, ic); + } + sar_prev = sar; + } + + /* Ok now we want to set up a server_hostname if the user was + * silly enough to forget one. + * XXX: This is silly we should just crash and burn. + */ + if (!s->server_hostname) { + if (has_default_vhost_addr) { + s->server_hostname = main_s->server_hostname; + } + else if (!s->addrs) { + /* what else can we do? at this point this vhost has + no configured name, probably because they used + DNS in the VirtualHost statement. It's disabled + anyhow by the host matching code. -djg */ + s->server_hostname = + apr_pstrdup(p, "bogus_host_without_forward_dns"); + } + else { + apr_status_t rv; + char *hostname; + + rv = apr_getnameinfo(&hostname, s->addrs->host_addr, 0); + if (rv == APR_SUCCESS) { + s->server_hostname = apr_pstrdup(p, hostname); + } + else { + /* again, what can we do? They didn't specify a + ServerName, and their DNS isn't working. -djg */ + char *ipaddr_str; + + apr_sockaddr_ip_get(&ipaddr_str, s->addrs->host_addr); + ap_log_error(APLOG_MARK, APLOG_ERR, rv, main_s, APLOGNO(00549) + "Failed to resolve server name " + "for %s (check DNS) -- or specify an explicit " + "ServerName", + ipaddr_str); + s->server_hostname = + apr_pstrdup(p, "bogus_host_without_reverse_dns"); + } + } + } + } + +#ifdef IPHASH_STATISTICS + dump_iphash_statistics(main_s); +#endif + if (ap_exists_config_define("DUMP_VHOSTS")) { + apr_file_t *thefile = NULL; + apr_file_open_stdout(&thefile, p); + dump_vhost_config(thefile); + } +} + +static int vhost_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + return config_error ? !OK : OK; +} + +/***************************************************************************** + * 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 == '-' || *ch == '_') { + is_dotted_decimal = 0; + } + else if (ch[0] == '.') { + dots++; + if (ch[1] == '0' && apr_isdigit(ch[2])) + leading_zeroes = 1; + } + else if (!apr_isdigit(*ch)) { + /* also takes care of multiple Host headers by denying commas */ + goto bad; + } + } + if (is_dotted_decimal) { + if (host[0] == '.' || (host[0] == '0' && apr_isdigit(host[1]))) + leading_zeroes = 1; + if (leading_zeroes || dots != 3) { + /* RFC 3986 7.4 */ + goto bad; + } + } + else { + /* The top-level domain must start with a letter (RFC 1123 2.1) */ + while (ch > host && *ch != '.') + ch--; + if (ch[0] == '.' && ch[1] != '\0' && !apr_isalpha(ch[1])) + goto bad; + } + return APR_SUCCESS; + +bad: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02415) + "[strict] Invalid host name '%s'%s%.6s", + host, *ch ? ", problem near: " : "", ch); + return APR_EINVAL; +} + +/* Lowercase and remove any trailing dot and/or :port from the hostname, + * and check that it is sane. + * + * In most configurations the exact syntax of the hostname isn't + * important so strict sanity checking isn't necessary. However, in + * mass hosting setups (using mod_vhost_alias or mod_rewrite) where + * the hostname is interpolated into the filename, we need to be sure + * that the interpolation doesn't expose parts of the filesystem. + * We don't do strict RFC 952 / RFC 1123 syntax checking in order + * to support iDNS and people who erroneously use underscores. + * 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) +{ + const char *src; + char *host, *scope_id; + apr_port_t port; + apr_status_t rv; + const char *c; + int is_v6literal = 0; + int strict = (http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); + + 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 = src; apr_isdigit(*c); ++c); + if (!*c) { + /* pure integer */ + if (strict) { + /* RFC 3986 7.4 */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02416) + "[strict] purely numeric host names not allowed: %s", + src); + 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; + } + else { + /* + * Already parsed, surrounding [ ] (if IPv6 literal) and :port have + * already been removed. + */ + host = apr_pstrdup(r->pool, r->hostname); + if (ap_strchr(host, ':') != NULL) + is_v6literal = 1; + } + + if (is_v6literal) { + rv = fix_hostname_v6_literal(r, host); + } + else { + rv = fix_hostname_non_v6(r, host); + if (strict && rv == APR_SUCCESS) + rv = strict_hostname_check(r, host); + } + if (rv != APR_SUCCESS) + goto bad; + + r->hostname = host; + return is_v6literal; + +bad: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00550) + "Client sent malformed Host header: %s", + src); +bad_nolog: + r->status = HTTP_BAD_REQUEST; + return is_v6literal; +} + +/* return 1 if host matches ServerName or ServerAliases */ +static int matches_aliases(server_rec *s, const char *host) +{ + int i; + apr_array_header_t *names; + + /* match ServerName */ + if (!strcasecmp(host, s->server_hostname)) { + return 1; + } + + /* search all the aliases from ServerAlias directive */ + names = s->names; + if (names) { + char **name = (char **) names->elts; + for (i = 0; i < names->nelts; ++i) { + if (!name[i]) continue; + if (!strcasecmp(host, name[i])) + return 1; + } + } + names = s->wild_names; + if (names) { + char **name = (char **) names->elts; + for (i = 0; i < names->nelts; ++i) { + if (!name[i]) continue; + if (!ap_strcasecmp_match(host, name[i])) + return 1; + } + } + return 0; +} + + +/* Suppose a request came in on the same socket as this r, and included + * a header "Host: host:port", would it map to r->server? It's more + * than just that though. When we do the normal matches for each request + * we don't even bother considering Host: etc on non-namevirtualhosts, + * we just call it a match. But here we require the host:port to match + * the ServerName and/or ServerAliases. + */ +AP_DECLARE(int) ap_matches_request_vhost(request_rec *r, const char *host, + apr_port_t port) +{ + server_rec *s; + server_addr_rec *sar; + + s = r->server; + + /* search all the <VirtualHost> values */ + /* XXX: If this is a NameVirtualHost then we may not be doing the Right Thing + * consider: + * + * NameVirtualHost 10.1.1.1 + * <VirtualHost 10.1.1.1> + * ServerName v1 + * </VirtualHost> + * <VirtualHost 10.1.1.1> + * ServerName v2 + * </VirtualHost> + * + * Suppose r->server is v2, and we're asked to match "10.1.1.1". We'll say + * "yup it's v2", when really it isn't... if a request came in for 10.1.1.1 + * it would really go to v1. + */ + for (sar = s->addrs; sar; sar = sar->next) { + if ((sar->host_port == 0 || port == sar->host_port) + && !strcasecmp(host, sar->virthost)) { + return 1; + } + } + + /* the Port has to match now, because the rest don't have ports associated + * with them. */ + if (port != s->port) { + return 0; + } + + return matches_aliases(s, host); +} + + +static void check_hostalias(request_rec *r) +{ + /* + * Even if the request has a Host: header containing a port we ignore + * that port. We always use the physical port of the socket. There + * are a few reasons for this: + * + * - the default of 80 or 443 for SSL is easier to handle this way + * - there is less of a possibility of a security problem + * - it simplifies the data structure + * - the client may have no idea that a proxy somewhere along the way + * translated the request to another ip:port + * - except for the addresses from the VirtualHost line, none of the other + * names we'll match have ports associated with them + */ + const char *host = r->hostname; + apr_port_t port; + server_rec *s; + server_rec *virthost_s; + server_rec *last_s; + name_chain *src; + + virthost_s = NULL; + last_s = NULL; + + port = r->connection->local_addr->port; + + /* Recall that the name_chain is a list of server_addr_recs, some of + * whose ports may not match. Also each server may appear more than + * once in the chain -- specifically, it will appear once for each + * address from its VirtualHost line which matched. We only want to + * do the full ServerName/ServerAlias comparisons once for each + * server, fortunately we know that all the VirtualHost addresses for + * a single server are adjacent to each other. + */ + + for (src = r->connection->vhost_lookup_data; src; src = src->next) { + server_addr_rec *sar; + + /* We only consider addresses on the name_chain which have a matching + * port + */ + sar = src->sar; + if (sar->host_port != 0 && port != sar->host_port) { + continue; + } + + s = src->server; + + /* If we still need to do ServerName and ServerAlias checks for this + * server, do them now. + */ + if (s != last_s) { + /* does it match any ServerName or ServerAlias directive? */ + if (matches_aliases(s, host)) { + goto found; + } + } + last_s = s; + + /* Fallback: does it match the virthost from the sar? */ + if (!strcasecmp(host, sar->virthost)) { + /* only the first match is used */ + if (virthost_s == NULL) { + virthost_s = s; + } + } + } + + /* If ServerName and ServerAlias check failed, we end up here. If it + * matches a VirtualHost, virthost_s is set. Use that as fallback + */ + if (virthost_s) { + s = virthost_s; + goto found; + } + + return; + +found: + /* s is the first matching server, we're done */ + r->server = s; +} + + +static void check_serverpath(request_rec *r) +{ + server_rec *s; + server_rec *last_s; + name_chain *src; + apr_port_t port; + + port = r->connection->local_addr->port; + + /* + * This is in conjunction with the ServerPath code in http_core, so we + * get the right host attached to a non- Host-sending request. + * + * See the comment in check_hostalias about how each vhost can be + * listed multiple times. + */ + + last_s = NULL; + for (src = r->connection->vhost_lookup_data; src; src = src->next) { + /* We only consider addresses on the name_chain which have a matching + * port + */ + if (src->sar->host_port != 0 && port != src->sar->host_port) { + continue; + } + + s = src->server; + if (s == last_s) { + continue; + } + last_s = s; + + if (s->path && !strncmp(r->uri, s->path, s->pathlen) && + (s->path[s->pathlen - 1] == '/' || + r->uri[s->pathlen] == '/' || + r->uri[s->pathlen] == '\0')) { + r->server = s; + return; + } + } +} + +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 = ap_get_core_module_config(r->server->module_config); + 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_setn(r->headers_in, "Host", repl); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02417) + "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) + check_hostalias(r); + else + check_serverpath(r); + } +} + +/** + * For every virtual host on this connection, call func_cb. + */ +AP_DECLARE(int) ap_vhost_iterate_given_conn(conn_rec *conn, + ap_vhost_iterate_conn_cb func_cb, + void* baton) +{ + server_rec *s; + server_rec *last_s; + name_chain *src; + apr_port_t port; + int rv = 0; + + if (conn->vhost_lookup_data) { + last_s = NULL; + port = conn->local_addr->port; + + for (src = conn->vhost_lookup_data; src; src = src->next) { + server_addr_rec *sar; + + /* We only consider addresses on the name_chain which have a + * matching port. + */ + sar = src->sar; + if (sar->host_port != 0 && port != sar->host_port) { + continue; + } + + s = src->server; + + if (s == last_s) { + /* we've already done a callback for this vhost. */ + continue; + } + + last_s = s; + + rv = func_cb(baton, conn, s); + + if (rv != 0) { + break; + } + } + } + else { + rv = func_cb(baton, conn, conn->base_server); + } + + return rv; +} + +/* Called for a new connection which has a known local_addr. Note that the + * new connection is assumed to have conn->server == main server. + */ +AP_DECLARE(void) ap_update_vhost_given_ip(conn_rec *conn) +{ + ipaddr_chain *trav; + apr_port_t port; + + /* scan the hash table for an exact match first */ + trav = find_ipaddr(conn->local_addr); + + if (trav) { + /* save the name_chain for later in case this is a name-vhost */ + conn->vhost_lookup_data = trav->names; + conn->base_server = trav->server; + return; + } + + /* maybe there's a default server or wildcard name-based vhost + * matching this port + */ + port = conn->local_addr->port; + + trav = find_default_server(port); + if (trav) { + conn->vhost_lookup_data = trav->names; + conn->base_server = trav->server; + return; + } + + /* otherwise we're stuck with just the main server + * and no name-based vhosts + */ + conn->vhost_lookup_data = NULL; +} |