1
0
mirror of https://github.com/apache/httpd.git synced 2026-01-13 21:42:17 +03:00
Files
apache/modules/proxy/proxy_util.c
Jim Jagielski 62f1105335 Haven't heard any vetoes regarding this, so might as well fold it
in. Not too happy about cluttering up the list of directives either.
But, at least, it allows 2.0 to proxy stupid IIS as much as 1.3.
Docs to come soon.
PR:
Obtained from:
Submitted by:
Reviewed by:


git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@97202 13f79535-47bb-0310-9956-ffa450edef68
2002-10-14 14:11:24 +00:00

1225 lines
37 KiB
C

/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2002 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
* Portions of this software are based upon public domain software
* originally written at the National Center for Supercomputing Applications,
* University of Illinois, Urbana-Champaign.
*/
/* Utility routines for Apache proxy */
#include "mod_proxy.h"
static int proxy_match_ipaddr(struct dirconn_entry *This, request_rec *r);
static int proxy_match_domainname(struct dirconn_entry *This, request_rec *r);
static int proxy_match_hostname(struct dirconn_entry *This, request_rec *r);
static int proxy_match_word(struct dirconn_entry *This, request_rec *r);
APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(proxy, PROXY, int, create_req,
(request_rec *r, request_rec *pr), (r, pr),
OK, DECLINED)
/* already called in the knowledge that the characters are hex digits */
PROXY_DECLARE(int) ap_proxy_hex2c(const char *x)
{
int i, ch;
#if !APR_CHARSET_EBCDIC
ch = x[0];
if (apr_isdigit(ch))
i = ch - '0';
else if (apr_isupper(ch))
i = ch - ('A' - 10);
else
i = ch - ('a' - 10);
i <<= 4;
ch = x[1];
if (apr_isdigit(ch))
i += ch - '0';
else if (apr_isupper(ch))
i += ch - ('A' - 10);
else
i += ch - ('a' - 10);
return i;
#else /*APR_CHARSET_EBCDIC*/
/* we assume that the hex value refers to an ASCII character
* so convert to EBCDIC so that it makes sense locally;
*
* example:
*
* client specifies %20 in URL to refer to a space char;
* at this point we're called with EBCDIC "20"; after turning
* EBCDIC "20" into binary 0x20, we then need to assume that 0x20
* represents an ASCII char and convert 0x20 to EBCDIC, yielding
* 0x40
*/
char buf[1];
if (1 == sscanf(x, "%2x", &i)) {
buf[0] = i & 0xFF;
ap_xlate_proto_from_ascii(buf, 1);
return buf[0];
}
else {
return 0;
}
#endif /*APR_CHARSET_EBCDIC*/
}
PROXY_DECLARE(void) ap_proxy_c2hex(int ch, char *x)
{
#if !APR_CHARSET_EBCDIC
int i;
x[0] = '%';
i = (ch & 0xF0) >> 4;
if (i >= 10)
x[1] = ('A' - 10) + i;
else
x[1] = '0' + i;
i = ch & 0x0F;
if (i >= 10)
x[2] = ('A' - 10) + i;
else
x[2] = '0' + i;
#else /*APR_CHARSET_EBCDIC*/
static const char ntoa[] = { "0123456789ABCDEF" };
char buf[1];
ch &= 0xFF;
buf[0] = ch;
ap_xlate_proto_to_ascii(buf, 1);
x[0] = '%';
x[1] = ntoa[(buf[0] >> 4) & 0x0F];
x[2] = ntoa[buf[0] & 0x0F];
x[3] = '\0';
#endif /*APR_CHARSET_EBCDIC*/
}
/*
* canonicalise a URL-encoded string
*/
/*
* Convert a URL-encoded string to canonical form.
* It decodes characters which need not be encoded,
* and encodes those which must be encoded, and does not touch
* those which must not be touched.
*/
PROXY_DECLARE(char *)ap_proxy_canonenc(apr_pool_t *p, const char *x, int len, enum enctype t,
int isenc)
{
int i, j, ch;
char *y;
char *allowed; /* characters which should not be encoded */
char *reserved; /* characters which much not be en/de-coded */
/* N.B. in addition to :@&=, this allows ';' in an http path
* and '?' in an ftp path -- this may be revised
*
* Also, it makes a '+' character in a search string reserved, as
* it may be form-encoded. (Although RFC 1738 doesn't allow this -
* it only permits ; / ? : @ = & as reserved chars.)
*/
if (t == enc_path)
allowed = "$-_.+!*'(),;:@&=";
else if (t == enc_search)
allowed = "$-_.!*'(),;:@&=";
else if (t == enc_user)
allowed = "$-_.+!*'(),;@&=";
else if (t == enc_fpath)
allowed = "$-_.+!*'(),?:@&=";
else /* if (t == enc_parm) */
allowed = "$-_.+!*'(),?/:@&=";
if (t == enc_path)
reserved = "/";
else if (t == enc_search)
reserved = "+";
else
reserved = "";
y = apr_palloc(p, 3 * len + 1);
for (i = 0, j = 0; i < len; i++, j++) {
/* always handle '/' first */
ch = x[i];
if (strchr(reserved, ch)) {
y[j] = ch;
continue;
}
/* decode it if not already done */
if (isenc && ch == '%') {
if (!apr_isxdigit(x[i + 1]) || !apr_isxdigit(x[i + 2]))
return NULL;
ch = ap_proxy_hex2c(&x[i + 1]);
i += 2;
if (ch != 0 && strchr(reserved, ch)) { /* keep it encoded */
ap_proxy_c2hex(ch, &y[j]);
j += 2;
continue;
}
}
/* recode it, if necessary */
if (!apr_isalnum(ch) && !strchr(allowed, ch)) {
ap_proxy_c2hex(ch, &y[j]);
j += 2;
}
else
y[j] = ch;
}
y[j] = '\0';
return y;
}
/*
* Parses network-location.
* urlp on input the URL; on output the path, after the leading /
* user NULL if no user/password permitted
* password holder for password
* host holder for host
* port port number; only set if one is supplied.
*
* Returns an error string.
*/
PROXY_DECLARE(char *)
ap_proxy_canon_netloc(apr_pool_t *p, char **const urlp, char **userp,
char **passwordp, char **hostp, apr_port_t *port)
{
int i;
char *strp, *host, *url = *urlp;
char *user = NULL, *password = NULL;
if (url[0] != '/' || url[1] != '/')
return "Malformed URL";
host = url + 2;
url = strchr(host, '/');
if (url == NULL)
url = "";
else
*(url++) = '\0'; /* skip seperating '/' */
/* find _last_ '@' since it might occur in user/password part */
strp = strrchr(host, '@');
if (strp != NULL) {
*strp = '\0';
user = host;
host = strp + 1;
/* find password */
strp = strchr(user, ':');
if (strp != NULL) {
*strp = '\0';
password = ap_proxy_canonenc(p, strp + 1, strlen(strp + 1), enc_user, 1);
if (password == NULL)
return "Bad %-escape in URL (password)";
}
user = ap_proxy_canonenc(p, user, strlen(user), enc_user, 1);
if (user == NULL)
return "Bad %-escape in URL (username)";
}
if (userp != NULL) {
*userp = user;
}
if (passwordp != NULL) {
*passwordp = password;
}
strp = strrchr(host, ':');
if (strp != NULL) {
*(strp++) = '\0';
for (i = 0; strp[i] != '\0'; i++)
if (!apr_isdigit(strp[i]))
break;
/* if (i == 0) the no port was given; keep default */
if (strp[i] != '\0') {
return "Bad port number in URL";
} else if (i > 0) {
int int_port = atoi(strp);
if (int_port > 65535)
return "Port number in URL > 65535";
*port = (apr_port_t)int_port;
}
}
ap_str_tolower(host); /* DNS names are case-insensitive */
if (*host == '\0')
return "Missing host in URL";
/* check hostname syntax */
for (i = 0; host[i] != '\0'; i++)
if (!apr_isdigit(host[i]) && host[i] != '.')
break;
/* must be an IP address */
if (host[i] == '\0' && (apr_inet_addr(host) == -1))
{
return "Bad IP address in URL";
}
/* if (strchr(host,'.') == NULL && domain != NULL)
host = pstrcat(p, host, domain, NULL);
*/
*urlp = url;
*hostp = host;
return NULL;
}
static const char * const lwday[7] =
{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
/*
* If the date is a valid RFC 850 date or asctime() date, then it
* is converted to the RFC 1123 format, otherwise it is not modified.
* This routine is not very fast at doing conversions, as it uses
* sscanf and sprintf. However, if the date is already correctly
* formatted, then it exits very quickly.
*/
PROXY_DECLARE(const char *)
ap_proxy_date_canon(apr_pool_t *p, const char *x1)
{
char *x = apr_pstrdup(p, x1);
int wk, mday, year, hour, min, sec, mon;
char *q, month[4], zone[4], week[4];
q = strchr(x, ',');
/* check for RFC 850 date */
if (q != NULL && q - x > 3 && q[1] == ' ') {
*q = '\0';
for (wk = 0; wk < 7; wk++)
if (strcmp(x, lwday[wk]) == 0)
break;
*q = ',';
if (wk == 7)
return x; /* not a valid date */
if (q[4] != '-' || q[8] != '-' || q[11] != ' ' || q[14] != ':' ||
q[17] != ':' || strcmp(&q[20], " GMT") != 0)
return x;
if (sscanf(q + 2, "%u-%3s-%u %u:%u:%u %3s", &mday, month, &year,
&hour, &min, &sec, zone) != 7)
return x;
if (year < 70)
year += 2000;
else
year += 1900;
}
else {
/* check for acstime() date */
if (x[3] != ' ' || x[7] != ' ' || x[10] != ' ' || x[13] != ':' ||
x[16] != ':' || x[19] != ' ' || x[24] != '\0')
return x;
if (sscanf(x, "%3s %3s %u %u:%u:%u %u", week, month, &mday, &hour,
&min, &sec, &year) != 7)
return x;
for (wk = 0; wk < 7; wk++)
if (strcmp(week, apr_day_snames[wk]) == 0)
break;
if (wk == 7)
return x;
}
/* check date */
for (mon = 0; mon < 12; mon++)
if (strcmp(month, apr_month_snames[mon]) == 0)
break;
if (mon == 12)
return x;
q = apr_palloc(p, 30);
apr_snprintf(q, 30, "%s, %.2d %s %d %.2d:%.2d:%.2d GMT", apr_day_snames[wk],
mday, apr_month_snames[mon], year, hour, min, sec);
return q;
}
PROXY_DECLARE(request_rec *)ap_proxy_make_fake_req(conn_rec *c, request_rec *r)
{
request_rec *rp = apr_pcalloc(c->pool, sizeof(*r));
rp->pool = c->pool;
rp->status = HTTP_OK;
rp->headers_in = apr_table_make(c->pool, 50);
rp->subprocess_env = apr_table_make(c->pool, 50);
rp->headers_out = apr_table_make(c->pool, 12);
rp->err_headers_out = apr_table_make(c->pool, 5);
rp->notes = apr_table_make(c->pool, 5);
rp->server = r->server;
rp->proxyreq = r->proxyreq;
rp->request_time = r->request_time;
rp->connection = c;
rp->output_filters = c->output_filters;
rp->input_filters = c->input_filters;
rp->proto_output_filters = c->output_filters;
rp->proto_input_filters = c->input_filters;
rp->request_config = ap_create_request_config(c->pool);
proxy_run_create_req(r, rp);
return rp;
}
/*
* Reads headers from a buffer and returns an array of headers.
* Returns NULL on file error
* This routine tries to deal with too long lines and continuation lines.
*
* Note: Currently the headers are passed through unmerged. This has to be
* done so that headers which react badly to merging (such as Set-Cookie
* headers, which contain commas within the date field) do not get stuffed
* up.
*/
PROXY_DECLARE(apr_table_t *)ap_proxy_read_headers(request_rec *r, request_rec *rr, char *buffer, int size, conn_rec *c)
{
apr_table_t *headers_out;
int len;
char *value, *end;
char field[MAX_STRING_LEN];
int saw_headers = 0;
void *sconf = r->server->module_config;
proxy_server_conf *psc;
psc = (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
headers_out = apr_table_make(r->pool, 20);
/*
* Read header lines until we get the empty separator line, a read error,
* the connection closes (EOF), or we timeout.
*/
while ((len = ap_getline(buffer, size, rr, 1)) > 0) {
if (!(value = strchr(buffer, ':'))) { /* Find the colon separator */
/* We may encounter invalid headers, usually from buggy
* MS IIS servers, so we need to determine just how to handle
* them. We can either ignore them, assume that they mark the
* start-of-body (eg: a missing CRLF) or (the default) mark
* the headers as totally bogus and return a 500. The sole
* exception is an extra "HTTP/1.0 200, OK" line sprinkled
* in between the usual MIME headers, which is a favorite
* IIS bug.
*/
/* XXX: The mask check is buggy if we ever see an HTTP/1.10 */
if (!apr_date_checkmask(buffer, "HTTP/#.# ###*")) {
if (psc->badopt == bad_error) {
/* Nope, it wasn't even an extra HTTP header. Give up. */
return NULL;
}
else if (psc->badopt == bad_body) {
/* if we've already started loading headers_out, then
* return what we've accumulated so far, in the hopes
* that they are useful. Otherwise, we completely bail.
*/
/* FIXME: We've already scarfed the supposed 1st line of
* the body, so the actual content may end up being bogus
* as well. If the content is HTML, we may be lucky.
*/
if (saw_headers) {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server,
"proxy: Starting body due to bogus non-header in headers "
"returned by %s (%s)", r->uri, r->method);
return headers_out;
} else {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server,
"proxy: No HTTP headers "
"returned by %s (%s)", r->uri, r->method);
return NULL;
}
}
}
/* this is the psc->badopt == bad_ignore case */
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server,
"proxy: Ignoring bogus HTTP header "
"returned by %s (%s)", r->uri, r->method);
continue;
}
*value = '\0';
++value;
/* XXX: RFC2068 defines only SP and HT as whitespace, this test is
* wrong... and so are many others probably.
*/
while (apr_isspace(*value))
++value; /* Skip to start of value */
/* should strip trailing whitespace as well */
for (end = &value[strlen(value)-1]; end > value && apr_isspace(*end); --end)
*end = '\0';
/* make sure we add so as not to destroy duplicated headers */
apr_table_add(headers_out, buffer, value);
saw_headers = 1;
/* the header was too long; at the least we should skip extra data */
if (len >= size - 1) {
while ((len = ap_getline(field, MAX_STRING_LEN, rr, 1))
>= MAX_STRING_LEN - 1) {
/* soak up the extra data */
}
if (len == 0) /* time to exit the larger loop as well */
break;
}
}
return headers_out;
}
/*
* list is a comma-separated list of case-insensitive tokens, with
* optional whitespace around the tokens.
* The return returns 1 if the token val is found in the list, or 0
* otherwise.
*/
PROXY_DECLARE(int) ap_proxy_liststr(const char *list, const char *val)
{
int len, i;
const char *p;
len = strlen(val);
while (list != NULL) {
p = ap_strchr_c(list, ',');
if (p != NULL) {
i = p - list;
do
p++;
while (apr_isspace(*p));
}
else
i = strlen(list);
while (i > 0 && apr_isspace(list[i - 1]))
i--;
if (i == len && strncasecmp(list, val, len) == 0)
return 1;
list = p;
}
return 0;
}
/*
* list is a comma-separated list of case-insensitive tokens, with
* optional whitespace around the tokens.
* if val appears on the list of tokens, it is removed from the list,
* and the new list is returned.
*/
PROXY_DECLARE(char *)ap_proxy_removestr(apr_pool_t *pool, const char *list, const char *val)
{
int len, i;
const char *p;
char *new = NULL;
len = strlen(val);
while (list != NULL) {
p = ap_strchr_c(list, ',');
if (p != NULL) {
i = p - list;
do
p++;
while (apr_isspace(*p));
}
else
i = strlen(list);
while (i > 0 && apr_isspace(list[i - 1]))
i--;
if (i == len && strncasecmp(list, val, len) == 0) {
/* do nothing */
}
else {
if (new)
new = apr_pstrcat(pool, new, ",", apr_pstrndup(pool, list, i), NULL);
else
new = apr_pstrndup(pool, list, i);
}
list = p;
}
return new;
}
/*
* Converts 8 hex digits to a time integer
*/
PROXY_DECLARE(int) ap_proxy_hex2sec(const char *x)
{
int i, ch;
unsigned int j;
for (i = 0, j = 0; i < 8; i++) {
ch = x[i];
j <<= 4;
if (apr_isdigit(ch))
j |= ch - '0';
else if (apr_isupper(ch))
j |= ch - ('A' - 10);
else
j |= ch - ('a' - 10);
}
if (j == 0xffffffff)
return -1; /* so that it works with 8-byte ints */
else
return j;
}
/*
* Converts a time integer to 8 hex digits
*/
PROXY_DECLARE(void) ap_proxy_sec2hex(int t, char *y)
{
int i, ch;
unsigned int j = t;
for (i = 7; i >= 0; i--) {
ch = j & 0xF;
j >>= 4;
if (ch >= 10)
y[i] = ch + ('A' - 10);
else
y[i] = ch + '0';
}
y[8] = '\0';
}
PROXY_DECLARE(int) ap_proxyerror(request_rec *r, int statuscode, const char *message)
{
apr_table_setn(r->notes, "error-notes",
apr_pstrcat(r->pool,
"The proxy server could not handle the request "
"<em><a href=\"", ap_escape_uri(r->pool, r->uri),
"\">", ap_escape_html(r->pool, r->method),
"&nbsp;",
ap_escape_html(r->pool, r->uri), "</a></em>.<p>\n"
"Reason: <strong>",
ap_escape_html(r->pool, message),
"</strong></p>", NULL));
/* Allow "error-notes" string to be printed by ap_send_error_response() */
apr_table_setn(r->notes, "verbose-error-to", apr_pstrdup(r->pool, "*"));
r->status_line = apr_psprintf(r->pool, "%3.3u Proxy Error", statuscode);
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"proxy: %s returned by %s", message, r->uri);
return statuscode;
}
static const char *
proxy_get_host_of_request(request_rec *r)
{
char *url, *user = NULL, *password = NULL, *err, *host;
apr_port_t port;
if (r->hostname != NULL)
return r->hostname;
/* Set url to the first char after "scheme://" */
if ((url = strchr(r->uri, ':')) == NULL
|| url[1] != '/' || url[2] != '/')
return NULL;
url = apr_pstrdup(r->pool, &url[1]); /* make it point to "//", which is what proxy_canon_netloc expects */
err = ap_proxy_canon_netloc(r->pool, &url, &user, &password, &host, &port);
if (err != NULL)
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"%s", err);
r->hostname = host;
return host; /* ought to return the port, too */
}
/* Return TRUE if addr represents an IP address (or an IP network address) */
PROXY_DECLARE(int) ap_proxy_is_ipaddr(struct dirconn_entry *This, apr_pool_t *p)
{
const char *addr = This->name;
long ip_addr[4];
int i, quads;
long bits;
/* if the address is given with an explicit netmask, use that */
/* Due to a deficiency in apr_inet_addr(), it is impossible to parse */
/* "partial" addresses (with less than 4 quads) correctly, i.e. */
/* 192.168.123 is parsed as 192.168.0.123, which is not what I want. */
/* I therefore have to parse the IP address manually: */
/*if (proxy_readmask(This->name, &This->addr.s_addr, &This->mask.s_addr) == 0) */
/* addr and mask were set by proxy_readmask() */
/*return 1; */
/* Parse IP addr manually, optionally allowing */
/* abbreviated net addresses like 192.168. */
/* Iterate over up to 4 (dotted) quads. */
for (quads = 0; quads < 4 && *addr != '\0'; ++quads) {
char *tmp;
if (*addr == '/' && quads > 0) /* netmask starts here. */
break;
if (!apr_isdigit(*addr))
return 0; /* no digit at start of quad */
ip_addr[quads] = strtol(addr, &tmp, 0);
if (tmp == addr) /* expected a digit, found something else */
return 0;
if (ip_addr[quads] < 0 || ip_addr[quads] > 255) {
/* invalid octet */
return 0;
}
addr = tmp;
if (*addr == '.' && quads != 3)
++addr; /* after the 4th quad, a dot would be illegal */
}
for (This->addr.s_addr = 0, i = 0; i < quads; ++i)
This->addr.s_addr |= htonl(ip_addr[i] << (24 - 8 * i));
if (addr[0] == '/' && apr_isdigit(addr[1])) { /* net mask follows: */
char *tmp;
++addr;
bits = strtol(addr, &tmp, 0);
if (tmp == addr) /* expected a digit, found something else */
return 0;
addr = tmp;
if (bits < 0 || bits > 32) /* netmask must be between 0 and 32 */
return 0;
}
else {
/* Determine (i.e., "guess") netmask by counting the */
/* number of trailing .0's; reduce #quads appropriately */
/* (so that 192.168.0.0 is equivalent to 192.168.) */
while (quads > 0 && ip_addr[quads - 1] == 0)
--quads;
/* "IP Address should be given in dotted-quad form, optionally followed by a netmask (e.g., 192.168.111.0/24)"; */
if (quads < 1)
return 0;
/* every zero-byte counts as 8 zero-bits */
bits = 8 * quads;
if (bits != 32) /* no warning for fully qualified IP address */
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"Warning: NetMask not supplied with IP-Addr; guessing: %s/%ld\n",
inet_ntoa(This->addr), bits);
}
This->mask.s_addr = htonl(APR_INADDR_NONE << (32 - bits));
if (*addr == '\0' && (This->addr.s_addr & ~This->mask.s_addr) != 0) {
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"Warning: NetMask and IP-Addr disagree in %s/%ld\n",
inet_ntoa(This->addr), bits);
This->addr.s_addr &= This->mask.s_addr;
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
" Set to %s/%ld\n",
inet_ntoa(This->addr), bits);
}
if (*addr == '\0') {
This->matcher = proxy_match_ipaddr;
return 1;
}
else
return (*addr == '\0'); /* okay iff we've parsed the whole string */
}
/* Return TRUE if addr represents an IP address (or an IP network address) */
static int proxy_match_ipaddr(struct dirconn_entry *This, request_rec *r)
{
int i, ip_addr[4];
struct in_addr addr, *ip;
const char *host = proxy_get_host_of_request(r);
if (host == NULL) /* oops! */
return 0;
memset(&addr, '\0', sizeof addr);
memset(ip_addr, '\0', sizeof ip_addr);
if (4 == sscanf(host, "%d.%d.%d.%d", &ip_addr[0], &ip_addr[1], &ip_addr[2], &ip_addr[3])) {
for (addr.s_addr = 0, i = 0; i < 4; ++i)
addr.s_addr |= htonl(ip_addr[i] << (24 - 8 * i));
if (This->addr.s_addr == (addr.s_addr & This->mask.s_addr)) {
#if DEBUGGING
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"1)IP-Match: %s[%s] <-> ", host, inet_ntoa(addr));
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"%s/", inet_ntoa(This->addr));
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"%s", inet_ntoa(This->mask));
#endif
return 1;
}
#if DEBUGGING
else {
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"1)IP-NoMatch: %s[%s] <-> ", host, inet_ntoa(addr));
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"%s/", inet_ntoa(This->addr));
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"%s", inet_ntoa(This->mask));
}
#endif
}
else {
struct apr_sockaddr_t *reqaddr;
if (apr_sockaddr_info_get(&reqaddr, host, APR_UNSPEC, 0, 0, r->pool)
!= APR_SUCCESS) {
#if DEBUGGING
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"2)IP-NoMatch: hostname=%s msg=Host not found",
host);
#endif
return 0;
}
/* Try to deal with multiple IP addr's for a host */
/* FIXME: This needs to be able to deal with IPv6 */
while (reqaddr) {
ip = (struct in_addr *) reqaddr->ipaddr_ptr;
if (This->addr.s_addr == (ip->s_addr & This->mask.s_addr)) {
#if DEBUGGING
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"3)IP-Match: %s[%s] <-> ", host,
inet_ntoa(*ip));
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"%s/", inet_ntoa(This->addr));
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"%s", inet_ntoa(This->mask));
#endif
return 1;
}
#if DEBUGGING
else {
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"3)IP-NoMatch: %s[%s] <-> ", host,
inet_ntoa(*ip));
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"%s/", inet_ntoa(This->addr));
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"%s", inet_ntoa(This->mask));
}
#endif
reqaddr = reqaddr->next;
}
}
return 0;
}
/* Return TRUE if addr represents a domain name */
PROXY_DECLARE(int) ap_proxy_is_domainname(struct dirconn_entry *This, apr_pool_t *p)
{
char *addr = This->name;
int i;
/* Domain name must start with a '.' */
if (addr[0] != '.')
return 0;
/* rfc1035 says DNS names must consist of "[-a-zA-Z0-9]" and '.' */
for (i = 0; apr_isalnum(addr[i]) || addr[i] == '-' || addr[i] == '.'; ++i)
continue;
#if 0
if (addr[i] == ':') {
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
"@@@@ handle optional port in proxy_is_domainname()");
/* @@@@ handle optional port */
}
#endif
if (addr[i] != '\0')
return 0;
/* Strip trailing dots */
for (i = strlen(addr) - 1; i > 0 && addr[i] == '.'; --i)
addr[i] = '\0';
This->matcher = proxy_match_domainname;
return 1;
}
/* Return TRUE if host "host" is in domain "domain" */
static int proxy_match_domainname(struct dirconn_entry *This, request_rec *r)
{
const char *host = proxy_get_host_of_request(r);
int d_len = strlen(This->name), h_len;
if (host == NULL) /* some error was logged already */
return 0;
h_len = strlen(host);
/* @@@ do this within the setup? */
/* Ignore trailing dots in domain comparison: */
while (d_len > 0 && This->name[d_len - 1] == '.')
--d_len;
while (h_len > 0 && host[h_len - 1] == '.')
--h_len;
return h_len > d_len
&& strncasecmp(&host[h_len - d_len], This->name, d_len) == 0;
}
/* Return TRUE if host represents a host name */
PROXY_DECLARE(int) ap_proxy_is_hostname(struct dirconn_entry *This, apr_pool_t *p)
{
struct apr_sockaddr_t *addr;
char *host = This->name;
int i;
/* Host names must not start with a '.' */
if (host[0] == '.')
return 0;
/* rfc1035 says DNS names must consist of "[-a-zA-Z0-9]" and '.' */
for (i = 0; apr_isalnum(host[i]) || host[i] == '-' || host[i] == '.'; ++i);
if (host[i] != '\0' || apr_sockaddr_info_get(&addr, host, APR_UNSPEC, 0, 0, p) != APR_SUCCESS)
return 0;
This->hostaddr = addr;
/* Strip trailing dots */
for (i = strlen(host) - 1; i > 0 && host[i] == '.'; --i)
host[i] = '\0';
This->matcher = proxy_match_hostname;
return 1;
}
/* Return TRUE if host "host" is equal to host2 "host2" */
static int proxy_match_hostname(struct dirconn_entry *This, request_rec *r)
{
char *host = This->name;
const char *host2 = proxy_get_host_of_request(r);
int h2_len;
int h1_len;
if (host == NULL || host2 == NULL)
return 0; /* oops! */
h2_len = strlen(host2);
h1_len = strlen(host);
#if 0
struct apr_sockaddr_t *addr = *This->hostaddr;
/* Try to deal with multiple IP addr's for a host */
while (addr) {
if (addr->ipaddr_ptr == ? ? ? ? ? ? ? ? ? ? ? ? ?)
return 1;
addr = addr->next;
}
#endif
/* Ignore trailing dots in host2 comparison: */
while (h2_len > 0 && host2[h2_len - 1] == '.')
--h2_len;
while (h1_len > 0 && host[h1_len - 1] == '.')
--h1_len;
return h1_len == h2_len
&& strncasecmp(host, host2, h1_len) == 0;
}
/* Return TRUE if addr is to be matched as a word */
PROXY_DECLARE(int) ap_proxy_is_word(struct dirconn_entry *This, apr_pool_t *p)
{
This->matcher = proxy_match_word;
return 1;
}
/* Return TRUE if string "str2" occurs literally in "str1" */
static int proxy_match_word(struct dirconn_entry *This, request_rec *r)
{
const char *host = proxy_get_host_of_request(r);
return host != NULL && ap_strstr_c(host, This->name) != NULL;
}
/* checks whether a host in uri_addr matches proxyblock */
PROXY_DECLARE(int) ap_proxy_checkproxyblock(request_rec *r, proxy_server_conf *conf,
apr_sockaddr_t *uri_addr)
{
int j;
/* XXX FIXME: conf->noproxies->elts is part of an opaque structure */
for (j = 0; j < conf->noproxies->nelts; j++) {
struct noproxy_entry *npent = (struct noproxy_entry *) conf->noproxies->elts;
struct apr_sockaddr_t *conf_addr = npent[j].addr;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
"proxy: checking remote machine [%s] against [%s]", uri_addr->hostname, npent[j].name);
if ((npent[j].name && ap_strstr_c(uri_addr->hostname, npent[j].name))
|| npent[j].name[0] == '*') {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server,
"proxy: connect to remote machine %s blocked: name %s matched", uri_addr->hostname, npent[j].name);
return HTTP_FORBIDDEN;
}
while (conf_addr) {
while (uri_addr) {
char *conf_ip;
char *uri_ip;
apr_sockaddr_ip_get(&conf_ip, conf_addr);
apr_sockaddr_ip_get(&uri_ip, uri_addr);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
"proxy: ProxyBlock comparing %s and %s", conf_ip, uri_ip);
if (!apr_strnatcasecmp(conf_ip, uri_ip)) {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server,
"proxy: connect to remote machine %s blocked: IP %s matched", uri_addr->hostname, conf_ip);
return HTTP_FORBIDDEN;
}
uri_addr = uri_addr->next;
}
conf_addr = conf_addr->next;
}
}
return OK;
}
/* set up the minimal filter set */
PROXY_DECLARE(int) ap_proxy_pre_http_request(conn_rec *c, request_rec *r)
{
ap_add_input_filter("HTTP_IN", NULL, r, c);
return OK;
}
/* converts a series of buckets into a string
* XXX: BillS says this function performs essentially the same function as
* ap_rgetline() in protocol.c. Deprecate this function and use ap_rgetline()
* instead? I think ap_proxy_string_read() will not work properly on non ASCII
* (EBCDIC) machines either.
*/
PROXY_DECLARE(apr_status_t) ap_proxy_string_read(conn_rec *c, apr_bucket_brigade *bb,
char *buff, apr_size_t bufflen, int *eos)
{
apr_bucket *e;
apr_status_t rv;
char *pos = buff;
char *response;
int found = 0;
apr_size_t len;
/* start with an empty string */
buff[0] = 0;
*eos = 0;
/* loop through each brigade */
while (!found) {
/* get brigade from network one line at a time */
if (APR_SUCCESS != (rv = ap_get_brigade(c->input_filters, bb,
AP_MODE_GETLINE,
APR_BLOCK_READ,
0))) {
return rv;
}
/* loop through each bucket */
while (!found) {
if (*eos || APR_BRIGADE_EMPTY(bb)) {
/* The connection aborted or timed out */
return APR_ECONNABORTED;
}
e = APR_BRIGADE_FIRST(bb);
if (APR_BUCKET_IS_EOS(e)) {
*eos = 1;
}
else {
if (APR_SUCCESS != apr_bucket_read(e, (const char **)&response, &len, APR_BLOCK_READ)) {
return rv;
}
/* is string LF terminated?
* XXX: This check can be made more efficient by simply checking
* if the last character in the 'response' buffer is an ASCII_LF.
* See ap_rgetline() for an example.
*/
if (memchr(response, APR_ASCII_LF, len)) {
found = 1;
}
/* concat strings until buff is full - then throw the data away */
if (len > ((bufflen-1)-(pos-buff))) {
len = (bufflen-1)-(pos-buff);
}
if (len > 0) {
pos = apr_cpystrn(pos, response, len);
}
}
APR_BUCKET_REMOVE(e);
apr_bucket_destroy(e);
}
}
return APR_SUCCESS;
}
/* unmerge an element in the table */
PROXY_DECLARE(void) ap_proxy_table_unmerge(apr_pool_t *p, apr_table_t *t, char *key)
{
apr_off_t offset = 0;
apr_off_t count = 0;
char *value = NULL;
/* get the value to unmerge */
const char *initial = apr_table_get(t, key);
if (!initial) {
return;
}
value = apr_pstrdup(p, initial);
/* remove the value from the headers */
apr_table_unset(t, key);
/* find each comma */
while (value[count]) {
if (value[count] == ',') {
value[count] = 0;
apr_table_add(t, key, value + offset);
offset = count + 1;
}
count++;
}
apr_table_add(t, key, value + offset);
}
PROXY_DECLARE(int) ap_proxy_connect_to_backend(apr_socket_t **newsock,
const char *proxy_function,
apr_sockaddr_t *backend_addr,
const char *backend_name,
proxy_server_conf *conf,
server_rec *s,
apr_pool_t *p)
{
apr_status_t rv;
int connected = 0;
int loglevel;
while (backend_addr && !connected) {
if ((rv = apr_socket_create(newsock, backend_addr->family,
SOCK_STREAM, p)) != APR_SUCCESS) {
loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
ap_log_error(APLOG_MARK, loglevel, rv, s,
"proxy: %s: error creating fam %d socket for target %s",
proxy_function,
backend_addr->family,
backend_name);
/* this could be an IPv6 address from the DNS but the
* local machine won't give us an IPv6 socket; hopefully the
* DNS returned an additional address to try
*/
backend_addr = backend_addr->next;
continue;
}
#if !defined(TPF) && !defined(BEOS)
if (conf->recv_buffer_size > 0 &&
(rv = apr_socket_opt_set(*newsock, APR_SO_RCVBUF,
conf->recv_buffer_size))) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
"apr_socket_opt_set(SO_RCVBUF): Failed to set "
"ProxyReceiveBufferSize, using default");
}
#endif
/* Set a timeout on the socket */
if (conf->timeout_set == 1) {
apr_socket_timeout_set(*newsock, conf->timeout);
}
else {
apr_socket_timeout_set(*newsock, s->timeout);
}
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"proxy: %s: fam %d socket created to connect to %s",
proxy_function, backend_addr->family, backend_name);
/* make the connection out of the socket */
rv = apr_connect(*newsock, backend_addr);
/* if an error occurred, loop round and try again */
if (rv != APR_SUCCESS) {
apr_socket_close(*newsock);
loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
ap_log_error(APLOG_MARK, loglevel, rv, s,
"proxy: %s: attempt to connect to %pI (%s) failed",
proxy_function,
backend_addr,
backend_name);
backend_addr = backend_addr->next;
continue;
}
connected = 1;
}
return connected ? 0 : 1;
}