/* 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.
*/
/* * We do health-checks only if that (sub)module is loaded in. This * allows for us to continue as is w/o requiring mod_watchdog for * those implementations which aren't using health checks
*/ static APR_OPTIONAL_FN_TYPE(set_worker_hc_param) *set_worker_hc_param_f = NULL;
/* * A Web proxy module. Stages: * * translate_name: set filename to proxy:<URL> * map_to_storage: run proxy_walk (rather than directory_walk/file_walk) * can't trust directory_walk/file_walk since these are * not in our filesystem. Prevents mod_http from serving * the TRACE request we will set aside to handle later. * fix_ups: convert the URL stored in the filename to the * canonical form. * handler: handle proxy requests
*/
/* -------------------------------------------------------------- */ /* Translate the URL into a 'filename' */
if (!strcasecmp(key, "loadfactor")) { /* Normalized load factor. Used with BalancerMember, * it is a number between 1 and 100.
*/ double fval = atof(val);
ival = fval * 100.0; if (ival < 100 || ival > 10000) return"LoadFactor must be a number between 1..100";
worker->s->lbfactor = ival;
} elseif (!strcasecmp(key, "retry")) { /* If set it will give the retry timeout for the worker * The default value is 60 seconds, meaning that if * in error state, it will be retried after that timeout.
*/
ival = atoi(val); if (ival < 0) return"Retry must be a positive value";
worker->s->retry = apr_time_from_sec(ival);
worker->s->retry_set = 1;
} elseif (!strcasecmp(key, "ttl")) { /* Time in seconds that will destroy all the connections * that exceed the smax
*/
ival = atoi(val); if (ival < 1) return"TTL must be at least one second";
worker->s->ttl = apr_time_from_sec(ival);
} elseif (!strcasecmp(key, "min")) { /* Initial number of connections to remote
*/
ival = atoi(val); if (ival < 0) return"Min must be a positive number";
worker->s->min = ival;
} elseif (!strcasecmp(key, "max")) { /* Maximum number of connections to remote
*/
ival = atoi(val); if (ival < 0) return"Max must be a positive number";
worker->s->hmax = ival;
} /* XXX: More intelligent naming needed */ elseif (!strcasecmp(key, "smax")) { /* Maximum number of connections to remote that * will not be destroyed
*/
ival = atoi(val); if (ival < 0) return"Smax must be a positive number";
worker->s->smax = ival;
} elseif (!strcasecmp(key, "acquire")) { /* Acquire timeout in given unit (default is milliseconds). * If set this will be the maximum time to * wait for a free connection.
*/ if (ap_timeout_parameter_parse(val, &timeout, "ms") != APR_SUCCESS) return"Acquire timeout has wrong format"; if (timeout < 1000) return"Acquire must be at least one millisecond";
worker->s->acquire = timeout;
worker->s->acquire_set = 1;
} elseif (!strcasecmp(key, "timeout")) { /* Connection timeout in seconds. * Defaults to server timeout.
*/
ival = atoi(val); if (ival < 1) return"Timeout must be at least one second";
worker->s->timeout = apr_time_from_sec(ival);
worker->s->timeout_set = 1;
} elseif (!strcasecmp(key, "iobuffersize")) { long s = atol(val); if (s < 512 && s) { return"IOBufferSize must be >= 512 bytes, or 0 for system default.";
}
worker->s->io_buffer_size = (s ? s : AP_IOBUFSIZE);
worker->s->io_buffer_size_set = 1;
} elseif (!strcasecmp(key, "receivebuffersize")) {
ival = atoi(val); if (ival < 512 && ival != 0) { return"ReceiveBufferSize must be >= 512 bytes, or 0 for system default.";
}
worker->s->recv_buffer_size = ival;
worker->s->recv_buffer_size_set = 1;
} elseif (!strcasecmp(key, "keepalive")) { if (!strcasecmp(val, "on"))
worker->s->keepalive = 1; elseif (!strcasecmp(val, "off"))
worker->s->keepalive = 0; else return"KeepAlive must be On|Off";
worker->s->keepalive_set = 1;
} elseif (!strcasecmp(key, "disablereuse")) { if (!strcasecmp(val, "on"))
worker->s->disablereuse = 1; elseif (!strcasecmp(val, "off"))
worker->s->disablereuse = 0; else return"DisableReuse must be On|Off";
worker->s->disablereuse_set = 1;
} elseif (!strcasecmp(key, "enablereuse")) { if (!strcasecmp(val, "on"))
worker->s->disablereuse = 0; elseif (!strcasecmp(val, "off"))
worker->s->disablereuse = 1; else return"EnableReuse must be On|Off";
worker->s->disablereuse_set = 1;
} elseif (!strcasecmp(key, "addressttl")) { /* Address TTL in seconds
*/
apr_interval_time_t ttl; if (strcmp(val, "-1") == 0) {
worker->s->address_ttl = -1;
} elseif (ap_timeout_parameter_parse(val, &ttl, "s") == APR_SUCCESS
&& (ttl <= apr_time_from_sec(APR_INT32_MAX))
&& (ttl % apr_time_from_sec(1)) == 0) {
worker->s->address_ttl = apr_time_sec(ttl);
} else { return"AddressTTL must be -1 or a number of seconds not " "exceeding " APR_STRINGIFY(APR_INT32_MAX);
}
worker->s->address_ttl_set = 1;
} elseif (!strcasecmp(key, "route")) { /* Worker route.
*/ if (strlen(val) >= sizeof(worker->s->route)) return apr_psprintf(p, "Route length must be < %d characters",
(int)sizeof(worker->s->route));
PROXY_STRNCPY(worker->s->route, val);
} elseif (!strcasecmp(key, "redirect")) { /* Worker redirection route.
*/ if (strlen(val) >= sizeof(worker->s->redirect)) return apr_psprintf(p, "Redirect length must be < %d characters",
(int)sizeof(worker->s->redirect));
PROXY_STRNCPY(worker->s->redirect, val);
} elseif (!strcasecmp(key, "status")) { constchar *v; int mode = 1;
apr_status_t rv; /* Worker status.
*/ for (v = val; *v; v++) { if (*v == '+') {
mode = 1;
v++;
} elseif (*v == '-') {
mode = 0;
v++;
}
rv = ap_proxy_set_wstatus(*v, mode, worker); if (rv != APR_SUCCESS) return"Unknown status parameter option";
}
} elseif (!strcasecmp(key, "flushpackets")) { if (!strcasecmp(val, "on"))
worker->s->flush_packets = flush_on; elseif (!strcasecmp(val, "off"))
worker->s->flush_packets = flush_off; elseif (!strcasecmp(val, "auto"))
worker->s->flush_packets = flush_auto; else return"flushpackets must be on|off|auto";
} elseif (!strcasecmp(key, "flushwait")) {
ival = atoi(val); if (ival > 1000 || ival < 0) { return"flushwait must be <= 1000, or 0 for system default of 10 millseconds.";
} if (ival == 0)
worker->s->flush_wait = PROXY_FLUSH_WAIT; else
worker->s->flush_wait = ival * 1000; /* change to microseconds */
} elseif (!strcasecmp(key, "ping")) { /* Ping/Pong timeout in given unit (default is second).
*/ if (ap_timeout_parameter_parse(val, &timeout, "s") != APR_SUCCESS) return"Ping/Pong timeout has wrong format"; if (timeout < 1000) return"Ping/Pong timeout must be at least one millisecond";
worker->s->ping_timeout = timeout;
worker->s->ping_timeout_set = 1;
} elseif (!strcasecmp(key, "lbset")) {
ival = atoi(val); if (ival < 0 || ival > 99) return"lbset must be between 0 and 99";
worker->s->lbset = ival;
} elseif (!strcasecmp(key, "connectiontimeout")) { /* Request timeout in given unit (default is second). * Defaults to connection timeout
*/ if (ap_timeout_parameter_parse(val, &timeout, "s") != APR_SUCCESS) return"Connectiontimeout has wrong format"; if (timeout < 1000) return"Connectiontimeout must be at least one millisecond.";
worker->s->conn_timeout = timeout;
worker->s->conn_timeout_set = 1;
} elseif (!strcasecmp(key, "flusher")) { if (PROXY_STRNCPY(worker->s->flusher, val) != APR_SUCCESS) { return apr_psprintf(p, "flusher name length must be < %d characters",
(int)sizeof(worker->s->flusher));
}
} elseif (!strcasecmp(key, "upgrade")) { if (PROXY_STRNCPY(worker->s->upgrade,
strcasecmp(val, "ANY") ? val : "*") != APR_SUCCESS) { return apr_psprintf(p, "upgrade protocol length must be < %d characters",
(int)sizeof(worker->s->upgrade));
}
} elseif (!strcasecmp(key, "responsefieldsize")) { long s = atol(val); if (s < 0) { return"ResponseFieldSize must be greater than 0 bytes, or 0 for system default.";
}
worker->s->response_field_size = (s ? s : HUGE_STRING_LEN);
worker->s->response_field_size_set = 1;
} elseif (!strcasecmp(key, "secret")) { if (PROXY_STRNCPY(worker->s->secret, val) != APR_SUCCESS) { return apr_psprintf(p, "Secret length must be < %d characters",
(int)sizeof(worker->s->secret));
}
} else { if (set_worker_hc_param_f) { return set_worker_hc_param_f(p, s, worker, key, val, NULL);
} else { return"unknown Worker parameter";
}
} return NULL;
}
int ival; if (!strcasecmp(key, "stickysession")) { char *path; /* Balancer sticky session name. * Set to something like JSESSIONID or * PHPSESSIONID, etc..,
*/ if (strlen(val) >= sizeof(balancer->s->sticky_path))
apr_psprintf(p, "stickysession length must be < %d characters",
(int)sizeof(balancer->s->sticky_path));
PROXY_STRNCPY(balancer->s->sticky_path, val);
PROXY_STRNCPY(balancer->s->sticky, val);
if ((path = strchr((char *)balancer->s->sticky, '|'))) {
*path++ = '\0';
PROXY_STRNCPY(balancer->s->sticky_path, path);
}
} elseif (!strcasecmp(key, "stickysessionsep")) { /* separator/delimiter for sessionid and route, * normally '.'
*/ if (strlen(val) != 1) { if (!strcasecmp(val, "off"))
balancer->s->sticky_separator = 0; else return"stickysessionsep must be a single character or Off";
} else
balancer->s->sticky_separator = *val;
balancer->s->sticky_separator_set = 1;
} elseif (!strcasecmp(key, "nofailover")) { /* If set to 'on' the session will break * if the worker is in error state or * disabled.
*/ if (!strcasecmp(val, "on"))
balancer->s->sticky_force = 1; elseif (!strcasecmp(val, "off"))
balancer->s->sticky_force = 0; else return"failover must be On|Off";
balancer->s->sticky_force_set = 1;
} elseif (!strcasecmp(key, "timeout")) { /* Balancer timeout in seconds. * If set this will be the maximum time to * wait for a free worker. * Default is not to wait.
*/
ival = atoi(val); if (ival < 1) return"timeout must be at least one second";
balancer->s->timeout = apr_time_from_sec(ival);
} elseif (!strcasecmp(key, "maxattempts")) { /* Maximum number of failover attempts before * giving up.
*/
ival = atoi(val); if (ival < 0) return"maximum number of attempts must be a positive number";
balancer->s->max_attempts = ival;
balancer->s->max_attempts_set = 1;
} elseif (!strcasecmp(key, "lbmethod")) {
proxy_balancer_method *provider; if (strlen(val) > (sizeof(balancer->s->lbpname)-1)) return"unknown lbmethod";
provider = ap_lookup_provider(PROXY_LBMETHOD, val, "0"); if (provider) {
balancer->lbmethod = provider; if (PROXY_STRNCPY(balancer->s->lbpname, val) == APR_SUCCESS) {
balancer->lbmethod_set = 1; return NULL;
} else { return"lbmethod name too large";
}
} return"unknown lbmethod";
} elseif (!strcasecmp(key, "scolonpathdelim")) { /* If set to 'on' then ';' will also be * used as a session path separator/delim (ala * mod_jk)
*/ if (!strcasecmp(val, "on"))
balancer->s->scolonsep = 1; elseif (!strcasecmp(val, "off"))
balancer->s->scolonsep = 0; else return"scolonpathdelim must be On|Off";
balancer->s->scolonsep_set = 1;
} elseif (!strcasecmp(key, "failonstatus")) { char *val_split; char *status; char *tok_state;
status = apr_strtok(val_split, ", ", &tok_state); while (status != NULL) {
ival = atoi(status); if (ap_is_HTTP_VALID_RESPONSE(ival)) {
*(int *)apr_array_push(balancer->errstatuses) = ival;
} else { return"failonstatus must be one or more HTTP response codes";
}
status = apr_strtok(NULL, ", ", &tok_state);
}
} elseif (!strcasecmp(key, "failontimeout")) { if (!strcasecmp(val, "on"))
balancer->failontimeout = 1; elseif (!strcasecmp(val, "off"))
balancer->failontimeout = 0; else return"failontimeout must be On|Off";
balancer->failontimeout_set = 1;
} elseif (!strcasecmp(key, "nonce")) { if (!strcasecmp(val, "None")) {
*balancer->s->nonce = '\0';
} else { if (PROXY_STRNCPY(balancer->s->nonce, val) != APR_SUCCESS) { return"Provided nonce is too large";
}
}
balancer->s->nonce_set = 1;
} elseif (!strcasecmp(key, "growth")) {
ival = atoi(val); if (ival < 1 || ival > 100) /* arbitrary limit here */ return"growth must be between 1 and 100";
balancer->growth = ival;
balancer->growth_set = 1;
} elseif (!strcasecmp(key, "forcerecovery")) { if (!strcasecmp(val, "on"))
balancer->s->forcerecovery = 1; elseif (!strcasecmp(val, "off"))
balancer->s->forcerecovery = 0; else return"forcerecovery must be On|Off";
balancer->s->forcerecovery_set = 1;
} else { return"unknown Balancer parameter";
} return NULL;
}
while (aliasp < end_fakename && urip < end_uri) { if (*aliasp == '/') { /* any number of '/' in the alias matches any number in * the supplied URI, but there must be at least one...
*/ if (*urip != '/') return 0;
while (*aliasp == '/')
++aliasp; while (*urip == '/')
++urip;
} else { /* Other characters are compared literally */ if (*urip++ != *aliasp++) return 0;
}
}
/* fixup badly encoded stuff (e.g. % as last character) */ if (aliasp > end_fakename) {
aliasp = end_fakename;
} if (urip > end_uri) {
urip = end_uri;
}
/* We reach the end of the uri before the end of "alias_fakename" * for example uri is "/" and alias_fakename "/examples"
*/ if (urip == end_uri && aliasp != end_fakename) { return 0;
}
/* Check last alias path component matched all the way */ if (aliasp[-1] != '/' && *urip != '\0' && *urip != '/') return 0;
/* Return number of characters from URI which matched (may be * greater than length of alias, since we may have matched * doubled slashes)
*/
/* Remove /xx/../ segments */ if (uri[uri_pos + 1] == '.'
&& (uri[uri_pos + 2] == '/'
|| uri[uri_pos + 2] == ';'
|| uri[uri_pos + 2] == '\0')) { /* Wind map segment back the previous one */ if (map_pos == 1) { /* Above root */ return 0;
} do {
map_pos--;
} while (map[map_pos - 1] != '/');
map[map_pos] = '\0';
/* Wind alias segment back, unless in deeper segment */ if (alias_depth == stack->nelts) { if (alias[alias_pos] == '\0') {
alias_pos--;
} while (alias_pos > 0 && alias[alias_pos] == '/') {
alias_pos--;
} while (alias_pos > 0 && alias[alias_pos - 1] != '/') {
alias_pos--;
}
AP_DEBUG_ASSERT(alias_pos > 0);
alias_depth--;
}
apr_array_pop(stack);
/* Move uri forward to the next segment */
uri_pos += 2; if (uri[uri_pos] == '/') {
uri_pos++;
}
first_pos = 0; continue;
}
} if (first_pos) { while (uri[first_pos] == '/') {
first_pos++;
}
}
/* New segment */
APR_ARRAY_PUSH(stack, int) = first_pos ? first_pos : uri_pos; if (alias[alias_pos] != '\0') { if (alias[alias_pos - 1] != '/') { /* Remain in pair with uri segments */ do {
alias_pos++;
} while (alias[alias_pos - 1] != '/' && alias[alias_pos]);
} while (alias[alias_pos] == '/') {
alias_pos++;
} if (alias[alias_pos] != '\0') {
alias_depth++;
}
}
}
if (alias[alias_pos] != '\0') { int *match = &APR_ARRAY_IDX(stack, alias_depth - 1, int); if (*match) { if (alias[alias_pos] != uri[uri_pos]) { /* Current segment does not match */
*match = 0;
} elseif (alias[alias_pos + 1] == '\0'
&& alias[alias_pos] != '/') { if (uri[uri_pos + 1] == ';') { /* We'll preserve the parameters of the last * segment if it does not end with '/', so mark * the match as negative for below handling.
*/
*match = -(uri_pos + 1);
} elseif (uri[uri_pos + 1] != '/'
&& uri[uri_pos + 1] != '\0') { /* Last segment does not match all the way */
*match = 0;
}
}
} /* Don't go past the segment if the uri isn't there yet */ if (alias[alias_pos] != '/' || uri[uri_pos] == '/') {
alias_pos++;
}
}
/* Can't reach the end of uri before the end of the alias, * for example if uri is "/" and alias is "/examples"
*/ if (alias[alias_pos] != '\0') { return 0;
}
/* Check whether each alias segment matched */ for (depth = 0; depth < alias_depth; ++depth) { if (!APR_ARRAY_IDX(stack, depth, int)) { return 0;
}
}
/* If alias_depth == stack->nelts we have a full match, i.e. * uri == alias so we can return uri_pos as is (the end of uri)
*/ if (alias_depth < stack->nelts) { /* Return the segment following the alias */
uri_pos = APR_ARRAY_IDX(stack, alias_depth, int); if (alias_depth) { /* But if the last segment of the alias does not end with '/' * and the corresponding segment of the uri has parameters, * we want to forward those parameters (see above for the * negative pos trick/mark).
*/ int pos = APR_ARRAY_IDX(stack, alias_depth - 1, int); if (pos < 0) {
uri_pos = -pos;
}
}
} /* If the alias lacks a trailing slash, take it from the uri (if any) */ if (alias[alias_pos - 1] != '/' && uri[uri_pos - 1] == '/') {
uri_pos--;
}
*urip = map; return uri_pos;
}
/* Detect if an absoluteURI should be proxied or not. Note that we * have to do this during this phase because later phases are * "short-circuiting"... i.e. translate_names will end when the first * module returns OK. So for example, if the request is something like: * * GET http://othervhost/cgi-bin/printenv HTTP/1.0 * * mod_alias will notice the /cgi-bin part and ScriptAlias it and * short-circuit the proxy... just because of the ordering in the * configuration file.
*/ staticint proxy_detect(request_rec *r)
{ void *sconf = r->server->module_config;
proxy_server_conf *conf =
(proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
/* Ick... msvc (perhaps others) promotes ternary short results to int */
if (conf->req && r->parsed_uri.scheme) { /* but it might be something vhosted */ if (!r->parsed_uri.hostname
|| ap_cstr_casecmp(r->parsed_uri.scheme, ap_http_scheme(r)) != 0
|| !ap_matches_request_vhost(r, r->parsed_uri.hostname,
(apr_port_t)(r->parsed_uri.port_str
? r->parsed_uri.port
: ap_default_port(r)))) {
r->proxyreq = PROXYREQ_PROXY;
r->uri = r->unparsed_uri;
r->filename = apr_pstrcat(r->pool, "proxy:", r->uri, NULL);
r->handler = "proxy-server";
}
} /* We need special treatment for CONNECT proxying: it has no scheme part */ elseif (conf->req && r->method_number == M_CONNECT
&& r->parsed_uri.hostname
&& r->parsed_uri.port_str) {
r->proxyreq = PROXYREQ_PROXY;
r->uri = r->unparsed_uri;
r->filename = apr_pstrcat(r->pool, "proxy:", r->uri, NULL);
r->handler = "proxy-server";
} return DECLINED;
}
if (dconf && (dconf->interpolate_env == 1) && (ent->flags & PROXYPASS_INTERPOLATE)) {
fake = ap_proxy_interpolate(r, ent->fake);
real = ap_proxy_interpolate(r, ent->real);
} else {
fake = ent->fake;
real = ent->real;
}
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, APLOGNO(03461) "attempting to match URI path '%s' against %s '%s' for " "proxying", r->uri, (ent->regex ? "pattern" : "prefix"),
fake);
if (ent->regex) { if (!ap_regexec(ent->regex, r->uri, AP_MAX_REG_MATCH, regm, 0)) { if ((real[0] == '!') && (real[1] == '\0')) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(03462) "proxying is explicitly disabled for URI path " "'%s'; declining", r->uri); return DECLINED;
} /* test that we haven't reduced the URI */ if (nocanon && ap_regexec(ent->regex, r->unparsed_uri,
AP_MAX_REG_MATCH, reg1, 0)) {
mismatch = 1;
use_uri = r->uri;
}
found = ap_pregsub(r->pool, real, use_uri, AP_MAX_REG_MATCH,
(use_uri == r->uri) ? regm : reg1); if (!found) {
ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(01135) "Substitution in regular expression failed. " "Replacement too long?"); return HTTP_INTERNAL_SERVER_ERROR;
}
/* Note: The strcmp() below catches cases where there * was no regex substitution. This is so cases like: * * ProxyPassMatch \.gif balancer://foo * * will work "as expected". The upshot is that the 2 * directives below act the exact same way (ie: $1 is implied): * * ProxyPassMatch ^(/.*\.gif)$ balancer://foo * ProxyPassMatch ^(/.*\.gif)$ balancer://foo$1 * * which may be confusing.
*/ if (strcmp(found, real) != 0) {
found = apr_pstrcat(r->pool, "proxy:", found, NULL);
} else {
found = apr_pstrcat(r->pool, "proxy:", real, use_uri, NULL);
}
}
} else { if ((ent->flags & PROXYPASS_MAP_SERVLET) == PROXYPASS_MAP_SERVLET) {
servlet_uri = r->uri;
len = alias_match_servlet(r->pool, &servlet_uri, fake);
nocanon = 0; /* ignored since servlet's normalization applies */
} else {
len = alias_match(r->uri, fake);
}
if (len != 0) { if ((real[0] == '!') && (real[1] == '\0')) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(03463) "proxying is explicitly disabled for URI path " "'%s'; declining", r->uri); return DECLINED;
} if (nocanon && len != alias_match(r->unparsed_uri, fake)) {
mismatch = 1;
use_uri = r->uri;
}
found = apr_pstrcat(r->pool, "proxy:", real, use_uri + len, NULL);
}
} if (mismatch) { /* We made a reducing transformation, so we can't safely use * unparsed_uri. Safe fallback is to ignore nocanon.
*/
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01136) "Unescaped URL path matched ProxyPass; ignoring unsafe nocanon");
}
if (found) { unsignedint encoded = ent->flags & PROXYPASS_MAP_ENCODED;
/* A proxy module is assigned this URL, check whether it's interested * in the request itself (e.g. proxy_wstunnel cares about Upgrade * requests only, and could hand over to proxy_http otherwise).
*/ int rc = proxy_run_check_trans(r, found + 6); if (rc != OK && rc != DECLINED) { return HTTP_CONTINUE;
}
r->filename = found;
r->handler = "proxy-server";
r->proxyreq = PROXYREQ_REVERSE; if (nocanon && !mismatch) { /* mod_proxy_http needs to be told. Different module. */
apr_table_setn(r->notes, "proxy-nocanon", "1");
} if (ent->flags & PROXYPASS_NOQUERY) {
apr_table_setn(r->notes, "proxy-noquery", "1");
} if (encoded) {
apr_table_setn(r->notes, "proxy-noencode", "1");
}
if (servlet_uri) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(10248) "Servlet path '%s' (%s) matches proxy handler '%s'",
r->uri, servlet_uri, found); /* Apply servlet normalization to r->uri so that <Location> or any * directory context match does not have to handle path parameters. * We change r->uri in-place so that r->parsed_uri.path is updated * too. Since normalized servlet_uri is necessarily shorter than * the original r->uri, strcpy() is fine.
*/
AP_DEBUG_ASSERT(strlen(r->uri) >= strlen(servlet_uri));
strcpy(r->uri, servlet_uri);
} else {
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(03464) "URI path '%s' matches proxy handler '%s'", r->uri,
found);
} return (encoded) ? DONE : OK;
}
return HTTP_CONTINUE;
}
staticint proxy_trans(request_rec *r, int pre_trans)
{ int i, enc; struct proxy_alias *ent;
proxy_dir_conf *dconf;
proxy_server_conf *conf;
if (r->proxyreq) { /* someone has already set up the proxy, it was possibly ourselves * in proxy_detect (DONE will prevent further decoding of r->uri, * only if proxyreq is set before pre_trans already).
*/ return pre_trans ? DONE : OK;
}
/* In early pre_trans hook, r->uri was not manipulated yet so we are * compliant with RFC1945 at this point. Otherwise, it probably isn't * an issue because this is a hybrid proxy/origin server.
*/
/* Always and only do PROXY_MAP_ENCODED mapping in pre_trans, when * r->uri is still encoded, or we might consider for instance that * a decoded sub-delim is now a delimiter (e.g. "%3B" => ';' for * path parameters), which it's not.
*/ if ((pre_trans && !conf->map_encoded_one)
|| (!pre_trans && conf->map_encoded_all)) { /* Fast path, nothing at this stage */ return DECLINED;
}
if (apr_table_get(r->subprocess_env, "no-proxy")) { return DECLINED;
}
/* short way - this location is reverse proxied? */ if (dconf->alias) {
enc = (dconf->alias->flags & PROXYPASS_MAP_ENCODED) != 0; if (!(pre_trans ^ enc)) { int rv = ap_proxy_trans_match(r, dconf->alias, dconf); if (rv != HTTP_CONTINUE) { return rv;
}
}
}
/* long way - walk the list of aliases, find a match */ for (i = 0; i < conf->aliases->nelts; i++) {
ent = &((struct proxy_alias *)conf->aliases->elts)[i];
enc = (ent->flags & PROXYPASS_MAP_ENCODED) != 0; if (!(pre_trans ^ enc)) { int rv = ap_proxy_trans_match(r, ent, dconf); if (rv != HTTP_CONTINUE) { return rv;
}
}
}
/* Don't let the core or mod_http map_to_storage hooks handle this, * We don't need directory/file_walk, and we want to TRACE on our own.
*/ if ((access_status = proxy_walk(r))) {
ap_die(access_status, r); return access_status;
}
return OK;
}
/* -------------------------------------------------------------- */ /* Fixup the filename */
/* XXX: Shouldn't we try this before we run the proxy_walk? */
return ap_proxy_canon_url(r);
}
/* Send a redirection if the request contains a hostname which is not */ /* fully qualified, i.e. doesn't have a domain name appended. Some proxy */ /* servers like Netscape's allow this and access hosts from the local */ /* domain in this case. I think it is better to redirect to a FQDN, since */ /* these will later be found in the bookmarks files. */ /* The "ProxyDomain" directive determines what domain will be appended */ staticint proxy_needsdomain(request_rec *r, constchar *url, constchar *domain)
{ char *nuri; constchar *ref;
/* We only want to worry about GETs */ if (!r->proxyreq || r->method_number != M_GET || !r->parsed_uri.hostname) return DECLINED;
/* If host does contain a dot already, or it is "localhost", decline */ if (strchr(r->parsed_uri.hostname, '.') != NULL /* has domain, or IPv4 literal */
|| strchr(r->parsed_uri.hostname, ':') != NULL /* IPv6 literal */
|| ap_cstr_casecmp(r->parsed_uri.hostname, "localhost") == 0) return DECLINED; /* host name has a dot already */
ref = apr_table_get(r->headers_in, "Referer");
/* Reassemble the request, but insert the domain after the host name */ /* Note that the domain name always starts with a dot */
r->parsed_uri.hostname = apr_pstrcat(r->pool, r->parsed_uri.hostname,
domain, NULL);
nuri = apr_uri_unparse(r->pool,
&r->parsed_uri,
APR_URI_UNP_REVEALPASSWORD);
apr_table_setn(r->headers_out, "Location", nuri);
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01138) "Domain missing: %s sent to %s%s%s", r->uri,
apr_uri_unparse(r->pool, &r->parsed_uri,
APR_URI_UNP_OMITUSERINFO),
ref ? " from " : "", ref ? ref : "");
/* is this for us? */ if (!r->filename) { return DECLINED;
}
/* We may have forced the proxy handler via config or .htaccess */ if (!r->proxyreq && r->handler && strncmp(r->handler, "proxy:", 6) == 0) { char *old_filename = r->filename;
if (coreconf->trace_enable == AP_TRACE_DISABLE)
{ /* Allow "error-notes" string to be printed by ap_send_error_response() * Note; this goes nowhere, canned error response need an overhaul.
*/
apr_table_setn(r->notes, "error-notes", "TRACE forbidden by server configuration");
apr_table_setn(r->notes, "verbose-error-to", "*");
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01139) "TRACE forbidden by server configuration"); return HTTP_METHOD_NOT_ALLOWED;
}
/* Can't test ap_should_client_block, we aren't ready to send * the client a 100 Continue response till the connection has * been established
*/ if (coreconf->trace_enable != AP_TRACE_EXTENDED
&& (r->read_length || r->read_chunked || r->remaining))
{ /* Allow "error-notes" string to be printed by ap_send_error_response() * Note; this goes nowhere, canned error response need an overhaul.
*/
apr_table_setn(r->notes, "error-notes", "TRACE with request body is not allowed");
apr_table_setn(r->notes, "verbose-error-to", "*");
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01140) "TRACE with request body is not allowed"); return HTTP_REQUEST_ENTITY_TOO_LARGE;
}
}
/* If the host doesn't have a domain name, add one and redirect. */ if (conf->domain != NULL) {
rc = proxy_needsdomain(r, uri, conf->domain); if (ap_is_HTTP_REDIRECT(rc)) return HTTP_MOVED_PERMANENTLY;
}
/* Check URI's destination host against NoProxy hosts */ /* Bypass ProxyRemote server lookup if configured as NoProxy */ for (direct_connect = i = 0; i < conf->dirconn->nelts &&
!direct_connect; i++) {
direct_connect = list[i].matcher(&list[i], r);
} #if DEBUGGING
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
(direct_connect) ? APLOGNO(03231) "NoProxy for %s" : APLOGNO(03232) "UseProxy for %s",
r->uri); #endif
do { char *url = uri; /* Try to obtain the most suitable worker */
access_status = ap_proxy_pre_request(&worker, &balancer, r, conf, &url); if (access_status != OK) { /* * Only return if access_status is not HTTP_SERVICE_UNAVAILABLE * This gives other modules the chance to hook into the * request_status hook and decide what to do in this situation.
*/ if (access_status != HTTP_SERVICE_UNAVAILABLE) return access_status; /* * Ensure that balancer is NULL if worker is NULL to prevent * potential problems in the post_request hook.
*/ if (!worker)
balancer = NULL; goto cleanup;
}
/* Initialise worker if needed, note the shared area must be initialized by the balancer logic */ if (balancer) {
ap_proxy_initialize_worker(worker, r->server, conf->pool);
}
if (balancer && balancer->s->max_attempts_set && !max_attempts)
max_attempts = balancer->s->max_attempts; /* firstly, try a proxy, unless a NoProxy directive is active */ if (!direct_connect) { for (i = 0; i < proxies->nelts; i++) {
p2 = ap_strchr_c(ents[i].scheme, ':'); /* is it a partial URL? */ if (strcmp(ents[i].scheme, "*") == 0 ||
(ents[i].use_regex &&
ap_regexec(ents[i].regexp, url, 0, NULL, 0) == 0) ||
(p2 == NULL && ap_cstr_casecmp(scheme, ents[i].scheme) == 0) ||
(p2 != NULL &&
ap_cstr_casecmpn(url, ents[i].scheme,
strlen(ents[i].scheme)) == 0)) {
/* handle the scheme */
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01142) "Trying to run scheme_handler against proxy");
if (ents[i].creds) apr_table_unset(r->notes, "proxy-basic-creds");
/* Did the scheme handler process the request? */ if (access_status != DECLINED) { constchar *cl_a;
apr_off_t cl;
/* * An fatal error or success, so no point in * retrying with a direct connection.
*/ if (access_status != HTTP_BAD_GATEWAY) { goto cleanup;
}
cl_a = apr_table_get(r->headers_in, "Content-Length"); if (cl_a && (!ap_parse_strict_length(&cl, cl_a)
|| cl > 0)) { /* * The request body is of length > 0. We cannot * retry with a direct connection since we already * sent (parts of) the request body to the proxy * and do not have any longer.
*/ goto cleanup;
} /* * Transfer-Encoding was set as input header, so we had * a request body. We cannot retry with a direct * connection for the same reason as above.
*/ if (apr_table_get(r->headers_in, "Transfer-Encoding")) { goto cleanup;
}
}
}
}
}
/* otherwise, try it direct */ /* N.B. what if we're behind a firewall, where we must use a proxy or * give up??
*/
/* handle the scheme */
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01143) "Running scheme %s handler (attempt %d)",
scheme, attempts);
AP_PROXY_RUN(r, worker, conf, url, attempts);
access_status = proxy_run_scheme_handler(r, worker, conf,
url, NULL, 0); if (access_status == OK
|| apr_table_get(r->notes, "proxy-error-override")) break; elseif (access_status == HTTP_INTERNAL_SERVER_ERROR) { /* Unrecoverable server error. * We can not failover to another worker. * Mark the worker as unusable if member of load balancer
*/ if (balancer
&& !(worker->s->status & PROXY_WORKER_IGNORE_ERRORS)) {
worker->s->status |= PROXY_WORKER_IN_ERROR;
worker->s->error_time = apr_time_now();
} break;
} elseif (access_status == HTTP_SERVICE_UNAVAILABLE) { /* Recoverable server error. * We can failover to another worker * Mark the worker as unusable if member of load balancer
*/ if (balancer
&& !(worker->s->status & PROXY_WORKER_IGNORE_ERRORS)) {
worker->s->status |= PROXY_WORKER_IN_ERROR;
worker->s->error_time = apr_time_now();
}
} else { /* Unrecoverable error. * Return the origin status code to the client.
*/ break;
} /* Try again if the worker is unusable and the service is * unavailable.
*/
} while (!PROXY_WORKER_IS_USABLE(worker) &&
max_attempts > attempts++);
if (DECLINED == access_status) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01144) "No protocol handler was valid for the URL %s " "(scheme '%s'). " "If you are using a DSO version of mod_proxy, make sure " "the proxy submodules are included in the configuration " "using LoadModule.", r->uri, scheme);
access_status = HTTP_INTERNAL_SERVER_ERROR; goto cleanup;
}
cleanup: /* * Save current r->status and set it to the value of access_status which * might be different (e.g. r->status could be HTTP_OK if e.g. we override * the error page on the proxy or if the error was not generated by the * backend itself but by the proxy e.g. a bad gateway) in order to give * ap_proxy_post_request a chance to act correctly on the status code. * But only do the above if access_status is not OK and not DONE, because * in this case r->status might contain the true status and overwriting * it with OK or DONE would be wrong.
*/ if ((access_status != OK) && (access_status != DONE)) {
saved_status = r->status;
r->status = access_status;
ap_proxy_post_request(worker, balancer, r, conf); /* * Only restore r->status if it has not been changed by * ap_proxy_post_request as we assume that this change was intentional.
*/ if (r->status == access_status) {
r->status = saved_status;
}
} else {
ap_proxy_post_request(worker, balancer, r, conf);
}
/* Check if the balancer is defined in both override and base configs: * a) If it is, Create copy of base balancer and change the configuration * which can be changed by ProxyPass. * b) Otherwise, copy the balancer to tocopy array and merge it later.
*/
b1 = (proxy_balancer *) base->elts; for (y = 0; y < base->nelts; y++) {
b2 = (proxy_balancer *) overrides->elts; for (x = 0, found = 0; x < overrides->nelts; x++) { if (b1->hash.def == b2->hash.def && b1->hash.fnv == b2->hash.fnv) {
tmp = *b2;
*b2 = *b1;
b2->s = tmp.s;
/* For shared memory entries, b2->s belongs to override * balancer, so if some entry is not set there, we have to
* update it according to the base balancer. */ if (*b2->s->sticky == 0 && *b1->s->sticky) {
PROXY_STRNCPY(b2->s->sticky_path, b1->s->sticky_path);
PROXY_STRNCPY(b2->s->sticky, b1->s->sticky);
} if (!b2->s->sticky_separator_set
&& b1->s->sticky_separator_set) {
b2->s->sticky_separator_set = b1->s->sticky_separator_set;
b2->s->sticky_separator = b1->s->sticky_separator;
} if (!b2->s->timeout && b1->s->timeout) {
b2->s->timeout = b1->s->timeout;
} if (!b2->s->max_attempts_set && b1->s->max_attempts_set) {
b2->s->max_attempts_set = b1->s->max_attempts_set;
b2->s->max_attempts = b1->s->max_attempts;
} if (!b2->s->nonce_set && b1->s->nonce_set) {
b2->s->nonce_set = b1->s->nonce_set;
PROXY_STRNCPY(b2->s->nonce, b1->s->nonce);
} if (!b2->s->sticky_force_set && b1->s->sticky_force_set) {
b2->s->sticky_force_set = b1->s->sticky_force_set;
b2->s->sticky_force = b1->s->sticky_force;
} if (!b2->s->scolonsep_set && b1->s->scolonsep_set) {
b2->s->scolonsep_set = b1->s->scolonsep_set;
b2->s->scolonsep = b1->s->scolonsep;
} if (!b2->s->forcerecovery_set && b1->s->forcerecovery_set) {
b2->s->forcerecovery_set = b1->s->forcerecovery_set;
b2->s->forcerecovery = b1->s->forcerecovery;
}
/* For non-shared memory entries, b2 is copy of b1, so we have
* to use tmp copy of b1 to detect changes done in override. */ if (tmp.lbmethod_set) {
b2->lbmethod_set = tmp.lbmethod_set;
b2->lbmethod = tmp.lbmethod;
} if (tmp.growth_set) {
b2->growth_set = tmp.growth_set;
b2->growth = tmp.growth;
} if (tmp.failontimeout_set) {
b2->failontimeout_set = tmp.failontimeout_set;
b2->failontimeout = tmp.failontimeout;
} if (!apr_is_empty_array(tmp.errstatuses)) {
apr_array_cat(tmp.errstatuses, b2->errstatuses);
b2->errstatuses = tmp.errstatuses;
}
found = 1; break;
}
b2++;
} if (!found) {
*(proxy_balancer *)apr_array_push(tocopy) = *b1;
}
b1++;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.