| /* 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" | |
| #include "apr_version.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" | |
| #include "http_main.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; | |
| /* | |
| * 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; | |
| } | |
| /* | |
| * 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 !APR_VERSION_AT_LEAST(1,7,0) | |
| if (scope_id) { | |
| return apr_pstrcat(p, | |
| "Scope ID in address '", w, | |
| "' not supported with APR " APR_VERSION_STRING, | |
| NULL); | |
| } | |
| #endif | |
| 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; | |
| } | |
| #if APR_VERSION_AT_LEAST(1,7,0) | |
| if (scope_id) { | |
| rv = apr_sockaddr_zone_set(my_addr, scope_id); | |
| if (rv) { | |
| ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(10103) | |
| "Could not set scope ID %s for %pI -- ignoring!", | |
| scope_id, my_addr); | |
| return NULL; | |
| } | |
| } | |
| #endif | |
| } | |
| /* 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 */ | |
| AP_DECLARE(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); | |
| } | |
| } | |
| /***************************************************************************** | |
| * 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); | |
| } | |
| /* | |
| * Updates r->server from ServerName/ServerAlias. Per the interaction | |
| * of ip and name-based vhosts, it only looks in the best match from the | |
| * connection-level ip-based matching. | |
| * Returns HTTP_BAD_REQUEST if there was no match. | |
| */ | |
| static int update_server_from_aliases(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; | |
| } | |
| } | |
| /* Fallback: does it match the virthost from the sar? | |
| * (only the first match is used) | |
| */ | |
| if (virthost_s == NULL) { | |
| if (!strcasecmp(host, sar->virthost)) { | |
| virthost_s = s; | |
| } | |
| } | |
| last_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; | |
| } | |
| if (!r->connection->vhost_lookup_data) { | |
| if (matches_aliases(r->server, host)) { | |
| s = r->server; | |
| goto found; | |
| } | |
| } | |
| return HTTP_BAD_REQUEST; | |
| found: | |
| /* s is the first matching server, we're done */ | |
| r->server = s; | |
| return HTTP_OK; | |
| } | |
| 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 update_server_from_aliases 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) | |
| { | |
| ap_update_vhost_from_headers_ex(r, 0); | |
| } | |
| AP_DECLARE(int) ap_update_vhost_from_headers_ex(request_rec *r, int require_match) | |
| { | |
| 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; | |
| int rc = HTTP_OK; | |
| 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 (!require_match && r->status != HTTP_OK) | |
| return HTTP_OK; | |
| 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) | |
| rc = update_server_from_aliases(r); | |
| else | |
| check_serverpath(r); | |
| } | |
| else if (require_match && r->hostname) { | |
| /* check the base server config */ | |
| rc = update_server_from_aliases(r); | |
| } | |
| return rc; | |
| } | |
| /** | |
| * 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; | |
| } |