summaryrefslogtreecommitdiffstats
path: root/modules/proxy
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-25 04:41:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-25 04:41:27 +0000
commitc54018b07a9085c0a3aedbc2bd01a85a3b3e20cf (patch)
treef6e1d6fcf9f6db3794c418b2f89ecf9e08ff41c8 /modules/proxy
parentAdding debian version 2.4.38-3+deb10u10. (diff)
downloadapache2-c54018b07a9085c0a3aedbc2bd01a85a3b3e20cf.tar.xz
apache2-c54018b07a9085c0a3aedbc2bd01a85a3b3e20cf.zip
Merging upstream version 2.4.59.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--modules/proxy/ajp.h4
-rw-r--r--modules/proxy/ajp_header.c48
-rw-r--r--modules/proxy/balancers/mod_lbmethod_heartbeat.c5
-rw-r--r--modules/proxy/mod_proxy.c704
-rw-r--r--modules/proxy/mod_proxy.h296
-rw-r--r--modules/proxy/mod_proxy_ajp.c135
-rw-r--r--modules/proxy/mod_proxy_balancer.c456
-rw-r--r--modules/proxy/mod_proxy_connect.c168
-rw-r--r--modules/proxy/mod_proxy_express.c29
-rw-r--r--modules/proxy/mod_proxy_fcgi.c196
-rw-r--r--modules/proxy/mod_proxy_fdpass.c4
-rw-r--r--modules/proxy/mod_proxy_ftp.c171
-rw-r--r--modules/proxy/mod_proxy_hcheck.c348
-rw-r--r--modules/proxy/mod_proxy_http.c1812
-rw-r--r--modules/proxy/mod_proxy_scgi.c20
-rw-r--r--modules/proxy/mod_proxy_uwsgi.c148
-rw-r--r--modules/proxy/mod_proxy_wstunnel.c266
-rw-r--r--modules/proxy/proxy_util.c2701
-rw-r--r--modules/proxy/proxy_util.h6
19 files changed, 5257 insertions, 2260 deletions
diff --git a/modules/proxy/ajp.h b/modules/proxy/ajp.h
index c119a7e..a950ee9 100644
--- a/modules/proxy/ajp.h
+++ b/modules/proxy/ajp.h
@@ -414,11 +414,13 @@ apr_status_t ajp_ilink_receive(apr_socket_t *sock, ajp_msg_t *msg);
* @param r current request
* @param buffsize max size of the AJP packet.
* @param uri requested uri
+ * @param secret authentication secret
* @return APR_SUCCESS or error
*/
apr_status_t ajp_send_header(apr_socket_t *sock, request_rec *r,
apr_size_t buffsize,
- apr_uri_t *uri);
+ apr_uri_t *uri,
+ const char *secret);
/**
* Read the ajp message and return the type of the message.
diff --git a/modules/proxy/ajp_header.c b/modules/proxy/ajp_header.c
index 67353a7..0266a7d 100644
--- a/modules/proxy/ajp_header.c
+++ b/modules/proxy/ajp_header.c
@@ -17,6 +17,8 @@
#include "ajp_header.h"
#include "ajp.h"
+#include "util_script.h"
+
APLOG_USE_MODULE(proxy_ajp);
static const char *response_trans_headers[] = {
@@ -59,6 +61,7 @@ static int sc_for_req_header(const char *header_name)
if (len < 4 || len > 15)
return UNKNOWN_METHOD;
+ memset(header, 0, sizeof header);
while (*p)
header[i++] = apr_toupper(*p++);
header[i] = '\0';
@@ -213,7 +216,8 @@ AJPV13_REQUEST/AJPV14_REQUEST=
static apr_status_t ajp_marshal_into_msgb(ajp_msg_t *msg,
request_rec *r,
- apr_uri_t *uri)
+ apr_uri_t *uri,
+ const char *secret)
{
int method;
apr_uint32_t i, num_headers = 0;
@@ -293,17 +297,15 @@ static apr_status_t ajp_marshal_into_msgb(ajp_msg_t *msg,
i, elts[i].key, elts[i].val);
}
-/* XXXX need to figure out how to do this
- if (s->secret) {
+ if (secret) {
if (ajp_msg_append_uint8(msg, SC_A_SECRET) ||
- ajp_msg_append_string(msg, s->secret)) {
+ ajp_msg_append_string(msg, secret)) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(03228)
- "Error ajp_marshal_into_msgb - "
+ "ajp_marshal_into_msgb: "
"Error appending secret");
return APR_EGENERAL;
}
}
- */
if (r->user) {
if (ajp_msg_append_uint8(msg, SC_A_REMOTE_USER) ||
@@ -584,8 +586,15 @@ static apr_status_t ajp_unmarshal_response(ajp_msg_t *msg,
r->headers_out = save_table;
}
else {
- r->headers_out = NULL;
+ /*
+ * Reset headers, but not to NULL because things below the chain expect
+ * this to be non NULL e.g. the ap_content_length_filter.
+ */
+ r->headers_out = apr_table_make(r->pool, 1);
num_headers = 0;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10405)
+ "ajp_unmarshal_response: Bad number of headers");
+ return rc;
}
ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
@@ -633,15 +642,15 @@ static apr_status_t ajp_unmarshal_response(ajp_msg_t *msg,
}
/* Set-Cookie need additional processing */
- if (!strcasecmp(stringname, "Set-Cookie")) {
+ if (!ap_cstr_casecmp(stringname, "Set-Cookie")) {
value = ap_proxy_cookie_reverse_map(r, dconf, value);
}
/* Location, Content-Location, URI and Destination need additional
* processing */
- else if (!strcasecmp(stringname, "Location")
- || !strcasecmp(stringname, "Content-Location")
- || !strcasecmp(stringname, "URI")
- || !strcasecmp(stringname, "Destination"))
+ else if (!ap_cstr_casecmp(stringname, "Location")
+ || !ap_cstr_casecmp(stringname, "Content-Location")
+ || !ap_cstr_casecmp(stringname, "URI")
+ || !ap_cstr_casecmp(stringname, "Destination"))
{
value = ap_proxy_location_reverse_map(r, dconf, value);
}
@@ -654,7 +663,7 @@ static apr_status_t ajp_unmarshal_response(ajp_msg_t *msg,
apr_table_add(r->headers_out, stringname, value);
/* Content-type needs an additional handling */
- if (strcasecmp(stringname, "Content-Type") == 0) {
+ if (ap_cstr_casecmp(stringname, "Content-Type") == 0) {
/* add corresponding filter */
ap_set_content_type(r, apr_pstrdup(r->pool, value));
ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r,
@@ -662,6 +671,14 @@ static apr_status_t ajp_unmarshal_response(ajp_msg_t *msg,
}
}
+ /* AJP has its own body framing mechanism which we don't
+ * match against any provided Content-Length, so let the
+ * core determine C-L vs T-E based on what's actually sent.
+ */
+ if (!apr_table_get(r->subprocess_env, AP_TRUST_CGILIKE_CL_ENVVAR))
+ apr_table_unset(r->headers_out, "Content-Length");
+ apr_table_unset(r->headers_out, "Transfer-Encoding");
+
return APR_SUCCESS;
}
@@ -671,7 +688,8 @@ static apr_status_t ajp_unmarshal_response(ajp_msg_t *msg,
apr_status_t ajp_send_header(apr_socket_t *sock,
request_rec *r,
apr_size_t buffsize,
- apr_uri_t *uri)
+ apr_uri_t *uri,
+ const char *secret)
{
ajp_msg_t *msg;
apr_status_t rc;
@@ -683,7 +701,7 @@ apr_status_t ajp_send_header(apr_socket_t *sock,
return rc;
}
- rc = ajp_marshal_into_msgb(msg, r, uri);
+ rc = ajp_marshal_into_msgb(msg, r, uri, secret);
if (rc != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00988)
"ajp_send_header: ajp_marshal_into_msgb failed");
diff --git a/modules/proxy/balancers/mod_lbmethod_heartbeat.c b/modules/proxy/balancers/mod_lbmethod_heartbeat.c
index 7aeaf71..0534e5b 100644
--- a/modules/proxy/balancers/mod_lbmethod_heartbeat.c
+++ b/modules/proxy/balancers/mod_lbmethod_heartbeat.c
@@ -115,7 +115,6 @@ static apr_status_t readfile_heartbeats(const char *path, apr_hash_t *servers,
{
char *t;
- int lineno = 0;
apr_bucket_alloc_t *ba = apr_bucket_alloc_create(pool);
apr_bucket_brigade *bb = apr_brigade_create(pool, ba);
apr_bucket_brigade *tmpbb = apr_brigade_create(pool, ba);
@@ -137,7 +136,6 @@ static apr_status_t readfile_heartbeats(const char *path, apr_hash_t *servers,
rv = apr_brigade_split_line(tmpbb, bb,
APR_BLOCK_READ, sizeof(buf));
- lineno++;
if (rv) {
return rv;
@@ -281,6 +279,7 @@ static proxy_worker *find_best_hb(proxy_balancer *balancer,
}
apr_pool_create(&tpool, r->pool);
+ apr_pool_tag(tpool, "lb_heartbeat_tpool");
servers = apr_hash_make(tpool);
@@ -302,7 +301,7 @@ static proxy_worker *find_best_hb(proxy_balancer *balancer,
if (!server) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(01214)
- "lb_heartbeat: No server for worker %s", (*worker)->s->name);
+ "lb_heartbeat: No server for worker %s", (*worker)->s->name_ex);
continue;
}
diff --git a/modules/proxy/mod_proxy.c b/modules/proxy/mod_proxy.c
index 69a35ce..c9cef7c 100644
--- a/modules/proxy/mod_proxy.c
+++ b/modules/proxy/mod_proxy.c
@@ -17,6 +17,7 @@
#include "mod_proxy.h"
#include "mod_core.h"
#include "apr_optional.h"
+#include "apr_strings.h"
#include "scoreboard.h"
#include "mod_status.h"
#include "proxy_util.h"
@@ -29,10 +30,6 @@ APR_DECLARE_OPTIONAL_FN(int, ssl_engine_disable, (conn_rec *));
APR_DECLARE_OPTIONAL_FN(int, ssl_engine_set, (conn_rec *,
ap_conf_vector_t *,
int proxy, int enable));
-APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
-APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup,
- (apr_pool_t *, server_rec *,
- conn_rec *, request_rec *, char *));
#endif
#ifndef MAX
@@ -55,6 +52,9 @@ proxy_hcmethods_t PROXY_DECLARE_DATA proxy_hcmethods[] = {
{GET, "GET", 1},
{CPING, "CPING", 0},
{PROVIDER, "PROVIDER", 0},
+ {OPTIONS11, "OPTIONS11", 1},
+ {HEAD11, "HEAD11", 1},
+ {GET11, "GET11", 1},
{EOT, NULL, 1}
};
@@ -149,7 +149,7 @@ static const char *set_worker_param(apr_pool_t *p,
return "Max must be a positive number";
worker->s->hmax = ival;
}
- /* XXX: More inteligent naming needed */
+ /* XXX: More intelligent naming needed */
else if (!strcasecmp(key, "smax")) {
/* Maximum number of connections to remote that
* will not be destroyed
@@ -224,6 +224,24 @@ static const char *set_worker_param(apr_pool_t *p,
return "EnableReuse must be On|Off";
worker->s->disablereuse_set = 1;
}
+ else if (!strcasecmp(key, "addressttl")) {
+ /* Address TTL in seconds
+ */
+ apr_interval_time_t ttl;
+ if (strcmp(val, "-1") == 0) {
+ worker->s->address_ttl = -1;
+ }
+ else if (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;
+ }
else if (!strcasecmp(key, "route")) {
/* Worker route.
*/
@@ -308,13 +326,14 @@ static const char *set_worker_param(apr_pool_t *p,
worker->s->conn_timeout_set = 1;
}
else if (!strcasecmp(key, "flusher")) {
- if (strlen(val) >= sizeof(worker->s->flusher))
- apr_psprintf(p, "flusher name length must be < %d characters",
- (int)sizeof(worker->s->flusher));
- PROXY_STRNCPY(worker->s->flusher, val);
+ 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));
+ }
}
else if (!strcasecmp(key, "upgrade")) {
- if (PROXY_STRNCPY(worker->s->upgrade, val) != APR_SUCCESS) {
+ 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));
}
@@ -327,6 +346,12 @@ static const char *set_worker_param(apr_pool_t *p,
worker->s->response_field_size = (s ? s : HUGE_STRING_LEN);
worker->s->response_field_size_set = 1;
}
+ else if (!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);
@@ -557,6 +582,201 @@ static int alias_match(const char *uri, const char *alias_fakename)
return urip - uri;
}
+/*
+ * Inspired by mod_jk's jk_servlet_normalize().
+ */
+static int alias_match_servlet(apr_pool_t *p,
+ const char **urip,
+ const char *alias)
+{
+ char *map;
+ const char *uri = *urip;
+ apr_array_header_t *stack;
+ int map_pos, uri_pos, alias_pos, first_pos;
+ int alias_depth = 0, depth;
+
+ /* Both uri and alias should start with '/' */
+ if (uri[0] != '/' || alias[0] != '/') {
+ return 0;
+ }
+
+ stack = apr_array_make(p, 5, sizeof(int));
+ map = apr_palloc(p, strlen(uri) + 1);
+ map[0] = '/';
+ map[1] = '\0';
+
+ map_pos = uri_pos = alias_pos = first_pos = 1;
+ while (uri[uri_pos] != '\0') {
+ /* Remove path parameters ;foo=bar/ from any path segment */
+ if (uri[uri_pos] == ';') {
+ do {
+ uri_pos++;
+ } while (uri[uri_pos] != '/' && uri[uri_pos] != '\0');
+ continue;
+ }
+
+ if (map[map_pos - 1] == '/') {
+ /* Collapse ///// sequences to / */
+ if (uri[uri_pos] == '/') {
+ do {
+ uri_pos++;
+ } while (uri[uri_pos] == '/');
+ continue;
+ }
+
+ if (uri[uri_pos] == '.') {
+ /* Remove /./ segments */
+ if (uri[uri_pos + 1] == '/'
+ || uri[uri_pos + 1] == ';'
+ || uri[uri_pos + 1] == '\0') {
+ uri_pos++;
+ if (uri[uri_pos] == '/') {
+ uri_pos++;
+ }
+ continue;
+ }
+
+ /* 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;
+ }
+ else if (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);
+ }
+ else if (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++;
+ }
+ }
+
+ if (uri[uri_pos] == '/') {
+ first_pos = uri_pos + 1;
+ }
+ map[map_pos++] = uri[uri_pos++];
+ map[map_pos] = '\0';
+ }
+
+ /* 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
@@ -578,11 +798,12 @@ static int proxy_detect(request_rec *r)
if (conf->req && r->parsed_uri.scheme) {
/* but it might be something vhosted */
- if (!(r->parsed_uri.hostname
- && !strcasecmp(r->parsed_uri.scheme, ap_http_scheme(r))
- && 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))))) {
+ 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);
@@ -667,6 +888,7 @@ PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r, struct proxy_alias *ent,
int mismatch = 0;
unsigned int nocanon = ent->flags & PROXYPASS_NOCANON;
const char *use_uri = nocanon ? r->unparsed_uri : r->uri;
+ const char *servlet_uri = NULL;
if (dconf && (dconf->interpolate_env == 1) && (ent->flags & PROXYPASS_INTERPOLATE)) {
fake = proxy_interpolate(r, ent->fake);
@@ -727,7 +949,14 @@ PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r, struct proxy_alias *ent,
}
}
else {
- len = alias_match(r->uri, fake);
+ 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')) {
@@ -736,7 +965,7 @@ PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r, struct proxy_alias *ent,
"'%s'; declining", r->uri);
return DECLINED;
}
- if (nocanon && len != alias_match(r->unparsed_uri, ent->fake)) {
+ if (nocanon && len != alias_match(r->unparsed_uri, fake)) {
mismatch = 1;
use_uri = r->uri;
}
@@ -752,6 +981,17 @@ PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r, struct proxy_alias *ent,
}
if (found) {
+ unsigned int 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;
@@ -762,29 +1002,67 @@ PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r, struct proxy_alias *ent,
if (ent->flags & PROXYPASS_NOQUERY) {
apr_table_setn(r->notes, "proxy-noquery", "1");
}
+ if (encoded) {
+ apr_table_setn(r->notes, "proxy-noencode", "1");
+ }
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(03464)
- "URI path '%s' matches proxy handler '%s'", r->uri,
- found);
-
- return OK;
+ 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 DONE;
+ return HTTP_CONTINUE;
}
-static int proxy_trans(request_rec *r)
+static int proxy_trans(request_rec *r, int pre_trans)
{
- int i;
+ 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
+ * in proxy_detect (DONE will prevent further decoding of r->uri,
+ * only if proxyreq is set before pre_trans already).
*/
- return OK;
+ 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.
+ */
+
+ dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
+ conf = (proxy_server_conf *) ap_get_module_config(r->server->module_config,
+ &proxy_module);
+
+ /* 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 ((r->unparsed_uri[0] == '*' && r->unparsed_uri[1] == '\0')
@@ -796,37 +1074,42 @@ static int proxy_trans(request_rec *r)
return DECLINED;
}
- /* XXX: since r->uri has been manipulated already we're not really
- * compliant with RFC1945 at this point. But this probably isn't
- * an issue because this is a hybrid proxy/origin server.
- */
-
- dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
-
/* short way - this location is reverse proxied? */
if (dconf->alias) {
- int rv = ap_proxy_trans_match(r, dconf->alias, dconf);
- if (DONE != rv) {
- return rv;
+ 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;
+ }
}
}
- conf = (proxy_server_conf *) ap_get_module_config(r->server->module_config,
- &proxy_module);
-
/* long way - walk the list of aliases, find a match */
- if (conf->aliases->nelts) {
- ent = (struct proxy_alias *) conf->aliases->elts;
- for (i = 0; i < conf->aliases->nelts; i++) {
- int rv = ap_proxy_trans_match(r, &ent[i], dconf);
- if (DONE != rv) {
+ 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;
}
}
}
+
return DECLINED;
}
+static int proxy_pre_translate_name(request_rec *r)
+{
+ return proxy_trans(r, 1);
+}
+
+static int proxy_translate_name(request_rec *r)
+{
+ return proxy_trans(r, 0);
+}
+
static int proxy_walk(request_rec *r)
{
proxy_server_conf *sconf = ap_get_module_config(r->server->module_config,
@@ -857,6 +1140,7 @@ static int proxy_walk(request_rec *r)
if (entry_proxy->refs && entry_proxy->refs->nelts) {
if (!rxpool) {
apr_pool_create(&rxpool, r->pool);
+ apr_pool_tag(rxpool, "proxy_rxpool");
}
nmatch = entry_proxy->refs->nelts;
pmatch = apr_palloc(rxpool, nmatch*sizeof(ap_regmatch_t));
@@ -979,7 +1263,7 @@ static int proxy_needsdomain(request_rec *r, const char *url, const char *domain
/* 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 */
- || strcasecmp(r->parsed_uri.hostname, "localhost") == 0)
+ || 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");
@@ -1049,9 +1333,10 @@ static int proxy_handler(request_rec *r)
char *end;
maxfwd = apr_strtoi64(str, &end, 10);
if (maxfwd < 0 || maxfwd == APR_INT64_MAX || *end) {
- return ap_proxyerror(r, HTTP_BAD_REQUEST,
- apr_psprintf(r->pool,
- "Max-Forwards value '%s' could not be parsed", str));
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(10188)
+ "Max-Forwards value '%s' could not be parsed", str);
+ return ap_proxyerror(r, HTTP_BAD_REQUEST,
+ "Max-Forwards request header could not be parsed");
}
else if (maxfwd == 0) {
switch (r->method_number) {
@@ -1185,23 +1470,31 @@ static int proxy_handler(request_rec *r)
if (strcmp(ents[i].scheme, "*") == 0 ||
(ents[i].use_regex &&
ap_regexec(ents[i].regexp, url, 0, NULL, 0) == 0) ||
- (p2 == NULL && strcasecmp(scheme, ents[i].scheme) == 0) ||
+ (p2 == NULL && ap_cstr_casecmp(scheme, ents[i].scheme) == 0) ||
(p2 != NULL &&
- strncasecmp(url, ents[i].scheme,
+ 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_set(r->notes, "proxy-basic-creds", ents[i].creds);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "Using proxy auth creds %s", ents[i].creds);
+ }
+
access_status = proxy_run_scheme_handler(r, worker,
conf, url,
ents[i].hostname,
ents[i].port);
+ if (ents[i].creds) apr_table_unset(r->notes, "proxy-basic-creds");
+
/* Did the scheme handler process the request? */
if (access_status != DECLINED) {
const char *cl_a;
- char *end;
apr_off_t cl;
/*
@@ -1211,18 +1504,17 @@ static int proxy_handler(request_rec *r)
if (access_status != HTTP_BAD_GATEWAY) {
goto cleanup;
}
+
cl_a = apr_table_get(r->headers_in, "Content-Length");
- if (cl_a) {
- apr_strtoff(&cl, cl_a, &end, 10);
+ 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.
*/
- if (cl > 0) {
- goto cleanup;
- }
+ goto cleanup;
}
/*
* Transfer-Encoding was set as input header, so we had
@@ -1347,6 +1639,8 @@ static void * create_proxy_config(apr_pool_t *p, server_rec *s)
ps->forward = NULL;
ps->reverse = NULL;
ps->domain = NULL;
+ ps->map_encoded_one = 0;
+ ps->map_encoded_all = 1;
ps->id = apr_psprintf(p, "p%x", 1); /* simply for storage size */
ps->viaopt = via_off; /* initially backward compatible with 1.3.1 */
ps->viaopt_set = 0; /* 0 means default */
@@ -1373,6 +1667,7 @@ static void * create_proxy_config(apr_pool_t *p, server_rec *s)
ps->source_address = NULL;
ps->source_address_set = 0;
apr_pool_create_ex(&ps->pool, p, NULL, NULL);
+ apr_pool_tag(ps->pool, "proxy_server_conf");
return ps;
}
@@ -1503,6 +1798,9 @@ static void * merge_proxy_config(apr_pool_t *p, void *basev, void *overridesv)
ps->forward = overrides->forward ? overrides->forward : base->forward;
ps->reverse = overrides->reverse ? overrides->reverse : base->reverse;
+ ps->map_encoded_one = overrides->map_encoded_one || base->map_encoded_one;
+ ps->map_encoded_all = overrides->map_encoded_all && base->map_encoded_all;
+
ps->domain = (overrides->domain == NULL) ? base->domain : overrides->domain;
ps->id = (overrides->id == NULL) ? base->id : overrides->id;
ps->viaopt = (overrides->viaopt_set == 0) ? base->viaopt : overrides->viaopt;
@@ -1560,6 +1858,7 @@ static void *create_proxy_dir_config(apr_pool_t *p, char *dummy)
new->raliases = apr_array_make(p, 10, sizeof(struct proxy_alias));
new->cookie_paths = apr_array_make(p, 10, sizeof(struct proxy_alias));
new->cookie_domains = apr_array_make(p, 10, sizeof(struct proxy_alias));
+ new->error_override_codes = apr_array_make(p, 10, sizeof(int));
new->preserve_host_set = 0;
new->preserve_host = 0;
new->interpolate_env = -1; /* unset */
@@ -1567,10 +1866,17 @@ static void *create_proxy_dir_config(apr_pool_t *p, char *dummy)
new->error_override_set = 0;
new->add_forwarded_headers = 1;
new->add_forwarded_headers_set = 0;
+ new->forward_100_continue = 1;
+ new->forward_100_continue_set = 0;
return (void *) new;
}
+static int int_order(const void *i1, const void *i2)
+{
+ return *(const int *)i1 - *(const int *)i2;
+}
+
static void *merge_proxy_dir_config(apr_pool_t *p, void *basev, void *addv)
{
proxy_dir_conf *new = (proxy_dir_conf *) apr_pcalloc(p, sizeof(proxy_dir_conf));
@@ -1588,6 +1894,17 @@ static void *merge_proxy_dir_config(apr_pool_t *p, void *basev, void *addv)
= apr_array_append(p, base->cookie_paths, add->cookie_paths);
new->cookie_domains
= apr_array_append(p, base->cookie_domains, add->cookie_domains);
+ new->error_override_codes
+ = apr_array_append(p, base->error_override_codes, add->error_override_codes);
+ /* Keep the array sorted for binary search (since "base" and "add" are
+ * already sorted, it's only needed only if both are merged).
+ */
+ if (base->error_override_codes->nelts
+ && add->error_override_codes->nelts) {
+ qsort(new->error_override_codes->elts,
+ new->error_override_codes->nelts,
+ sizeof(int), int_order);
+ }
new->interpolate_env = (add->interpolate_env == -1) ? base->interpolate_env
: add->interpolate_env;
new->preserve_host = (add->preserve_host_set == 0) ? base->preserve_host
@@ -1603,12 +1920,17 @@ static void *merge_proxy_dir_config(apr_pool_t *p, void *basev, void *addv)
: add->add_forwarded_headers;
new->add_forwarded_headers_set = add->add_forwarded_headers_set
|| base->add_forwarded_headers_set;
-
+ new->forward_100_continue =
+ (add->forward_100_continue_set == 0) ? base->forward_100_continue
+ : add->forward_100_continue;
+ new->forward_100_continue_set = add->forward_100_continue_set
+ || base->forward_100_continue_set;
+
return new;
}
-static const char *
- add_proxy(cmd_parms *cmd, void *dummy, const char *f1, const char *r1, int regex)
+static const char *add_proxy(cmd_parms *cmd, void *dummy, const char *f1,
+ const char *r1, const char *creds, int regex)
{
server_rec *s = cmd->server;
proxy_server_conf *conf =
@@ -1666,19 +1988,24 @@ static const char *
new->port = port;
new->regexp = reg;
new->use_regex = regex;
+ if (creds) {
+ new->creds = apr_pstrcat(cmd->pool, "Basic ",
+ ap_pbase64encode(cmd->pool, (char *)creds),
+ NULL);
+ }
return NULL;
}
-static const char *
- add_proxy_noregex(cmd_parms *cmd, void *dummy, const char *f1, const char *r1)
+static const char *add_proxy_noregex(cmd_parms *cmd, void *dummy, const char *f1,
+ const char *r1, const char *creds)
{
- return add_proxy(cmd, dummy, f1, r1, 0);
+ return add_proxy(cmd, dummy, f1, r1, creds, 0);
}
-static const char *
- add_proxy_regex(cmd_parms *cmd, void *dummy, const char *f1, const char *r1)
+static const char *add_proxy_regex(cmd_parms *cmd, void *dummy, const char *f1,
+ const char *r1, const char *creds)
{
- return add_proxy(cmd, dummy, f1, r1, 1);
+ return add_proxy(cmd, dummy, f1, r1, creds, 1);
}
PROXY_DECLARE(const char *) ap_proxy_de_socketfy(apr_pool_t *p, const char *url)
@@ -1688,8 +2015,8 @@ PROXY_DECLARE(const char *) ap_proxy_de_socketfy(apr_pool_t *p, const char *url)
* We could be passed a URL during the config stage that contains
* the UDS path... ignore it
*/
- if (!strncasecmp(url, "unix:", 5) &&
- ((ptr = ap_strchr_c(url, '|')) != NULL)) {
+ if (!ap_cstr_casecmpn(url, "unix:", 5) &&
+ ((ptr = ap_strchr_c(url + 5, '|')) != NULL)) {
/* move past the 'unix:...|' UDS path info */
const char *ret, *c;
@@ -1721,12 +2048,14 @@ static const char *
struct proxy_alias *new;
char *f = cmd->path;
char *r = NULL;
+ const char *real;
char *word;
apr_table_t *params = apr_table_make(cmd->pool, 5);
const apr_array_header_t *arr;
const apr_table_entry_t *elts;
int i;
- int use_regex = is_regex;
+ unsigned int worker_type = (is_regex) ? AP_PROXY_WORKER_IS_MATCH
+ : AP_PROXY_WORKER_IS_PREFIX;
unsigned int flags = 0;
const char *err;
@@ -1742,7 +2071,7 @@ static const char *
if (is_regex) {
return "ProxyPassMatch invalid syntax ('~' usage).";
}
- use_regex = 1;
+ worker_type = AP_PROXY_WORKER_IS_MATCH;
continue;
}
f = word;
@@ -1777,15 +2106,39 @@ static const char *
"in the form 'key=value'.";
}
}
- else
+ else {
*val++ = '\0';
- apr_table_setn(params, word, val);
+ }
+ if (!strcasecmp(word, "mapping")) {
+ if (!strcasecmp(val, "encoded")) {
+ flags |= PROXYPASS_MAP_ENCODED;
+ }
+ else if (!strcasecmp(val, "servlet")) {
+ flags |= PROXYPASS_MAP_SERVLET;
+ }
+ else {
+ return "unknown mapping";
+ }
+ }
+ else {
+ apr_table_setn(params, word, val);
+ }
}
- };
+ }
+ if (flags & PROXYPASS_MAP_ENCODED) {
+ conf->map_encoded_one = 1;
+ }
+ else {
+ conf->map_encoded_all = 0;
+ }
if (r == NULL) {
return "ProxyPass|ProxyPassMatch needs a path when not defined in a location";
}
+ if (!(real = ap_proxy_de_socketfy(cmd->temp_pool, r))) {
+ return "ProxyPass|ProxyPassMatch uses an invalid \"unix:\" URL";
+ }
+
/* if per directory, save away the single alias */
if (cmd->path) {
@@ -1793,7 +2146,7 @@ static const char *
dconf->alias_set = 1;
new = dconf->alias;
if (apr_fnmatch_test(f)) {
- use_regex = 1;
+ worker_type = AP_PROXY_WORKER_IS_MATCH;
}
}
/* if per server, add to the alias array */
@@ -1802,9 +2155,9 @@ static const char *
}
new->fake = apr_pstrdup(cmd->pool, f);
- new->real = apr_pstrdup(cmd->pool, ap_proxy_de_socketfy(cmd->pool, r));
+ new->real = apr_pstrdup(cmd->pool, real);
new->flags = flags;
- if (use_regex) {
+ if (worker_type & AP_PROXY_WORKER_IS_MATCH) {
new->regex = ap_pregcomp(cmd->pool, f, AP_REG_EXTENDED);
if (new->regex == NULL)
return "Regular expression could not be compiled.";
@@ -1828,7 +2181,7 @@ static const char *
* cannot be parsed anyway with apr_uri_parse later on in
* ap_proxy_define_balancer / ap_proxy_update_balancer
*/
- if (use_regex) {
+ if (worker_type & AP_PROXY_WORKER_IS_MATCH) {
fake_copy = NULL;
}
else {
@@ -1851,15 +2204,20 @@ static const char *
new->balancer = balancer;
}
else {
- proxy_worker *worker = ap_proxy_get_worker(cmd->temp_pool, NULL, conf, ap_proxy_de_socketfy(cmd->pool, r));
int reuse = 0;
+ proxy_worker *worker = ap_proxy_get_worker_ex(cmd->temp_pool, NULL,
+ conf, new->real,
+ worker_type);
if (!worker) {
- const char *err = ap_proxy_define_worker(cmd->pool, &worker, NULL, conf, r, 0);
+ const char *err;
+ err = ap_proxy_define_worker_ex(cmd->pool, &worker, NULL,
+ conf, r, worker_type);
if (err)
return apr_pstrcat(cmd->temp_pool, "ProxyPass ", err, NULL);
PROXY_COPY_CONF_PARAMS(worker, conf);
- } else {
+ }
+ else {
reuse = 1;
ap_log_error(APLOG_MARK, APLOG_INFO, 0, cmd->server, APLOGNO(01145)
"Sharing worker '%s' instead of creating new worker '%s'",
@@ -2078,14 +2436,50 @@ static const char *
}
static const char *
- set_proxy_error_override(cmd_parms *parms, void *dconf, int flag)
+ set_proxy_error_override(cmd_parms *parms, void *dconf, const char *arg)
{
proxy_dir_conf *conf = dconf;
- conf->error_override = flag;
- conf->error_override_set = 1;
+ if (strcasecmp(arg, "Off") == 0) {
+ conf->error_override = 0;
+ conf->error_override_set = 1;
+ }
+ else if (strcasecmp(arg, "On") == 0) {
+ conf->error_override = 1;
+ conf->error_override_set = 1;
+ }
+ else if (conf->error_override_set == 1) {
+ int *newcode;
+ int argcode, i;
+ if (!apr_isdigit(arg[0]))
+ return "ProxyErrorOverride: status codes to intercept must be numeric";
+ if (!conf->error_override)
+ return "ProxyErrorOverride: status codes must follow a value of 'on'";
+
+ argcode = strtol(arg, NULL, 10);
+ if (!ap_is_HTTP_ERROR(argcode))
+ return "ProxyErrorOverride: status codes to intercept must be valid HTTP Status Codes >=400 && <600";
+
+ newcode = apr_array_push(conf->error_override_codes);
+ *newcode = argcode;
+
+ /* Keep the array sorted for binary search. */
+ for (i = conf->error_override_codes->nelts - 1; i > 0; --i) {
+ int *oldcode = &((int *)conf->error_override_codes->elts)[i - 1];
+ if (*oldcode <= argcode) {
+ break;
+ }
+ *newcode = *oldcode;
+ *oldcode = argcode;
+ newcode = oldcode;
+ }
+ }
+ else
+ return "ProxyErrorOverride first parameter must be one of: off | on";
+
return NULL;
}
+
static const char *
add_proxy_http_headers(cmd_parms *parms, void *dconf, int flag)
{
@@ -2103,6 +2497,14 @@ static const char *
conf->preserve_host_set = 1;
return NULL;
}
+static const char *
+ forward_100_continue(cmd_parms *parms, void *dconf, int flag)
+{
+ proxy_dir_conf *conf = dconf;
+ conf->forward_100_continue = flag;
+ conf->forward_100_continue_set = 1;
+ return NULL;
+}
static const char *
set_recv_buffer_size(cmd_parms *parms, void *dummy, const char *arg)
@@ -2279,6 +2681,7 @@ static const char *add_member(cmd_parms *cmd, void *dummy, const char *arg)
proxy_worker *worker;
char *path = cmd->path;
char *name = NULL;
+ const char *real;
char *word;
apr_table_t *params = apr_table_make(cmd->pool, 5);
const apr_array_header_t *arr;
@@ -2319,6 +2722,9 @@ static const char *add_member(cmd_parms *cmd, void *dummy, const char *arg)
return "BalancerMember must define balancer name when outside <Proxy > section";
if (!name)
return "BalancerMember must define remote proxy server";
+ if (!(real = ap_proxy_de_socketfy(cmd->temp_pool, name))) {
+ return "BalancerMember uses an invalid \"unix:\" URL";
+ }
ap_str_tolower(path); /* lowercase scheme://hostname */
@@ -2331,7 +2737,7 @@ static const char *add_member(cmd_parms *cmd, void *dummy, const char *arg)
}
/* Try to find existing worker */
- worker = ap_proxy_get_worker(cmd->temp_pool, balancer, conf, ap_proxy_de_socketfy(cmd->temp_pool, name));
+ worker = ap_proxy_get_worker(cmd->temp_pool, balancer, conf, real);
if (!worker) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01147)
"Defining worker '%s' for balancer '%s'",
@@ -2361,7 +2767,7 @@ static const char *add_member(cmd_parms *cmd, void *dummy, const char *arg)
elts[i].key, elts[i].val, ap_proxy_worker_name(cmd->pool, worker));
} else {
err = set_worker_param(cmd->pool, cmd->server, worker, elts[i].key,
- elts[i].val);
+ elts[i].val);
if (err)
return apr_pstrcat(cmd->temp_pool, "BalancerMember ", err, NULL);
}
@@ -2380,6 +2786,7 @@ static const char *
char *word, *val;
proxy_balancer *balancer = NULL;
proxy_worker *worker = NULL;
+ unsigned int worker_type = 0;
int in_proxy_section = 0;
/* XXX: Should this be NOT_IN_DIRECTORY|NOT_IN_FILES? */
const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
@@ -2396,6 +2803,13 @@ static const char *
name = ap_getword_conf(cmd->temp_pool, &pargs);
if ((word = ap_strchr(name, '>')))
*word = '\0';
+ if (strncasecmp(cmd->directive->parent->directive + 6,
+ "Match", 5) == 0) {
+ worker_type = AP_PROXY_WORKER_IS_MATCH;
+ }
+ else {
+ worker_type = AP_PROXY_WORKER_IS_PREFIX;
+ }
in_proxy_section = 1;
}
else {
@@ -2420,11 +2834,18 @@ static const char *
}
}
else {
- worker = ap_proxy_get_worker(cmd->temp_pool, NULL, conf, ap_proxy_de_socketfy(cmd->temp_pool, name));
+ const char *real;
+
+ if (!(real = ap_proxy_de_socketfy(cmd->temp_pool, name))) {
+ return "ProxySet uses an invalid \"unix:\" URL";
+ }
+
+ worker = ap_proxy_get_worker_ex(cmd->temp_pool, NULL, conf,
+ real, worker_type);
if (!worker) {
if (in_proxy_section) {
- err = ap_proxy_define_worker(cmd->pool, &worker, NULL,
- conf, name, 0);
+ err = ap_proxy_define_worker_ex(cmd->pool, &worker, NULL,
+ conf, name, worker_type);
if (err)
return apr_pstrcat(cmd->temp_pool, "ProxySet ",
err, NULL);
@@ -2478,7 +2899,7 @@ static const char *proxysection(cmd_parms *cmd, void *mconfig, const char *arg)
char *word, *val;
proxy_balancer *balancer = NULL;
proxy_worker *worker = NULL;
-
+ unsigned int worker_type = AP_PROXY_WORKER_IS_PREFIX;
const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT);
proxy_server_conf *sconf =
(proxy_server_conf *) ap_get_module_config(cmd->server->module_config, &proxy_module);
@@ -2516,6 +2937,7 @@ static const char *proxysection(cmd_parms *cmd, void *mconfig, const char *arg)
if (!r) {
return "Regex could not be compiled";
}
+ worker_type = AP_PROXY_WORKER_IS_MATCH;
}
/* initialize our config and fetch it */
@@ -2562,11 +2984,17 @@ static const char *proxysection(cmd_parms *cmd, void *mconfig, const char *arg)
}
}
else {
- worker = ap_proxy_get_worker(cmd->temp_pool, NULL, sconf,
- ap_proxy_de_socketfy(cmd->temp_pool, (char*)conf->p));
+ const char *real;
+
+ if (!(real = ap_proxy_de_socketfy(cmd->temp_pool, conf->p))) {
+ return "<Proxy/ProxyMatch > uses an invalid \"unix:\" URL";
+ }
+
+ worker = ap_proxy_get_worker_ex(cmd->temp_pool, NULL, sconf,
+ real, worker_type);
if (!worker) {
- err = ap_proxy_define_worker(cmd->pool, &worker, NULL,
- sconf, conf->p, 0);
+ err = ap_proxy_define_worker_ex(cmd->pool, &worker, NULL, sconf,
+ conf->p, worker_type);
if (err)
return apr_pstrcat(cmd->temp_pool, thiscmd->name,
" ", err, NULL);
@@ -2616,9 +3044,9 @@ static const command_rec proxy_cmds[] =
"location, in regular expression syntax"),
AP_INIT_FLAG("ProxyRequests", set_proxy_req, NULL, RSRC_CONF,
"on if the true proxy requests should be accepted"),
- AP_INIT_TAKE2("ProxyRemote", add_proxy_noregex, NULL, RSRC_CONF,
+ AP_INIT_TAKE23("ProxyRemote", add_proxy_noregex, NULL, RSRC_CONF,
"a scheme, partial URL or '*' and a proxy server"),
- AP_INIT_TAKE2("ProxyRemoteMatch", add_proxy_regex, NULL, RSRC_CONF,
+ AP_INIT_TAKE23("ProxyRemoteMatch", add_proxy_regex, NULL, RSRC_CONF,
"a regex pattern and a proxy server"),
AP_INIT_FLAG("ProxyPassInterpolateEnv", ap_set_flag_slot_char,
(void*)APR_OFFSETOF(proxy_dir_conf, interpolate_env),
@@ -2647,7 +3075,7 @@ static const command_rec proxy_cmds[] =
"The default intranet domain name (in absence of a domain in the URL)"),
AP_INIT_TAKE1("ProxyVia", set_via_opt, NULL, RSRC_CONF,
"Configure Via: proxy header header to one of: on | off | block | full"),
- AP_INIT_FLAG("ProxyErrorOverride", set_proxy_error_override, NULL, RSRC_CONF|ACCESS_CONF,
+ AP_INIT_ITERATE("ProxyErrorOverride", set_proxy_error_override, NULL, RSRC_CONF|ACCESS_CONF,
"use our error handling pages instead of the servers' we are proxying"),
AP_INIT_FLAG("ProxyPreserveHost", set_preserve_host, NULL, RSRC_CONF|ACCESS_CONF,
"on if we should preserve host header while proxying"),
@@ -2676,14 +3104,15 @@ static const command_rec proxy_cmds[] =
"Configure local source IP used for request forward"),
AP_INIT_FLAG("ProxyAddHeaders", add_proxy_http_headers, NULL, RSRC_CONF|ACCESS_CONF,
"on if X-Forwarded-* headers should be added or completed"),
+ AP_INIT_FLAG("Proxy100Continue", forward_100_continue, NULL, RSRC_CONF|ACCESS_CONF,
+ "on if 100-Continue should be forwarded to the origin server, off if the "
+ "proxy should handle it by itself"),
{NULL}
};
static APR_OPTIONAL_FN_TYPE(ssl_proxy_enable) *proxy_ssl_enable = NULL;
static APR_OPTIONAL_FN_TYPE(ssl_engine_disable) *proxy_ssl_disable = NULL;
static APR_OPTIONAL_FN_TYPE(ssl_engine_set) *proxy_ssl_engine = NULL;
-static APR_OPTIONAL_FN_TYPE(ssl_is_https) *proxy_is_https = NULL;
-static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *proxy_ssl_val = NULL;
PROXY_DECLARE(int) ap_proxy_ssl_enable(conn_rec *c)
{
@@ -2691,20 +3120,15 @@ PROXY_DECLARE(int) ap_proxy_ssl_enable(conn_rec *c)
* if c == NULL just check if the optional function was imported
* else run the optional function so ssl filters are inserted
*/
- if (proxy_ssl_enable) {
- return c ? proxy_ssl_enable(c) : 1;
+ if (c == NULL) {
+ return ap_ssl_has_outgoing_handlers();
}
-
- return 0;
+ return ap_ssl_bind_outgoing(c, NULL, 1) == OK;
}
PROXY_DECLARE(int) ap_proxy_ssl_disable(conn_rec *c)
{
- if (proxy_ssl_disable) {
- return proxy_ssl_disable(c);
- }
-
- return 0;
+ return ap_ssl_bind_outgoing(c, NULL, 0) == OK;
}
PROXY_DECLARE(int) ap_proxy_ssl_engine(conn_rec *c,
@@ -2715,41 +3139,22 @@ PROXY_DECLARE(int) ap_proxy_ssl_engine(conn_rec *c,
* if c == NULL just check if the optional function was imported
* else run the optional function so ssl filters are inserted
*/
- if (proxy_ssl_engine) {
- return c ? proxy_ssl_engine(c, per_dir_config, 1, enable) : 1;
+ if (c == NULL) {
+ return ap_ssl_has_outgoing_handlers();
}
-
- if (!per_dir_config) {
- if (enable) {
- return ap_proxy_ssl_enable(c);
- }
- else {
- return ap_proxy_ssl_disable(c);
- }
- }
-
- return 0;
+ return ap_ssl_bind_outgoing(c, per_dir_config, enable) == OK;
}
PROXY_DECLARE(int) ap_proxy_conn_is_https(conn_rec *c)
{
- if (proxy_is_https) {
- return proxy_is_https(c);
- }
- else
- return 0;
+ return ap_ssl_conn_is_ssl(c);
}
PROXY_DECLARE(const char *) ap_proxy_ssl_val(apr_pool_t *p, server_rec *s,
conn_rec *c, request_rec *r,
const char *var)
{
- if (proxy_ssl_val) {
- /* XXX Perhaps the casting useless */
- return (const char *)proxy_ssl_val(p, s, c, r, (char *)var);
- }
- else
- return NULL;
+ return ap_ssl_var_lookup(p, s, c, r, var);
}
static int proxy_post_config(apr_pool_t *pconf, apr_pool_t *plog,
@@ -2767,8 +3172,6 @@ static int proxy_post_config(apr_pool_t *pconf, apr_pool_t *plog,
proxy_ssl_enable = APR_RETRIEVE_OPTIONAL_FN(ssl_proxy_enable);
proxy_ssl_disable = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_disable);
proxy_ssl_engine = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_set);
- proxy_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
- proxy_ssl_val = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
ap_proxy_strmatch_path = apr_strmatch_precompile(pconf, "path=", 0);
ap_proxy_strmatch_domain = apr_strmatch_precompile(pconf, "domain=", 0);
@@ -2867,7 +3270,7 @@ static int proxy_status_hook(request_rec *r, int flags)
}
else {
ap_rprintf(r, "ProxyBalancer[%d]Worker[%d]Name: %s\n",
- i, n, (*worker)->s->name);
+ i, n, (*worker)->s->name_ex);
ap_rprintf(r, "ProxyBalancer[%d]Worker[%d]Status: %s\n",
i, n, ap_proxy_parse_wstatus(r->pool, *worker));
ap_rprintf(r, "ProxyBalancer[%d]Worker[%d]Elected: %"
@@ -2939,45 +3342,49 @@ static void child_init(apr_pool_t *p, server_rec *s)
*/
worker = (proxy_worker *)conf->workers->elts;
for (i = 0; i < conf->workers->nelts; i++, worker++) {
- ap_proxy_initialize_worker(worker, s, conf->pool);
+ ap_proxy_initialize_worker(worker, s, p);
}
/* Create and initialize forward worker if defined */
if (conf->req_set && conf->req) {
proxy_worker *forward;
- ap_proxy_define_worker(p, &forward, NULL, NULL, "http://www.apache.org", 0);
+ ap_proxy_define_worker(conf->pool, &forward, NULL, NULL,
+ "http://www.apache.org", 0);
conf->forward = forward;
PROXY_STRNCPY(conf->forward->s->name, "proxy:forward");
+ PROXY_STRNCPY(conf->forward->s->name_ex, "proxy:forward");
PROXY_STRNCPY(conf->forward->s->hostname, "*"); /* for compatibility */
PROXY_STRNCPY(conf->forward->s->hostname_ex, "*");
PROXY_STRNCPY(conf->forward->s->scheme, "*");
conf->forward->hash.def = conf->forward->s->hash.def =
- ap_proxy_hashfunc(conf->forward->s->name, PROXY_HASHFUNC_DEFAULT);
+ ap_proxy_hashfunc(conf->forward->s->name_ex, PROXY_HASHFUNC_DEFAULT);
conf->forward->hash.fnv = conf->forward->s->hash.fnv =
- ap_proxy_hashfunc(conf->forward->s->name, PROXY_HASHFUNC_FNV);
+ ap_proxy_hashfunc(conf->forward->s->name_ex, PROXY_HASHFUNC_FNV);
/* Do not disable worker in case of errors */
conf->forward->s->status |= PROXY_WORKER_IGNORE_ERRORS;
/* Mark as the "generic" worker */
conf->forward->s->status |= PROXY_WORKER_GENERIC;
- ap_proxy_initialize_worker(conf->forward, s, conf->pool);
+ ap_proxy_initialize_worker(conf->forward, s, p);
/* Disable address cache for generic forward worker */
conf->forward->s->is_address_reusable = 0;
}
if (!reverse) {
- ap_proxy_define_worker(p, &reverse, NULL, NULL, "http://www.apache.org", 0);
+ ap_proxy_define_worker(conf->pool, &reverse, NULL, NULL,
+ "http://www.apache.org", 0);
PROXY_STRNCPY(reverse->s->name, "proxy:reverse");
+ PROXY_STRNCPY(reverse->s->name_ex, "proxy:reverse");
PROXY_STRNCPY(reverse->s->hostname, "*"); /* for compatibility */
PROXY_STRNCPY(reverse->s->hostname_ex, "*");
PROXY_STRNCPY(reverse->s->scheme, "*");
reverse->hash.def = reverse->s->hash.def =
- ap_proxy_hashfunc(reverse->s->name, PROXY_HASHFUNC_DEFAULT);
+ ap_proxy_hashfunc(reverse->s->name_ex, PROXY_HASHFUNC_DEFAULT);
reverse->hash.fnv = reverse->s->hash.fnv =
- ap_proxy_hashfunc(reverse->s->name, PROXY_HASHFUNC_FNV);
+ ap_proxy_hashfunc(reverse->s->name_ex, PROXY_HASHFUNC_FNV);
/* Do not disable worker in case of errors */
reverse->s->status |= PROXY_WORKER_IGNORE_ERRORS;
/* Mark as the "generic" worker */
reverse->s->status |= PROXY_WORKER_GENERIC;
conf->reverse = reverse;
- ap_proxy_initialize_worker(conf->reverse, s, conf->pool);
+ ap_proxy_initialize_worker(conf->reverse, s, p);
/* Disable address cache for generic reverse worker */
reverse->s->is_address_reusable = 0;
}
@@ -3003,7 +3410,7 @@ static int proxy_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
APR_OPTIONAL_HOOK(ap, status_hook, proxy_status_hook, NULL, NULL,
APR_HOOK_MIDDLE);
- /* Reset workers count on gracefull restart */
+ /* Reset workers count on graceful restart */
proxy_lb_workers = 0;
set_worker_hc_param_f = APR_RETRIEVE_OPTIONAL_FN(set_worker_hc_param);
return OK;
@@ -3023,7 +3430,10 @@ static void register_hooks(apr_pool_t *p)
/* handler */
ap_hook_handler(proxy_handler, NULL, NULL, APR_HOOK_FIRST);
/* filename-to-URI translation */
- ap_hook_translate_name(proxy_trans, aszSucc, NULL, APR_HOOK_FIRST);
+ ap_hook_pre_translate_name(proxy_pre_translate_name, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ ap_hook_translate_name(proxy_translate_name, aszSucc, NULL,
+ APR_HOOK_FIRST);
/* walk <Proxy > entries and suppress default TRACE behavior */
ap_hook_map_to_storage(proxy_map_location, NULL,NULL, APR_HOOK_FIRST);
/* fixups */
@@ -3058,6 +3468,7 @@ APR_HOOK_STRUCT(
APR_HOOK_LINK(pre_request)
APR_HOOK_LINK(post_request)
APR_HOOK_LINK(request_status)
+ APR_HOOK_LINK(check_trans)
)
APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(proxy, PROXY, int, scheme_handler,
@@ -3066,6 +3477,9 @@ APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(proxy, PROXY, int, scheme_handler,
char *url, const char *proxyhost,
apr_port_t proxyport),(r,worker,conf,
url,proxyhost,proxyport),DECLINED)
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(proxy, PROXY, int, check_trans,
+ (request_rec *r, const char *url),
+ (r, url), DECLINED)
APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(proxy, PROXY, int, canon_handler,
(request_rec *r, char *url),(r,
url),DECLINED)
diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h
index aabd09f..51a55f8 100644
--- a/modules/proxy/mod_proxy.h
+++ b/modules/proxy/mod_proxy.h
@@ -58,6 +58,7 @@
#include "http_main.h"
#include "http_log.h"
#include "http_connection.h"
+#include "http_ssl.h"
#include "util_filter.h"
#include "util_ebcdic.h"
#include "ap_provider.h"
@@ -75,8 +76,12 @@ enum enctype {
enc_path, enc_search, enc_user, enc_fpath, enc_parm
};
+/* Flags for ap_proxy_canonenc_ex */
+#define PROXY_CANONENC_FORCEDEC 0x01
+#define PROXY_CANONENC_NOENCODEDSLASHENCODING 0x02
+
typedef enum {
- NONE, TCP, OPTIONS, HEAD, GET, CPING, PROVIDER, EOT
+ NONE, TCP, OPTIONS, HEAD, GET, CPING, PROVIDER, OPTIONS11, HEAD11, GET11, EOT
} hcmethod_t;
typedef struct {
@@ -116,6 +121,7 @@ struct proxy_remote {
const char *protocol; /* the scheme used to talk to this proxy */
const char *hostname; /* the hostname of this proxy */
ap_regex_t *regexp; /* compiled regex (if any) for the remote */
+ const char *creds; /* auth credentials (if any) for the proxy */
int use_regex; /* simple boolean. True if we have a regex pattern */
apr_port_t port; /* the port for this proxy */
};
@@ -123,6 +129,8 @@ struct proxy_remote {
#define PROXYPASS_NOCANON 0x01
#define PROXYPASS_INTERPOLATE 0x02
#define PROXYPASS_NOQUERY 0x04
+#define PROXYPASS_MAP_ENCODED 0x08
+#define PROXYPASS_MAP_SERVLET 0x18 /* + MAP_ENCODED */
struct proxy_alias {
const char *real;
const char *fake;
@@ -199,9 +207,10 @@ typedef struct {
unsigned int inherit_set:1;
unsigned int ppinherit:1;
unsigned int ppinherit_set:1;
+ unsigned int map_encoded_one:1;
+ unsigned int map_encoded_all:1;
} proxy_server_conf;
-
typedef struct {
const char *p; /* The path */
ap_regex_t *r; /* Is this a regex? */
@@ -240,6 +249,10 @@ typedef struct {
/** Named back references */
apr_array_header_t *refs;
+ unsigned int forward_100_continue:1;
+ unsigned int forward_100_continue_set:1;
+
+ apr_array_header_t *error_override_codes;
} proxy_dir_conf;
/* if we interpolate env vars per-request, we'll need a per-request
@@ -251,6 +264,8 @@ typedef struct {
apr_array_header_t* cookie_domains;
} proxy_req_conf;
+struct proxy_address; /* opaque TTL'ed and refcount'ed address */
+
typedef struct {
conn_rec *connection;
request_rec *r; /* Request record of the backend request
@@ -276,6 +291,9 @@ typedef struct {
* and its scpool/bucket_alloc (NULL before),
* must be left cleaned when used (locally).
*/
+ apr_pool_t *uds_pool; /* Subpool for reusing UDS paths */
+ apr_pool_t *fwd_pool; /* Subpool for reusing ProxyRemote infos */
+ struct proxy_address *address; /* Current remote address */
} proxy_conn_rec;
typedef struct {
@@ -285,12 +303,15 @@ typedef struct {
/* Connection pool */
struct proxy_conn_pool {
- apr_pool_t *pool; /* The pool used in constructor and destructor calls */
- apr_sockaddr_t *addr; /* Preparsed remote address info */
- apr_reslist_t *res; /* Connection resource list */
- proxy_conn_rec *conn; /* Single connection for prefork mpm */
+ apr_pool_t *pool; /* The pool used in constructor and destructor calls */
+ apr_sockaddr_t *addr; /* Preparsed remote address info */
+ apr_reslist_t *res; /* Connection resource list */
+ proxy_conn_rec *conn; /* Single connection for prefork mpm */
+ apr_pool_t *dns_pool; /* The pool used for worker scoped DNS resolutions */
};
+#define AP_VOLATILIZE_T(T, x) (*(T volatile *)&(x))
+
/* worker status bits */
/*
* NOTE: Keep up-to-date w/ proxy_wstat_tbl[]
@@ -343,6 +364,8 @@ PROXY_WORKER_HC_FAIL )
#define PROXY_WORKER_IS_HCFAILED(f) ( (f)->s->status & PROXY_WORKER_HC_FAIL )
+#define PROXY_WORKER_IS_ERROR(f) ( (f)->s->status & PROXY_WORKER_IN_ERROR )
+
#define PROXY_WORKER_IS(f, b) ( (f)->s->status & (b) )
/* default worker retry timeout in seconds */
@@ -357,8 +380,10 @@ PROXY_WORKER_HC_FAIL )
#define PROXY_WORKER_MAX_HOSTNAME_SIZE 64
#define PROXY_BALANCER_MAX_HOSTNAME_SIZE PROXY_WORKER_MAX_HOSTNAME_SIZE
#define PROXY_BALANCER_MAX_STICKY_SIZE 64
+#define PROXY_WORKER_MAX_SECRET_SIZE 64
#define PROXY_RFC1035_HOSTNAME_SIZE 256
+#define PROXY_WORKER_EXT_NAME_SIZE 384
/* RFC-1035 mentions limits of 255 for host-names and 253 for domain-names,
* dotted together(?) this would fit the below size (+ trailing NUL).
@@ -379,6 +404,15 @@ do { \
(w)->s->io_buffer_size_set = (c)->io_buffer_size_set; \
} while (0)
+#define PROXY_SHOULD_PING_100_CONTINUE(w, r) \
+ ((w)->s->ping_timeout_set \
+ && (PROXYREQ_REVERSE == (r)->proxyreq) \
+ && ap_request_has_body((r)))
+
+#define PROXY_DO_100_CONTINUE(w, r) \
+ (PROXY_SHOULD_PING_100_CONTINUE(w, r) \
+ && !apr_table_get((r)->subprocess_env, "force-proxy-request-1.0"))
+
/* use 2 hashes */
typedef struct {
unsigned int def;
@@ -441,6 +475,7 @@ typedef struct {
unsigned int keepalive_set:1;
unsigned int disablereuse_set:1;
unsigned int was_malloced:1;
+ unsigned int is_name_matchable:1;
char hcuri[PROXY_WORKER_MAX_ROUTE_SIZE]; /* health check uri */
char hcexpr[PROXY_WORKER_MAX_SCHEME_SIZE]; /* name of condition expr for health check */
int passes; /* number of successes for check to pass */
@@ -453,6 +488,11 @@ typedef struct {
char hostname_ex[PROXY_RFC1035_HOSTNAME_SIZE]; /* RFC1035 compliant version of the remote backend address */
apr_size_t response_field_size; /* Size of proxy response buffer in bytes. */
unsigned int response_field_size_set:1;
+ char secret[PROXY_WORKER_MAX_SECRET_SIZE]; /* authentication secret (e.g. AJP13) */
+ char name_ex[PROXY_WORKER_EXT_NAME_SIZE]; /* Extended name (>96 chars for 2.4.x) */
+ unsigned int address_ttl_set:1;
+ apr_int32_t address_ttl; /* backend address' TTL (seconds) */
+ apr_uint32_t address_expiry; /* backend address' next expiry time */
} proxy_worker_shared;
#define ALIGNED_PROXY_WORKER_SHARED_SIZE (APR_ALIGN_DEFAULT(sizeof(proxy_worker_shared)))
@@ -464,9 +504,12 @@ struct proxy_worker {
proxy_conn_pool *cp; /* Connection pool to use */
proxy_worker_shared *s; /* Shared data */
proxy_balancer *balancer; /* which balancer am I in? */
+#if APR_HAS_THREADS
apr_thread_mutex_t *tmutex; /* Thread lock for updating address cache */
+#endif
void *context; /* general purpose storage */
ap_conf_vector_t *section_config; /* <Proxy>-section wherein defined */
+ struct proxy_address *volatile address; /* current worker address (if reusable) */
};
/* default to health check every 30 seconds */
@@ -523,7 +566,9 @@ struct proxy_balancer {
apr_time_t wupdated; /* timestamp of last change to workers list */
proxy_balancer_method *lbmethod;
apr_global_mutex_t *gmutex; /* global lock for updating list of workers */
+#if APR_HAS_THREADS
apr_thread_mutex_t *tmutex; /* Thread lock for updating shm */
+#endif
proxy_server_conf *sconf;
void *context; /* general purpose storage */
proxy_balancer_shared *s; /* Shared data */
@@ -602,6 +647,8 @@ APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, scheme_handler,
(request_rec *r, proxy_worker *worker,
proxy_server_conf *conf, char *url,
const char *proxyhost, apr_port_t proxyport))
+APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, check_trans,
+ (request_rec *r, const char *url))
APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, canon_handler,
(request_rec *r, char *url))
@@ -643,6 +690,8 @@ PROXY_DECLARE(apr_status_t) ap_proxy_strncpy(char *dst, const char *src,
apr_size_t dlen);
PROXY_DECLARE(int) ap_proxy_hex2c(const char *x);
PROXY_DECLARE(void) ap_proxy_c2hex(int ch, char *x);
+PROXY_DECLARE(char *)ap_proxy_canonenc_ex(apr_pool_t *p, const char *x, int len, enum enctype t,
+ int flags, int proxyreq);
PROXY_DECLARE(char *)ap_proxy_canonenc(apr_pool_t *p, const char *x, int len, enum enctype t,
int forcedec, int proxyreq);
PROXY_DECLARE(char *)ap_proxy_canon_netloc(apr_pool_t *p, char **const urlp, char **userp,
@@ -656,7 +705,7 @@ PROXY_DECLARE(int) ap_proxy_checkproxyblock(request_rec *r, proxy_server_conf *c
* @param conf server configuration
* @param hostname hostname from request URI
* @param addr resolved address of hostname, or NULL if not known
- * @return OK on success, or else an errro
+ * @return OK on success, or else an error
*/
PROXY_DECLARE(int) ap_proxy_checkproxyblock2(request_rec *r, proxy_server_conf *conf,
const char *hostname, apr_sockaddr_t *addr);
@@ -708,7 +757,42 @@ PROXY_DECLARE(char *) ap_proxy_worker_name(apr_pool_t *p,
proxy_worker *worker);
/**
- * Get the worker from proxy configuration
+ * Return whether a worker upgrade configuration matches Upgrade header
+ * @param p memory pool used for displaying worker name
+ * @param worker the worker
+ * @param upgrade the Upgrade header to match
+ * @param dflt default protocol (NULL for none)
+ * @return 1 (true) or 0 (false)
+ */
+PROXY_DECLARE(int) ap_proxy_worker_can_upgrade(apr_pool_t *p,
+ const proxy_worker *worker,
+ const char *upgrade,
+ const char *dflt);
+
+/* Bitmask for ap_proxy_{define,get}_worker_ex(). */
+#define AP_PROXY_WORKER_IS_PREFIX (1u << 0)
+#define AP_PROXY_WORKER_IS_MATCH (1u << 1)
+#define AP_PROXY_WORKER_IS_MALLOCED (1u << 2)
+#define AP_PROXY_WORKER_NO_UDS (1u << 3)
+
+/**
+ * Get the worker from proxy configuration, looking for either PREFIXED or
+ * MATCHED or both types of workers according to given mask
+ * @param p memory pool used for finding worker
+ * @param balancer the balancer that the worker belongs to
+ * @param conf current proxy server configuration
+ * @param url url to find the worker from
+ * @param mask bitmask of AP_PROXY_WORKER_IS_*
+ * @return proxy_worker or NULL if not found
+ */
+PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker_ex(apr_pool_t *p,
+ proxy_balancer *balancer,
+ proxy_server_conf *conf,
+ const char *url,
+ unsigned int mask);
+
+/**
+ * Get the worker from proxy configuration, both types
* @param p memory pool used for finding worker
* @param balancer the balancer that the worker belongs to
* @param conf current proxy server configuration
@@ -719,7 +803,26 @@ PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker(apr_pool_t *p,
proxy_balancer *balancer,
proxy_server_conf *conf,
const char *url);
+
/**
+ * Define and Allocate space for the worker to proxy configuration, of either
+ * PREFIXED or MATCHED type according to given mask
+ * @param p memory pool to allocate worker from
+ * @param worker the new worker
+ * @param balancer the balancer that the worker belongs to
+ * @param conf current proxy server configuration
+ * @param url url containing worker name
+ * @param mask bitmask of AP_PROXY_WORKER_IS_*
+ * @return error message or NULL if successful (*worker is new worker)
+ */
+PROXY_DECLARE(char *) ap_proxy_define_worker_ex(apr_pool_t *p,
+ proxy_worker **worker,
+ proxy_balancer *balancer,
+ proxy_server_conf *conf,
+ const char *url,
+ unsigned int mask);
+
+ /**
* Define and Allocate space for the worker to proxy configuration
* @param p memory pool to allocate worker from
* @param worker the new worker
@@ -737,6 +840,25 @@ PROXY_DECLARE(char *) ap_proxy_define_worker(apr_pool_t *p,
int do_malloc);
/**
+ * Define and Allocate space for the ap_strcmp_match()able worker to proxy
+ * configuration.
+ * @param p memory pool to allocate worker from
+ * @param worker the new worker
+ * @param balancer the balancer that the worker belongs to
+ * @param conf current proxy server configuration
+ * @param url url containing worker name (produces match pattern)
+ * @param do_malloc true if shared struct should be malloced
+ * @return error message or NULL if successful (*worker is new worker)
+ * @deprecated Replaced by ap_proxy_define_worker_ex()
+ */
+PROXY_DECLARE(char *) ap_proxy_define_match_worker(apr_pool_t *p,
+ proxy_worker **worker,
+ proxy_balancer *balancer,
+ proxy_server_conf *conf,
+ const char *url,
+ int do_malloc);
+
+/**
* Share a defined proxy worker via shm
* @param worker worker to be shared
* @param shm location of shared info
@@ -912,6 +1034,29 @@ PROXY_DECLARE(int) ap_proxy_post_request(proxy_worker *worker,
request_rec *r,
proxy_server_conf *conf);
+/* Bitmask for ap_proxy_determine_address() */
+#define PROXY_DETERMINE_ADDRESS_CHECK (1u << 0)
+/**
+ * Resolve an address, reusing the one of the worker if any.
+ * @param proxy_function calling proxy scheme (http, ajp, ...)
+ * @param conn proxy connection the address is used for
+ * @param hostname host to resolve (should be the worker's if reusable)
+ * @param hostport port to resolve (should be the worker's if reusable)
+ * @param flags bitmask of PROXY_DETERMINE_ADDRESS_*
+ * @param r current request (if any)
+ * @param s current server (or NULL if r != NULL and ap_proxyerror()
+ * should be called on error)
+ * @return APR_SUCCESS or an error, APR_EEXIST if the address is still
+ * the same and PROXY_DETERMINE_ADDRESS_CHECK is asked
+ */
+PROXY_DECLARE(apr_status_t) ap_proxy_determine_address(const char *proxy_function,
+ proxy_conn_rec *conn,
+ const char *hostname,
+ apr_port_t hostport,
+ unsigned int flags,
+ request_rec *r,
+ server_rec *s);
+
/**
* Determine backend hostname and port
* @param p memory pool used for processing
@@ -1112,13 +1257,27 @@ PROXY_DECLARE(apr_status_t) ap_proxy_sync_balancer(proxy_balancer *b,
server_rec *s,
proxy_server_conf *conf);
+/**
+ * Configure and create workers (and balancer) in mod_balancer.
+ * @param r request
+ * @param params table with the parameters like b=mycluster etc.
+ * @return 404 when the worker/balancer doesn't exist,
+ * 400 if something is invalid
+ * 200 for success.
+ */
+APR_DECLARE_OPTIONAL_FN(apr_status_t, balancer_manage,
+ (request_rec *, apr_table_t *params));
/**
* Find the matched alias for this request and setup for proxy handler
* @param r request
* @param ent proxy_alias record
* @param dconf per-dir config or NULL
- * @return DECLINED, DONE or OK if matched
+ * @return OK if the alias matched,
+ * DONE if the alias matched and r->uri was normalized so
+ * no further transformation should happen on it,
+ * DECLINED if proxying is disabled for this alias,
+ * HTTP_CONTINUE if the alias did not match
*/
PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r,
struct proxy_alias *ent,
@@ -1151,6 +1310,55 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p,
char **old_te_val);
/**
+ * Prefetch the client request body (in memory), up to a limit.
+ * Read what's in the client pipe. If nonblocking is set and read is EAGAIN,
+ * pass a FLUSH bucket to the backend and read again in blocking mode.
+ * @param r client request
+ * @param backend backend connection
+ * @param input_brigade input brigade to use/fill
+ * @param block blocking or non-blocking mode
+ * @param bytes_read number of bytes read
+ * @param max_read maximum number of bytes to read
+ * @return OK or HTTP_* error code
+ * @note max_read is rounded up to APR_BUCKET_BUFF_SIZE
+ */
+PROXY_DECLARE(int) ap_proxy_prefetch_input(request_rec *r,
+ proxy_conn_rec *backend,
+ apr_bucket_brigade *input_brigade,
+ apr_read_type_e block,
+ apr_off_t *bytes_read,
+ apr_off_t max_read);
+
+/**
+ * Spool the client request body to memory, or disk above given limit.
+ * @param r client request
+ * @param backend backend connection
+ * @param input_brigade input brigade to use/fill
+ * @param bytes_spooled number of bytes spooled
+ * @param max_mem_spool maximum number of in-memory bytes
+ * @return OK or HTTP_* error code
+ */
+PROXY_DECLARE(int) ap_proxy_spool_input(request_rec *r,
+ proxy_conn_rec *backend,
+ apr_bucket_brigade *input_brigade,
+ apr_off_t *bytes_spooled,
+ apr_off_t max_mem_spool);
+
+/**
+ * Read what's in the client pipe. If the read would block (EAGAIN),
+ * pass a FLUSH bucket to the backend and read again in blocking mode.
+ * @param r client request
+ * @param backend backend connection
+ * @param input_brigade brigade to use/fill
+ * @param max_read maximum number of bytes to read
+ * @return OK or HTTP_* error code
+ */
+PROXY_DECLARE(int) ap_proxy_read_input(request_rec *r,
+ proxy_conn_rec *backend,
+ apr_bucket_brigade *input_brigade,
+ apr_off_t max_read);
+
+/**
* @param bucket_alloc bucket allocator
* @param r request
* @param p_conn proxy connection
@@ -1164,6 +1372,41 @@ PROXY_DECLARE(int) ap_proxy_pass_brigade(apr_bucket_alloc_t *bucket_alloc,
conn_rec *origin, apr_bucket_brigade *bb,
int flush);
+struct proxy_tunnel_conn; /* opaque */
+typedef struct {
+ request_rec *r;
+ const char *scheme;
+ apr_pollset_t *pollset;
+ apr_array_header_t *pfds;
+ apr_interval_time_t timeout;
+ struct proxy_tunnel_conn *client,
+ *origin;
+ apr_size_t read_buf_size;
+ int replied; /* TODO 2.5+: one bit to merge in below bitmask */
+ unsigned int nohalfclose :1;
+} proxy_tunnel_rec;
+
+/**
+ * Create a tunnel, to be activated by ap_proxy_tunnel_run().
+ * @param tunnel tunnel created
+ * @param r client request
+ * @param c_o connection to origin
+ * @param scheme caller proxy scheme (connect, ws(s), http(s), ...)
+ * @return APR_SUCCESS or error status
+ */
+PROXY_DECLARE(apr_status_t) ap_proxy_tunnel_create(proxy_tunnel_rec **tunnel,
+ request_rec *r, conn_rec *c_o,
+ const char *scheme);
+
+/**
+ * Forward anything from either side of the tunnel to the other,
+ * until one end aborts or a polling timeout/error occurs.
+ * @param tunnel tunnel to run
+ * @return OK if completion is full, HTTP_GATEWAY_TIME_OUT on timeout
+ * or another HTTP_ error otherwise.
+ */
+PROXY_DECLARE(int) ap_proxy_tunnel_run(proxy_tunnel_rec *tunnel);
+
/**
* Clear the headers referenced by the Connection header from the given
* table, and remove the Connection header.
@@ -1175,6 +1418,15 @@ APR_DECLARE_OPTIONAL_FN(int, ap_proxy_clear_connection,
(request_rec *r, apr_table_t *headers));
/**
+ * Do a AJP CPING and wait for CPONG on the socket
+ *
+ */
+APR_DECLARE_OPTIONAL_FN(apr_status_t, ajp_handle_cping_cpong,
+ (apr_socket_t *sock, request_rec *r,
+ apr_interval_time_t timeout));
+
+
+/**
* @param socket socket to test
* @return TRUE if socket is connected/active
*/
@@ -1194,6 +1446,15 @@ PROXY_DECLARE(int) ap_proxy_is_socket_connected(apr_socket_t *socket);
int ap_proxy_lb_workers(void);
/**
+ * Returns 1 if a response with the given status should be overridden.
+ *
+ * @param conf proxy directory configuration
+ * @param code http status code
+ * @return 1 if code is considered an error-code, 0 otherwise
+ */
+PROXY_DECLARE(int) ap_proxy_should_override(proxy_dir_conf *conf, int code);
+
+/**
* Return the port number of a known scheme (eg: http -> 80).
* @param scheme scheme to test
* @return port number or 0 if unknown
@@ -1238,6 +1499,15 @@ PROXY_DECLARE(apr_status_t) ap_proxy_buckets_lifetime_transform(request_rec *r,
apr_bucket_brigade *from,
apr_bucket_brigade *to);
+/*
+ * The flags for ap_proxy_transfer_between_connections(), where for legacy and
+ * compatibility reasons FLUSH_EACH and FLUSH_AFTER are boolean values.
+ */
+#define AP_PROXY_TRANSFER_FLUSH_EACH (0x00)
+#define AP_PROXY_TRANSFER_FLUSH_AFTER (0x01)
+#define AP_PROXY_TRANSFER_YIELD_PENDING (0x02)
+#define AP_PROXY_TRANSFER_YIELD_MAX_READS (0x04)
+
/*
* Sends all data that can be read non blocking from the input filter chain of
* c_i and send it down the output filter chain of c_o. For reading it uses
@@ -1255,10 +1525,12 @@ PROXY_DECLARE(apr_status_t) ap_proxy_buckets_lifetime_transform(request_rec *r,
* @param name string for logging from where data was pulled
* @param sent if not NULL will be set to 1 if data was sent through c_o
* @param bsize maximum amount of data pulled in one iteration from c_i
- * @param after if set flush data on c_o only once after the loop
+ * @param flags AP_PROXY_TRANSFER_* bitmask
* @return apr_status_t of the operation. Could be any error returned from
* either the input filter chain of c_i or the output filter chain
- * of c_o. APR_EPIPE if the outgoing connection was aborted.
+ * of c_o, APR_EPIPE if the outgoing connection was aborted, or
+ * APR_INCOMPLETE if AP_PROXY_TRANSFER_YIELD_PENDING was set and
+ * the output stack gets full before the input stack is exhausted.
*/
PROXY_DECLARE(apr_status_t) ap_proxy_transfer_between_connections(
request_rec *r,
@@ -1269,7 +1541,7 @@ PROXY_DECLARE(apr_status_t) ap_proxy_transfer_between_connections(
const char *name,
int *sent,
apr_off_t bsize,
- int after);
+ int flags);
extern module PROXY_DECLARE_DATA proxy_module;
diff --git a/modules/proxy/mod_proxy_ajp.c b/modules/proxy/mod_proxy_ajp.c
index 73716af..32ec912 100644
--- a/modules/proxy/mod_proxy_ajp.c
+++ b/modules/proxy/mod_proxy_ajp.c
@@ -35,7 +35,7 @@ static int proxy_ajp_canon(request_rec *r, char *url)
apr_port_t port, def_port;
/* ap_port_of_scheme() */
- if (strncasecmp(url, "ajp:", 4) == 0) {
+ if (ap_cstr_casecmpn(url, "ajp:", 4) == 0) {
url += 4;
}
else {
@@ -65,13 +65,37 @@ static int proxy_ajp_canon(request_rec *r, char *url)
if (apr_table_get(r->notes, "proxy-nocanon")) {
path = url; /* this is the raw path */
}
+ else if (apr_table_get(r->notes, "proxy-noencode")) {
+ path = url; /* this is the encoded path already */
+ search = r->args;
+ }
else {
- path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
- r->proxyreq);
+ core_dir_config *d = ap_get_core_module_config(r->per_dir_config);
+ int flags = d->allow_encoded_slashes && !d->decode_encoded_slashes ? PROXY_CANONENC_NOENCODEDSLASHENCODING : 0;
+
+ path = ap_proxy_canonenc_ex(r->pool, url, strlen(url), enc_path, flags,
+ r->proxyreq);
+ if (!path) {
+ return HTTP_BAD_REQUEST;
+ }
search = r->args;
}
- if (path == NULL)
- return HTTP_BAD_REQUEST;
+ /*
+ * If we have a raw control character or a ' ' in nocanon path or
+ * r->args, correct encoding was missed.
+ */
+ if (path == url && *ap_scan_vchar_obstext(path)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10418)
+ "To be forwarded path contains control "
+ "characters or spaces");
+ return HTTP_FORBIDDEN;
+ }
+ if (search && *ap_scan_vchar_obstext(search)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10406)
+ "To be forwarded query string contains control "
+ "characters or spaces");
+ return HTTP_FORBIDDEN;
+ }
if (port != def_port)
apr_snprintf(sport, sizeof(sport), ":%d", port);
@@ -126,11 +150,8 @@ static apr_off_t get_content_length(request_rec * r)
if (r->main == NULL) {
const char *clp = apr_table_get(r->headers_in, "Content-Length");
- if (clp) {
- char *errp;
- if (apr_strtoff(&len, clp, &errp, 10) || *errp || len < 0) {
- len = 0; /* parse error */
- }
+ if (clp && !ap_parse_strict_length(&len, clp)) {
+ len = -1; /* parse error */
}
}
@@ -193,6 +214,7 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r,
apr_off_t content_length = 0;
int original_status = r->status;
const char *original_status_line = r->status_line;
+ const char *secret = NULL;
if (psf->io_buffer_size_set)
maxsize = psf->io_buffer_size;
@@ -202,18 +224,20 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r,
maxsize = AJP_MSG_BUFFER_SZ;
maxsize = APR_ALIGN(maxsize, 1024);
+ if (*conn->worker->s->secret)
+ secret = conn->worker->s->secret;
+
/*
* Send the AJP request to the remote server
*/
/* send request headers */
- status = ajp_send_header(conn->sock, r, maxsize, uri);
+ status = ajp_send_header(conn->sock, r, maxsize, uri, secret);
if (status != APR_SUCCESS) {
conn->close = 1;
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00868)
- "request failed to %pI (%s)",
- conn->worker->cp->addr,
- conn->worker->s->hostname_ex);
+ "request failed to %pI (%s:%hu)",
+ conn->addr, conn->hostname, conn->port);
if (status == AJP_EOVERFLOW)
return HTTP_BAD_REQUEST;
else if (status == AJP_EBAD_METHOD) {
@@ -242,19 +266,34 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r,
return HTTP_INTERNAL_SERVER_ERROR;
}
- /* read the first bloc of data */
+ /* read the first block of data */
input_brigade = apr_brigade_create(p, r->connection->bucket_alloc);
tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
- if (tenc && (strcasecmp(tenc, "chunked") == 0)) {
- /* The AJP protocol does not want body data yet */
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00870) "request is chunked");
+ if (tenc) {
+ if (ap_cstr_casecmp(tenc, "chunked") == 0) {
+ /* The AJP protocol does not want body data yet */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00870)
+ "request is chunked");
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10396)
+ "%s Transfer-Encoding is not supported",
+ tenc);
+ /* We had a failure: Close connection to backend */
+ conn->close = 1;
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
} else {
/* Get client provided Content-Length header */
content_length = get_content_length(r);
- status = ap_get_brigade(r->input_filters, input_brigade,
- AP_MODE_READBYTES, APR_BLOCK_READ,
- maxsize - AJP_HEADER_SZ);
-
+ if (content_length < 0) {
+ status = APR_EINVAL;
+ }
+ else {
+ status = ap_get_brigade(r->input_filters, input_brigade,
+ AP_MODE_READBYTES, APR_BLOCK_READ,
+ maxsize - AJP_HEADER_SZ);
+ }
if (status != APR_SUCCESS) {
/* We had a failure: Close connection to backend */
conn->close = 1;
@@ -295,9 +334,8 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r,
conn->close = 1;
apr_brigade_destroy(input_brigade);
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00876)
- "send failed to %pI (%s)",
- conn->worker->cp->addr,
- conn->worker->s->hostname_ex);
+ "send failed to %pI (%s:%hu)",
+ conn->addr, conn->hostname, conn->port);
/*
* It is fatal when we failed to send a (part) of the request
* body.
@@ -336,15 +374,15 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r,
conn->close = 1;
apr_brigade_destroy(input_brigade);
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00878)
- "read response failed from %pI (%s)",
- conn->worker->cp->addr,
- conn->worker->s->hostname_ex);
+ "read response failed from %pI (%s:%hu)",
+ conn->addr, conn->hostname, conn->port);
/* If we had a successful cping/cpong and then a timeout
* we assume it is a request that cause a back-end timeout,
* but doesn't affect the whole worker.
*/
- if (APR_STATUS_IS_TIMEUP(status) && conn->worker->s->ping_timeout_set) {
+ if (APR_STATUS_IS_TIMEUP(status) &&
+ conn->worker->s->ping_timeout_set) {
return HTTP_GATEWAY_TIME_OUT;
}
@@ -470,7 +508,7 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r,
/* If we are overriding the errors, we can't put the content
* of the page into the brigade.
*/
- if (!conf->error_override || !ap_is_HTTP_ERROR(r->status)) {
+ if (!ap_proxy_should_override(conf, r->status)) {
/* AJP13_SEND_BODY_CHUNK with zero length
* is explicit flush message
*/
@@ -493,8 +531,7 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r,
* error status so that an underlying error (eg HTTP_NOT_FOUND)
* doesn't become an HTTP_OK.
*/
- if (conf->error_override && !ap_is_HTTP_ERROR(r->status)
- && ap_is_HTTP_ERROR(original_status)) {
+ if (ap_proxy_should_override(conf, original_status)) {
r->status = original_status;
r->status_line = original_status_line;
}
@@ -543,7 +580,7 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r,
if (status != APR_SUCCESS) {
backend_failed = 1;
}
- if (!conf->error_override || !ap_is_HTTP_ERROR(r->status)) {
+ if (!ap_proxy_should_override(conf, r->status)) {
e = apr_bucket_eos_create(r->connection->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(output_brigade, e);
if (ap_pass_brigade(r->output_filters,
@@ -634,11 +671,10 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r,
}
else {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00892)
- "got response from %pI (%s)",
- conn->worker->cp->addr,
- conn->worker->s->hostname_ex);
+ "got response from %pI (%s:%hu)",
+ conn->addr, conn->hostname, conn->port);
- if (conf->error_override && ap_is_HTTP_ERROR(r->status)) {
+ if (ap_proxy_should_override(conf, r->status)) {
/* clear r->status for override error, otherwise ErrorDocument
* thinks that this is a recursive error, and doesn't find the
* custom error page
@@ -658,9 +694,8 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r,
if (backend_failed) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00893)
- "dialog to %pI (%s) failed",
- conn->worker->cp->addr,
- conn->worker->s->hostname_ex);
+ "dialog to %pI (%s:%hu) failed",
+ conn->addr, conn->hostname, conn->port);
/*
* If we already send data, signal a broken backend connection
* upwards in the chain.
@@ -676,7 +711,18 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r,
*/
rv = HTTP_SERVICE_UNAVAILABLE;
} else {
- rv = HTTP_INTERNAL_SERVER_ERROR;
+ /* If we had a successful cping/cpong and then a timeout
+ * we assume it is a request that cause a back-end timeout,
+ * but doesn't affect the whole worker.
+ */
+ if (APR_STATUS_IS_TIMEUP(status) &&
+ conn->worker->s->ping_timeout_set) {
+ apr_table_setn(r->notes, "proxy_timedout", "1");
+ rv = HTTP_GATEWAY_TIME_OUT;
+ }
+ else {
+ rv = HTTP_INTERNAL_SERVER_ERROR;
+ }
}
}
else if (client_failed) {
@@ -735,7 +781,7 @@ static int proxy_ajp_handler(request_rec *r, proxy_worker *worker,
apr_pool_t *p = r->pool;
apr_uri_t *uri;
- if (strncasecmp(url, "ajp:", 4) != 0) {
+ if (ap_cstr_casecmpn(url, "ajp:", 4) != 0) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00894) "declining URL %s", url);
return DECLINED;
}
@@ -794,8 +840,8 @@ static int proxy_ajp_handler(request_rec *r, proxy_worker *worker,
if (status != APR_SUCCESS) {
backend->close = 1;
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00897)
- "cping/cpong failed to %pI (%s)",
- worker->cp->addr, worker->s->hostname_ex);
+ "cping/cpong failed to %pI (%s:%hu)",
+ backend->addr, backend->hostname, backend->port);
status = HTTP_SERVICE_UNAVAILABLE;
retry++;
continue;
@@ -816,6 +862,7 @@ static void ap_proxy_http_register_hook(apr_pool_t *p)
{
proxy_hook_scheme_handler(proxy_ajp_handler, NULL, NULL, APR_HOOK_FIRST);
proxy_hook_canon_handler(proxy_ajp_canon, NULL, NULL, APR_HOOK_FIRST);
+ APR_REGISTER_OPTIONAL_FN(ajp_handle_cping_cpong);
}
AP_DECLARE_MODULE(proxy_ajp) = {
diff --git a/modules/proxy/mod_proxy_balancer.c b/modules/proxy/mod_proxy_balancer.c
index c59f5e9..b8b452d 100644
--- a/modules/proxy/mod_proxy_balancer.c
+++ b/modules/proxy/mod_proxy_balancer.c
@@ -75,7 +75,7 @@ static int proxy_balancer_canon(request_rec *r, char *url)
apr_port_t port = 0;
/* TODO: offset of BALANCER_PREFIX ?? */
- if (strncasecmp(url, "balancer:", 9) == 0) {
+ if (ap_cstr_casecmpn(url, "balancer:", 9) == 0) {
url += 9;
}
else {
@@ -102,13 +102,37 @@ static int proxy_balancer_canon(request_rec *r, char *url)
if (apr_table_get(r->notes, "proxy-nocanon")) {
path = url; /* this is the raw path */
}
+ else if (apr_table_get(r->notes, "proxy-noencode")) {
+ path = url; /* this is the encoded path already */
+ search = r->args;
+ }
else {
- path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
- r->proxyreq);
+ core_dir_config *d = ap_get_core_module_config(r->per_dir_config);
+ int flags = d->allow_encoded_slashes && !d->decode_encoded_slashes ? PROXY_CANONENC_NOENCODEDSLASHENCODING : 0;
+
+ path = ap_proxy_canonenc_ex(r->pool, url, strlen(url), enc_path, flags,
+ r->proxyreq);
+ if (!path) {
+ return HTTP_BAD_REQUEST;
+ }
search = r->args;
}
- if (path == NULL)
- return HTTP_BAD_REQUEST;
+ /*
+ * If we have a raw control character or a ' ' in nocanon path or
+ * r->args, correct encoding was missed.
+ */
+ if (path == url && *ap_scan_vchar_obstext(path)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10416)
+ "To be forwarded path contains control "
+ "characters or spaces");
+ return HTTP_FORBIDDEN;
+ }
+ if (search && *ap_scan_vchar_obstext(search)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10407)
+ "To be forwarded query string contains control "
+ "characters or spaces");
+ return HTTP_FORBIDDEN;
+ }
r->filename = apr_pstrcat(r->pool, "proxy:" BALANCER_PREFIX, host,
"/", path, (search) ? "?" : "", (search) ? search : "", NULL);
@@ -346,23 +370,27 @@ static proxy_worker *find_best_worker(proxy_balancer *balancer,
proxy_worker *candidate = NULL;
apr_status_t rv;
+#if APR_HAS_THREADS
if ((rv = PROXY_THREAD_LOCK(balancer)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01163)
"%s: Lock failed for find_best_worker()",
balancer->s->name);
return NULL;
}
+#endif
candidate = (*balancer->lbmethod->finder)(balancer, r);
if (candidate)
candidate->s->elected++;
+#if APR_HAS_THREADS
if ((rv = PROXY_THREAD_UNLOCK(balancer)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01164)
"%s: Unlock failed for find_best_worker()",
balancer->s->name);
}
+#endif
if (candidate == NULL) {
/* All the workers are in error state or disabled.
@@ -376,7 +404,7 @@ static proxy_worker *find_best_worker(proxy_balancer *balancer,
/* XXX: This can perhaps be build using some
* smarter mechanism, like tread_cond.
* But since the statuses can came from
- * different childs, use the provided algo.
+ * different children, use the provided algo.
*/
apr_interval_time_t timeout = balancer->s->timeout;
apr_interval_time_t step, tval = 0;
@@ -417,7 +445,7 @@ static int rewrite_url(request_rec *r, proxy_worker *worker,
NULL));
}
- *url = apr_pstrcat(r->pool, worker->s->name, path, NULL);
+ *url = apr_pstrcat(r->pool, worker->s->name_ex, path, NULL);
return OK;
}
@@ -451,8 +479,9 @@ static void force_recovery(proxy_balancer *balancer, server_rec *s)
++(*worker)->s->retries;
(*worker)->s->status &= ~PROXY_WORKER_IN_ERROR;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01165)
- "%s: Forcing recovery for worker (%s)",
- balancer->s->name, (*worker)->s->hostname_ex);
+ "%s: Forcing recovery for worker (%s:%d)",
+ balancer->s->name, (*worker)->s->hostname_ex,
+ (int)(*worker)->s->port);
}
}
}
@@ -492,11 +521,13 @@ static int proxy_balancer_pre_request(proxy_worker **worker,
/* Step 2: Lock the LoadBalancer
* XXX: perhaps we need the process lock here
*/
+#if APR_HAS_THREADS
if ((rv = PROXY_THREAD_LOCK(*balancer)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01166)
"%s: Lock failed for pre_request", (*balancer)->s->name);
return DECLINED;
}
+#endif
/* Step 3: force recovery */
force_recovery(*balancer, r->server);
@@ -557,20 +588,24 @@ static int proxy_balancer_pre_request(proxy_worker **worker,
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01167)
"%s: All workers are in error state for route (%s)",
(*balancer)->s->name, route);
+#if APR_HAS_THREADS
if ((rv = PROXY_THREAD_UNLOCK(*balancer)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01168)
"%s: Unlock failed for pre_request",
(*balancer)->s->name);
}
+#endif
return HTTP_SERVICE_UNAVAILABLE;
}
}
+#if APR_HAS_THREADS
if ((rv = PROXY_THREAD_UNLOCK(*balancer)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01169)
"%s: Unlock failed for pre_request",
(*balancer)->s->name);
}
+#endif
if (!*worker) {
runtime = find_best_worker(*balancer, r);
if (!runtime) {
@@ -608,7 +643,7 @@ static int proxy_balancer_pre_request(proxy_worker **worker,
apr_table_setn(r->subprocess_env,
"BALANCER_NAME", (*balancer)->s->name);
apr_table_setn(r->subprocess_env,
- "BALANCER_WORKER_NAME", (*worker)->s->name);
+ "BALANCER_WORKER_NAME", (*worker)->s->name_ex);
apr_table_setn(r->subprocess_env,
"BALANCER_WORKER_ROUTE", (*worker)->s->route);
@@ -631,7 +666,7 @@ static int proxy_balancer_pre_request(proxy_worker **worker,
}
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01172)
"%s: worker (%s) rewritten to %s",
- (*balancer)->s->name, (*worker)->s->name, *url);
+ (*balancer)->s->name, (*worker)->s->name_ex, *url);
return access_status;
}
@@ -644,12 +679,14 @@ static int proxy_balancer_post_request(proxy_worker *worker,
apr_status_t rv;
+#if APR_HAS_THREADS
if ((rv = PROXY_THREAD_LOCK(balancer)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01173)
"%s: Lock failed for post_request",
balancer->s->name);
return HTTP_INTERNAL_SERVER_ERROR;
}
+#endif
if (!apr_is_empty_array(balancer->errstatuses)
&& !(worker->s->status & PROXY_WORKER_IGNORE_ERRORS)) {
@@ -681,11 +718,12 @@ static int proxy_balancer_post_request(proxy_worker *worker,
worker->s->error_time = apr_time_now();
}
-
+#if APR_HAS_THREADS
if ((rv = PROXY_THREAD_UNLOCK(balancer)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01175)
"%s: Unlock failed for post_request", balancer->s->name);
}
+#endif
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01176)
"proxy_balancer_post_request for (%s)", balancer->s->name);
@@ -784,7 +822,7 @@ static apr_status_t lock_remove(void *data)
/*
* First try to compute an unique ID for each vhost with minimal criteria,
* that is the first Host/IP:port and ServerName. For most cases this should
- * be enough and avoids changing the ID unnecessarily accross restart (or
+ * be enough and avoids changing the ID unnecessarily across restart (or
* stop/start w.r.t. persisted files) for things that this module does not
* care about.
*
@@ -945,7 +983,6 @@ static int balancer_post_config(apr_pool_t *pconf, apr_pool_t *plog,
PROXY_STRNCPY(balancer->s->sname, sname); /* We know this will succeed */
balancer->max_workers = balancer->workers->nelts + balancer->growth;
-
/* Create global mutex */
rv = ap_global_mutex_create(&(balancer->gmutex), NULL, balancer_mutex_type,
balancer->s->sname, s, pconf, 0);
@@ -955,7 +992,6 @@ static int balancer_post_config(apr_pool_t *pconf, apr_pool_t *plog,
balancer->s->sname);
return HTTP_INTERNAL_SERVER_ERROR;
}
-
apr_pool_cleanup_register(pconf, (void *)s, lock_remove,
apr_pool_cleanup_null);
@@ -1078,6 +1114,8 @@ static void push2table(const char *input, apr_table_t *params,
}
ap_unescape_url(key);
ap_unescape_url(val);
+ /* hcuri, worker name, balancer name, at least are escaped when building the form, so twice */
+ ap_unescape_url(val);
if (allowed == NULL) { /* allow all */
apr_table_set(params, key, val);
}
@@ -1095,105 +1133,32 @@ static void push2table(const char *input, apr_table_t *params,
}
}
-/* Manages the loadfactors and member status
- * The balancer, worker and nonce are obtained from
- * the request args (?b=...&w=...&nonce=....).
- * All other params are pulled from any POST
- * data that exists.
- * TODO:
- * /.../<whatever>/balancer/worker/nonce
- */
-static int balancer_handler(request_rec *r)
+/* Returns non-zero if the Referer: header value passed matches the
+ * host of the request. */
+static int safe_referer(request_rec *r, const char *ref)
{
- void *sconf;
- proxy_server_conf *conf;
- proxy_balancer *balancer, *bsel = NULL;
- proxy_worker *worker, *wsel = NULL;
- proxy_worker **workers = NULL;
- apr_table_t *params;
- int i, n;
- int ok2change = 1;
- const char *name;
- const char *action;
- apr_status_t rv;
-
- /* is this for us? */
- if (strcmp(r->handler, "balancer-manager")) {
- return DECLINED;
- }
-
- r->allowed = 0
- | (AP_METHOD_BIT << M_GET)
- | (AP_METHOD_BIT << M_POST);
- if ((r->method_number != M_GET) && (r->method_number != M_POST)) {
- return DECLINED;
- }
-
- sconf = r->server->module_config;
- conf = (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
- params = apr_table_make(r->pool, 10);
-
- balancer = (proxy_balancer *)conf->balancers->elts;
- for (i = 0; i < conf->balancers->nelts; i++, balancer++) {
- if ((rv = PROXY_THREAD_LOCK(balancer)) != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01189)
- "%s: Lock failed for balancer_handler",
- balancer->s->name);
- }
- ap_proxy_sync_balancer(balancer, r->server, conf);
- if ((rv = PROXY_THREAD_UNLOCK(balancer)) != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01190)
- "%s: Unlock failed for balancer_handler",
- balancer->s->name);
- }
- }
+ apr_uri_t uri;
- if (r->args && (r->method_number == M_GET)) {
- const char *allowed[] = { "w", "b", "nonce", "xml", NULL };
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01191) "parsing r->args");
-
- push2table(r->args, params, allowed, r->pool);
- }
- if (r->method_number == M_POST) {
- apr_bucket_brigade *ib;
- apr_size_t len = 1024;
- char *buf = apr_pcalloc(r->pool, len+1);
-
- ib = apr_brigade_create(r->connection->pool, r->connection->bucket_alloc);
- rv = ap_get_brigade(r->input_filters, ib, AP_MODE_READBYTES,
- APR_BLOCK_READ, len);
- if (rv != APR_SUCCESS) {
- return ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
- }
- apr_brigade_flatten(ib, buf, &len);
- buf[len] = '\0';
- push2table(buf, params, NULL, r->pool);
- }
- if ((name = apr_table_get(params, "b")))
- bsel = ap_proxy_get_balancer(r->pool, conf,
- apr_pstrcat(r->pool, BALANCER_PREFIX, name, NULL), 0);
-
- if ((name = apr_table_get(params, "w"))) {
- wsel = ap_proxy_get_worker(r->pool, bsel, conf, name);
- }
+ if (apr_uri_parse(r->pool, ref, &uri) || !uri.hostname)
+ return 0;
+ return strcasecmp(uri.hostname, ap_get_server_name(r)) == 0;
+}
- /* Check that the supplied nonce matches this server's nonce;
- * otherwise ignore all parameters, to prevent a CSRF attack. */
- if (!bsel ||
- (*bsel->s->nonce &&
- (
- (name = apr_table_get(params, "nonce")) == NULL ||
- strcmp(bsel->s->nonce, name) != 0
- )
- )
- ) {
- apr_table_clear(params);
- ok2change = 0;
- }
+/*
+ * Process the paramters and add or update the worker of the
+ * balancer. Must only be called if the nonce has been validated to
+ * match, to avoid XSS attacks.
+ */
+static int balancer_process_balancer_worker(request_rec *r, proxy_server_conf *conf,
+ proxy_balancer *bsel,
+ proxy_worker *wsel,
+ apr_table_t *params)
+{
+ apr_status_t rv;
/* First set the params */
- if (wsel && ok2change) {
+ if (wsel) {
const char *val;
int was_usable = PROXY_WORKER_IS_USABLE(wsel);
@@ -1275,7 +1240,7 @@ static int balancer_handler(request_rec *r)
if ((val = apr_table_get(params, "w_hm"))) {
proxy_hcmethods_t *method = proxy_hcmethods;
for (; method->name; method++) {
- if (!strcasecmp(method->name, val) && method->implemented)
+ if (!ap_cstr_casecmp(method->name, val) && method->implemented)
wsel->s->method = method->method;
}
}
@@ -1292,7 +1257,7 @@ static int balancer_handler(request_rec *r)
*wsel->s->hcexpr = '\0';
}
/* If the health check method doesn't support an expr, then null it */
- if (wsel->s->method == NONE || wsel->s->method == TCP) {
+ if (wsel->s->method == NONE || wsel->s->method == TCP || wsel->s->method == CPING) {
*wsel->s->hcexpr = '\0';
}
/* if enabling, we need to reset all lb params */
@@ -1302,7 +1267,7 @@ static int balancer_handler(request_rec *r)
}
- if (bsel && ok2change) {
+ if (bsel) {
const char *val;
int ival;
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01193)
@@ -1359,11 +1324,13 @@ static int balancer_handler(request_rec *r)
proxy_worker *nworker;
nworker = ap_proxy_get_worker(r->pool, bsel, conf, val);
if (!nworker && storage->num_free_slots(bsel->wslot)) {
+#if APR_HAS_THREADS
if ((rv = PROXY_GLOBAL_LOCK(bsel)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01194)
"%s: Lock failed for adding worker",
bsel->s->name);
}
+#endif
ret = ap_proxy_define_worker(conf->pool, &nworker, bsel, conf, val, 0);
if (!ret) {
unsigned int index;
@@ -1372,63 +1339,137 @@ static int balancer_handler(request_rec *r)
if ((rv = storage->grab(bsel->wslot, &index)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, APLOGNO(01195)
"worker slotmem_grab failed");
+#if APR_HAS_THREADS
if ((rv = PROXY_GLOBAL_UNLOCK(bsel)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01196)
"%s: Unlock failed for adding worker",
bsel->s->name);
}
+#endif
return HTTP_BAD_REQUEST;
}
if ((rv = storage->dptr(bsel->wslot, index, (void *)&shm)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, APLOGNO(01197)
"worker slotmem_dptr failed");
+#if APR_HAS_THREADS
if ((rv = PROXY_GLOBAL_UNLOCK(bsel)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01198)
"%s: Unlock failed for adding worker",
bsel->s->name);
}
+#endif
return HTTP_BAD_REQUEST;
}
if ((rv = ap_proxy_share_worker(nworker, shm, index)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, APLOGNO(01199)
"Cannot share worker");
+#if APR_HAS_THREADS
if ((rv = PROXY_GLOBAL_UNLOCK(bsel)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01200)
"%s: Unlock failed for adding worker",
bsel->s->name);
}
+#endif
return HTTP_BAD_REQUEST;
}
if ((rv = ap_proxy_initialize_worker(nworker, r->server, conf->pool)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, APLOGNO(01201)
"Cannot init worker");
+#if APR_HAS_THREADS
if ((rv = PROXY_GLOBAL_UNLOCK(bsel)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01202)
"%s: Unlock failed for adding worker",
bsel->s->name);
}
+#endif
return HTTP_BAD_REQUEST;
}
/* sync all timestamps */
bsel->wupdated = bsel->s->wupdated = nworker->s->updated = apr_time_now();
/* by default, all new workers are disabled */
ap_proxy_set_wstatus(PROXY_WORKER_DISABLED_FLAG, 1, nworker);
+ } else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10163)
+ "%s: failed to add worker %s",
+ bsel->s->name, val);
+#if APR_HAS_THREADS
+ PROXY_GLOBAL_UNLOCK(bsel);
+#endif
+ return HTTP_BAD_REQUEST;
}
+#if APR_HAS_THREADS
if ((rv = PROXY_GLOBAL_UNLOCK(bsel)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01203)
"%s: Unlock failed for adding worker",
bsel->s->name);
}
+#endif
+ } else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10164)
+ "%s: failed to add worker %s",
+ bsel->s->name, val);
+ return HTTP_BAD_REQUEST;
}
}
}
+ return APR_SUCCESS;
+}
+
+/*
+ * Process a request for balancer or worker management from another module
+ */
+static apr_status_t balancer_manage(request_rec *r, apr_table_t *params)
+{
+ void *sconf;
+ proxy_server_conf *conf;
+ proxy_balancer *bsel = NULL;
+ proxy_worker *wsel = NULL;
+ const char *name;
+ sconf = r->server->module_config;
+ conf = (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
+
+ /* Process the parameters */
+ if ((name = apr_table_get(params, "b"))) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "balancer_manage "
+ "balancer: %s", name);
+ bsel = ap_proxy_get_balancer(r->pool, conf,
+ apr_pstrcat(r->pool, BALANCER_PREFIX, name, NULL), 0);
+ }
+
+ if ((name = apr_table_get(params, "w"))) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "balancer_manage "
+ "worker: %s", name);
+ wsel = ap_proxy_get_worker(r->pool, bsel, conf, name);
+ }
+ if (bsel) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "balancer_manage "
+ "balancer: %s", bsel->s->name);
+ return(balancer_process_balancer_worker(r, conf, bsel, wsel, params));
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "balancer_manage failed: "
+ "No balancer!");
+ return HTTP_BAD_REQUEST;
+}
+/*
+ * builds the page and links to configure via HTLM or XML.
+ */
+static void balancer_display_page(request_rec *r, proxy_server_conf *conf,
+ proxy_balancer *bsel,
+ proxy_worker *wsel,
+ int usexml)
+{
+ const char *action;
+ proxy_balancer *balancer;
+ proxy_worker *worker;
+ proxy_worker **workers;
+ int i, n;
action = ap_construct_url(r->pool, r->uri, r);
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01204) "genning page");
- if (apr_table_get(params, "xml")) {
+ if (usexml) {
char date[APR_RFC822_DATE_LEN];
ap_set_content_type(r, "text/xml");
ap_rputs("<?xml version='1.0' encoding='UTF-8' ?>\n", r);
@@ -1440,7 +1481,7 @@ static int balancer_handler(request_rec *r)
/* Start proxy_balancer */
ap_rvputs(r, " <httpd:name>", balancer->s->name, "</httpd:name>\n", NULL);
if (*balancer->s->sticky) {
- ap_rvputs(r, " <httpd:stickysession>", balancer->s->sticky,
+ ap_rvputs(r, " <httpd:stickysession>", ap_escape_html(r->pool, balancer->s->sticky),
"</httpd:stickysession>\n", NULL);
ap_rprintf(r,
" <httpd:nofailover>%s</httpd:nofailover>\n",
@@ -1551,7 +1592,7 @@ static int balancer_handler(request_rec *r)
ap_rprintf(r, " <httpd:lbset>%d</httpd:lbset>\n",
worker->s->lbset);
/* End proxy_worker_stat */
- if (!strcasecmp(worker->s->scheme, "ajp")) {
+ if (!ap_cstr_casecmp(worker->s->scheme, "ajp")) {
ap_rputs(" <httpd:flushpackets>", r);
switch (worker->s->flush_packets) {
case flush_off:
@@ -1650,10 +1691,10 @@ static int balancer_handler(request_rec *r)
for (i = 0; i < conf->balancers->nelts; i++) {
ap_rputs("<hr />\n<h3>LoadBalancer Status for ", r);
- ap_rvputs(r, "<a href='", ap_escape_uri(r->pool, r->uri), "?b=",
+ ap_rvputs(r, "<a href=\"", ap_escape_uri(r->pool, r->uri), "?b=",
balancer->s->name + sizeof(BALANCER_PREFIX) - 1,
"&amp;nonce=", balancer->s->nonce,
- "'>", NULL);
+ "\">", NULL);
ap_rvputs(r, balancer->s->name, "</a> [",balancer->s->sname, "]</h3>\n", NULL);
ap_rputs("\n\n<table><tr>"
"<th>MaxMembers</th><th>StickySession</th><th>DisableFailover</th><th>Timeout</th><th>FailoverAttempts</th><th>Method</th>"
@@ -1664,11 +1705,11 @@ static int balancer_handler(request_rec *r)
balancer->max_workers - (int)storage->num_free_slots(balancer->wslot));
if (*balancer->s->sticky) {
if (strcmp(balancer->s->sticky, balancer->s->sticky_path)) {
- ap_rvputs(r, "<td>", balancer->s->sticky, " | ",
- balancer->s->sticky_path, NULL);
+ ap_rvputs(r, "<td>", ap_escape_html(r->pool, balancer->s->sticky), " | ",
+ ap_escape_html(r->pool, balancer->s->sticky_path), NULL);
}
else {
- ap_rvputs(r, "<td>", balancer->s->sticky, NULL);
+ ap_rvputs(r, "<td>", ap_escape_html(r->pool, balancer->s->sticky), NULL);
}
}
else {
@@ -1688,7 +1729,7 @@ static int balancer_handler(request_rec *r)
ap_rvputs(r, balancer->s->vpath, "</td>\n", NULL);
ap_rprintf(r, "<td>%s</td>\n",
!balancer->s->inactive ? "Yes" : "No");
- ap_rputs("</table>\n<br />", r);
+ ap_rputs("</tr>\n</table>\n<br />", r);
ap_rputs("\n\n<table><tr>"
"<th>Worker URL</th>"
"<th>Route</th><th>RouteRedir</th>"
@@ -1703,12 +1744,12 @@ static int balancer_handler(request_rec *r)
for (n = 0; n < balancer->workers->nelts; n++) {
char fbuf[50];
worker = *workers;
- ap_rvputs(r, "<tr>\n<td><a href='",
+ ap_rvputs(r, "<tr>\n<td><a href=\"",
ap_escape_uri(r->pool, r->uri), "?b=",
balancer->s->name + sizeof(BALANCER_PREFIX) - 1, "&amp;w=",
- ap_escape_uri(r->pool, worker->s->name),
+ ap_escape_uri(r->pool, worker->s->name_ex),
"&amp;nonce=", balancer->s->nonce,
- "'>", NULL);
+ "\">", NULL);
ap_rvputs(r, (*worker->s->uds_path ? "<i>" : ""), ap_proxy_worker_name(r->pool, worker),
(*worker->s->uds_path ? "</i>" : ""), "</a></td>", NULL);
ap_rvputs(r, "<td>", ap_escape_html(r->pool, worker->s->route),
@@ -1730,7 +1771,7 @@ static int balancer_handler(request_rec *r)
ap_rprintf(r, "<td>%" APR_TIME_T_FMT "ms</td>", apr_time_as_msec(worker->s->interval));
ap_rprintf(r, "<td>%d (%d)</td>", worker->s->passes,worker->s->pcount);
ap_rprintf(r, "<td>%d (%d)</td>", worker->s->fails, worker->s->fcount);
- ap_rprintf(r, "<td>%s</td>", worker->s->hcuri);
+ ap_rprintf(r, "<td>%s</td>", ap_escape_html(r->pool, worker->s->hcuri));
ap_rprintf(r, "<td>%s", worker->s->hcexpr);
}
ap_rputs("</td></tr>\n", r);
@@ -1747,20 +1788,20 @@ static int balancer_handler(request_rec *r)
if (wsel && bsel) {
ap_rputs("<h3>Edit worker settings for ", r);
ap_rvputs(r, (*wsel->s->uds_path?"<i>":""), ap_proxy_worker_name(r->pool, wsel), (*wsel->s->uds_path?"</i>":""), "</h3>\n", NULL);
- ap_rputs("<form method='POST' enctype='application/x-www-form-urlencoded' action='", r);
- ap_rvputs(r, ap_escape_uri(r->pool, action), "'>\n", NULL);
+ ap_rputs("<form method='POST' enctype='application/x-www-form-urlencoded' action=\"", r);
+ ap_rvputs(r, ap_escape_uri(r->pool, action), "\">\n", NULL);
ap_rputs("<table><tr><td>Load factor:</td><td><input name='w_lf' id='w_lf' type=text ", r);
ap_rprintf(r, "value='%.2f'></td></tr>\n", (float)(wsel->s->lbfactor)/100.0);
ap_rputs("<tr><td>LB Set:</td><td><input name='w_ls' id='w_ls' type=text ", r);
ap_rprintf(r, "value='%d'></td></tr>\n", wsel->s->lbset);
ap_rputs("<tr><td>Route:</td><td><input name='w_wr' id='w_wr' type=text ", r);
- ap_rvputs(r, "value='", ap_escape_html(r->pool, wsel->s->route),
+ ap_rvputs(r, "value=\"", ap_escape_html(r->pool, wsel->s->route),
NULL);
- ap_rputs("'></td></tr>\n", r);
+ ap_rputs("\"></td></tr>\n", r);
ap_rputs("<tr><td>Route Redirect:</td><td><input name='w_rr' id='w_rr' type=text ", r);
- ap_rvputs(r, "value='", ap_escape_html(r->pool, wsel->s->redirect),
+ ap_rvputs(r, "value=\"", ap_escape_html(r->pool, wsel->s->redirect),
NULL);
- ap_rputs("'></td></tr>\n", r);
+ ap_rputs("\"></td></tr>\n", r);
ap_rputs("<tr><td>Status:</td>", r);
ap_rputs("<td><table><tr>"
"<th>Ignore Errors</th>"
@@ -1798,22 +1839,22 @@ static int balancer_handler(request_rec *r)
ap_rputs("<tr><td>Expr</td><td><select name='w_he'>\n", r);
hc_select_exprs_f(r, wsel->s->hcexpr);
ap_rputs("</select>\n</td></tr>\n", r);
- ap_rprintf(r, "<tr><td>Interval (ms)</td><td><input name='w_hi' id='w_hi' type='text'"
+ ap_rprintf(r, "<tr><td>Interval (ms)</td><td><input name='w_hi' id='w_hi' type='text' "
"value='%" APR_TIME_T_FMT "'></td></tr>\n", apr_time_as_msec(wsel->s->interval));
- ap_rprintf(r, "<tr><td>Passes trigger</td><td><input name='w_hp' id='w_hp' type='text'"
+ ap_rprintf(r, "<tr><td>Passes trigger</td><td><input name='w_hp' id='w_hp' type='text' "
"value='%d'></td></tr>\n", wsel->s->passes);
- ap_rprintf(r, "<tr><td>Fails trigger)</td><td><input name='w_hf' id='w_hf' type='text'"
+ ap_rprintf(r, "<tr><td>Fails trigger)</td><td><input name='w_hf' id='w_hf' type='text' "
"value='%d'></td></tr>\n", wsel->s->fails);
- ap_rprintf(r, "<tr><td>HC uri</td><td><input name='w_hu' id='w_hu' type='text'"
- "value='%s'</td></tr>\n", ap_escape_html(r->pool, wsel->s->hcuri));
+ ap_rprintf(r, "<tr><td>HC uri</td><td><input name='w_hu' id='w_hu' type='text' "
+ "value=\"%s\"></td></tr>\n", ap_escape_html(r->pool, wsel->s->hcuri));
ap_rputs("</table>\n</td></tr>\n", r);
}
ap_rputs("<tr><td colspan='2'><input type=submit value='Submit'></td></tr>\n", r);
ap_rvputs(r, "</table>\n<input type=hidden name='w' id='w' ", NULL);
- ap_rvputs(r, "value='", ap_escape_uri(r->pool, wsel->s->name), "'>\n", NULL);
+ ap_rvputs(r, "value=\"", ap_escape_uri(r->pool, wsel->s->name_ex), "\">\n", NULL);
ap_rvputs(r, "<input type=hidden name='b' id='b' ", NULL);
- ap_rvputs(r, "value='", bsel->s->name + sizeof(BALANCER_PREFIX) - 1,
- "'>\n", NULL);
+ ap_rvputs(r, "value=\"", ap_escape_html(r->pool, bsel->s->name + sizeof(BALANCER_PREFIX) - 1),
+ "\">\n", NULL);
ap_rvputs(r, "<input type=hidden name='nonce' id='nonce' value='",
bsel->s->nonce, "'>\n", NULL);
ap_rputs("</form>\n", r);
@@ -1823,9 +1864,9 @@ static int balancer_handler(request_rec *r)
const ap_list_provider_names_t *pname;
int i;
ap_rputs("<h3>Edit balancer settings for ", r);
- ap_rvputs(r, bsel->s->name, "</h3>\n", NULL);
- ap_rputs("<form method='POST' enctype='application/x-www-form-urlencoded' action='", r);
- ap_rvputs(r, ap_escape_uri(r->pool, action), "'>\n", NULL);
+ ap_rvputs(r, ap_escape_html(r->pool, bsel->s->name), "</h3>\n", NULL);
+ ap_rputs("<form method='POST' enctype='application/x-www-form-urlencoded' action=\"", r);
+ ap_rvputs(r, ap_escape_uri(r->pool, action), "\">\n", NULL);
ap_rputs("<table>\n", r);
provs = ap_list_provider_names(r->pool, PROXY_LBMETHOD, "0");
if (provs) {
@@ -1846,15 +1887,16 @@ static int balancer_handler(request_rec *r)
ap_rprintf(r, "value='%d'></td></tr>\n", bsel->s->max_attempts);
ap_rputs("<tr><td>Disable Failover:</td>", r);
create_radio("b_sforce", bsel->s->sticky_force, r);
+ ap_rputs("</tr>\n", r);
ap_rputs("<tr><td>Sticky Session:</td><td><input name='b_ss' id='b_ss' size=64 type=text ", r);
if (strcmp(bsel->s->sticky, bsel->s->sticky_path)) {
- ap_rvputs(r, "value ='", bsel->s->sticky, " | ",
- bsel->s->sticky_path, NULL);
+ ap_rvputs(r, "value =\"", ap_escape_html(r->pool, bsel->s->sticky), " | ",
+ ap_escape_html(r->pool, bsel->s->sticky_path), NULL);
}
else {
- ap_rvputs(r, "value ='", bsel->s->sticky, NULL);
+ ap_rvputs(r, "value =\"", ap_escape_html(r->pool, bsel->s->sticky), NULL);
}
- ap_rputs("'>&nbsp;&nbsp;&nbsp;&nbsp;(Use '-' to delete)</td></tr>\n", r);
+ ap_rputs("\">&nbsp;&nbsp;&nbsp;&nbsp;(Use '-' to delete)</td></tr>\n", r);
if (storage->num_free_slots(bsel->wslot) != 0) {
ap_rputs("<tr><td>Add New Worker:</td><td><input name='b_nwrkr' id='b_nwrkr' size=32 type=text>"
"&nbsp;&nbsp;&nbsp;&nbsp;Are you sure? <input name='b_wyes' id='b_wyes' type=checkbox value='1'>"
@@ -1862,8 +1904,8 @@ static int balancer_handler(request_rec *r)
}
ap_rputs("<tr><td colspan=2><input type=submit value='Submit'></td></tr>\n", r);
ap_rvputs(r, "</table>\n<input type=hidden name='b' id='b' ", NULL);
- ap_rvputs(r, "value='", bsel->s->name + sizeof(BALANCER_PREFIX) - 1,
- "'>\n", NULL);
+ ap_rvputs(r, "value=\"", ap_escape_html(r->pool, bsel->s->name + sizeof(BALANCER_PREFIX) - 1),
+ "\">\n", NULL);
ap_rvputs(r, "<input type=hidden name='nonce' id='nonce' value='",
bsel->s->nonce, "'>\n", NULL);
ap_rputs("</form>\n", r);
@@ -1873,6 +1915,123 @@ static int balancer_handler(request_rec *r)
ap_rputs("</body></html>\n", r);
ap_rflush(r);
}
+}
+
+/* Manages the loadfactors and member status
+ * The balancer, worker and nonce are obtained from
+ * the request args (?b=...&w=...&nonce=....).
+ * All other params are pulled from any POST
+ * data that exists.
+ * TODO:
+ * /.../<whatever>/balancer/worker/nonce
+ */
+static int balancer_handler(request_rec *r)
+{
+ void *sconf;
+ proxy_server_conf *conf;
+ proxy_balancer *balancer, *bsel = NULL;
+ proxy_worker *wsel = NULL;
+ apr_table_t *params;
+ int i;
+ const char *name, *ref;
+ apr_status_t rv;
+
+ /* is this for us? */
+ if (strcmp(r->handler, "balancer-manager")) {
+ return DECLINED;
+ }
+
+ r->allowed = 0
+ | (AP_METHOD_BIT << M_GET)
+ | (AP_METHOD_BIT << M_POST);
+ if ((r->method_number != M_GET) && (r->method_number != M_POST)) {
+ return DECLINED;
+ }
+
+ sconf = r->server->module_config;
+ conf = (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
+ params = apr_table_make(r->pool, 10);
+
+ balancer = (proxy_balancer *)conf->balancers->elts;
+ for (i = 0; i < conf->balancers->nelts; i++, balancer++) {
+#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_LOCK(balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01189)
+ "%s: Lock failed for balancer_handler",
+ balancer->s->name);
+ }
+#endif
+ ap_proxy_sync_balancer(balancer, r->server, conf);
+#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_UNLOCK(balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01190)
+ "%s: Unlock failed for balancer_handler",
+ balancer->s->name);
+ }
+#endif
+ }
+
+ if (r->args && (r->method_number == M_GET)) {
+ const char *allowed[] = { "w", "b", "nonce", "xml", NULL };
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01191) "parsing r->args");
+
+ push2table(r->args, params, allowed, r->pool);
+ }
+ if (r->method_number == M_POST) {
+ apr_bucket_brigade *ib;
+ apr_size_t len = 1024;
+ char *buf = apr_pcalloc(r->pool, len+1);
+
+ ib = apr_brigade_create(r->connection->pool, r->connection->bucket_alloc);
+ rv = ap_get_brigade(r->input_filters, ib, AP_MODE_READBYTES,
+ APR_BLOCK_READ, len);
+ if (rv != APR_SUCCESS) {
+ return ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
+ }
+ apr_brigade_flatten(ib, buf, &len);
+ buf[len] = '\0';
+ push2table(buf, params, NULL, r->pool);
+ }
+
+ /* Ignore parameters if this looks like XSRF */
+ ref = apr_table_get(r->headers_in, "Referer");
+ if (apr_table_elts(params)
+ && (!ref || !safe_referer(r, ref))) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10187)
+ "ignoring params in balancer-manager cross-site access %s: %s", ref, ap_get_server_name(r));
+ apr_table_clear(params);
+ }
+
+ /* Process the parameters */
+ if ((name = apr_table_get(params, "b")))
+ bsel = ap_proxy_get_balancer(r->pool, conf,
+ apr_pstrcat(r->pool, BALANCER_PREFIX, name, NULL), 0);
+
+ if ((name = apr_table_get(params, "w"))) {
+ wsel = ap_proxy_get_worker(r->pool, bsel, conf, name);
+ }
+
+
+ /* Check that the supplied nonce matches this server's nonce;
+ * otherwise ignore all parameters, to prevent a CSRF
+ * attack. */
+ if (bsel
+ && (*bsel->s->nonce
+ && ((name = apr_table_get(params, "nonce")) != NULL
+ && strcmp(bsel->s->nonce, name) == 0))) {
+ /* Process the parameters and add the worker to the balancer */
+ rv = balancer_process_balancer_worker(r, conf, bsel, wsel, params);
+ if (rv != APR_SUCCESS) {
+ return HTTP_BAD_REQUEST;
+ }
+ }
+
+ /* display the HTML or XML page */
+ if (apr_table_get(params, "xml")) {
+ balancer_display_page(r, conf, bsel, wsel, 1);
+ } else {
+ balancer_display_page(r, conf, bsel, wsel, 0);
+ }
return DONE;
}
@@ -1905,7 +2064,7 @@ static void balancer_child_init(apr_pool_t *p, server_rec *s)
balancer->s->name);
exit(1); /* Ugly, but what else? */
}
- init_balancer_members(conf->pool, s, balancer);
+ init_balancer_members(p, s, balancer);
}
s = s->next;
}
@@ -1921,6 +2080,7 @@ static void ap_proxy_balancer_register_hook(apr_pool_t *p)
static const char *const aszPred[] = { "mpm_winnt.c", "mod_slotmem_shm.c", NULL};
static const char *const aszPred2[] = { "mod_proxy.c", NULL};
/* manager handler */
+ APR_REGISTER_OPTIONAL_FN(balancer_manage);
ap_hook_post_config(balancer_post_config, aszPred2, NULL, APR_HOOK_MIDDLE);
ap_hook_pre_config(balancer_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_handler(balancer_handler, NULL, NULL, APR_HOOK_FIRST);
diff --git a/modules/proxy/mod_proxy_connect.c b/modules/proxy/mod_proxy_connect.c
index 7be6a6a..5a68135 100644
--- a/modules/proxy/mod_proxy_connect.c
+++ b/modules/proxy/mod_proxy_connect.c
@@ -39,7 +39,7 @@ module AP_MODULE_DECLARE_DATA proxy_connect_module;
* that may be okay, since the data is supposed to
* be transparent. In fact, this doesn't log at all
* yet. 8^)
- * FIXME: doesn't check any headers initally sent from the
+ * FIXME: doesn't check any headers initially sent from the
* client.
* FIXME: should allow authentication, but hopefully the
* generic proxy authentication is good enough.
@@ -156,25 +156,21 @@ static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
apr_socket_t *sock;
conn_rec *c = r->connection;
conn_rec *backconn;
- int done = 0;
- apr_bucket_brigade *bb_front;
- apr_bucket_brigade *bb_back;
apr_status_t rv;
apr_size_t nbytes;
char buffer[HUGE_STRING_LEN];
- apr_socket_t *client_socket = ap_get_conn_socket(c);
+
+ apr_bucket_brigade *bb;
+ proxy_tunnel_rec *tunnel;
int failed, rc;
- apr_pollset_t *pollset;
- apr_pollfd_t pollfd;
- const apr_pollfd_t *signalled;
- apr_int32_t pollcnt, pi;
- apr_int16_t pollevent;
- apr_sockaddr_t *nexthop;
apr_uri_t uri;
const char *connectname;
apr_port_t connectport = 0;
+ apr_sockaddr_t *nexthop;
+
+ apr_interval_time_t current_timeout;
/* is this for us? */
if (r->method_number != M_CONNECT) {
@@ -261,28 +257,6 @@ static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
}
}
- /* setup polling for connection */
- ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "setting up poll()");
-
- if ((rv = apr_pollset_create(&pollset, 2, r->pool, 0)) != APR_SUCCESS) {
- apr_socket_close(sock);
- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01020)
- "error apr_pollset_create()");
- return HTTP_INTERNAL_SERVER_ERROR;
- }
-
- /* Add client side to the poll */
- pollfd.p = r->pool;
- pollfd.desc_type = APR_POLL_SOCKET;
- pollfd.reqevents = APR_POLLIN | APR_POLLHUP;
- pollfd.desc.s = client_socket;
- pollfd.client_data = NULL;
- apr_pollset_add(pollset, &pollfd);
-
- /* Add the server side to the poll */
- pollfd.desc.s = sock;
- apr_pollset_add(pollset, &pollfd);
-
/*
* Step Three: Send the Request
*
@@ -300,13 +274,22 @@ static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
return HTTP_INTERNAL_SERVER_ERROR;
}
ap_proxy_ssl_engine(backconn, r->per_dir_config, 0);
+
+ /*
+ * save the timeout of the socket because core_pre_connection
+ * will set it to base_server->timeout
+ * (core TimeOut directive).
+ */
+ apr_socket_timeout_get(sock, &current_timeout);
rc = ap_run_pre_connection(backconn, sock);
if (rc != OK && rc != DONE) {
backconn->aborted = 1;
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01022)
"pre_connection setup failed (%d)", rc);
+ apr_socket_close(sock);
return HTTP_INTERNAL_SERVER_ERROR;
}
+ apr_socket_timeout_set(sock, current_timeout);
ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
"connection complete to %pI (%s)",
@@ -314,9 +297,7 @@ static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
apr_table_setn(r->notes, "proxy-source-port", apr_psprintf(r->pool, "%hu",
backconn->local_addr->port));
-
- bb_front = apr_brigade_create(p, c->bucket_alloc);
- bb_back = apr_brigade_create(p, backconn->bucket_alloc);
+ bb = apr_brigade_create(p, c->bucket_alloc);
/* If we are connecting through a remote proxy, we need to pass
* the CONNECT request on to it.
@@ -326,24 +307,24 @@ static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
*/
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
"sending the CONNECT request to the remote proxy");
- ap_fprintf(backconn->output_filters, bb_back,
+ ap_fprintf(backconn->output_filters, bb,
"CONNECT %s HTTP/1.0" CRLF, r->uri);
- ap_fprintf(backconn->output_filters, bb_back,
+ ap_fprintf(backconn->output_filters, bb,
"Proxy-agent: %s" CRLF CRLF, ap_get_server_banner());
- ap_fflush(backconn->output_filters, bb_back);
+ ap_fflush(backconn->output_filters, bb);
}
else {
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "Returning 200 OK");
nbytes = apr_snprintf(buffer, sizeof(buffer),
"HTTP/1.0 200 Connection Established" CRLF);
ap_xlate_proto_to_ascii(buffer, nbytes);
- ap_fwrite(c->output_filters, bb_front, buffer, nbytes);
+ ap_fwrite(c->output_filters, bb, buffer, nbytes);
nbytes = apr_snprintf(buffer, sizeof(buffer),
"Proxy-agent: %s" CRLF CRLF,
ap_get_server_banner());
ap_xlate_proto_to_ascii(buffer, nbytes);
- ap_fwrite(c->output_filters, bb_front, buffer, nbytes);
- ap_fflush(c->output_filters, bb_front);
+ ap_fwrite(c->output_filters, bb, buffer, nbytes);
+ ap_fflush(c->output_filters, bb);
#if 0
/* This is safer code, but it doesn't work yet. I'm leaving it
* here so that I can fix it later.
@@ -354,8 +335,7 @@ static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
ap_rflush(r);
#endif
}
-
- ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "setting up poll()");
+ apr_brigade_cleanup(bb);
/*
* Step Four: Handle Data Transfer
@@ -363,88 +343,28 @@ static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
* Handle two way transfer of data over the socket (this is a tunnel).
*/
- /* we are now acting as a tunnel - the input/output filter stacks should
- * not contain any non-connection filters.
- */
- r->output_filters = c->output_filters;
- r->proto_output_filters = c->output_filters;
- r->input_filters = c->input_filters;
- r->proto_input_filters = c->input_filters;
-/* r->sent_bodyct = 1;*/
-
- do { /* Loop until done (one side closes the connection, or an error) */
- rv = apr_pollset_poll(pollset, -1, &pollcnt, &signalled);
- if (rv != APR_SUCCESS) {
- if (APR_STATUS_IS_EINTR(rv)) {
- continue;
- }
- apr_socket_close(sock);
- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01023) "error apr_poll()");
- return HTTP_INTERNAL_SERVER_ERROR;
- }
-#ifdef DEBUGGING
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01024)
- "woke from poll(), i=%d", pollcnt);
-#endif
-
- for (pi = 0; pi < pollcnt; pi++) {
- const apr_pollfd_t *cur = &signalled[pi];
+ /* r->sent_bodyct = 1; */
- if (cur->desc.s == sock) {
- pollevent = cur->rtnevents;
- if (pollevent & (APR_POLLIN | APR_POLLHUP)) {
-#ifdef DEBUGGING
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01025)
- "sock was readable");
-#endif
- done |= ap_proxy_transfer_between_connections(r, backconn,
- c, bb_back,
- bb_front,
- "sock", NULL,
- CONN_BLKSZ, 1)
- != APR_SUCCESS;
- }
- else if (pollevent & APR_POLLERR) {
- ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(01026)
- "err on backconn");
- backconn->aborted = 1;
- done = 1;
- }
- }
- else if (cur->desc.s == client_socket) {
- pollevent = cur->rtnevents;
- if (pollevent & (APR_POLLIN | APR_POLLHUP)) {
-#ifdef DEBUGGING
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01027)
- "client was readable");
-#endif
- done |= ap_proxy_transfer_between_connections(r, c,
- backconn,
- bb_front,
- bb_back,
- "client",
- NULL,
- CONN_BLKSZ, 1)
- != APR_SUCCESS;
- }
- else if (pollevent & APR_POLLERR) {
- ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02827)
- "err on client");
- c->aborted = 1;
- done = 1;
- }
- }
- else {
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01028)
- "unknown socket in pollset");
- done = 1;
- }
+ rv = ap_proxy_tunnel_create(&tunnel, r, backconn, "CONNECT");
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10208)
+ "can't create tunnel for %pI (%s)",
+ nexthop, connectname);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ rc = ap_proxy_tunnel_run(tunnel);
+ if (ap_is_HTTP_ERROR(rc)) {
+ if (rc == HTTP_GATEWAY_TIME_OUT) {
+ /* ap_proxy_tunnel_run() didn't log this */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10224)
+ "tunnel timed out");
}
- } while (!done);
-
- ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
- "finished with poll() - cleaning up");
+ /* Don't send an error page if we sent data already */
+ if (proxyport && !tunnel->replied) {
+ return rc;
+ }
+ }
/*
* Step Five: Clean Up
@@ -457,8 +377,6 @@ static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
else
ap_lingering_close(backconn);
- c->keepalive = AP_CONN_CLOSE;
-
return OK;
}
diff --git a/modules/proxy/mod_proxy_express.c b/modules/proxy/mod_proxy_express.c
index 0f5d604..5d458c4 100644
--- a/modules/proxy/mod_proxy_express.c
+++ b/modules/proxy/mod_proxy_express.c
@@ -19,6 +19,11 @@
module AP_MODULE_DECLARE_DATA proxy_express_module;
+#include "apr_version.h"
+#if !APR_VERSION_AT_LEAST(2,0,0)
+#include "apu_version.h"
+#endif
+
static int proxy_available = 0;
typedef struct {
@@ -115,6 +120,10 @@ static int xlate_name(request_rec *r)
struct proxy_alias *ralias;
proxy_dir_conf *dconf;
express_server_conf *sconf;
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
sconf = ap_get_module_config(r->server->module_config, &proxy_express_module);
dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
@@ -132,11 +141,31 @@ static int xlate_name(request_rec *r)
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01002)
"proxy_express: Opening DBM file: %s (%s)",
sconf->dbmfile, sconf->dbmtype);
+
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ rv = apr_dbm_get_driver(&driver, sconf->dbmtype, &err, r->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ APLOGNO(10275) "The dbm library '%s' could not be loaded: %s (%s: %d)",
+ sconf->dbmtype, err->msg, err->reason, err->rc);
+ return DECLINED;
+ }
+
+ rv = apr_dbm_open2(&db, driver, sconf->dbmfile, APR_DBM_READONLY,
+ APR_OS_DEFAULT, r->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ APLOGNO(10276) "The '%s' file '%s' could not be loaded",
+ sconf->dbmtype, sconf->dbmfile);
+ return DECLINED;
+ }
+#else
rv = apr_dbm_open_ex(&db, sconf->dbmtype, sconf->dbmfile, APR_DBM_READONLY,
APR_OS_DEFAULT, r->pool);
if (rv != APR_SUCCESS) {
return DECLINED;
}
+#endif
name = ap_get_server_name(r);
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01003)
diff --git a/modules/proxy/mod_proxy_fcgi.c b/modules/proxy/mod_proxy_fcgi.c
index 2e97408..d420df6 100644
--- a/modules/proxy/mod_proxy_fcgi.c
+++ b/modules/proxy/mod_proxy_fcgi.c
@@ -92,15 +92,30 @@ static int proxy_fcgi_canon(request_rec *r, char *url)
host = apr_pstrcat(r->pool, "[", host, "]", NULL);
}
- if (apr_table_get(r->notes, "proxy-nocanon")) {
- path = url; /* this is the raw path */
+ if (apr_table_get(r->notes, "proxy-nocanon")
+ || apr_table_get(r->notes, "proxy-noencode")) {
+ path = url; /* this is the raw/encoded path */
}
else {
- path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
- r->proxyreq);
+ core_dir_config *d = ap_get_core_module_config(r->per_dir_config);
+ int flags = d->allow_encoded_slashes && !d->decode_encoded_slashes ? PROXY_CANONENC_NOENCODEDSLASHENCODING : 0;
+
+ path = ap_proxy_canonenc_ex(r->pool, url, strlen(url), enc_path, flags,
+ r->proxyreq);
+ if (!path) {
+ return HTTP_BAD_REQUEST;
+ }
+ }
+ /*
+ * If we have a raw control character or a ' ' in nocanon path,
+ * correct encoding was missed.
+ */
+ if (path == url && *ap_scan_vchar_obstext(path)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10414)
+ "To be forwarded path contains control "
+ "characters or spaces");
+ return HTTP_FORBIDDEN;
}
- if (path == NULL)
- return HTTP_BAD_REQUEST;
r->filename = apr_pstrcat(r->pool, "proxy:fcgi://", host, sport, "/",
path, NULL);
@@ -164,7 +179,7 @@ static int proxy_fcgi_canon(request_rec *r, char *url)
ProxyFCGISetEnvIf "reqenv('PATH_INFO') =~ m#/foo(\d+)\.php$#" PATH_INFO "/foo.php"
ProxyFCGISetEnvIf "reqenv('PATH_TRANSLATED') =~ m#(/.*foo)(\d+)(.*)#" PATH_TRANSLATED "$1$3"
*/
-static void fix_cgivars(request_rec *r, fcgi_dirconf_t *dconf)
+static apr_status_t fix_cgivars(request_rec *r, fcgi_dirconf_t *dconf)
{
sei_entry *entries;
const char *err, *src;
@@ -175,10 +190,21 @@ static void fix_cgivars(request_rec *r, fcgi_dirconf_t *dconf)
for (i = 0; i < dconf->env_fixups->nelts; i++) {
sei_entry *entry = &entries[i];
+ rc = ap_expr_exec_re(r, entry->cond, AP_MAX_REG_MATCH, regm, &src, &err);
+ if (rc < 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10241)
+ "fix_cgivars: Condition eval returned %d: %s",
+ rc, err);
+ return APR_EGENERAL;
+ }
+ else if (rc == 0) {
+ continue; /* evaluated false */
+ }
+
if (entry->envname[0] == '!') {
apr_table_unset(r->subprocess_env, entry->envname+1);
}
- else if (0 < (rc = ap_expr_exec_re(r, entry->cond, AP_MAX_REG_MATCH, regm, &src, &err))) {
+ else {
const char *val = ap_expr_str_exec_re(r, entry->subst, AP_MAX_REG_MATCH, regm, &src, &err);
if (err) {
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(03514)
@@ -195,10 +221,8 @@ static void fix_cgivars(request_rec *r, fcgi_dirconf_t *dconf)
}
apr_table_setn(r->subprocess_env, entry->envname, val);
}
- else {
- ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r, "fix_cgivars: Condition returned %d", rc);
- }
}
+ return APR_SUCCESS;
}
/* Wrapper for apr_socket_sendv that handles updating the worker stats. */
@@ -367,7 +391,9 @@ static apr_status_t send_environment(proxy_conn_rec *conn, request_rec *r,
/* XXX are there any FastCGI specific env vars we need to send? */
/* Give admins final option to fine-tune env vars */
- fix_cgivars(r, dconf);
+ if (APR_SUCCESS != (rv = fix_cgivars(r, dconf))) {
+ return rv;
+ }
/* XXX mod_cgi/mod_cgid use ap_create_environment here, which fills in
* the TZ value specially. We could use that, but it would mean
@@ -521,7 +547,8 @@ static int handle_headers(request_rec *r, int *state,
static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf,
request_rec *r, apr_pool_t *setaside_pool,
apr_uint16_t request_id, const char **err,
- int *bad_request, int *has_responded)
+ int *bad_request, int *has_responded,
+ apr_bucket_brigade *input_brigade)
{
apr_bucket_brigade *ib, *ob;
int seen_end_of_headers = 0, done = 0, ignore_body = 0;
@@ -583,9 +610,26 @@ static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf,
int last_stdin = 0;
char *iobuf_cursor;
- rv = ap_get_brigade(r->input_filters, ib,
- AP_MODE_READBYTES, APR_BLOCK_READ,
- iobuf_size);
+ if (APR_BRIGADE_EMPTY(input_brigade)) {
+ rv = ap_get_brigade(r->input_filters, ib,
+ AP_MODE_READBYTES, APR_BLOCK_READ,
+ iobuf_size);
+ }
+ else {
+ apr_bucket *e;
+ APR_BRIGADE_CONCAT(ib, input_brigade);
+ rv = apr_brigade_partition(ib, iobuf_size, &e);
+ if (rv == APR_SUCCESS) {
+ while (e != APR_BRIGADE_SENTINEL(ib)
+ && APR_BUCKET_IS_METADATA(e)) {
+ e = APR_BUCKET_NEXT(e);
+ }
+ apr_brigade_split_ex(ib, e, input_brigade);
+ }
+ else if (rv == APR_INCOMPLETE) {
+ rv = APR_SUCCESS;
+ }
+ }
if (rv != APR_SUCCESS) {
*err = "reading input brigade";
*bad_request = 1;
@@ -735,6 +779,15 @@ recv_again:
status = ap_scan_script_header_err_brigade_ex(r, ob,
NULL, APLOG_MODULE_INDEX);
+
+ /* FCGI has its own body framing mechanism which we don't
+ * match against any provided Content-Length, so let the
+ * core determine C-L vs T-E based on what's actually sent.
+ */
+ if (!apr_table_get(r->subprocess_env, AP_TRUST_CGILIKE_CL_ENVVAR))
+ apr_table_unset(r->headers_out, "Content-Length");
+ apr_table_unset(r->headers_out, "Transfer-Encoding");
+
/* suck in all the rest */
if (status != OK) {
apr_bucket *tmp_b;
@@ -771,8 +824,7 @@ recv_again:
}
}
- if (conf->error_override
- && ap_is_HTTP_ERROR(r->status) && ap_is_initial_req(r)) {
+ if (ap_proxy_should_override(conf, r->status) && ap_is_initial_req(r)) {
/*
* set script_error_status to discard
* everything after the headers
@@ -924,7 +976,8 @@ static int fcgi_do_request(apr_pool_t *p, request_rec *r,
conn_rec *origin,
proxy_dir_conf *conf,
apr_uri_t *uri,
- char *url, char *server_portstr)
+ char *url, char *server_portstr,
+ apr_bucket_brigade *input_brigade)
{
/* Request IDs are arbitrary numbers that we assign to a
* single request. This would allow multiplex/pipelining of
@@ -948,6 +1001,7 @@ static int fcgi_do_request(apr_pool_t *p, request_rec *r,
}
apr_pool_create(&temp_pool, r->pool);
+ apr_pool_tag(temp_pool, "proxy_fcgi_do_request");
/* Step 2: Send Environment via FCGI_PARAMS */
rv = send_environment(conn, r, temp_pool, request_id);
@@ -960,7 +1014,8 @@ static int fcgi_do_request(apr_pool_t *p, request_rec *r,
/* Step 3: Read records from the back end server and handle them. */
rv = dispatch(conn, conf, r, temp_pool, request_id,
- &err, &bad_request, &has_responded);
+ &err, &bad_request, &has_responded,
+ input_brigade);
if (rv != APR_SUCCESS) {
/* If the client aborted the connection during retrieval or (partially)
* sending the response, don't return a HTTP_SERVICE_UNAVAILABLE, since
@@ -996,6 +1051,8 @@ static int fcgi_do_request(apr_pool_t *p, request_rec *r,
#define FCGI_SCHEME "FCGI"
+#define MAX_MEM_SPOOL 16384
+
/*
* This handles fcgi:(dest) URLs
*/
@@ -1008,6 +1065,8 @@ static int proxy_fcgi_handler(request_rec *r, proxy_worker *worker,
char server_portstr[32];
conn_rec *origin = NULL;
proxy_conn_rec *backend = NULL;
+ apr_bucket_brigade *input_brigade;
+ apr_off_t input_bytes = 0;
apr_uri_t *uri;
proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
@@ -1050,6 +1109,101 @@ static int proxy_fcgi_handler(request_rec *r, proxy_worker *worker,
goto cleanup;
}
+ /* We possibly reuse input data prefetched in previous call(s), e.g. for a
+ * balancer fallback scenario.
+ */
+ apr_pool_userdata_get((void **)&input_brigade, "proxy-fcgi-input", p);
+ if (input_brigade == NULL) {
+ const char *old_te = apr_table_get(r->headers_in, "Transfer-Encoding");
+ const char *old_cl = NULL;
+ if (old_te) {
+ apr_table_unset(r->headers_in, "Content-Length");
+ }
+ else {
+ old_cl = apr_table_get(r->headers_in, "Content-Length");
+ }
+
+ input_brigade = apr_brigade_create(p, r->connection->bucket_alloc);
+ apr_pool_userdata_setn(input_brigade, "proxy-fcgi-input", NULL, p);
+
+ /* Prefetch (nonlocking) the request body so to increase the chance
+ * to get the whole (or enough) body and determine Content-Length vs
+ * chunked or spooled. By doing this before connecting or reusing the
+ * backend, we want to minimize the delay between this connection is
+ * considered alive and the first bytes sent (should the client's link
+ * be slow or some input filter retain the data). This is a best effort
+ * to prevent the backend from closing (from under us) what it thinks is
+ * an idle connection, hence to reduce to the minimum the unavoidable
+ * local is_socket_connected() vs remote keepalive race condition.
+ */
+ status = ap_proxy_prefetch_input(r, backend, input_brigade,
+ APR_NONBLOCK_READ, &input_bytes,
+ MAX_MEM_SPOOL);
+ if (status != OK) {
+ goto cleanup;
+ }
+
+ /*
+ * The request body is streamed by default, using either C-L or
+ * chunked T-E, like this:
+ *
+ * The whole body (including no body) was received on prefetch, i.e.
+ * the input brigade ends with EOS => C-L = input_bytes.
+ *
+ * C-L is known and reliable, i.e. only protocol filters in the input
+ * chain thus none should change the body => use C-L from client.
+ *
+ * The administrator has not "proxy-sendcl" which prevents T-E => use
+ * T-E and chunks.
+ *
+ * Otherwise we need to determine and set a content-length, so spool
+ * the entire request body to memory/temporary file (MAX_MEM_SPOOL),
+ * such that we finally know its length => C-L = input_bytes.
+ */
+ if (!APR_BRIGADE_EMPTY(input_brigade)
+ && APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+ /* The whole thing fit, so our decision is trivial, use the input
+ * bytes for the Content-Length. If we expected no body, and read
+ * no body, do not set the Content-Length.
+ */
+ if (old_cl || old_te || input_bytes) {
+ apr_table_setn(r->headers_in, "Content-Length",
+ apr_off_t_toa(p, input_bytes));
+ if (old_te) {
+ apr_table_unset(r->headers_in, "Transfer-Encoding");
+ }
+ }
+ }
+ else if (old_cl && r->input_filters == r->proto_input_filters) {
+ /* Streaming is possible by preserving the existing C-L */
+ }
+ else if (!apr_table_get(r->subprocess_env, "proxy-sendcl")) {
+ /* Streaming is possible using T-E: chunked */
+ }
+ else {
+ /* No streaming, C-L is the only option so spool to memory/file */
+ apr_bucket_brigade *tmp_bb;
+ apr_off_t remaining_bytes = 0;
+
+ AP_DEBUG_ASSERT(MAX_MEM_SPOOL >= input_bytes);
+ tmp_bb = apr_brigade_create(p, r->connection->bucket_alloc);
+ status = ap_proxy_spool_input(r, backend, tmp_bb, &remaining_bytes,
+ MAX_MEM_SPOOL - input_bytes);
+ if (status != OK) {
+ goto cleanup;
+ }
+
+ APR_BRIGADE_CONCAT(input_brigade, tmp_bb);
+ input_bytes += remaining_bytes;
+
+ apr_table_setn(r->headers_in, "Content-Length",
+ apr_off_t_toa(p, input_bytes));
+ if (old_te) {
+ apr_table_unset(r->headers_in, "Transfer-Encoding");
+ }
+ }
+ }
+
/* This scheme handler does not reuse connections by default, to
* avoid tying up a fastcgi that isn't expecting to work on
* parallel requests. But if the user went out of their way to
@@ -1074,7 +1228,7 @@ static int proxy_fcgi_handler(request_rec *r, proxy_worker *worker,
/* Step Three: Process the Request */
status = fcgi_do_request(p, r, backend, origin, dconf, uri, url,
- server_portstr);
+ server_portstr, input_brigade);
cleanup:
ap_proxy_release_connection(FCGI_SCHEME, backend, r->server);
diff --git a/modules/proxy/mod_proxy_fdpass.c b/modules/proxy/mod_proxy_fdpass.c
index 195b0fd..8f9893d 100644
--- a/modules/proxy/mod_proxy_fdpass.c
+++ b/modules/proxy/mod_proxy_fdpass.c
@@ -32,7 +32,7 @@ static int proxy_fdpass_canon(request_rec *r, char *url)
{
const char *path;
- if (strncasecmp(url, "fd://", 5) == 0) {
+ if (ap_cstr_casecmpn(url, "fd://", 5) == 0) {
url += 5;
}
else {
@@ -129,7 +129,7 @@ static int proxy_fdpass_handler(request_rec *r, proxy_worker *worker,
apr_socket_t *sock;
apr_socket_t *clientsock;
- if (strncasecmp(url, "fd://", 5) == 0) {
+ if (ap_cstr_casecmpn(url, "fd://", 5) == 0) {
url += 5;
}
else {
diff --git a/modules/proxy/mod_proxy_ftp.c b/modules/proxy/mod_proxy_ftp.c
index 4a10987..e0032e5 100644
--- a/modules/proxy/mod_proxy_ftp.c
+++ b/modules/proxy/mod_proxy_ftp.c
@@ -23,11 +23,6 @@
#endif
#include "apr_version.h"
-#if (APR_MAJOR_VERSION < 1)
-#undef apr_socket_create
-#define apr_socket_create apr_socket_create_ex
-#endif
-
#define AUTODETECT_PWD
/* Automatic timestamping (Last-Modified header) based on MDTM is used if:
* 1) the FTP server supports the MDTM command and
@@ -218,7 +213,7 @@ static int ftp_check_string(const char *x)
* (EBCDIC) machines either.
*/
static apr_status_t ftp_string_read(conn_rec *c, apr_bucket_brigade *bb,
- char *buff, apr_size_t bufflen, int *eos)
+ char *buff, apr_size_t bufflen, int *eos, apr_size_t *outlen)
{
apr_bucket *e;
apr_status_t rv;
@@ -230,6 +225,7 @@ static apr_status_t ftp_string_read(conn_rec *c, apr_bucket_brigade *bb,
/* start with an empty string */
buff[0] = 0;
*eos = 0;
+ *outlen = 0;
/* loop through each brigade */
while (!found) {
@@ -273,6 +269,7 @@ static apr_status_t ftp_string_read(conn_rec *c, apr_bucket_brigade *bb,
if (len > 0) {
memcpy(pos, response, len);
pos += len;
+ *outlen += len;
}
}
apr_bucket_delete(e);
@@ -292,9 +289,11 @@ static int proxy_ftp_canon(request_rec *r, char *url)
apr_pool_t *p = r->pool;
const char *err;
apr_port_t port, def_port;
+ core_dir_config *d = ap_get_core_module_config(r->per_dir_config);
+ int flags = d->allow_encoded_slashes && !d->decode_encoded_slashes ? PROXY_CANONENC_NOENCODEDSLASHENCODING : 0;
/* */
- if (strncasecmp(url, "ftp:", 4) == 0) {
+ if (ap_cstr_casecmpn(url, "ftp:", 4) == 0) {
url += 4;
}
else {
@@ -330,7 +329,8 @@ static int proxy_ftp_canon(request_rec *r, char *url)
else
parms = "";
- path = ap_proxy_canonenc(p, url, strlen(url), enc_path, 0, r->proxyreq);
+ path = ap_proxy_canonenc_ex(p, url, strlen(url), enc_path, flags,
+ r->proxyreq);
if (path == NULL)
return HTTP_BAD_REQUEST;
if (!ftp_check_string(path))
@@ -385,28 +385,36 @@ static int ftp_getrc_msg(conn_rec *ftp_ctrl, apr_bucket_brigade *bb, char *msgbu
char buff[5];
char *mb = msgbuf, *me = &msgbuf[msglen];
apr_status_t rv;
+ apr_size_t nread;
+
int eos;
- if (APR_SUCCESS != (rv = ftp_string_read(ftp_ctrl, bb, response, sizeof(response), &eos))) {
+ if (APR_SUCCESS != (rv = ftp_string_read(ftp_ctrl, bb, response, sizeof(response), &eos, &nread))) {
return -1;
}
/*
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, APLOGNO(03233)
"<%s", response);
*/
+ if (nread < 4) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(10229) "Malformed FTP response '%s'", response);
+ *mb = '\0';
+ return -1;
+ }
+
if (!apr_isdigit(response[0]) || !apr_isdigit(response[1]) ||
- !apr_isdigit(response[2]) || (response[3] != ' ' && response[3] != '-'))
+ !apr_isdigit(response[2]) || (response[3] != ' ' && response[3] != '-'))
status = 0;
else
status = 100 * response[0] + 10 * response[1] + response[2] - 111 * '0';
mb = apr_cpystrn(mb, response + 4, me - mb);
- if (response[3] == '-') {
+ if (response[3] == '-') { /* multi-line reply "123-foo\nbar\n123 baz" */
memcpy(buff, response, 3);
buff[3] = ' ';
do {
- if (APR_SUCCESS != (rv = ftp_string_read(ftp_ctrl, bb, response, sizeof(response), &eos))) {
+ if (APR_SUCCESS != (rv = ftp_string_read(ftp_ctrl, bb, response, sizeof(response), &eos, &nread))) {
return -1;
}
mb = apr_cpystrn(mb, response + (' ' == response[0] ? 1 : 4), me - mb);
@@ -494,7 +502,7 @@ static apr_status_t proxy_send_dir_filter(ap_filter_t *f,
path = apr_uri_unparse(p, &f->r->parsed_uri, APR_URI_UNP_OMITSITEPART | APR_URI_UNP_OMITQUERY);
/* If path began with /%2f, change the basedir */
- if (strncasecmp(path, "/%2f", 4) == 0) {
+ if (ap_cstr_casecmpn(path, "/%2f", 4) == 0) {
basedir = "/%2f";
}
@@ -813,17 +821,19 @@ proxy_ftp_command(const char *cmd, request_rec *r, conn_rec *ftp_ctrl,
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_flush_create(c->bucket_alloc));
ap_pass_brigade(ftp_ctrl->output_filters, bb);
- /* strip off the CRLF for logging */
- apr_cpystrn(message, cmd, sizeof(message));
- if ((crlf = strchr(message, '\r')) != NULL ||
- (crlf = strchr(message, '\n')) != NULL)
- *crlf = '\0';
- if (strncmp(message,"PASS ", 5) == 0)
- strcpy(&message[5], "****");
- ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, ">%s", message);
+ if (APLOGrtrace2(r)) {
+ /* strip off the CRLF for logging */
+ apr_cpystrn(message, cmd, sizeof(message));
+ if ((crlf = strchr(message, '\r')) != NULL ||
+ (crlf = strchr(message, '\n')) != NULL)
+ *crlf = '\0';
+ if (strncmp(message,"PASS ", 5) == 0)
+ strcpy(&message[5], "****");
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, ">%s", message);
+ }
}
- rc = ftp_getrc_msg(ftp_ctrl, bb, message, sizeof message);
+ rc = ftp_getrc_msg(ftp_ctrl, bb, message, sizeof(message));
if (rc == -1 || rc == 421)
strcpy(message,"<unable to read result>");
if ((crlf = strchr(message, '\r')) != NULL ||
@@ -909,7 +919,7 @@ static char *ftp_get_PWD(request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade
* with username and password (which was presumably queried from the user)
* supplied in the Authorization: header.
* Note that we "invent" a realm name which consists of the
- * ftp://user@host part of the reqest (sans password -if supplied but invalid-)
+ * ftp://user@host part of the request (sans password -if supplied but invalid-)
*/
static int ftp_unauthorized(request_rec *r, int log_it)
{
@@ -965,12 +975,9 @@ static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
conn_rec *c = r->connection;
proxy_conn_rec *backend;
apr_socket_t *sock, *local_sock, *data_sock = NULL;
- apr_sockaddr_t *connect_addr = NULL;
- apr_status_t rv;
conn_rec *origin, *data = NULL;
apr_status_t err = APR_SUCCESS;
- apr_status_t uerr = APR_SUCCESS;
- apr_bucket_brigade *bb = apr_brigade_create(p, c->bucket_alloc);
+ apr_bucket_brigade *bb;
char *buf, *connectname;
apr_port_t connectport;
char *ftpmessage = NULL;
@@ -993,8 +1000,8 @@ static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
/* stuff for PASV mode */
int connect = 0, use_port = 0;
char dates[APR_RFC822_DATE_LEN];
+ apr_status_t rv;
int status;
- apr_pool_t *address_pool;
/* is this for us? */
if (proxyhost) {
@@ -1003,7 +1010,7 @@ static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
proxyhost);
return DECLINED; /* proxy connections are via HTTP */
}
- if (strncasecmp(url, "ftp:", 4)) {
+ if (ap_cstr_casecmpn(url, "ftp:", 4)) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
"declining URL %s - not ftp:", url);
return DECLINED; /* only interested in FTP */
@@ -1024,8 +1031,9 @@ static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
/* We break the URL into host, port, path-search */
if (r->parsed_uri.hostname == NULL) {
if (APR_SUCCESS != apr_uri_parse(p, url, &uri)) {
- return ap_proxyerror(r, HTTP_BAD_REQUEST,
- apr_psprintf(p, "URI cannot be parsed: %s", url));
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(10189)
+ "URI cannot be parsed: %s", url);
+ return ap_proxyerror(r, HTTP_BAD_REQUEST, "URI cannot be parsed");
}
connectname = uri.hostname;
connectport = uri.port;
@@ -1074,7 +1082,7 @@ static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
* still smaller that the URL is logged regularly.
*/
if ((password = apr_table_get(r->headers_in, "Authorization")) != NULL
- && strcasecmp(ap_getword(r->pool, &password, ' '), "Basic") == 0
+ && ap_cstr_casecmp(ap_getword(r->pool, &password, ' '), "Basic") == 0
&& (password = ap_pbase64decode(r->pool, password))[0] != ':') {
/* Check the decoded string for special characters. */
if (!ftp_check_string(password)) {
@@ -1107,61 +1115,35 @@ static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01036)
"connecting %s to %s:%d", url, connectname, connectport);
- if (worker->s->is_address_reusable) {
- if (!worker->cp->addr) {
- if ((err = PROXY_THREAD_LOCK(worker->balancer)) != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, err, r, APLOGNO(01037) "lock");
- return HTTP_INTERNAL_SERVER_ERROR;
+ /* create space for state information */
+ backend = ap_get_module_config(c->conn_config, &proxy_ftp_module);
+ if (!backend) {
+ status = ap_proxy_acquire_connection("FTP", &backend, worker, r->server);
+ if (status != OK) {
+ if (backend) {
+ backend->close = 1;
+ ap_proxy_release_connection("FTP", backend, r->server);
}
+ return status;
}
- connect_addr = worker->cp->addr;
- address_pool = worker->cp->pool;
+ ap_set_module_config(c->conn_config, &proxy_ftp_module, backend);
}
- else
- address_pool = r->pool;
- /* do a DNS lookup for the destination host */
- if (!connect_addr)
- err = apr_sockaddr_info_get(&(connect_addr),
- connectname, APR_UNSPEC,
- connectport, 0,
- address_pool);
- if (worker->s->is_address_reusable && !worker->cp->addr) {
- worker->cp->addr = connect_addr;
- if ((uerr = PROXY_THREAD_UNLOCK(worker->balancer)) != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, uerr, r, APLOGNO(01038) "unlock");
- }
- }
/*
* get all the possible IP addresses for the destname and loop through
* them until we get a successful connection
*/
+ err = ap_proxy_determine_address("FTP", backend, connectname, connectport,
+ 0, r, r->server);
if (APR_SUCCESS != err) {
- return ap_proxyerror(r, HTTP_BAD_GATEWAY, apr_pstrcat(p,
- "DNS lookup failure for: ",
- connectname, NULL));
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error resolving backend address");
}
/* check if ProxyBlock directive on this host */
- if (OK != ap_proxy_checkproxyblock2(r, conf, connectname, connect_addr)) {
- return ap_proxyerror(r, HTTP_FORBIDDEN,
- "Connect to remote machine blocked");
- }
-
- /* create space for state information */
- backend = (proxy_conn_rec *) ap_get_module_config(c->conn_config, &proxy_ftp_module);
- if (!backend) {
- status = ap_proxy_acquire_connection("FTP", &backend, worker, r->server);
- if (status != OK) {
- if (backend) {
- backend->close = 1;
- ap_proxy_release_connection("FTP", backend, r->server);
- }
- return status;
- }
- /* TODO: see if ftp could use determine_connection */
- backend->addr = connect_addr;
- ap_set_module_config(c->conn_config, &proxy_ftp_module, backend);
+ if (OK != ap_proxy_checkproxyblock2(r, conf, connectname, backend->addr)) {
+ return ftp_proxyerror(r, backend, HTTP_FORBIDDEN,
+ "Connect to remote machine blocked");
}
@@ -1171,21 +1153,15 @@ static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
* We have determined who to connect to. Now make the connection.
*/
-
if (ap_proxy_connect_backend("FTP", backend, worker, r->server)) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01039)
- "an error occurred creating a new connection to %pI (%s)",
- connect_addr, connectname);
proxy_ftp_cleanup(r, backend);
return HTTP_SERVICE_UNAVAILABLE;
}
- if (!backend->connection) {
- status = ap_proxy_connection_create_ex("FTP", backend, r);
- if (status != OK) {
- proxy_ftp_cleanup(r, backend);
- return status;
- }
+ status = ap_proxy_connection_create_ex("FTP", backend, r);
+ if (status != OK) {
+ proxy_ftp_cleanup(r, backend);
+ return status;
}
/* Use old naming */
@@ -1203,6 +1179,7 @@ static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
* correct directory...
*/
+ bb = apr_brigade_create(p, c->bucket_alloc);
/* possible results: */
/* 120 Service ready in nnn minutes. */
@@ -1306,7 +1283,7 @@ static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
/* Special handling for leading "%2f": this enforces a "cwd /"
* out of the $HOME directory which was the starting point after login
*/
- if (strncasecmp(path, "%2f", 3) == 0) {
+ if (ap_cstr_casecmpn(path, "%2f", 3) == 0) {
path += 3;
while (*path == '/') /* skip leading '/' (after root %2f) */
++path;
@@ -1520,7 +1497,8 @@ static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
"PASV contacting host %d.%d.%d.%d:%d",
h3, h2, h1, h0, pasvport);
- if ((rv = apr_socket_create(&data_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
+ if ((rv = apr_socket_create(&data_sock, backend->addr->family,
+ SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01045)
"error creating PASV socket");
proxy_ftp_cleanup(r, backend);
@@ -1542,7 +1520,14 @@ static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
}
/* make the connection */
- apr_sockaddr_info_get(&pasv_addr, apr_psprintf(p, "%d.%d.%d.%d", h3, h2, h1, h0), connect_addr->family, pasvport, 0, p);
+ err = apr_sockaddr_info_get(&pasv_addr, apr_psprintf(p, "%d.%d.%d.%d",
+ h3, h2, h1, h0),
+ backend->addr->family, pasvport, 0, p);
+ if (APR_SUCCESS != err) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ apr_pstrcat(p, "DNS lookup failure for: ",
+ connectname, NULL));
+ }
rv = apr_socket_connect(data_sock, pasv_addr);
if (rv != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01048)
@@ -1565,7 +1550,8 @@ static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
apr_port_t local_port;
unsigned int h0, h1, h2, h3, p0, p1;
- if ((rv = apr_socket_create(&local_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
+ if ((rv = apr_socket_create(&local_sock, backend->addr->family,
+ SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01049)
"error creating local socket");
proxy_ftp_cleanup(r, backend);
@@ -1585,7 +1571,12 @@ static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
#endif /* _OSD_POSIX */
}
- apr_sockaddr_info_get(&local_addr, local_ip, APR_UNSPEC, local_port, 0, r->pool);
+ err = apr_sockaddr_info_get(&local_addr, local_ip, APR_UNSPEC, local_port, 0, r->pool);
+ if (APR_SUCCESS != err) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ apr_pstrcat(p, "DNS lookup failure for: ",
+ connectname, NULL));
+ }
if ((rv = apr_socket_bind(local_sock, local_addr)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01051)
diff --git a/modules/proxy/mod_proxy_hcheck.c b/modules/proxy/mod_proxy_hcheck.c
index 2783a58..70f1de8 100644
--- a/modules/proxy/mod_proxy_hcheck.c
+++ b/modules/proxy/mod_proxy_hcheck.c
@@ -20,6 +20,7 @@
#if APR_HAS_THREADS
#include "apr_thread_pool.h"
#endif
+#include "http_ssl.h"
module AP_MODULE_DECLARE_DATA proxy_hcheck_module;
@@ -33,7 +34,6 @@ module AP_MODULE_DECLARE_DATA proxy_hcheck_module;
#endif
#else
#define HC_USE_THREADS 0
-typedef void apr_thread_pool_t;
#endif
typedef struct {
@@ -65,6 +65,7 @@ typedef struct {
const char *method; /* Method string for the HTTP/AJP request */
const char *req; /* pre-formatted HTTP/AJP request */
proxy_worker *w; /* Pointer to the actual worker */
+ const char *protocol; /* HTTP 1.0 or 1.1? */
} wctx_t;
typedef struct {
@@ -73,9 +74,11 @@ typedef struct {
proxy_balancer *balancer;
proxy_worker *worker;
proxy_worker *hc;
- apr_time_t now;
+ apr_time_t *now;
} baton_t;
+static APR_OPTIONAL_FN_TYPE(ajp_handle_cping_cpong) *ajp_handle_cping_cpong = NULL;
+
static void *hc_create_config(apr_pool_t *p, server_rec *s)
{
sctx_t *ctx = apr_pcalloc(p, sizeof(sctx_t));
@@ -89,7 +92,10 @@ static void *hc_create_config(apr_pool_t *p, server_rec *s)
}
static ap_watchdog_t *watchdog;
-static int tpsize = HC_THREADPOOL_SIZE;
+#if HC_USE_THREADS
+static apr_thread_pool_t *hctp;
+static int tpsize;
+#endif
/*
* This serves double duty by not only validating (and creating)
@@ -110,6 +116,10 @@ static const char *set_worker_hc_param(apr_pool_t *p,
if (!worker && !v) {
return "Bad call to set_worker_hc_param()";
}
+ if (!ctx) {
+ ctx = hc_create_config(p, s);
+ ap_set_module_config(s->module_config, &proxy_hcheck_module, ctx);
+ }
temp = (hc_template_t *)v;
if (!strcasecmp(key, "hctemplate")) {
hc_template_t *template;
@@ -333,7 +343,8 @@ static const char *set_hc_tpsize (cmd_parms *cmd, void *dummy, const char *arg)
*/
static request_rec *create_request_rec(apr_pool_t *p, server_rec *s,
proxy_balancer *balancer,
- const char *method)
+ const char *method,
+ const char *protocol)
{
request_rec *r;
@@ -391,10 +402,12 @@ static request_rec *create_request_rec(apr_pool_t *p, server_rec *s,
else {
r->header_only = 0;
}
-
r->protocol = "HTTP/1.0";
r->proto_num = HTTP_VERSION(1, 0);
-
+ if ( protocol && (protocol[7] == '1') ) {
+ r->protocol = "HTTP/1.1";
+ r->proto_num = HTTP_VERSION(1, 1);
+ }
r->hostname = NULL;
return r;
@@ -418,31 +431,43 @@ static void create_hcheck_req(wctx_t *wctx, proxy_worker *hc,
{
char *req = NULL;
const char *method = NULL;
+ const char *protocol = NULL;
+
+ /* TODO: Fold into switch/case below? This seems more obvious */
+ if ( (hc->s->method == OPTIONS11) || (hc->s->method == HEAD11) || (hc->s->method == GET11) ) {
+ protocol = "HTTP/1.1";
+ } else {
+ protocol = "HTTP/1.0";
+ }
switch (hc->s->method) {
case OPTIONS:
+ case OPTIONS11:
method = "OPTIONS";
req = apr_psprintf(p,
- "OPTIONS * HTTP/1.0\r\n"
+ "OPTIONS * %s\r\n"
"Host: %s:%d\r\n"
- "\r\n",
+ "\r\n", protocol,
hc->s->hostname_ex, (int)hc->s->port);
break;
case HEAD:
+ case HEAD11:
method = "HEAD";
/* fallthru */
case GET:
+ case GET11:
if (!method) { /* did we fall thru? If not, we are GET */
method = "GET";
}
req = apr_psprintf(p,
- "%s %s%s%s HTTP/1.0\r\n"
+ "%s %s%s%s %s\r\n"
"Host: %s:%d\r\n"
"\r\n",
method,
(wctx->path ? wctx->path : ""),
(wctx->path && *hc->s->hcuri ? "/" : "" ),
(*hc->s->hcuri ? hc->s->hcuri : ""),
+ protocol,
hc->s->hostname_ex, (int)hc->s->port);
break;
@@ -451,6 +476,7 @@ static void create_hcheck_req(wctx_t *wctx, proxy_worker *hc,
}
wctx->req = req;
wctx->method = method;
+ wctx->protocol = protocol;
}
static proxy_worker *hc_get_hcworker(sctx_t *ctx, proxy_worker *worker,
@@ -463,7 +489,7 @@ static proxy_worker *hc_get_hcworker(sctx_t *ctx, proxy_worker *worker,
if (!hc) {
apr_uri_t uri;
apr_status_t rv;
- const char *url = worker->s->name;
+ const char *url = worker->s->name_ex;
wctx_t *wctx = apr_pcalloc(ctx->p, sizeof(wctx_t));
port = (worker->s->port ? worker->s->port
@@ -473,16 +499,25 @@ static proxy_worker *hc_get_hcworker(sctx_t *ctx, proxy_worker *worker,
worker, worker->s->scheme, worker->s->hostname_ex,
(int)port);
- ap_proxy_define_worker(ctx->p, &hc, NULL, NULL, worker->s->name, 0);
+ ap_proxy_define_worker(ctx->p, &hc, NULL, NULL, worker->s->name_ex, 0);
apr_snprintf(hc->s->name, sizeof hc->s->name, "%pp", worker);
+ apr_snprintf(hc->s->name_ex, sizeof hc->s->name_ex, "%pp", worker);
PROXY_STRNCPY(hc->s->hostname, worker->s->hostname); /* for compatibility */
PROXY_STRNCPY(hc->s->hostname_ex, worker->s->hostname_ex);
PROXY_STRNCPY(hc->s->scheme, worker->s->scheme);
PROXY_STRNCPY(hc->s->hcuri, worker->s->hcuri);
PROXY_STRNCPY(hc->s->hcexpr, worker->s->hcexpr);
- hc->hash.def = hc->s->hash.def = ap_proxy_hashfunc(hc->s->name, PROXY_HASHFUNC_DEFAULT);
- hc->hash.fnv = hc->s->hash.fnv = ap_proxy_hashfunc(hc->s->name, PROXY_HASHFUNC_FNV);
+ hc->hash.def = hc->s->hash.def = ap_proxy_hashfunc(hc->s->name_ex,
+ PROXY_HASHFUNC_DEFAULT);
+ hc->hash.fnv = hc->s->hash.fnv = ap_proxy_hashfunc(hc->s->name_ex,
+ PROXY_HASHFUNC_FNV);
hc->s->port = port;
+ hc->s->conn_timeout_set = worker->s->conn_timeout_set;
+ hc->s->conn_timeout = worker->s->conn_timeout;
+ hc->s->ping_timeout_set = worker->s->ping_timeout_set;
+ hc->s->ping_timeout = worker->s->ping_timeout;
+ hc->s->timeout_set = worker->s->timeout_set;
+ hc->s->timeout = worker->s->timeout;
/* Do not disable worker in case of errors */
hc->s->status |= PROXY_WORKER_IGNORE_ERRORS;
/* Mark as the "generic" worker */
@@ -516,33 +551,78 @@ static proxy_worker *hc_get_hcworker(sctx_t *ctx, proxy_worker *worker,
return hc;
}
-static int hc_determine_connection(sctx_t *ctx, proxy_worker *worker,
- apr_sockaddr_t **addr, apr_pool_t *p)
+static int hc_determine_connection(const char *proxy_function,
+ proxy_conn_rec *backend,
+ server_rec *s)
{
- apr_status_t rv = APR_SUCCESS;
+ proxy_worker *worker = backend->worker;
+ apr_status_t rv;
+
/*
* normally, this is done in ap_proxy_determine_connection().
* TODO: Look at using ap_proxy_determine_connection() with a
* fake request_rec
*/
- if (worker->cp->addr) {
- *addr = worker->cp->addr;
+ rv = ap_proxy_determine_address(proxy_function, backend,
+ worker->s->hostname_ex, worker->s->port,
+ 0, NULL, s);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(03249)
+ "DNS lookup failure for: %s:%hu",
+ worker->s->hostname_ex, worker->s->port);
+ return !OK;
}
- else {
- rv = apr_sockaddr_info_get(addr, worker->s->hostname_ex,
- APR_UNSPEC, worker->s->port, 0, p);
- if (rv != APR_SUCCESS) {
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ctx->s, APLOGNO(03249)
- "DNS lookup failure for: %s:%d",
- worker->s->hostname_ex, (int)worker->s->port);
+
+ return OK;
+}
+
+static apr_status_t backend_cleanup(const char *proxy_function, proxy_conn_rec *backend,
+ server_rec *s, int status)
+{
+ if (backend) {
+ backend->close = 1;
+ ap_proxy_release_connection(proxy_function, backend, s);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03251)
+ "Health check %s Status (%d) for %s.",
+ ap_proxy_show_hcmethod(backend->worker->s->method),
+ status,
+ backend->worker->s->name_ex);
+ }
+ if (status != OK) {
+ return APR_EGENERAL;
+ }
+ return APR_SUCCESS;
+}
+
+static int hc_get_backend(const char *proxy_function, proxy_conn_rec **backend,
+ proxy_worker *hc, sctx_t *ctx)
+{
+ int status;
+
+ status = ap_proxy_acquire_connection(proxy_function, backend, hc, ctx->s);
+ if (status != OK) {
+ return status;
+ }
+
+ if (strcmp(hc->s->scheme, "https") == 0 || strcmp(hc->s->scheme, "wss") == 0 ) {
+ if (!ap_ssl_has_outgoing_handlers()) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ctx->s, APLOGNO(03252)
+ "mod_ssl not configured?");
+ return !OK;
}
+ (*backend)->is_ssl = 1;
}
- return (rv == APR_SUCCESS ? OK : !OK);
+
+ return hc_determine_connection(proxy_function, *backend, ctx->s);
}
-static apr_status_t hc_init_worker(sctx_t *ctx, proxy_worker *worker)
+static apr_status_t hc_init_baton(baton_t *baton)
{
+ sctx_t *ctx = baton->ctx;
+ proxy_worker *worker = baton->worker, *hc;
apr_status_t rv = APR_SUCCESS;
+ int once = 0;
+
/*
* Since this is the watchdog, workers never actually handle a
* request here, and so the local data isn't initialized (of
@@ -555,52 +635,67 @@ static apr_status_t hc_init_worker(sctx_t *ctx, proxy_worker *worker)
ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ctx->s, APLOGNO(03250) "Cannot init worker");
return rv;
}
- if (worker->s->is_address_reusable && !worker->s->disablereuse &&
- hc_determine_connection(ctx, worker, &worker->cp->addr,
- worker->cp->pool) != OK) {
+ once = 1;
+ }
+
+ baton->hc = hc = hc_get_hcworker(ctx, worker, baton->ptemp);
+
+ /* Try to resolve the worker address once if it's reusable */
+ if (once && worker->s->is_address_reusable) {
+ proxy_conn_rec *backend = NULL;
+ if (hc_get_backend("HCHECK", &backend, hc, ctx)) {
rv = APR_EGENERAL;
}
+ if (backend) {
+ backend->close = 1;
+ ap_proxy_release_connection("HCHECK", backend, ctx->s);
+ }
}
- return rv;
-}
-static apr_status_t backend_cleanup(const char *proxy_function, proxy_conn_rec *backend,
- server_rec *s, int status)
-{
- if (backend) {
- backend->close = 1;
- ap_proxy_release_connection(proxy_function, backend, s);
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03251)
- "Health check %s Status (%d) for %s.",
- ap_proxy_show_hcmethod(backend->worker->s->method),
- status,
- backend->worker->s->name);
- }
- if (status != OK) {
- return APR_EGENERAL;
- }
- return APR_SUCCESS;
+ return rv;
}
-static int hc_get_backend(const char *proxy_function, proxy_conn_rec **backend,
- proxy_worker *hc, sctx_t *ctx, apr_pool_t *ptemp)
+static apr_status_t hc_check_cping(baton_t *baton, apr_thread_t *thread)
{
int status;
- status = ap_proxy_acquire_connection(proxy_function, backend, hc, ctx->s);
- if (status == OK) {
- (*backend)->addr = hc->cp->addr;
- (*backend)->hostname = hc->s->hostname_ex;
- if (strcmp(hc->s->scheme, "https") == 0) {
- if (!ap_proxy_ssl_enable(NULL)) {
- ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ctx->s, APLOGNO(03252)
- "mod_ssl not configured?");
- return !OK;
- }
- (*backend)->is_ssl = 1;
- }
+ sctx_t *ctx = baton->ctx;
+ proxy_worker *hc = baton->hc;
+ proxy_conn_rec *backend = NULL;
+ apr_pool_t *ptemp = baton->ptemp;
+ request_rec *r;
+ apr_interval_time_t timeout;
+ if (!ajp_handle_cping_cpong) {
+ return APR_ENOTIMPL;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, baton->ctx->s, "HCCPING starting");
+ if ((status = hc_get_backend("HCCPING", &backend, hc, ctx)) != OK) {
+ return backend_cleanup("HCCPING", backend, ctx->s, status);
+ }
+ if ((status = ap_proxy_connect_backend("HCCPING", backend, hc, ctx->s)) != OK) {
+ return backend_cleanup("HCCPING", backend, ctx->s, status);
}
- return hc_determine_connection(ctx, hc, &(*backend)->addr, ptemp);
+ r = create_request_rec(ptemp, ctx->s, baton->balancer, "CPING", NULL);
+ if ((status = ap_proxy_connection_create_ex("HCCPING", backend, r)) != OK) {
+ return backend_cleanup("HCCPING", backend, ctx->s, status);
+ }
+ set_request_connection(r, backend->connection);
+ backend->connection->current_thread = thread;
+
+ if (hc->s->ping_timeout_set) {
+ timeout = hc->s->ping_timeout;
+ } else if ( hc->s->conn_timeout_set) {
+ timeout = hc->s->conn_timeout;
+ } else if ( hc->s->timeout_set) {
+ timeout = hc->s->timeout;
+ } else {
+ /* default to socket timeout */
+ apr_socket_timeout_get(backend->sock, &timeout);
+ }
+ status = ajp_handle_cping_cpong(backend->sock, r, timeout);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, baton->ctx->s, "HCCPING done %d", status);
+ return backend_cleanup("HCCPING", backend, ctx->s, status);
}
static apr_status_t hc_check_tcp(baton_t *baton)
@@ -610,7 +705,7 @@ static apr_status_t hc_check_tcp(baton_t *baton)
proxy_worker *hc = baton->hc;
proxy_conn_rec *backend = NULL;
- status = hc_get_backend("HCTCP", &backend, hc, ctx, baton->ptemp);
+ status = hc_get_backend("HCTCP", &backend, hc, ctx);
if (status == OK) {
status = ap_proxy_connect_backend("HCTCP", backend, hc, ctx->s);
/* does an unconditional ap_proxy_is_socket_connected() */
@@ -636,6 +731,7 @@ static int hc_read_headers(request_rec *r)
{
char buffer[HUGE_STRING_LEN];
int len;
+ const char *ct;
len = ap_getline(buffer, sizeof(buffer), r, 1);
if (len <= 0) {
@@ -670,6 +766,7 @@ static int hc_read_headers(request_rec *r)
} else {
return !OK;
}
+
/* OK, 1st line is OK... scarf in the headers */
while ((len = ap_getline(buffer, sizeof(buffer), r, 1)) > 0) {
char *value, *end;
@@ -686,6 +783,11 @@ static int hc_read_headers(request_rec *r)
*end = '\0';
apr_table_add(r->headers_out, buffer, value);
}
+
+ /* Set the Content-Type for the request if set */
+ if ((ct = apr_table_get(r->headers_out, "Content-Type")) != NULL)
+ ap_set_content_type(r, ct);
+
return OK;
}
@@ -736,7 +838,7 @@ static int hc_read_body(request_rec *r, apr_bucket_brigade *bb)
* then apply those to the resulting response, otherwise
* any status code 2xx or 3xx is considered "passing"
*/
-static apr_status_t hc_check_http(baton_t *baton)
+static apr_status_t hc_check_http(baton_t *baton, apr_thread_t *thread)
{
int status;
proxy_conn_rec *backend = NULL;
@@ -754,20 +856,19 @@ static apr_status_t hc_check_http(baton_t *baton)
return APR_ENOTIMPL;
}
- if ((status = hc_get_backend("HCOH", &backend, hc, ctx, ptemp)) != OK) {
+ if ((status = hc_get_backend("HCOH", &backend, hc, ctx)) != OK) {
return backend_cleanup("HCOH", backend, ctx->s, status);
}
if ((status = ap_proxy_connect_backend("HCOH", backend, hc, ctx->s)) != OK) {
return backend_cleanup("HCOH", backend, ctx->s, status);
}
- r = create_request_rec(ptemp, ctx->s, baton->balancer, wctx->method);
- if (!backend->connection) {
- if ((status = ap_proxy_connection_create_ex("HCOH", backend, r)) != OK) {
- return backend_cleanup("HCOH", backend, ctx->s, status);
- }
+ r = create_request_rec(ptemp, ctx->s, baton->balancer, wctx->method, wctx->protocol);
+ if ((status = ap_proxy_connection_create_ex("HCOH", backend, r)) != OK) {
+ return backend_cleanup("HCOH", backend, ctx->s, status);
}
set_request_connection(r, backend->connection);
+ backend->connection->current_thread = thread;
bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
@@ -796,22 +897,22 @@ static apr_status_t hc_check_http(baton_t *baton)
if (ok > 0) {
ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, ctx->s,
"Condition %s for %s (%s): passed", worker->s->hcexpr,
- hc->s->name, worker->s->name);
+ hc->s->name_ex, worker->s->name_ex);
} else if (ok < 0 || err) {
ap_log_error(APLOG_MARK, APLOG_INFO, 0, ctx->s, APLOGNO(03301)
"Error on checking condition %s for %s (%s): %s", worker->s->hcexpr,
- hc->s->name, worker->s->name, err);
+ hc->s->name_ex, worker->s->name_ex, err);
status = !OK;
} else {
ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, ctx->s,
"Condition %s for %s (%s) : failed", worker->s->hcexpr,
- hc->s->name, worker->s->name);
+ hc->s->name_ex, worker->s->name_ex);
status = !OK;
}
} else if (r->status < 200 || r->status > 399) {
ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, ctx->s,
"Response status %i for %s (%s): failed", r->status,
- hc->s->name, worker->s->name);
+ hc->s->name_ex, worker->s->name_ex);
status = !OK;
}
return backend_cleanup("HCOH", backend, ctx->s, status);
@@ -823,29 +924,31 @@ static void * APR_THREAD_FUNC hc_check(apr_thread_t *thread, void *b)
server_rec *s = baton->ctx->s;
proxy_worker *worker = baton->worker;
proxy_worker *hc = baton->hc;
- apr_time_t now = baton->now;
+ apr_time_t now;
apr_status_t rv;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03256)
"%sHealth checking %s", (thread ? "Threaded " : ""),
- worker->s->name);
+ worker->s->name_ex);
- worker->s->updated = now;
if (hc->s->method == TCP) {
rv = hc_check_tcp(baton);
}
+ else if (hc->s->method == CPING) {
+ rv = hc_check_cping(baton, thread);
+ }
else {
- rv = hc_check_http(baton);
+ rv = hc_check_http(baton, thread);
}
+
+ now = apr_time_now();
if (rv == APR_ENOTIMPL) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(03257)
"Somehow tried to use unimplemented hcheck method: %d",
(int)hc->s->method);
- apr_pool_destroy(baton->ptemp);
- return NULL;
}
/* what state are we in ? */
- if (PROXY_WORKER_IS_HCFAILED(worker)) {
+ else if (PROXY_WORKER_IS_HCFAILED(worker) || PROXY_WORKER_IS_ERROR(worker)) {
if (rv == APR_SUCCESS) {
worker->s->pcount += 1;
if (worker->s->pcount >= worker->s->passes) {
@@ -854,11 +957,12 @@ static void * APR_THREAD_FUNC hc_check(apr_thread_t *thread, void *b)
worker->s->pcount = 0;
ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(03302)
"%sHealth check ENABLING %s", (thread ? "Threaded " : ""),
- worker->s->name);
+ worker->s->name_ex);
}
}
- } else {
+ }
+ else {
if (rv != APR_SUCCESS) {
worker->s->error_time = now;
worker->s->fcount += 1;
@@ -867,11 +971,16 @@ static void * APR_THREAD_FUNC hc_check(apr_thread_t *thread, void *b)
worker->s->fcount = 0;
ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(03303)
"%sHealth check DISABLING %s", (thread ? "Threaded " : ""),
- worker->s->name);
+ worker->s->name_ex);
}
}
}
+ if (baton->now) {
+ *baton->now = now;
+ }
apr_pool_destroy(baton->ptemp);
+ worker->s->updated = now;
+
return NULL;
}
@@ -879,12 +988,10 @@ static apr_status_t hc_watchdog_callback(int state, void *data,
apr_pool_t *pool)
{
apr_status_t rv = APR_SUCCESS;
- apr_time_t now = apr_time_now();
proxy_balancer *balancer;
sctx_t *ctx = (sctx_t *)data;
server_rec *s = ctx->s;
proxy_server_conf *conf;
- static apr_thread_pool_t *hctp = NULL;
switch (state) {
case AP_WATCHDOG_STATE_STARTING:
@@ -911,15 +1018,11 @@ static apr_status_t hc_watchdog_callback(int state, void *data,
"Skipping apr_thread_pool_create()");
hctp = NULL;
}
-
#endif
break;
case AP_WATCHDOG_STATE_RUNNING:
/* loop thru all workers */
- ap_log_error(APLOG_MARK, APLOG_TRACE5, 0, s,
- "Run of %s watchdog.",
- HCHECK_WATHCHDOG_NAME);
if (s) {
int i;
conf = (proxy_server_conf *) ap_get_module_config(s->module_config, &proxy_module);
@@ -927,45 +1030,52 @@ static apr_status_t hc_watchdog_callback(int state, void *data,
ctx->s = s;
for (i = 0; i < conf->balancers->nelts; i++, balancer++) {
int n;
+ apr_time_t now;
proxy_worker **workers;
proxy_worker *worker;
/* Have any new balancers or workers been added dynamically? */
ap_proxy_sync_balancer(balancer, s, conf);
workers = (proxy_worker **)balancer->workers->elts;
+ now = apr_time_now();
for (n = 0; n < balancer->workers->nelts; n++) {
worker = *workers;
if (!PROXY_WORKER_IS(worker, PROXY_WORKER_STOPPED) &&
- (worker->s->method != NONE) &&
- (now > worker->s->updated + worker->s->interval)) {
+ (worker->s->method != NONE) &&
+ (worker->s->updated != 0) &&
+ (now > worker->s->updated + worker->s->interval)) {
baton_t *baton;
apr_pool_t *ptemp;
+
ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
"Checking %s worker: %s [%d] (%pp)", balancer->s->name,
- worker->s->name, worker->s->method, worker);
+ worker->s->name_ex, worker->s->method, worker);
- if ((rv = hc_init_worker(ctx, worker)) != APR_SUCCESS) {
- return rv;
- }
- /* This pool must last the lifetime of the (possible) thread */
+ /* This pool has the lifetime of the check */
apr_pool_create(&ptemp, ctx->p);
apr_pool_tag(ptemp, "hc_request");
- baton = apr_palloc(ptemp, sizeof(baton_t));
+ baton = apr_pcalloc(ptemp, sizeof(baton_t));
baton->ctx = ctx;
- baton->now = now;
baton->balancer = balancer;
baton->worker = worker;
baton->ptemp = ptemp;
- baton->hc = hc_get_hcworker(ctx, worker, ptemp);
-
- if (!hctp) {
- hc_check(NULL, baton);
+ if ((rv = hc_init_baton(baton))) {
+ worker->s->updated = now;
+ apr_pool_destroy(ptemp);
+ return rv;
}
+ worker->s->updated = 0;
#if HC_USE_THREADS
- else {
- rv = apr_thread_pool_push(hctp, hc_check, (void *)baton,
- APR_THREAD_TASK_PRIORITY_NORMAL, NULL);
+ if (hctp) {
+ apr_thread_pool_push(hctp, hc_check, (void *)baton,
+ APR_THREAD_TASK_PRIORITY_NORMAL,
+ NULL);
}
+ else
#endif
+ {
+ baton->now = &now;
+ hc_check(NULL, baton);
+ }
}
workers++;
}
@@ -984,9 +1094,9 @@ static apr_status_t hc_watchdog_callback(int state, void *data,
ap_log_error(APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(03315)
"apr_thread_pool_destroy() failed");
}
+ hctp = NULL;
}
#endif
- hctp = NULL;
break;
}
return rv;
@@ -994,7 +1104,22 @@ static apr_status_t hc_watchdog_callback(int state, void *data,
static int hc_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
apr_pool_t *ptemp)
{
+#if HC_USE_THREADS
+ hctp = NULL;
tpsize = HC_THREADPOOL_SIZE;
+#endif
+
+ ajp_handle_cping_cpong = APR_RETRIEVE_OPTIONAL_FN(ajp_handle_cping_cpong);
+ if (ajp_handle_cping_cpong) {
+ proxy_hcmethods_t *method = proxy_hcmethods;
+ for (; method->name; method++) {
+ if (method->method == CPING) {
+ method->implemented = 1;
+ break;
+ }
+ }
+ }
+
return OK;
}
static int hc_post_config(apr_pool_t *p, apr_pool_t *plog,
@@ -1050,6 +1175,7 @@ static int hc_post_config(apr_pool_t *p, apr_pool_t *plog,
"watchdog callback registered (%s for %s)", HCHECK_WATHCHDOG_NAME, s->server_hostname);
s = s->next;
}
+
return OK;
}
@@ -1060,6 +1186,8 @@ static void hc_show_exprs(request_rec *r)
int i;
sctx_t *ctx = (sctx_t *) ap_get_module_config(r->server->module_config,
&proxy_hcheck_module);
+ if (!ctx)
+ return;
if (apr_is_empty_table(ctx->conditions))
return;
@@ -1089,6 +1217,8 @@ static void hc_select_exprs(request_rec *r, const char *expr)
int i;
sctx_t *ctx = (sctx_t *) ap_get_module_config(r->server->module_config,
&proxy_hcheck_module);
+ if (!ctx)
+ return;
if (apr_is_empty_table(ctx->conditions))
return;
@@ -1112,6 +1242,8 @@ static int hc_valid_expr(request_rec *r, const char *expr)
int i;
sctx_t *ctx = (sctx_t *) ap_get_module_config(r->server->module_config,
&proxy_hcheck_module);
+ if (!ctx)
+ return 0;
if (apr_is_empty_table(ctx->conditions))
return 0;
diff --git a/modules/proxy/mod_proxy_http.c b/modules/proxy/mod_proxy_http.c
index 56af9a8..bd57b4d 100644
--- a/modules/proxy/mod_proxy_http.c
+++ b/modules/proxy/mod_proxy_http.c
@@ -31,36 +31,71 @@ static apr_status_t ap_proxy_http_cleanup(const char *scheme,
static apr_status_t ap_proxygetline(apr_bucket_brigade *bb, char *s, int n,
request_rec *r, int flags, int *read);
+static const char *get_url_scheme(const char **url, int *is_ssl)
+{
+ const char *u = *url;
+
+ switch (u[0]) {
+ case 'h':
+ case 'H':
+ if (strncasecmp(u + 1, "ttp", 3) == 0) {
+ if (u[4] == ':') {
+ *is_ssl = 0;
+ *url = u + 5;
+ return "http";
+ }
+ if (apr_tolower(u[4]) == 's' && u[5] == ':') {
+ *is_ssl = 1;
+ *url = u + 6;
+ return "https";
+ }
+ }
+ break;
+
+ case 'w':
+ case 'W':
+ if (apr_tolower(u[1]) == 's') {
+ if (u[2] == ':') {
+ *is_ssl = 0;
+ *url = u + 3;
+ return "ws";
+ }
+ if (apr_tolower(u[2]) == 's' && u[3] == ':') {
+ *is_ssl = 1;
+ *url = u + 4;
+ return "wss";
+ }
+ }
+ break;
+ }
+
+ *is_ssl = 0;
+ return NULL;
+}
+
/*
* Canonicalise http-like URLs.
* scheme is the scheme for the URL
* url is the URL starting with the first '/'
- * def_port is the default port for this scheme.
*/
static int proxy_http_canon(request_rec *r, char *url)
{
+ const char *base_url = url;
char *host, *path, sport[7];
char *search = NULL;
const char *err;
const char *scheme;
apr_port_t port, def_port;
+ int is_ssl = 0;
- /* ap_port_of_scheme() */
- if (strncasecmp(url, "http:", 5) == 0) {
- url += 5;
- scheme = "http";
- }
- else if (strncasecmp(url, "https:", 6) == 0) {
- url += 6;
- scheme = "https";
- }
- else {
+ scheme = get_url_scheme((const char **)&url, &is_ssl);
+ if (!scheme) {
return DECLINED;
}
- port = def_port = ap_proxy_port_of_scheme(scheme);
+ port = def_port = (is_ssl) ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT;
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
- "HTTP: canonicalising URL %s", url);
+ "HTTP: canonicalising URL %s", base_url);
/* do syntatic check.
* We break the URL into host, port, path, search
@@ -68,7 +103,7 @@ static int proxy_http_canon(request_rec *r, char *url)
err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
if (err) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01083)
- "error parsing URL %s: %s", url, err);
+ "error parsing URL %s: %s", base_url, err);
return HTTP_BAD_REQUEST;
}
@@ -86,9 +121,19 @@ static int proxy_http_canon(request_rec *r, char *url)
if (apr_table_get(r->notes, "proxy-nocanon")) {
path = url; /* this is the raw path */
}
+ else if (apr_table_get(r->notes, "proxy-noencode")) {
+ path = url; /* this is the encoded path already */
+ search = r->args;
+ }
else {
- path = ap_proxy_canonenc(r->pool, url, strlen(url),
- enc_path, 0, r->proxyreq);
+ core_dir_config *d = ap_get_core_module_config(r->per_dir_config);
+ int flags = d->allow_encoded_slashes && !d->decode_encoded_slashes ? PROXY_CANONENC_NOENCODEDSLASHENCODING : 0;
+
+ path = ap_proxy_canonenc_ex(r->pool, url, strlen(url), enc_path,
+ flags, r->proxyreq);
+ if (!path) {
+ return HTTP_BAD_REQUEST;
+ }
search = r->args;
}
break;
@@ -96,9 +141,22 @@ static int proxy_http_canon(request_rec *r, char *url)
path = url;
break;
}
-
- if (path == NULL)
- return HTTP_BAD_REQUEST;
+ /*
+ * If we have a raw control character or a ' ' in nocanon path or
+ * r->args, correct encoding was missed.
+ */
+ if (path == url && *ap_scan_vchar_obstext(path)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10415)
+ "To be forwarded path contains control "
+ "characters or spaces");
+ return HTTP_FORBIDDEN;
+ }
+ if (search && *ap_scan_vchar_obstext(search)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10408)
+ "To be forwarded query string contains control "
+ "characters or spaces");
+ return HTTP_FORBIDDEN;
+ }
if (port != def_port)
apr_snprintf(sport, sizeof(sport), ":%d", port);
@@ -108,8 +166,9 @@ static int proxy_http_canon(request_rec *r, char *url)
if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */
host = apr_pstrcat(r->pool, "[", host, "]", NULL);
}
+
r->filename = apr_pstrcat(r->pool, "proxy:", scheme, "://", host, sport,
- "/", path, (search) ? "?" : "", (search) ? search : "", NULL);
+ "/", path, (search) ? "?" : "", search, NULL);
return OK;
}
@@ -216,486 +275,230 @@ static void add_cl(apr_pool_t *p,
APR_BRIGADE_INSERT_TAIL(header_brigade, e);
}
-#define ASCII_CRLF "\015\012"
-#define ASCII_ZERO "\060"
-
-static void terminate_headers(apr_bucket_alloc_t *bucket_alloc,
- apr_bucket_brigade *header_brigade)
-{
- apr_bucket *e;
-
- /* add empty line at the end of the headers */
- e = apr_bucket_immortal_create(ASCII_CRLF, 2, bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(header_brigade, e);
-}
-
+#ifndef CRLF_ASCII
+#define CRLF_ASCII "\015\012"
+#endif
+#ifndef ZERO_ASCII
+#define ZERO_ASCII "\060"
+#endif
#define MAX_MEM_SPOOL 16384
-static int stream_reqbody_chunked(apr_pool_t *p,
- request_rec *r,
- proxy_conn_rec *p_conn,
- conn_rec *origin,
- apr_bucket_brigade *header_brigade,
- apr_bucket_brigade *input_brigade)
-{
- int seen_eos = 0, rv = OK;
- apr_size_t hdr_len;
- apr_off_t bytes;
- apr_status_t status;
- apr_bucket_alloc_t *bucket_alloc = r->connection->bucket_alloc;
- apr_bucket_brigade *bb;
- apr_bucket *e;
-
- add_te_chunked(p, bucket_alloc, header_brigade);
- terminate_headers(bucket_alloc, header_brigade);
+typedef enum {
+ RB_INIT = 0,
+ RB_STREAM_CL,
+ RB_STREAM_CHUNKED,
+ RB_SPOOL_CL
+} rb_methods;
- while (!APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(input_brigade)))
- {
- char chunk_hdr[20]; /* must be here due to transient bucket. */
+typedef struct {
+ apr_pool_t *p;
+ request_rec *r;
+ const char *proto;
+ proxy_worker *worker;
+ proxy_server_conf *sconf;
- /* If this brigade contains EOS, either stop or remove it. */
- if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
- seen_eos = 1;
-
- /* We can't pass this EOS to the output_filters. */
- e = APR_BRIGADE_LAST(input_brigade);
- apr_bucket_delete(e);
- }
+ char server_portstr[32];
+ proxy_conn_rec *backend;
+ conn_rec *origin;
- apr_brigade_length(input_brigade, 1, &bytes);
+ apr_bucket_alloc_t *bucket_alloc;
+ apr_bucket_brigade *header_brigade;
+ apr_bucket_brigade *input_brigade;
+ char *old_cl_val, *old_te_val;
+ apr_off_t cl_val;
- hdr_len = apr_snprintf(chunk_hdr, sizeof(chunk_hdr),
- "%" APR_UINT64_T_HEX_FMT CRLF,
- (apr_uint64_t)bytes);
+ rb_methods rb_method;
- ap_xlate_proto_to_ascii(chunk_hdr, hdr_len);
- e = apr_bucket_transient_create(chunk_hdr, hdr_len,
- bucket_alloc);
- APR_BRIGADE_INSERT_HEAD(input_brigade, e);
+ const char *upgrade;
- /*
- * Append the end-of-chunk CRLF
- */
- e = apr_bucket_immortal_create(ASCII_CRLF, 2, bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(input_brigade, e);
+ unsigned int do_100_continue :1,
+ prefetch_nonblocking :1,
+ force10 :1;
+} proxy_http_req_t;
- if (header_brigade) {
- /* we never sent the header brigade, so go ahead and
- * take care of that now
- */
- bb = header_brigade;
+static int stream_reqbody(proxy_http_req_t *req)
+{
+ request_rec *r = req->r;
+ int seen_eos = 0, rv = OK;
+ apr_size_t hdr_len;
+ char chunk_hdr[20]; /* must be here due to transient bucket. */
+ conn_rec *origin = req->origin;
+ proxy_conn_rec *p_conn = req->backend;
+ apr_bucket_alloc_t *bucket_alloc = req->bucket_alloc;
+ apr_bucket_brigade *header_brigade = req->header_brigade;
+ apr_bucket_brigade *input_brigade = req->input_brigade;
+ rb_methods rb_method = req->rb_method;
+ apr_off_t bytes, bytes_streamed = 0;
+ apr_bucket *e;
- /*
- * Save input_brigade in bb brigade. (At least) in the SSL case
- * input_brigade contains transient buckets whose data would get
- * overwritten during the next call of ap_get_brigade in the loop.
- * ap_save_brigade ensures these buckets to be set aside.
- * Calling ap_save_brigade with NULL as filter is OK, because
- * bb brigade already has been created and does not need to get
- * created by ap_save_brigade.
- */
- status = ap_save_brigade(NULL, &bb, &input_brigade, p);
- if (status != APR_SUCCESS) {
- return HTTP_INTERNAL_SERVER_ERROR;
+ do {
+ if (APR_BRIGADE_EMPTY(input_brigade)
+ && APR_BRIGADE_EMPTY(header_brigade)) {
+ rv = ap_proxy_read_input(r, p_conn, input_brigade,
+ HUGE_STRING_LEN);
+ if (rv != OK) {
+ return rv;
}
-
- header_brigade = NULL;
- }
- else {
- bb = input_brigade;
}
- /* The request is flushed below this loop with chunk EOS header */
- rv = ap_proxy_pass_brigade(bucket_alloc, r, p_conn, origin, bb, 0);
- if (rv != OK) {
- return rv;
- }
-
- if (seen_eos) {
- break;
- }
-
- status = ap_get_brigade(r->input_filters, input_brigade,
- AP_MODE_READBYTES, APR_BLOCK_READ,
- HUGE_STRING_LEN);
-
- if (status != APR_SUCCESS) {
- conn_rec *c = r->connection;
- ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02608)
- "read request body failed to %pI (%s)"
- " from %s (%s)", p_conn->addr,
- p_conn->hostname ? p_conn->hostname: "",
- c->client_ip, c->remote_host ? c->remote_host: "");
- return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
- }
- }
-
- if (header_brigade) {
- /* we never sent the header brigade because there was no request body;
- * send it now
- */
- bb = header_brigade;
- }
- else {
if (!APR_BRIGADE_EMPTY(input_brigade)) {
- /* input brigade still has an EOS which we can't pass to the output_filters. */
- e = APR_BRIGADE_LAST(input_brigade);
- AP_DEBUG_ASSERT(APR_BUCKET_IS_EOS(e));
- apr_bucket_delete(e);
- }
- bb = input_brigade;
- }
-
- e = apr_bucket_immortal_create(ASCII_ZERO ASCII_CRLF
- /* <trailers> */
- ASCII_CRLF,
- 5, bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, e);
-
- if (apr_table_get(r->subprocess_env, "proxy-sendextracrlf")) {
- e = apr_bucket_immortal_create(ASCII_CRLF, 2, bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, e);
- }
-
- /* Now we have headers-only, or the chunk EOS mark; flush it */
- rv = ap_proxy_pass_brigade(bucket_alloc, r, p_conn, origin, bb, 1);
- return rv;
-}
-
-static int stream_reqbody_cl(apr_pool_t *p,
- request_rec *r,
- proxy_conn_rec *p_conn,
- conn_rec *origin,
- apr_bucket_brigade *header_brigade,
- apr_bucket_brigade *input_brigade,
- char *old_cl_val)
-{
- int seen_eos = 0, rv = 0;
- apr_status_t status = APR_SUCCESS;
- apr_bucket_alloc_t *bucket_alloc = r->connection->bucket_alloc;
- apr_bucket_brigade *bb;
- apr_bucket *e;
- apr_off_t cl_val = 0;
- apr_off_t bytes;
- apr_off_t bytes_streamed = 0;
-
- if (old_cl_val) {
- char *endstr;
-
- add_cl(p, bucket_alloc, header_brigade, old_cl_val);
- status = apr_strtoff(&cl_val, old_cl_val, &endstr, 10);
-
- if (status || *endstr || endstr == old_cl_val || cl_val < 0) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01085)
- "could not parse request Content-Length (%s)",
- old_cl_val);
- return HTTP_BAD_REQUEST;
- }
- }
- terminate_headers(bucket_alloc, header_brigade);
+ /* If this brigade contains EOS, remove it and be done. */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+ seen_eos = 1;
- while (!APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(input_brigade)))
- {
- apr_brigade_length(input_brigade, 1, &bytes);
- bytes_streamed += bytes;
+ /* We can't pass this EOS to the output_filters. */
+ e = APR_BRIGADE_LAST(input_brigade);
+ apr_bucket_delete(e);
+ }
- /* If this brigade contains EOS, either stop or remove it. */
- if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
- seen_eos = 1;
+ apr_brigade_length(input_brigade, 1, &bytes);
+ bytes_streamed += bytes;
- /* We can't pass this EOS to the output_filters. */
- e = APR_BRIGADE_LAST(input_brigade);
- apr_bucket_delete(e);
+ if (rb_method == RB_STREAM_CHUNKED) {
+ if (bytes) {
+ /*
+ * Prepend the size of the chunk
+ */
+ hdr_len = apr_snprintf(chunk_hdr, sizeof(chunk_hdr),
+ "%" APR_UINT64_T_HEX_FMT CRLF,
+ (apr_uint64_t)bytes);
+ ap_xlate_proto_to_ascii(chunk_hdr, hdr_len);
+ e = apr_bucket_transient_create(chunk_hdr, hdr_len,
+ bucket_alloc);
+ APR_BRIGADE_INSERT_HEAD(input_brigade, e);
+
+ /*
+ * Append the end-of-chunk CRLF
+ */
+ e = apr_bucket_immortal_create(CRLF_ASCII, 2, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(input_brigade, e);
+ }
+ if (seen_eos) {
+ /*
+ * Append the tailing 0-size chunk
+ */
+ e = apr_bucket_immortal_create(ZERO_ASCII CRLF_ASCII
+ /* <trailers> */
+ CRLF_ASCII,
+ 5, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(input_brigade, e);
+ }
+ }
+ else if (rb_method == RB_STREAM_CL
+ && (bytes_streamed > req->cl_val
+ || (seen_eos && bytes_streamed < req->cl_val))) {
+ /* C-L != bytes streamed?!?
+ *
+ * Prevent HTTP Request/Response Splitting.
+ *
+ * We can't stream more (or less) bytes at the back end since
+ * they could be interpreted in separate requests (more bytes
+ * now would start a new request, less bytes would make the
+ * first bytes of the next request be part of the current one).
+ *
+ * It can't happen from the client connection here thanks to
+ * ap_http_filter(), but some module's filter may be playing
+ * bad games, hence the HTTP_INTERNAL_SERVER_ERROR.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01086)
+ "read %s bytes of request body than expected "
+ "(got %" APR_OFF_T_FMT ", expected "
+ "%" APR_OFF_T_FMT ")",
+ bytes_streamed > req->cl_val ? "more" : "less",
+ bytes_streamed, req->cl_val);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
- if (apr_table_get(r->subprocess_env, "proxy-sendextracrlf")) {
- e = apr_bucket_immortal_create(ASCII_CRLF, 2, bucket_alloc);
+ if (seen_eos && apr_table_get(r->subprocess_env,
+ "proxy-sendextracrlf")) {
+ e = apr_bucket_immortal_create(CRLF_ASCII, 2, bucket_alloc);
APR_BRIGADE_INSERT_TAIL(input_brigade, e);
}
}
- /* C-L < bytes streamed?!?
- * We will error out after the body is completely
- * consumed, but we can't stream more bytes at the
- * back end since they would in part be interpreted
- * as another request! If nothing is sent, then
- * just send nothing.
- *
- * Prevents HTTP Response Splitting.
+ /* If we never sent the header brigade, go ahead and take care of
+ * that now by prepending it (once only since header_brigade will be
+ * empty afterward).
*/
- if (bytes_streamed > cl_val) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01086)
- "read more bytes of request body than expected "
- "(got %" APR_OFF_T_FMT ", expected %" APR_OFF_T_FMT ")",
- bytes_streamed, cl_val);
- return HTTP_INTERNAL_SERVER_ERROR;
- }
-
- if (header_brigade) {
- /* we never sent the header brigade, so go ahead and
- * take care of that now
- */
- bb = header_brigade;
-
- /*
- * Save input_brigade in bb brigade. (At least) in the SSL case
- * input_brigade contains transient buckets whose data would get
- * overwritten during the next call of ap_get_brigade in the loop.
- * ap_save_brigade ensures these buckets to be set aside.
- * Calling ap_save_brigade with NULL as filter is OK, because
- * bb brigade already has been created and does not need to get
- * created by ap_save_brigade.
- */
- status = ap_save_brigade(NULL, &bb, &input_brigade, p);
- if (status != APR_SUCCESS) {
- return HTTP_INTERNAL_SERVER_ERROR;
- }
-
- header_brigade = NULL;
- }
- else {
- bb = input_brigade;
- }
+ APR_BRIGADE_PREPEND(input_brigade, header_brigade);
- /* Once we hit EOS, we are ready to flush. */
- rv = ap_proxy_pass_brigade(bucket_alloc, r, p_conn, origin, bb, seen_eos);
+ /* Flush here on EOS because we won't ap_proxy_read_input() again. */
+ rv = ap_proxy_pass_brigade(bucket_alloc, r, p_conn, origin,
+ input_brigade, seen_eos);
if (rv != OK) {
- return rv ;
- }
-
- if (seen_eos) {
- break;
- }
-
- status = ap_get_brigade(r->input_filters, input_brigade,
- AP_MODE_READBYTES, APR_BLOCK_READ,
- HUGE_STRING_LEN);
-
- if (status != APR_SUCCESS) {
- conn_rec *c = r->connection;
- ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02609)
- "read request body failed to %pI (%s)"
- " from %s (%s)", p_conn->addr,
- p_conn->hostname ? p_conn->hostname: "",
- c->client_ip, c->remote_host ? c->remote_host: "");
- return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
+ return rv;
}
- }
-
- if (bytes_streamed != cl_val) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01087)
- "client %s given Content-Length did not match"
- " number of body bytes read", r->connection->client_ip);
- return HTTP_BAD_REQUEST;
- }
-
- if (header_brigade) {
- /* we never sent the header brigade since there was no request
- * body; send it now with the flush flag
- */
- bb = header_brigade;
- return(ap_proxy_pass_brigade(bucket_alloc, r, p_conn, origin, bb, 1));
- }
+ } while (!seen_eos);
return OK;
}
-static int spool_reqbody_cl(apr_pool_t *p,
- request_rec *r,
- proxy_conn_rec *p_conn,
- conn_rec *origin,
- apr_bucket_brigade *header_brigade,
- apr_bucket_brigade *input_brigade,
- int force_cl)
+static void terminate_headers(proxy_http_req_t *req)
{
- int seen_eos = 0;
- apr_status_t status;
- apr_bucket_alloc_t *bucket_alloc = r->connection->bucket_alloc;
- apr_bucket_brigade *body_brigade;
+ apr_bucket_alloc_t *bucket_alloc = req->bucket_alloc;
apr_bucket *e;
- apr_off_t bytes, bytes_spooled = 0, fsize = 0;
- apr_file_t *tmpfile = NULL;
- apr_off_t limit;
-
- body_brigade = apr_brigade_create(p, bucket_alloc);
-
- limit = ap_get_limit_req_body(r);
-
- while (!APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(input_brigade)))
- {
- /* If this brigade contains EOS, either stop or remove it. */
- if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
- seen_eos = 1;
+ char *buf;
- /* We can't pass this EOS to the output_filters. */
- e = APR_BRIGADE_LAST(input_brigade);
- apr_bucket_delete(e);
+ /*
+ * Handle Connection: header if we do HTTP/1.1 request:
+ * If we plan to close the backend connection sent Connection: close
+ * otherwise sent Connection: Keep-Alive.
+ */
+ if (!req->force10) {
+ if (req->upgrade) {
+ buf = apr_pstrdup(req->p, "Connection: Upgrade" CRLF);
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), req->p, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(req->header_brigade, e);
+
+ /* Tell the backend that it can upgrade the connection. */
+ buf = apr_pstrcat(req->p, "Upgrade: ", req->upgrade, CRLF, NULL);
}
-
- apr_brigade_length(input_brigade, 1, &bytes);
-
- if (bytes_spooled + bytes > MAX_MEM_SPOOL) {
- /*
- * LimitRequestBody does not affect Proxy requests (Should it?).
- * Let it take effect if we decide to store the body in a
- * temporary file on disk.
- */
- if (limit && (bytes_spooled + bytes > limit)) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01088)
- "Request body is larger than the configured "
- "limit of %" APR_OFF_T_FMT, limit);
- return HTTP_REQUEST_ENTITY_TOO_LARGE;
- }
- /* can't spool any more in memory; write latest brigade to disk */
- if (tmpfile == NULL) {
- const char *temp_dir;
- char *template;
-
- status = apr_temp_dir_get(&temp_dir, p);
- if (status != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01089)
- "search for temporary directory failed");
- return HTTP_INTERNAL_SERVER_ERROR;
- }
- apr_filepath_merge(&template, temp_dir,
- "modproxy.tmp.XXXXXX",
- APR_FILEPATH_NATIVE, p);
- status = apr_file_mktemp(&tmpfile, template, 0, p);
- if (status != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01090)
- "creation of temporary file in directory "
- "%s failed", temp_dir);
- return HTTP_INTERNAL_SERVER_ERROR;
- }
- }
- for (e = APR_BRIGADE_FIRST(input_brigade);
- e != APR_BRIGADE_SENTINEL(input_brigade);
- e = APR_BUCKET_NEXT(e)) {
- const char *data;
- apr_size_t bytes_read, bytes_written;
-
- apr_bucket_read(e, &data, &bytes_read, APR_BLOCK_READ);
- status = apr_file_write_full(tmpfile, data, bytes_read, &bytes_written);
- if (status != APR_SUCCESS) {
- const char *tmpfile_name;
-
- if (apr_file_name_get(&tmpfile_name, tmpfile) != APR_SUCCESS) {
- tmpfile_name = "(unknown)";
- }
- ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01091)
- "write to temporary file %s failed",
- tmpfile_name);
- return HTTP_INTERNAL_SERVER_ERROR;
- }
- AP_DEBUG_ASSERT(bytes_read == bytes_written);
- fsize += bytes_written;
- }
- apr_brigade_cleanup(input_brigade);
+ else if (ap_proxy_connection_reusable(req->backend)) {
+ buf = apr_pstrdup(req->p, "Connection: Keep-Alive" CRLF);
}
else {
-
- /*
- * Save input_brigade in body_brigade. (At least) in the SSL case
- * input_brigade contains transient buckets whose data would get
- * overwritten during the next call of ap_get_brigade in the loop.
- * ap_save_brigade ensures these buckets to be set aside.
- * Calling ap_save_brigade with NULL as filter is OK, because
- * body_brigade already has been created and does not need to get
- * created by ap_save_brigade.
- */
- status = ap_save_brigade(NULL, &body_brigade, &input_brigade, p);
- if (status != APR_SUCCESS) {
- return HTTP_INTERNAL_SERVER_ERROR;
- }
-
- }
-
- bytes_spooled += bytes;
-
- if (seen_eos) {
- break;
- }
-
- status = ap_get_brigade(r->input_filters, input_brigade,
- AP_MODE_READBYTES, APR_BLOCK_READ,
- HUGE_STRING_LEN);
-
- if (status != APR_SUCCESS) {
- conn_rec *c = r->connection;
- ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02610)
- "read request body failed to %pI (%s)"
- " from %s (%s)", p_conn->addr,
- p_conn->hostname ? p_conn->hostname: "",
- c->client_ip, c->remote_host ? c->remote_host: "");
- return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
+ buf = apr_pstrdup(req->p, "Connection: close" CRLF);
}
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), req->p, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(req->header_brigade, e);
}
- if (bytes_spooled || force_cl) {
- add_cl(p, bucket_alloc, header_brigade, apr_off_t_toa(p, bytes_spooled));
- }
- terminate_headers(bucket_alloc, header_brigade);
- APR_BRIGADE_CONCAT(header_brigade, body_brigade);
- if (tmpfile) {
- apr_brigade_insert_file(header_brigade, tmpfile, 0, fsize, p);
- }
- if (apr_table_get(r->subprocess_env, "proxy-sendextracrlf")) {
- e = apr_bucket_immortal_create(ASCII_CRLF, 2, bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(header_brigade, e);
- }
- /* This is all a single brigade, pass with flush flagged */
- return(ap_proxy_pass_brigade(bucket_alloc, r, p_conn, origin, header_brigade, 1));
+ /* add empty line at the end of the headers */
+ e = apr_bucket_immortal_create(CRLF_ASCII, 2, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(req->header_brigade, e);
}
-static
-int ap_proxy_http_request(apr_pool_t *p, request_rec *r,
- proxy_conn_rec *p_conn, proxy_worker *worker,
- proxy_server_conf *conf,
- apr_uri_t *uri,
- char *url, char *server_portstr)
+static int ap_proxy_http_prefetch(proxy_http_req_t *req,
+ apr_uri_t *uri, char *url)
{
+ apr_pool_t *p = req->p;
+ request_rec *r = req->r;
conn_rec *c = r->connection;
- apr_bucket_alloc_t *bucket_alloc = c->bucket_alloc;
- apr_bucket_brigade *header_brigade;
- apr_bucket_brigade *input_brigade;
- apr_bucket_brigade *temp_brigade;
+ proxy_conn_rec *p_conn = req->backend;
+ apr_bucket_alloc_t *bucket_alloc = req->bucket_alloc;
+ apr_bucket_brigade *header_brigade = req->header_brigade;
+ apr_bucket_brigade *input_brigade = req->input_brigade;
apr_bucket *e;
- char *buf;
- apr_status_t status;
- enum rb_methods {RB_INIT, RB_STREAM_CL, RB_STREAM_CHUNKED, RB_SPOOL_CL};
- enum rb_methods rb_method = RB_INIT;
- char *old_cl_val = NULL;
- char *old_te_val = NULL;
apr_off_t bytes_read = 0;
apr_off_t bytes;
- int force10, rv;
- conn_rec *origin = p_conn->connection;
-
- if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) {
- if (r->expecting_100) {
- return HTTP_EXPECTATION_FAILED;
- }
- force10 = 1;
- } else {
- force10 = 0;
- }
+ int rv;
- header_brigade = apr_brigade_create(p, bucket_alloc);
rv = ap_proxy_create_hdrbrgd(p, header_brigade, r, p_conn,
- worker, conf, uri, url, server_portstr,
- &old_cl_val, &old_te_val);
+ req->worker, req->sconf,
+ uri, url, req->server_portstr,
+ &req->old_cl_val, &req->old_te_val);
if (rv != OK) {
return rv;
}
- /* We have headers, let's figure out our request body... */
- input_brigade = apr_brigade_create(p, bucket_alloc);
-
/* sub-requests never use keepalives, and mustn't pass request bodies.
* Because the new logic looks at input_brigade, we will self-terminate
* input_brigade and jump past all of the request body logic...
* Reading anything with ap_get_brigade is likely to consume the
- * main request's body or read beyond EOS - which would be unplesant.
+ * main request's body or read beyond EOS - which would be unpleasant.
*
* An exception: when a kept_body is present, then subrequest CAN use
* pass request bodies, and we DONT skip the body.
@@ -703,9 +506,9 @@ int ap_proxy_http_request(apr_pool_t *p, request_rec *r,
if (!r->kept_body && r->main) {
/* XXX: Why DON'T sub-requests use keepalives? */
p_conn->close = 1;
- old_cl_val = NULL;
- old_te_val = NULL;
- rb_method = RB_STREAM_CL;
+ req->old_te_val = NULL;
+ req->old_cl_val = NULL;
+ req->rb_method = RB_STREAM_CL;
e = apr_bucket_eos_create(input_brigade->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(input_brigade, e);
goto skip_body;
@@ -719,73 +522,29 @@ int ap_proxy_http_request(apr_pool_t *p, request_rec *r,
* encoding has been done by the extensions' handler, and
* do not modify add_te_chunked's logic
*/
- if (old_te_val && strcasecmp(old_te_val, "chunked") != 0) {
+ if (req->old_te_val && ap_cstr_casecmp(req->old_te_val, "chunked") != 0) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01093)
- "%s Transfer-Encoding is not supported", old_te_val);
+ "%s Transfer-Encoding is not supported",
+ req->old_te_val);
return HTTP_INTERNAL_SERVER_ERROR;
}
- if (old_cl_val && old_te_val) {
+ if (req->old_cl_val && req->old_te_val) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01094)
"client %s (%s) requested Transfer-Encoding "
"chunked body with Content-Length (C-L ignored)",
c->client_ip, c->remote_host ? c->remote_host: "");
- old_cl_val = NULL;
- origin->keepalive = AP_CONN_CLOSE;
+ req->old_cl_val = NULL;
p_conn->close = 1;
}
- /* Prefetch MAX_MEM_SPOOL bytes
- *
- * This helps us avoid any election of C-L v.s. T-E
- * request bodies, since we are willing to keep in
- * memory this much data, in any case. This gives
- * us an instant C-L election if the body is of some
- * reasonable size.
- */
- temp_brigade = apr_brigade_create(p, bucket_alloc);
- do {
- status = ap_get_brigade(r->input_filters, temp_brigade,
- AP_MODE_READBYTES, APR_BLOCK_READ,
- MAX_MEM_SPOOL - bytes_read);
- if (status != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01095)
- "prefetch request body failed to %pI (%s)"
- " from %s (%s)",
- p_conn->addr, p_conn->hostname ? p_conn->hostname: "",
- c->client_ip, c->remote_host ? c->remote_host: "");
- return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
- }
-
- apr_brigade_length(temp_brigade, 1, &bytes);
- bytes_read += bytes;
-
- /*
- * Save temp_brigade in input_brigade. (At least) in the SSL case
- * temp_brigade contains transient buckets whose data would get
- * overwritten during the next call of ap_get_brigade in the loop.
- * ap_save_brigade ensures these buckets to be set aside.
- * Calling ap_save_brigade with NULL as filter is OK, because
- * input_brigade already has been created and does not need to get
- * created by ap_save_brigade.
- */
- status = ap_save_brigade(NULL, &input_brigade, &temp_brigade, p);
- if (status != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01096)
- "processing prefetched request body failed"
- " to %pI (%s) from %s (%s)",
- p_conn->addr, p_conn->hostname ? p_conn->hostname: "",
- c->client_ip, c->remote_host ? c->remote_host: "");
- return HTTP_INTERNAL_SERVER_ERROR;
- }
-
- /* Ensure we don't hit a wall where we have a buffer too small
- * for ap_get_brigade's filters to fetch us another bucket,
- * surrender once we hit 80 bytes less than MAX_MEM_SPOOL
- * (an arbitrary value.)
- */
- } while ((bytes_read < MAX_MEM_SPOOL - 80)
- && !APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade)));
+ rv = ap_proxy_prefetch_input(r, req->backend, input_brigade,
+ req->prefetch_nonblocking ? APR_NONBLOCK_READ
+ : APR_BLOCK_READ,
+ &bytes_read, MAX_MEM_SPOOL);
+ if (rv != OK) {
+ return rv;
+ }
/* Use chunked request body encoding or send a content-length body?
*
@@ -812,7 +571,7 @@ int ap_proxy_http_request(apr_pool_t *p, request_rec *r,
* To reduce server resource use, setenv proxy-sendchunked
*
* Then address specific servers with conditional setenv
- * options to restore the default behavior where desireable.
+ * options to restore the default behavior where desirable.
*
* We have to compute content length by reading the entire request
* body; if request body is not small, we'll spool the remaining
@@ -822,7 +581,8 @@ int ap_proxy_http_request(apr_pool_t *p, request_rec *r,
* is absent, and the filters are unchanged (the body won't
* be resized by another content filter).
*/
- if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+ if (!APR_BRIGADE_EMPTY(input_brigade)
+ && APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
/* The whole thing fit, so our decision is trivial, use
* the filtered bytes read from the client for the request
* body Content-Length.
@@ -830,34 +590,41 @@ int ap_proxy_http_request(apr_pool_t *p, request_rec *r,
* If we expected no body, and read no body, do not set
* the Content-Length.
*/
- if (old_cl_val || old_te_val || bytes_read) {
- old_cl_val = apr_off_t_toa(r->pool, bytes_read);
+ if (req->old_cl_val || req->old_te_val || bytes_read) {
+ req->old_cl_val = apr_off_t_toa(r->pool, bytes_read);
+ req->cl_val = bytes_read;
}
- rb_method = RB_STREAM_CL;
+ req->rb_method = RB_STREAM_CL;
}
- else if (old_te_val) {
- if (force10
+ else if (req->old_te_val) {
+ if (req->force10
|| (apr_table_get(r->subprocess_env, "proxy-sendcl")
&& !apr_table_get(r->subprocess_env, "proxy-sendchunks")
&& !apr_table_get(r->subprocess_env, "proxy-sendchunked"))) {
- rb_method = RB_SPOOL_CL;
+ req->rb_method = RB_SPOOL_CL;
}
else {
- rb_method = RB_STREAM_CHUNKED;
+ req->rb_method = RB_STREAM_CHUNKED;
}
}
- else if (old_cl_val) {
+ else if (req->old_cl_val) {
if (r->input_filters == r->proto_input_filters) {
- rb_method = RB_STREAM_CL;
+ if (!ap_parse_strict_length(&req->cl_val, req->old_cl_val)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01085)
+ "could not parse request Content-Length (%s)",
+ req->old_cl_val);
+ return HTTP_BAD_REQUEST;
+ }
+ req->rb_method = RB_STREAM_CL;
}
- else if (!force10
+ else if (!req->force10
&& (apr_table_get(r->subprocess_env, "proxy-sendchunks")
|| apr_table_get(r->subprocess_env, "proxy-sendchunked"))
&& !apr_table_get(r->subprocess_env, "proxy-sendcl")) {
- rb_method = RB_STREAM_CHUNKED;
+ req->rb_method = RB_STREAM_CHUNKED;
}
else {
- rb_method = RB_SPOOL_CL;
+ req->rb_method = RB_SPOOL_CL;
}
}
else {
@@ -865,44 +632,60 @@ int ap_proxy_http_request(apr_pool_t *p, request_rec *r,
* requests, and has the behavior that it will not add any C-L
* when the old_cl_val is NULL.
*/
- rb_method = RB_SPOOL_CL;
+ req->rb_method = RB_SPOOL_CL;
}
-/* Yes I hate gotos. This is the subrequest shortcut */
-skip_body:
- /*
- * Handle Connection: header if we do HTTP/1.1 request:
- * If we plan to close the backend connection sent Connection: close
- * otherwise sent Connection: Keep-Alive.
- */
- if (!force10) {
- if (!ap_proxy_connection_reusable(p_conn)) {
- buf = apr_pstrdup(p, "Connection: close" CRLF);
- }
- else {
- buf = apr_pstrdup(p, "Connection: Keep-Alive" CRLF);
- }
- ap_xlate_proto_to_ascii(buf, strlen(buf));
- e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(header_brigade, e);
- }
-
- /* send the request body, if any. */
- switch(rb_method) {
+ switch (req->rb_method) {
case RB_STREAM_CHUNKED:
- rv = stream_reqbody_chunked(p, r, p_conn, origin, header_brigade,
- input_brigade);
+ add_te_chunked(req->p, bucket_alloc, header_brigade);
break;
+
case RB_STREAM_CL:
- rv = stream_reqbody_cl(p, r, p_conn, origin, header_brigade,
- input_brigade, old_cl_val);
+ if (req->old_cl_val) {
+ add_cl(req->p, bucket_alloc, header_brigade, req->old_cl_val);
+ }
break;
+
+ default: /* => RB_SPOOL_CL */
+ /* If we have to spool the body, do it now, before connecting or
+ * reusing the backend connection.
+ */
+ rv = ap_proxy_spool_input(r, p_conn, input_brigade,
+ &bytes, MAX_MEM_SPOOL);
+ if (rv != OK) {
+ return rv;
+ }
+ if (bytes || req->old_te_val || req->old_cl_val) {
+ add_cl(p, bucket_alloc, header_brigade, apr_off_t_toa(p, bytes));
+ }
+ }
+
+/* Yes I hate gotos. This is the subrequest shortcut */
+skip_body:
+ terminate_headers(req);
+
+ return OK;
+}
+
+static int ap_proxy_http_request(proxy_http_req_t *req)
+{
+ int rv;
+ request_rec *r = req->r;
+
+ /* send the request header/body, if any. */
+ switch (req->rb_method) {
case RB_SPOOL_CL:
- rv = spool_reqbody_cl(p, r, p_conn, origin, header_brigade,
- input_brigade, (old_cl_val != NULL)
- || (old_te_val != NULL)
- || (bytes_read > 0));
+ case RB_STREAM_CL:
+ case RB_STREAM_CHUNKED:
+ if (req->do_100_continue) {
+ rv = ap_proxy_pass_brigade(req->bucket_alloc, r, req->backend,
+ req->origin, req->header_brigade, 1);
+ }
+ else {
+ rv = stream_reqbody(req);
+ }
break;
+
default:
/* shouldn't be possible */
rv = HTTP_INTERNAL_SERVER_ERROR;
@@ -910,10 +693,12 @@ skip_body:
}
if (rv != OK) {
+ conn_rec *c = r->connection;
/* apr_status_t value has been logged in lower level method */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01097)
"pass request body failed to %pI (%s) from %s (%s)",
- p_conn->addr, p_conn->hostname ? p_conn->hostname: "",
+ req->backend->addr,
+ req->backend->hostname ? req->backend->hostname: "",
c->client_ip, c->remote_host ? c->remote_host: "");
return rv;
}
@@ -950,6 +735,7 @@ static request_rec *make_fake_req(conn_rec *c, request_rec *r)
request_rec *rp;
apr_pool_create(&pool, c->pool);
+ apr_pool_tag(pool, "proxy_http_rp");
rp = apr_pcalloc(pool, sizeof(*r));
@@ -1001,14 +787,14 @@ static void process_proxy_header(request_rec *r, proxy_dir_conf *c,
};
int i;
for (i = 0; date_hdrs[i]; ++i) {
- if (!strcasecmp(date_hdrs[i], key)) {
+ if (!ap_cstr_casecmp(date_hdrs[i], key)) {
apr_table_add(r->headers_out, key,
date_canon(r->pool, value));
return;
}
}
for (i = 0; transform_hdrs[i].name; ++i) {
- if (!strcasecmp(transform_hdrs[i].name, key)) {
+ if (!ap_cstr_casecmp(transform_hdrs[i].name, key)) {
apr_table_add(r->headers_out, key,
(*transform_hdrs[i].func)(r, c, value));
return;
@@ -1025,7 +811,7 @@ static void process_proxy_header(request_rec *r, proxy_dir_conf *c,
* any sense at all, since we depend on buffer still containing
* what was read by ap_getline() upon return.
*/
-static void ap_proxy_read_headers(request_rec *r, request_rec *rr,
+static apr_status_t ap_proxy_read_headers(request_rec *r, request_rec *rr,
char *buffer, int size,
conn_rec *c, int *pread_len)
{
@@ -1057,19 +843,26 @@ static void ap_proxy_read_headers(request_rec *r, request_rec *rr,
rc = ap_proxygetline(tmp_bb, buffer, size, rr,
AP_GETLINE_FOLD | AP_GETLINE_NOSPC_EOL, &len);
- if (len <= 0)
- break;
- if (APR_STATUS_IS_ENOSPC(rc)) {
- /* The header could not fit in the provided buffer, warn.
- * XXX: falls through with the truncated header, 5xx instead?
- */
- int trunc = (len > 128 ? 128 : len) / 2;
- ap_log_rerror(APLOG_MARK, APLOG_WARNING, rc, r, APLOGNO(10124)
- "header size is over the limit allowed by "
- "ResponseFieldSize (%d bytes). "
- "Bad response header: '%.*s[...]%s'",
- size, trunc, buffer, buffer + len - trunc);
+ if (rc != APR_SUCCESS) {
+ if (APR_STATUS_IS_ENOSPC(rc)) {
+ int trunc = (len > 128 ? 128 : len) / 2;
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rc, r, APLOGNO(10124)
+ "header size is over the limit allowed by "
+ "ResponseFieldSize (%d bytes). "
+ "Bad response header: '%.*s[...]%s'",
+ size, trunc, buffer, buffer + len - trunc);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rc, r, APLOGNO(10404)
+ "Error reading headers from backend");
+ }
+ r->headers_out = NULL;
+ return rc;
+ }
+
+ if (len <= 0) {
+ break;
}
else {
ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r, "%s", buffer);
@@ -1092,7 +885,7 @@ static void ap_proxy_read_headers(request_rec *r, request_rec *rr,
if (psc->badopt == bad_error) {
/* Nope, it wasn't even an extra HTTP header. Give up. */
r->headers_out = NULL;
- return;
+ return APR_EINVAL;
}
else if (psc->badopt == bad_body) {
/* if we've already started loading headers_out, then
@@ -1106,13 +899,13 @@ static void ap_proxy_read_headers(request_rec *r, request_rec *rr,
"in headers returned by %s (%s)",
r->uri, r->method);
*pread_len = len;
- return;
+ return APR_SUCCESS;
}
else {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01099)
"No HTTP headers returned by %s (%s)",
r->uri, r->method);
- return;
+ return APR_SUCCESS;
}
}
}
@@ -1142,6 +935,7 @@ static void ap_proxy_read_headers(request_rec *r, request_rec *rr,
process_proxy_header(r, dconf, buffer, value);
saw_headers = 1;
}
+ return APR_SUCCESS;
}
@@ -1188,13 +982,48 @@ static int add_trailers(void *data, const char *key, const char *val)
return 1;
}
+static int send_continue_body(proxy_http_req_t *req)
+{
+ int status;
+
+ /* Send the request body (fully). */
+ switch(req->rb_method) {
+ case RB_SPOOL_CL:
+ case RB_STREAM_CL:
+ case RB_STREAM_CHUNKED:
+ status = stream_reqbody(req);
+ break;
+ default:
+ /* Shouldn't happen */
+ status = HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ }
+ if (status != OK) {
+ conn_rec *c = req->r->connection;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req->r,
+ APLOGNO(10154) "pass request body failed "
+ "to %pI (%s) from %s (%s) with status %i",
+ req->backend->addr,
+ req->backend->hostname ? req->backend->hostname : "",
+ c->client_ip, c->remote_host ? c->remote_host : "",
+ status);
+ req->backend->close = 1;
+ }
+ return status;
+}
+
static
-apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
- proxy_conn_rec **backend_ptr,
- proxy_worker *worker,
- proxy_server_conf *conf,
- char *server_portstr) {
+int ap_proxy_http_process_response(proxy_http_req_t *req)
+{
+ apr_pool_t *p = req->p;
+ request_rec *r = req->r;
conn_rec *c = r->connection;
+ proxy_worker *worker = req->worker;
+ proxy_conn_rec *backend = req->backend;
+ conn_rec *origin = req->origin;
+ int do_100_continue = req->do_100_continue;
+ int status;
+
char *buffer;
char fixed_buffer[HUGE_STRING_LEN];
const char *buf;
@@ -1217,19 +1046,11 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
int proxy_status = OK;
const char *original_status_line = r->status_line;
const char *proxy_status_line = NULL;
- proxy_conn_rec *backend = *backend_ptr;
- conn_rec *origin = backend->connection;
apr_interval_time_t old_timeout = 0;
proxy_dir_conf *dconf;
- int do_100_continue;
dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
- do_100_continue = (worker->s->ping_timeout_set
- && ap_request_has_body(r)
- && (PROXYREQ_REVERSE == r->proxyreq)
- && !(apr_table_get(r->subprocess_env, "force-proxy-request-1.0")));
-
bb = apr_brigade_create(p, c->bucket_alloc);
pass_bb = apr_brigade_create(p, c->bucket_alloc);
@@ -1248,7 +1069,7 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
}
/* Setup for 100-Continue timeout if appropriate */
- if (do_100_continue) {
+ if (do_100_continue && worker->s->ping_timeout_set) {
apr_socket_timeout_get(backend->sock, &old_timeout);
if (worker->s->ping_timeout != old_timeout) {
apr_status_t rc;
@@ -1273,6 +1094,9 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
origin->local_addr->port));
do {
apr_status_t rc;
+ const char *upgrade = NULL;
+ int major = 0, minor = 0;
+ int toclose = 0;
apr_brigade_cleanup(bb);
@@ -1291,7 +1115,8 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
apr_table_setn(r->notes, "proxy_timedout", "1");
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01103) "read timeout");
if (do_100_continue) {
- return ap_proxyerror(r, HTTP_SERVICE_UNAVAILABLE, "Timeout on 100-Continue");
+ return ap_proxyerror(r, HTTP_SERVICE_UNAVAILABLE,
+ "Timeout on 100-Continue");
}
}
/*
@@ -1340,15 +1165,30 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
ap_pass_brigade(r->output_filters, bb);
/* Mark the backend connection for closing */
backend->close = 1;
- /* Need to return OK to avoid sending an error message */
- return OK;
+ if (origin->keepalives) {
+ /* We already had a request on this backend connection and
+ * might just have run into a keepalive race. Hence we
+ * think positive and assume that the backend is fine and
+ * we do not need to signal an error on backend side.
+ */
+ return OK;
+ }
+ /*
+ * This happened on our first request on this connection to the
+ * backend. This indicates something fishy with the backend.
+ * Return HTTP_INTERNAL_SERVER_ERROR to signal an unrecoverable
+ * server error. We do not worry about r->status code and a
+ * possible error response here as the ap_http_outerror_filter
+ * will fix all of this for us.
+ */
+ return HTTP_INTERNAL_SERVER_ERROR;
}
- else if (!c->keepalives) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01105)
- "NOT Closing connection to client"
- " although reading from backend server %s:%d"
- " failed.",
- backend->hostname, backend->port);
+ if (!c->keepalives) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01105)
+ "NOT Closing connection to client"
+ " although reading from backend server %s:%d"
+ " failed.",
+ backend->hostname, backend->port);
}
return ap_proxyerror(r, HTTP_BAD_GATEWAY,
"Error reading from remote server");
@@ -1360,9 +1200,6 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
* This is buggy if we ever see an HTTP/1.10
*/
if (apr_date_checkmask(buffer, "HTTP/#.# ###*")) {
- int major, minor;
- int toclose;
-
major = buffer[5] - '0';
minor = buffer[7] - '0';
@@ -1371,8 +1208,8 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
*/
if ((major != 1) || (len >= response_field_size - 1)) {
return ap_proxyerror(r, HTTP_BAD_GATEWAY,
- apr_pstrcat(p, "Corrupt status line returned by remote "
- "server: ", buffer, NULL));
+ apr_pstrcat(p, "Corrupt status line returned "
+ "by remote server: ", buffer, NULL));
}
backasswards = 0;
@@ -1404,7 +1241,7 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
/* read the headers. */
/* N.B. for HTTP/1.0 clients, we have to fold line-wrapped headers*/
- /* Also, take care with headers with multiple occurences. */
+ /* Also, take care with headers with multiple occurrences. */
/* First, tuck away all already existing cookies */
save_table = apr_table_make(r->pool, 2);
@@ -1412,10 +1249,10 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
"Set-Cookie", NULL);
/* shove the headers direct into r->headers_out */
- ap_proxy_read_headers(r, backend->r, buffer, response_field_size, origin,
- &pread_len);
+ rc = ap_proxy_read_headers(r, backend->r, buffer, response_field_size,
+ origin, &pread_len);
- if (r->headers_out == NULL) {
+ if (rc != APR_SUCCESS || r->headers_out == NULL) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01106)
"bad HTTP/%d.%d header returned by %s (%s)",
major, minor, r->uri, r->method);
@@ -1443,9 +1280,14 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
save_table);
}
+ /*
+ * Save a possible Transfer-Encoding header as we need it later for
+ * ap_http_filter to know where to end.
+ */
+ te = apr_table_get(r->headers_out, "Transfer-Encoding");
+
/* can't have both Content-Length and Transfer-Encoding */
- if (apr_table_get(r->headers_out, "Transfer-Encoding")
- && apr_table_get(r->headers_out, "Content-Length")) {
+ if (te && apr_table_get(r->headers_out, "Content-Length")) {
/*
* 2616 section 4.4, point 3: "if both Transfer-Encoding
* and Content-Length are received, the latter MUST be
@@ -1463,16 +1305,29 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
backend->close = 1;
}
- /*
- * Save a possible Transfer-Encoding header as we need it later for
- * ap_http_filter to know where to end.
- */
- te = apr_table_get(r->headers_out, "Transfer-Encoding");
+ upgrade = apr_table_get(r->headers_out, "Upgrade");
+ if (proxy_status == HTTP_SWITCHING_PROTOCOLS) {
+ if (!upgrade || !req->upgrade || (strcasecmp(req->upgrade,
+ upgrade) != 0)) {
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ apr_pstrcat(p, "Unexpected Upgrade: ",
+ upgrade ? upgrade : "n/a",
+ " (expecting ",
+ req->upgrade ? req->upgrade
+ : "n/a", ")",
+ NULL));
+ }
+ backend->close = 1;
+ }
/* strip connection listed hop-by-hop headers from response */
toclose = ap_proxy_clear_connection_fn(r, r->headers_out);
if (toclose) {
backend->close = 1;
+ if (toclose < 0) {
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ "Malformed connection header");
+ }
}
if ((buf = apr_table_get(r->headers_out, "Content-Type"))) {
@@ -1491,7 +1346,8 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
r->headers_out = ap_proxy_clean_warnings(p, r->headers_out);
/* handle Via header in response */
- if (conf->viaopt != via_off && conf->viaopt != via_block) {
+ if (req->sconf->viaopt != via_off
+ && req->sconf->viaopt != via_block) {
const char *server_name = ap_get_server_name(r);
/* If USE_CANONICAL_NAME_OFF was configured for the proxy virtual host,
* then the server name returned by ap_get_server_name() is the
@@ -1502,18 +1358,18 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
server_name = r->server->server_hostname;
/* create a "Via:" response header entry and merge it */
apr_table_addn(r->headers_out, "Via",
- (conf->viaopt == via_full)
+ (req->sconf->viaopt == via_full)
? apr_psprintf(p, "%d.%d %s%s (%s)",
HTTP_VERSION_MAJOR(r->proto_num),
HTTP_VERSION_MINOR(r->proto_num),
server_name,
- server_portstr,
+ req->server_portstr,
AP_SERVER_BASEVERSION)
: apr_psprintf(p, "%d.%d %s%s",
HTTP_VERSION_MAJOR(r->proto_num),
HTTP_VERSION_MINOR(r->proto_num),
server_name,
- server_portstr)
+ req->server_portstr)
);
}
@@ -1522,27 +1378,25 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
backend->close = 1;
origin->keepalive = AP_CONN_CLOSE;
}
+ else {
+ /*
+ * Keep track of the number of keepalives we processed on this
+ * connection.
+ */
+ origin->keepalives++;
+ }
+
} else {
/* an http/0.9 response */
backasswards = 1;
- r->status = 200;
+ r->status = proxy_status = 200;
r->status_line = "200 OK";
backend->close = 1;
}
if (ap_is_HTTP_INFO(proxy_status)) {
- interim_response++;
- /* Reset to old timeout iff we've adjusted it */
- if (do_100_continue
- && (r->status == HTTP_CONTINUE)
- && (worker->s->ping_timeout != old_timeout)) {
- apr_socket_timeout_set(backend->sock, old_timeout);
- }
- }
- else {
- interim_response = 0;
- }
- if (interim_response) {
+ const char *policy = NULL;
+
/* RFC2616 tells us to forward this.
*
* OTOH, an interim response here may mean the backend
@@ -1555,15 +1409,32 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
*
* So let's make it configurable.
*
- * We need to set "r->expecting_100 = 1" otherwise origin
- * server behaviour will apply.
+ * We need to force "r->expecting_100 = 1" for RFC behaviour
+ * otherwise ap_send_interim_response() does nothing when
+ * the client did not ask for 100-continue.
+ *
+ * 101 Switching Protocol has its own configuration which
+ * shouldn't be interfered by "proxy-interim-response".
*/
- const char *policy = apr_table_get(r->subprocess_env,
- "proxy-interim-response");
+ if (proxy_status != HTTP_SWITCHING_PROTOCOLS) {
+ policy = apr_table_get(r->subprocess_env,
+ "proxy-interim-response");
+ }
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
- "HTTP: received interim %d response", r->status);
+ "HTTP: received interim %d response (policy: %s)",
+ r->status, policy ? policy : "n/a");
if (!policy
- || (!strcasecmp(policy, "RFC") && ((r->expecting_100 = 1)))) {
+ || (!strcasecmp(policy, "RFC")
+ && (proxy_status != HTTP_CONTINUE
+ || (r->expecting_100 = 1)))) {
+ switch (proxy_status) {
+ case HTTP_SWITCHING_PROTOCOLS:
+ AP_DEBUG_ASSERT(upgrade != NULL);
+ apr_table_setn(r->headers_out, "Connection", "Upgrade");
+ apr_table_setn(r->headers_out, "Upgrade",
+ apr_pstrdup(p, upgrade));
+ break;
+ }
ap_send_interim_response(r, 1);
}
/* FIXME: refine this to be able to specify per-response-status
@@ -1573,57 +1444,144 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01108)
"undefined proxy interim response policy");
}
+ interim_response++;
}
- /* Moved the fixups of Date headers and those affected by
- * ProxyPassReverse/etc from here to ap_proxy_read_headers
+ else {
+ interim_response = 0;
+ }
+
+ /* If we still do 100-continue (end-to-end or ping), either the
+ * current response is the expected "100 Continue" and we are done
+ * with this mode, or this is another interim response and we'll wait
+ * for the next one, or this is a final response and hence the backend
+ * did not honor our expectation.
*/
+ if (do_100_continue && (!interim_response
+ || proxy_status == HTTP_CONTINUE)) {
+ /* RFC 7231 - Section 5.1.1 - Expect - Requirement for servers
+ * A server that responds with a final status code before
+ * reading the entire message body SHOULD indicate in that
+ * response whether it intends to close the connection or
+ * continue reading and discarding the request message.
+ *
+ * So, if this response is not an interim 100 Continue, we can
+ * avoid sending the request body if the backend responded with
+ * "Connection: close" or HTTP < 1.1, and either let the core
+ * discard it or the caller try another balancer member with the
+ * same body (given status 503, though not implemented yet).
+ */
+ int do_send_body = (proxy_status == HTTP_CONTINUE
+ || (!toclose && major > 0 && minor > 0));
- if ((proxy_status == 401) && (dconf->error_override)) {
- const char *buf;
- const char *wa = "WWW-Authenticate";
- if ((buf = apr_table_get(r->headers_out, wa))) {
- apr_table_set(r->err_headers_out, wa, buf);
- } else {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01109)
- "origin server sent 401 without "
- "WWW-Authenticate header");
+ /* Reset to old timeout iff we've adjusted it. */
+ if (worker->s->ping_timeout_set) {
+ apr_socket_timeout_set(backend->sock, old_timeout);
}
- }
- r->sent_bodyct = 1;
- /*
- * Is it an HTTP/0.9 response or did we maybe preread the 1st line of
- * the response? If so, load the extra data. These are 2 mutually
- * exclusive possibilities, that just happen to require very
- * similar behavior.
- */
- if (backasswards || pread_len) {
- apr_ssize_t cntr = (apr_ssize_t)pread_len;
- if (backasswards) {
- /*@@@FIXME:
- * At this point in response processing of a 0.9 response,
- * we don't know yet whether data is binary or not.
- * mod_charset_lite will get control later on, so it cannot
- * decide on the conversion of this buffer full of data.
- * However, chances are that we are not really talking to an
- * HTTP/0.9 server, but to some different protocol, therefore
- * the best guess IMHO is to always treat the buffer as "text/x":
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10153)
+ "HTTP: %s100 continue sent by %pI (%s): "
+ "%ssending body (response: HTTP/%i.%i %s)",
+ proxy_status != HTTP_CONTINUE ? "no " : "",
+ backend->addr,
+ backend->hostname ? backend->hostname : "",
+ do_send_body ? "" : "not ",
+ major, minor, proxy_status_line);
+
+ if (do_send_body) {
+ status = send_continue_body(req);
+ if (status != OK) {
+ return status;
+ }
+ }
+ else {
+ /* If we don't read the client connection any further, since
+ * there are pending data it should be "Connection: close"d to
+ * prevent reuse. We don't exactly c->keepalive = AP_CONN_CLOSE
+ * here though, because error_override or a potential retry on
+ * another backend could finally read that data and finalize
+ * the request processing, making keep-alive possible. So what
+ * we do is leaving r->expecting_100 alone, ap_set_keepalive()
+ * will do the right thing according to the final response and
+ * any later update of r->expecting_100.
*/
- ap_xlate_proto_to_ascii(buffer, len);
- cntr = (apr_ssize_t)len;
}
- e = apr_bucket_heap_create(buffer, cntr, NULL, c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, e);
+
+ /* Once only! */
+ do_100_continue = 0;
+ }
+
+ if (proxy_status == HTTP_SWITCHING_PROTOCOLS) {
+ apr_status_t rv;
+ proxy_tunnel_rec *tunnel;
+ apr_interval_time_t client_timeout = -1,
+ backend_timeout = -1;
+
+ /* If we didn't send the full body yet, do it now */
+ if (do_100_continue) {
+ r->expecting_100 = 0;
+ status = send_continue_body(req);
+ if (status != OK) {
+ return status;
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10239)
+ "HTTP: tunneling protocol %s", upgrade);
+
+ rv = ap_proxy_tunnel_create(&tunnel, r, origin, upgrade);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10240)
+ "can't create tunnel for %s", upgrade);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Set timeout to the highest configured for client or backend */
+ apr_socket_timeout_get(backend->sock, &backend_timeout);
+ apr_socket_timeout_get(ap_get_conn_socket(c), &client_timeout);
+ if (backend_timeout >= 0 && backend_timeout > client_timeout) {
+ tunnel->timeout = backend_timeout;
+ }
+ else {
+ tunnel->timeout = client_timeout;
+ }
+
+ /* Let proxy tunnel forward everything */
+ status = ap_proxy_tunnel_run(tunnel);
+
+ /* We are done with both connections */
+ return DONE;
+ }
+
+ if (interim_response) {
+ /* Already forwarded above, read next response */
+ continue;
}
+
+ /* Moved the fixups of Date headers and those affected by
+ * ProxyPassReverse/etc from here to ap_proxy_read_headers
+ */
+
/* PR 41646: get HEAD right with ProxyErrorOverride */
- if (ap_is_HTTP_ERROR(r->status) && dconf->error_override) {
+ if (ap_proxy_should_override(dconf, proxy_status)) {
+ if (proxy_status == HTTP_UNAUTHORIZED) {
+ const char *buf;
+ const char *wa = "WWW-Authenticate";
+ if ((buf = apr_table_get(r->headers_out, wa))) {
+ apr_table_set(r->err_headers_out, wa, buf);
+ } else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01109)
+ "origin server sent 401 without "
+ "WWW-Authenticate header");
+ }
+ }
+
/* clear r->status for override error, otherwise ErrorDocument
* thinks that this is a recursive error, and doesn't find the
* custom error page
*/
r->status = HTTP_OK;
/* Discard body, if one is expected */
- if (!r->header_only && !AP_STATUS_IS_HEADER_ONLY(proxy_status)) {
+ if (!r->header_only && !AP_STATUS_IS_HEADER_ONLY(proxy_status)) {
const char *tmp;
/* Add minimal headers needed to allow http_in filter
* detecting end of body without waiting for a timeout. */
@@ -1646,11 +1604,49 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
return proxy_status;
}
+ /* Forward back Upgrade header if it matches the configured one(s), it
+ * may be an HTTP_UPGRADE_REQUIRED response or some other status where
+ * Upgrade makes sense to negotiate the protocol by other means.
+ */
+ if (upgrade && ap_proxy_worker_can_upgrade(p, worker, upgrade,
+ (*req->proto == 'w')
+ ? "WebSocket" : NULL)) {
+ apr_table_setn(r->headers_out, "Connection", "Upgrade");
+ apr_table_setn(r->headers_out, "Upgrade", apr_pstrdup(p, upgrade));
+ }
+
+ r->sent_bodyct = 1;
+ /*
+ * Is it an HTTP/0.9 response or did we maybe preread the 1st line of
+ * the response? If so, load the extra data. These are 2 mutually
+ * exclusive possibilities, that just happen to require very
+ * similar behavior.
+ */
+ if (backasswards || pread_len) {
+ apr_ssize_t cntr = (apr_ssize_t)pread_len;
+ if (backasswards) {
+ /*@@@FIXME:
+ * At this point in response processing of a 0.9 response,
+ * we don't know yet whether data is binary or not.
+ * mod_charset_lite will get control later on, so it cannot
+ * decide on the conversion of this buffer full of data.
+ * However, chances are that we are not really talking to an
+ * HTTP/0.9 server, but to some different protocol, therefore
+ * the best guess IMHO is to always treat the buffer as "text/x":
+ */
+ ap_xlate_proto_to_ascii(buffer, len);
+ cntr = (apr_ssize_t)len;
+ }
+ e = apr_bucket_heap_create(buffer, cntr, NULL, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ }
+
/* send body - but only if a body is expected */
if ((!r->header_only) && /* not HEAD request */
- !interim_response && /* not any 1xx response */
(proxy_status != HTTP_NO_CONTENT) && /* not 204 */
(proxy_status != HTTP_NOT_MODIFIED)) { /* not 304 */
+ apr_read_type_e mode;
+ int finish;
/* We need to copy the output headers and treat them as input
* headers as well. BUT, we need to do this before we remove
@@ -1671,152 +1667,148 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "start body send");
- /*
- * if we are overriding the errors, we can't put the content
- * of the page into the brigade
+ /* read the body, pass it to the output filters */
+
+ /* Handle the case where the error document is itself reverse
+ * proxied and was successful. We must maintain any previous
+ * error status so that an underlying error (eg HTTP_NOT_FOUND)
+ * doesn't become an HTTP_OK.
*/
- if (!dconf->error_override || !ap_is_HTTP_ERROR(proxy_status)) {
- /* read the body, pass it to the output filters */
- apr_read_type_e mode = APR_NONBLOCK_READ;
- int finish = FALSE;
-
- /* Handle the case where the error document is itself reverse
- * proxied and was successful. We must maintain any previous
- * error status so that an underlying error (eg HTTP_NOT_FOUND)
- * doesn't become an HTTP_OK.
- */
- if (dconf->error_override && !ap_is_HTTP_ERROR(proxy_status)
- && ap_is_HTTP_ERROR(original_status)) {
- r->status = original_status;
- r->status_line = original_status_line;
- }
+ if (ap_proxy_should_override(dconf, original_status)) {
+ r->status = original_status;
+ r->status_line = original_status_line;
+ }
- do {
- apr_off_t readbytes;
- apr_status_t rv;
-
- rv = ap_get_brigade(backend->r->input_filters, bb,
- AP_MODE_READBYTES, mode,
- conf->io_buffer_size);
-
- /* ap_get_brigade will return success with an empty brigade
- * for a non-blocking read which would block: */
- if (mode == APR_NONBLOCK_READ
- && (APR_STATUS_IS_EAGAIN(rv)
- || (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(bb)))) {
- /* flush to the client and switch to blocking mode */
- e = apr_bucket_flush_create(c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, e);
- if (ap_pass_brigade(r->output_filters, bb)
- || c->aborted) {
- backend->close = 1;
- break;
- }
- apr_brigade_cleanup(bb);
- mode = APR_BLOCK_READ;
- continue;
- }
- else if (rv == APR_EOF) {
- backend->close = 1;
- break;
- }
- else if (rv != APR_SUCCESS) {
- /* In this case, we are in real trouble because
- * our backend bailed on us. Pass along a 502 error
- * error bucket
- */
- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01110)
- "error reading response");
- ap_proxy_backend_broke(r, bb);
- ap_pass_brigade(r->output_filters, bb);
- backend_broke = 1;
+ mode = APR_NONBLOCK_READ;
+ finish = FALSE;
+ do {
+ apr_off_t readbytes;
+ apr_status_t rv;
+
+ rv = ap_get_brigade(backend->r->input_filters, bb,
+ AP_MODE_READBYTES, mode,
+ req->sconf->io_buffer_size);
+
+ /* ap_get_brigade will return success with an empty brigade
+ * for a non-blocking read which would block: */
+ if (mode == APR_NONBLOCK_READ
+ && (APR_STATUS_IS_EAGAIN(rv)
+ || (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(bb)))) {
+ /* flush to the client and switch to blocking mode */
+ e = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ if (ap_pass_brigade(r->output_filters, bb)
+ || c->aborted) {
backend->close = 1;
break;
}
- /* next time try a non-blocking read */
- mode = APR_NONBLOCK_READ;
+ apr_brigade_cleanup(bb);
+ mode = APR_BLOCK_READ;
+ continue;
+ }
+ else if (rv == APR_EOF) {
+ backend->close = 1;
+ break;
+ }
+ else if (rv != APR_SUCCESS) {
+ /* In this case, we are in real trouble because
+ * our backend bailed on us. Pass along a 502 error
+ * error bucket
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01110)
+ "error reading response");
+ apr_brigade_cleanup(bb);
+ ap_proxy_backend_broke(r, bb);
+ ap_pass_brigade(r->output_filters, bb);
+ backend_broke = 1;
+ backend->close = 1;
+ break;
+ }
+ /* next time try a non-blocking read */
+ mode = APR_NONBLOCK_READ;
- if (!apr_is_empty_table(backend->r->trailers_in)) {
- apr_table_do(add_trailers, r->trailers_out,
- backend->r->trailers_in, NULL);
- apr_table_clear(backend->r->trailers_in);
- }
+ if (!apr_is_empty_table(backend->r->trailers_in)) {
+ apr_table_do(add_trailers, r->trailers_out,
+ backend->r->trailers_in, NULL);
+ apr_table_clear(backend->r->trailers_in);
+ }
- apr_brigade_length(bb, 0, &readbytes);
- backend->worker->s->read += readbytes;
+ apr_brigade_length(bb, 0, &readbytes);
+ backend->worker->s->read += readbytes;
#if DEBUGGING
- {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01111)
- "readbytes: %#x", readbytes);
- }
+ {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01111)
+ "readbytes: %#x", readbytes);
+ }
#endif
- /* sanity check */
- if (APR_BRIGADE_EMPTY(bb)) {
- break;
- }
+ /* sanity check */
+ if (APR_BRIGADE_EMPTY(bb)) {
+ break;
+ }
- /* Switch the allocator lifetime of the buckets */
- ap_proxy_buckets_lifetime_transform(r, bb, pass_bb);
-
- /* found the last brigade? */
- if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(pass_bb))) {
-
- /* signal that we must leave */
- finish = TRUE;
-
- /* the brigade may contain transient buckets that contain
- * data that lives only as long as the backend connection.
- * Force a setaside so these transient buckets become heap
- * buckets that live as long as the request.
- */
- for (e = APR_BRIGADE_FIRST(pass_bb); e
- != APR_BRIGADE_SENTINEL(pass_bb); e
- = APR_BUCKET_NEXT(e)) {
- apr_bucket_setaside(e, r->pool);
- }
-
- /* finally it is safe to clean up the brigade from the
- * connection pool, as we have forced a setaside on all
- * buckets.
- */
- apr_brigade_cleanup(bb);
-
- /* make sure we release the backend connection as soon
- * as we know we are done, so that the backend isn't
- * left waiting for a slow client to eventually
- * acknowledge the data.
- */
- ap_proxy_release_connection(backend->worker->s->scheme,
- backend, r->server);
- /* Ensure that the backend is not reused */
- *backend_ptr = NULL;
+ /* Switch the allocator lifetime of the buckets */
+ ap_proxy_buckets_lifetime_transform(r, bb, pass_bb);
- }
+ /* found the last brigade? */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(pass_bb))) {
- /* try send what we read */
- if (ap_pass_brigade(r->output_filters, pass_bb) != APR_SUCCESS
- || c->aborted) {
- /* Ack! Phbtt! Die! User aborted! */
- /* Only close backend if we haven't got all from the
- * backend. Furthermore if *backend_ptr is NULL it is no
- * longer safe to fiddle around with backend as it might
- * be already in use by another thread.
- */
- if (*backend_ptr) {
- backend->close = 1; /* this causes socket close below */
- }
- finish = TRUE;
+ /* signal that we must leave */
+ finish = TRUE;
+
+ /* the brigade may contain transient buckets that contain
+ * data that lives only as long as the backend connection.
+ * Force a setaside so these transient buckets become heap
+ * buckets that live as long as the request.
+ */
+ for (e = APR_BRIGADE_FIRST(pass_bb); e
+ != APR_BRIGADE_SENTINEL(pass_bb); e
+ = APR_BUCKET_NEXT(e)) {
+ apr_bucket_setaside(e, r->pool);
}
- /* make sure we always clean up after ourselves */
- apr_brigade_cleanup(pass_bb);
+ /* finally it is safe to clean up the brigade from the
+ * connection pool, as we have forced a setaside on all
+ * buckets.
+ */
apr_brigade_cleanup(bb);
- } while (!finish);
- }
+ /* make sure we release the backend connection as soon
+ * as we know we are done, so that the backend isn't
+ * left waiting for a slow client to eventually
+ * acknowledge the data.
+ */
+ ap_proxy_release_connection(backend->worker->s->scheme,
+ backend, r->server);
+ /* Ensure that the backend is not reused */
+ req->backend = NULL;
+
+ }
+
+ /* try send what we read */
+ if (ap_pass_brigade(r->output_filters, pass_bb) != APR_SUCCESS
+ || c->aborted) {
+ /* Ack! Phbtt! Die! User aborted! */
+ /* Only close backend if we haven't got all from the
+ * backend. Furthermore if req->backend is NULL it is no
+ * longer safe to fiddle around with backend as it might
+ * be already in use by another thread.
+ */
+ if (req->backend) {
+ /* this causes socket close below */
+ req->backend->close = 1;
+ }
+ finish = TRUE;
+ }
+
+ /* make sure we always clean up after ourselves */
+ apr_brigade_cleanup(pass_bb);
+ apr_brigade_cleanup(bb);
+
+ } while (!finish);
+
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "end body send");
}
- else if (!interim_response) {
+ else {
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "header only");
/* make sure we release the backend connection as soon
@@ -1826,7 +1818,8 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
*/
ap_proxy_release_connection(backend->worker->s->scheme,
backend, r->server);
- *backend_ptr = NULL;
+ /* Ensure that the backend is not reused */
+ req->backend = NULL;
/* Pass EOS bucket down the filter chain. */
e = apr_bucket_eos_create(c->bucket_alloc);
@@ -1880,62 +1873,108 @@ static int proxy_http_handler(request_rec *r, proxy_worker *worker,
apr_port_t proxyport)
{
int status;
- char server_portstr[32];
- char *scheme;
- const char *proxy_function;
- const char *u;
+ const char *scheme;
+ const char *u = url;
+ proxy_http_req_t *req = NULL;
proxy_conn_rec *backend = NULL;
+ apr_bucket_brigade *input_brigade = NULL;
int is_ssl = 0;
conn_rec *c = r->connection;
+ proxy_dir_conf *dconf;
int retry = 0;
+ char *locurl = url;
+ int toclose = 0;
/*
* Use a shorter-lived pool to reduce memory usage
* and avoid a memory leak
*/
apr_pool_t *p = r->pool;
- apr_uri_t *uri = apr_palloc(p, sizeof(*uri));
+ apr_uri_t *uri;
- /* find the scheme */
- u = strchr(url, ':');
- if (u == NULL || u[1] != '/' || u[2] != '/' || u[3] == '\0')
- return DECLINED;
- if ((u - url) > 14)
- return HTTP_BAD_REQUEST;
- scheme = apr_pstrmemdup(p, url, u - url);
- /* scheme is lowercase */
- ap_str_tolower(scheme);
- /* is it for us? */
- if (strcmp(scheme, "https") == 0) {
- if (!ap_proxy_ssl_enable(NULL)) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01112)
- "HTTPS: declining URL %s (mod_ssl not configured?)",
- url);
- return DECLINED;
- }
- is_ssl = 1;
- proxy_function = "HTTPS";
+ scheme = get_url_scheme(&u, &is_ssl);
+ if (!scheme && proxyname && strncasecmp(url, "ftp:", 4) == 0) {
+ u = url + 4;
+ scheme = "ftp";
+ is_ssl = 0;
}
- else if (!(strcmp(scheme, "http") == 0 || (strcmp(scheme, "ftp") == 0 && proxyname))) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01113) "HTTP: declining URL %s",
- url);
- return DECLINED; /* only interested in HTTP, or FTP via proxy */
+ if (!scheme || u[0] != '/' || u[1] != '/' || u[2] == '\0') {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01113)
+ "HTTP: declining URL %s", url);
+ return DECLINED; /* only interested in HTTP, WS or FTP via proxy */
}
- else {
- if (*scheme == 'h')
- proxy_function = "HTTP";
- else
- proxy_function = "FTP";
+ if (is_ssl && !ap_ssl_has_outgoing_handlers()) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01112)
+ "HTTP: declining URL %s (mod_ssl not configured?)", url);
+ return DECLINED;
}
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "HTTP: serving URL %s", url);
-
/* create space for state information */
- if ((status = ap_proxy_acquire_connection(proxy_function, &backend,
- worker, r->server)) != OK)
- goto cleanup;
+ if ((status = ap_proxy_acquire_connection(scheme, &backend,
+ worker, r->server)) != OK) {
+ return status;
+ }
backend->is_ssl = is_ssl;
+ req = apr_pcalloc(p, sizeof(*req));
+ req->p = p;
+ req->r = r;
+ req->sconf = conf;
+ req->worker = worker;
+ req->backend = backend;
+ req->proto = scheme;
+ req->bucket_alloc = c->bucket_alloc;
+ req->rb_method = RB_INIT;
+
+ dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
+
+ if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) {
+ req->force10 = 1;
+ }
+ else if (*worker->s->upgrade || *req->proto == 'w') {
+ /* Forward Upgrade header if it matches the configured one(s),
+ * the default being "WebSocket" for ws[s] schemes.
+ */
+ const char *upgrade = apr_table_get(r->headers_in, "Upgrade");
+ if (upgrade && ap_proxy_worker_can_upgrade(p, worker, upgrade,
+ (*req->proto == 'w')
+ ? "WebSocket" : NULL)) {
+ req->upgrade = upgrade;
+ }
+ }
+
+ /* We possibly reuse input data prefetched in previous call(s), e.g. for a
+ * balancer fallback scenario, and in this case the 100 continue settings
+ * should be consistent between balancer members. If not, we need to ignore
+ * Proxy100Continue on=>off once we tried to prefetch already, otherwise
+ * the HTTP_IN filter won't send 100 Continue for us anymore, and we might
+ * deadlock with the client waiting for each other. Note that off=>on is
+ * not an issue because in this case r->expecting_100 is false (the 100
+ * Continue is out already), but we make sure that prefetch will be
+ * nonblocking to avoid passing more time there.
+ */
+ apr_pool_userdata_get((void **)&input_brigade, "proxy-req-input", p);
+
+ /* Should we handle end-to-end or ping 100-continue? */
+ if (!req->force10
+ && ((r->expecting_100 && (dconf->forward_100_continue || input_brigade))
+ || PROXY_SHOULD_PING_100_CONTINUE(worker, r))) {
+ /* Tell ap_proxy_create_hdrbrgd() to preserve/add the Expect header */
+ apr_table_setn(r->notes, "proxy-100-continue", "1");
+ req->do_100_continue = 1;
+ }
+
+ /* Should we block while prefetching the body or try nonblocking and flush
+ * data to the backend ASAP?
+ */
+ if (input_brigade
+ || req->do_100_continue
+ || apr_table_get(r->subprocess_env,
+ "proxy-prefetch-nonblocking")) {
+ req->prefetch_nonblocking = 1;
+ }
+
/*
* In the case that we are handling a reverse proxy connection and this
* is not a request that is coming over an already kept alive connection
@@ -1949,20 +1988,68 @@ static int proxy_http_handler(request_rec *r, proxy_worker *worker,
backend->close = 1;
}
+ /* Step One: Determine Who To Connect To */
+ uri = apr_palloc(p, sizeof(*uri));
+ if ((status = ap_proxy_determine_connection(p, r, conf, worker, backend,
+ uri, &locurl, proxyname,
+ proxyport, req->server_portstr,
+ sizeof(req->server_portstr))))
+ goto cleanup;
+
+ /* The header is always (re-)built since it depends on worker settings,
+ * but the body can be fetched only once (even partially), so it's saved
+ * in between proxy_http_handler() calls should we come back here.
+ */
+ req->header_brigade = apr_brigade_create(p, req->bucket_alloc);
+ if (input_brigade == NULL) {
+ input_brigade = apr_brigade_create(p, req->bucket_alloc);
+ apr_pool_userdata_setn(input_brigade, "proxy-req-input", NULL, p);
+ }
+ req->input_brigade = input_brigade;
+
+ /* Prefetch (nonlocking) the request body so to increase the chance to get
+ * the whole (or enough) body and determine Content-Length vs chunked or
+ * spooled. By doing this before connecting or reusing the backend, we want
+ * to minimize the delay between this connection is considered alive and
+ * the first bytes sent (should the client's link be slow or some input
+ * filter retain the data). This is a best effort to prevent the backend
+ * from closing (from under us) what it thinks is an idle connection, hence
+ * to reduce to the minimum the unavoidable local is_socket_connected() vs
+ * remote keepalive race condition.
+ */
+ if ((status = ap_proxy_http_prefetch(req, uri, locurl)) != OK)
+ goto cleanup;
+
+ /* We need to reset backend->close now, since ap_proxy_http_prefetch() set
+ * it to disable the reuse of the connection *after* this request (no keep-
+ * alive), not to close any reusable connection before this request. However
+ * assure what is expected later by using a local flag and do the right thing
+ * when ap_proxy_connect_backend() below provides the connection to close.
+ */
+ toclose = backend->close;
+ backend->close = 0;
+
while (retry < 2) {
- char *locurl = url;
+ if (retry) {
+ char *newurl = url;
- /* Step One: Determine Who To Connect To */
- if ((status = ap_proxy_determine_connection(p, r, conf, worker, backend,
- uri, &locurl, proxyname,
- proxyport, server_portstr,
- sizeof(server_portstr))) != OK)
- break;
+ /* Step One (again): (Re)Determine Who To Connect To */
+ if ((status = ap_proxy_determine_connection(p, r, conf, worker,
+ backend, uri, &newurl, proxyname, proxyport,
+ req->server_portstr, sizeof(req->server_portstr))))
+ break;
+
+ /* The code assumes locurl is not changed during the loop, or
+ * ap_proxy_http_prefetch() would have to be called every time,
+ * and header_brigade be changed accordingly...
+ */
+ AP_DEBUG_ASSERT(strcmp(newurl, locurl) == 0);
+ }
/* Step Two: Make the Connection */
- if (ap_proxy_check_connection(proxy_function, backend, r->server, 1,
+ if (ap_proxy_check_connection(scheme, backend, r->server, 1,
PROXY_CHECK_CONN_EMPTY)
- && ap_proxy_connect_backend(proxy_function, backend, worker,
+ && ap_proxy_connect_backend(scheme, backend, worker,
r->server)) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01114)
"HTTP: failed to make connection to backend: %s",
@@ -1972,54 +2059,45 @@ static int proxy_http_handler(request_rec *r, proxy_worker *worker,
}
/* Step Three: Create conn_rec */
- if (!backend->connection) {
- if ((status = ap_proxy_connection_create_ex(proxy_function,
- backend, r)) != OK)
- break;
- /*
- * On SSL connections set a note on the connection what CN is
- * requested, such that mod_ssl can check if it is requested to do
- * so.
- */
- if (backend->ssl_hostname) {
- apr_table_setn(backend->connection->notes,
- "proxy-request-hostname",
- backend->ssl_hostname);
- }
+ if ((status = ap_proxy_connection_create_ex(scheme, backend, r)) != OK)
+ break;
+ req->origin = backend->connection;
+
+ /* Don't recycle the connection if prefetch (above) told not to do so */
+ if (toclose) {
+ backend->close = 1;
+ req->origin->keepalive = AP_CONN_CLOSE;
}
/* Step Four: Send the Request
* On the off-chance that we forced a 100-Continue as a
* kinda HTTP ping test, allow for retries
*/
- if ((status = ap_proxy_http_request(p, r, backend, worker,
- conf, uri, locurl, server_portstr)) != OK) {
- if ((status == HTTP_SERVICE_UNAVAILABLE) && worker->s->ping_timeout_set) {
- backend->close = 1;
+ status = ap_proxy_http_request(req);
+ if (status != OK) {
+ if (req->do_100_continue && status == HTTP_SERVICE_UNAVAILABLE) {
ap_log_rerror(APLOG_MARK, APLOG_INFO, status, r, APLOGNO(01115)
- "HTTP: 100-Continue failed to %pI (%s)",
- worker->cp->addr, worker->s->hostname_ex);
+ "HTTP: 100-Continue failed to %pI (%s:%d)",
+ backend->addr, backend->hostname, backend->port);
+ backend->close = 1;
retry++;
continue;
- } else {
- break;
}
-
+ break;
}
/* Step Five: Receive the Response... Fall thru to cleanup */
- status = ap_proxy_http_process_response(p, r, &backend, worker,
- conf, server_portstr);
+ status = ap_proxy_http_process_response(req);
break;
}
/* Step Six: Clean Up */
cleanup:
- if (backend) {
+ if (req->backend) {
if (status != OK)
- backend->close = 1;
- ap_proxy_http_cleanup(proxy_function, r, backend);
+ req->backend->close = 1;
+ ap_proxy_http_cleanup(scheme, r, req->backend);
}
return status;
}
diff --git a/modules/proxy/mod_proxy_scgi.c b/modules/proxy/mod_proxy_scgi.c
index 11f75de..d63c833 100644
--- a/modules/proxy/mod_proxy_scgi.c
+++ b/modules/proxy/mod_proxy_scgi.c
@@ -179,8 +179,10 @@ static int scgi_canon(request_rec *r, char *url)
char *host, sport[sizeof(":65535")];
const char *err, *path;
apr_port_t port, def_port;
+ core_dir_config *d = ap_get_core_module_config(r->per_dir_config);
+ int flags = d->allow_encoded_slashes && !d->decode_encoded_slashes ? PROXY_CANONENC_NOENCODEDSLASHENCODING : 0;
- if (strncasecmp(url, SCHEME "://", sizeof(SCHEME) + 2)) {
+ if (ap_cstr_casecmpn(url, SCHEME "://", sizeof(SCHEME) + 2)) {
return DECLINED;
}
url += sizeof(SCHEME); /* Keep slashes */
@@ -205,8 +207,8 @@ static int scgi_canon(request_rec *r, char *url)
host = apr_pstrcat(r->pool, "[", host, "]", NULL);
}
- path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
- r->proxyreq);
+ path = ap_proxy_canonenc_ex(r->pool, url, strlen(url), enc_path, flags,
+ r->proxyreq);
if (!path) {
return HTTP_BAD_REQUEST;
}
@@ -388,6 +390,14 @@ static int pass_response(request_rec *r, proxy_conn_rec *conn)
return status;
}
+ /* SCGI has its own body framing mechanism which we don't
+ * match against any provided Content-Length, so let the
+ * core determine C-L vs T-E based on what's actually sent.
+ */
+ if (!apr_table_get(r->subprocess_env, AP_TRUST_CGILIKE_CL_ENVVAR))
+ apr_table_unset(r->headers_out, "Content-Length");
+ apr_table_unset(r->headers_out, "Transfer-Encoding");
+
conf = ap_get_module_config(r->per_dir_config, &proxy_scgi_module);
if (conf->sendfile && conf->sendfile != scgi_sendfile_off) {
short err = 1;
@@ -434,7 +444,7 @@ static int pass_response(request_rec *r, proxy_conn_rec *conn)
if (location && *location == '/') {
scgi_request_config *req_conf = apr_palloc(r->pool,
sizeof(*req_conf));
- if (strcasecmp(location_header, "Location")) {
+ if (ap_cstr_casecmp(location_header, "Location")) {
if (err) {
apr_table_unset(r->err_headers_out, location_header);
}
@@ -533,7 +543,7 @@ static int scgi_handler(request_rec *r, proxy_worker *worker,
apr_uri_t *uri;
char dummy;
- if (strncasecmp(url, SCHEME "://", sizeof(SCHEME) + 2)) {
+ if (ap_cstr_casecmpn(url, SCHEME "://", sizeof(SCHEME) + 2)) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00865)
"declining URL %s", url);
return DECLINED;
diff --git a/modules/proxy/mod_proxy_uwsgi.c b/modules/proxy/mod_proxy_uwsgi.c
index c5d4f8e..4e57196 100644
--- a/modules/proxy/mod_proxy_uwsgi.c
+++ b/modules/proxy/mod_proxy_uwsgi.c
@@ -84,10 +84,29 @@ static int uwsgi_canon(request_rec *r, char *url)
host = apr_pstrcat(r->pool, "[", host, "]", NULL);
}
- path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
- r->proxyreq);
- if (!path) {
- return HTTP_BAD_REQUEST;
+ if (apr_table_get(r->notes, "proxy-nocanon")
+ || apr_table_get(r->notes, "proxy-noencode")) {
+ path = url; /* this is the raw/encoded path */
+ }
+ else {
+ core_dir_config *d = ap_get_core_module_config(r->per_dir_config);
+ int flags = d->allow_encoded_slashes && !d->decode_encoded_slashes ? PROXY_CANONENC_NOENCODEDSLASHENCODING : 0;
+
+ path = ap_proxy_canonenc_ex(r->pool, url, strlen(url), enc_path, flags,
+ r->proxyreq);
+ if (!path) {
+ return HTTP_BAD_REQUEST;
+ }
+ }
+ /*
+ * If we have a raw control character or a ' ' in nocanon path,
+ * correct encoding was missed.
+ */
+ if (path == url && *ap_scan_vchar_obstext(path)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10417)
+ "To be forwarded path contains control "
+ "characters or spaces");
+ return HTTP_FORBIDDEN;
}
r->filename =
@@ -136,7 +155,7 @@ static int uwsgi_send_headers(request_rec *r, proxy_conn_rec * conn)
int j;
apr_size_t headerlen = 4;
- apr_uint16_t pktsize, keylen, vallen;
+ apr_size_t pktsize, keylen, vallen;
const char *script_name;
const char *path_info;
const char *auth;
@@ -175,7 +194,16 @@ static int uwsgi_send_headers(request_rec *r, proxy_conn_rec * conn)
env = (apr_table_entry_t *) env_table->elts;
for (j = 0; j < env_table->nelts; ++j) {
- headerlen += 2 + strlen(env[j].key) + 2 + strlen(env[j].val);
+ headerlen += 2 + strlen(env[j].key) + 2 + (env[j].val ? strlen(env[j].val) : 0);
+ }
+
+ pktsize = headerlen - 4;
+ if (pktsize > APR_UINT16_MAX) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10259)
+ "can't send headers to %s:%u: packet size too "
+ "large (%" APR_SIZE_T_FMT ")",
+ conn->hostname, conn->port, pktsize);
+ return HTTP_INTERNAL_SERVER_ERROR;
}
ptr = buf = apr_palloc(r->pool, headerlen);
@@ -189,15 +217,15 @@ static int uwsgi_send_headers(request_rec *r, proxy_conn_rec * conn)
memcpy(ptr, env[j].key, keylen);
ptr += keylen;
- vallen = strlen(env[j].val);
+ vallen = env[j].val ? strlen(env[j].val) : 0;
*ptr++ = (apr_byte_t) (vallen & 0xff);
*ptr++ = (apr_byte_t) ((vallen >> 8) & 0xff);
- memcpy(ptr, env[j].val, vallen);
+ if (env[j].val) {
+ memcpy(ptr, env[j].val, vallen);
+ }
ptr += vallen;
}
- pktsize = headerlen - 4;
-
buf[0] = 0;
buf[1] = (apr_byte_t) (pktsize & 0xff);
buf[2] = (apr_byte_t) ((pktsize >> 8) & 0xff);
@@ -238,6 +266,7 @@ static request_rec *make_fake_req(conn_rec *c, request_rec *r)
request_rec *rp;
apr_pool_create(&pool, c->pool);
+ apr_pool_tag(pool, "proxy_uwsgi_rp");
rp = apr_pcalloc(pool, sizeof(*r));
@@ -297,18 +326,16 @@ static int uwsgi_response(request_rec *r, proxy_conn_rec * backend,
pass_bb = apr_brigade_create(r->pool, c->bucket_alloc);
len = ap_getline(buffer, sizeof(buffer), rp, 1);
-
if (len <= 0) {
- /* oops */
+ /* invalid or empty */
return HTTP_INTERNAL_SERVER_ERROR;
}
-
backend->worker->s->read += len;
-
- if (len >= sizeof(buffer) - 1) {
- /* oops */
+ if ((apr_size_t)len >= sizeof(buffer)) {
+ /* too long */
return HTTP_INTERNAL_SERVER_ERROR;
}
+
/* Position of http status code */
if (apr_date_checkmask(buffer, "HTTP/#.# ###*")) {
status_start = 9;
@@ -317,8 +344,8 @@ static int uwsgi_response(request_rec *r, proxy_conn_rec * backend,
status_start = 7;
}
else {
- /* oops */
- return HTTP_INTERNAL_SERVER_ERROR;
+ /* not HTTP */
+ return HTTP_BAD_GATEWAY;
}
status_end = status_start + 3;
@@ -338,21 +365,50 @@ static int uwsgi_response(request_rec *r, proxy_conn_rec * backend,
}
r->status_line = apr_pstrdup(r->pool, &buffer[status_start]);
- /* start parsing headers */
+ /* parse headers */
while ((len = ap_getline(buffer, sizeof(buffer), rp, 1)) > 0) {
+ if ((apr_size_t)len >= sizeof(buffer)) {
+ /* too long */
+ len = -1;
+ break;
+ }
value = strchr(buffer, ':');
- /* invalid header skip */
- if (!value)
- continue;
- *value = '\0';
- ++value;
+ if (!value) {
+ /* invalid header */
+ len = -1;
+ break;
+ }
+ *value++ = '\0';
+ if (*ap_scan_http_token(buffer)) {
+ /* invalid name */
+ len = -1;
+ break;
+ }
while (apr_isspace(*value))
++value;
for (end = &value[strlen(value) - 1];
end > value && apr_isspace(*end); --end)
*end = '\0';
+ if (*ap_scan_http_field_content(value)) {
+ /* invalid value */
+ len = -1;
+ break;
+ }
apr_table_add(r->headers_out, buffer, value);
}
+ if (len < 0) {
+ /* Reset headers, but not to NULL because things below the chain expect
+ * this to be non NULL e.g. the ap_content_length_filter.
+ */
+ r->headers_out = apr_table_make(r->pool, 1);
+ return HTTP_BAD_GATEWAY;
+ }
+
+ /* T-E wins over C-L */
+ if (apr_table_get(r->headers_out, "Transfer-Encoding")) {
+ apr_table_unset(r->headers_out, "Content-Length");
+ backend->close = 1;
+ }
if ((buf = apr_table_get(r->headers_out, "Content-Type"))) {
ap_set_content_type(r, apr_pstrdup(r->pool, buf));
@@ -362,9 +418,9 @@ static int uwsgi_response(request_rec *r, proxy_conn_rec * backend,
#if AP_MODULE_MAGIC_AT_LEAST(20101106,0)
dconf =
ap_get_module_config(r->per_dir_config, &proxy_module);
- if (dconf->error_override && ap_is_HTTP_ERROR(r->status)) {
+ if (ap_proxy_should_override(dconf, r->status)) {
#else
- if (conf->error_override && ap_is_HTTP_ERROR(r->status)) {
+ if (ap_proxy_should_override(conf, r->status)) {
#endif
int status = r->status;
r->status = HTTP_OK;
@@ -446,11 +502,8 @@ static int uwsgi_handler(request_rec *r, proxy_worker * worker,
const char *proxyname, apr_port_t proxyport)
{
int status;
- int delta = 0;
- int decode_status;
proxy_conn_rec *backend = NULL;
apr_pool_t *p = r->pool;
- size_t w_len;
char server_portstr[32];
char *u_path_info;
apr_uri_t *uri;
@@ -462,24 +515,23 @@ static int uwsgi_handler(request_rec *r, proxy_worker * worker,
uri = apr_palloc(r->pool, sizeof(*uri));
- /* ADD PATH_INFO */
-#if AP_MODULE_MAGIC_AT_LEAST(20111130,0)
- w_len = strlen(worker->s->name);
-#else
- w_len = strlen(worker->name);
-#endif
- u_path_info = r->filename + 6 + w_len;
- if (u_path_info[0] != '/') {
- delta = 1;
+ /* ADD PATH_INFO (unescaped) */
+ u_path_info = ap_strchr(url + sizeof(UWSGI_SCHEME) + 2, '/');
+ if (!u_path_info) {
+ u_path_info = apr_pstrdup(r->pool, "/");
}
- decode_status = ap_unescape_url(url + w_len - delta);
- if (decode_status) {
+ else if (ap_unescape_url(u_path_info) != OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10100)
- "unable to decode uri: %s", url + w_len - delta);
+ "unable to decode uwsgi uri: %s", url);
return HTTP_INTERNAL_SERVER_ERROR;
}
- apr_table_add(r->subprocess_env, "PATH_INFO", url + w_len - delta);
-
+ else {
+ /* Remove duplicate slashes at the beginning of PATH_INFO */
+ while (u_path_info[1] == '/') {
+ u_path_info++;
+ }
+ }
+ apr_table_add(r->subprocess_env, "PATH_INFO", u_path_info);
/* Create space for state information */
status = ap_proxy_acquire_connection(UWSGI_SCHEME, &backend, worker,
@@ -509,12 +561,10 @@ static int uwsgi_handler(request_rec *r, proxy_worker * worker,
}
/* Step Three: Create conn_rec */
- if (!backend->connection) {
- if ((status = ap_proxy_connection_create(UWSGI_SCHEME, backend,
- r->connection,
- r->server)) != OK)
- goto cleanup;
- }
+ if ((status = ap_proxy_connection_create(UWSGI_SCHEME, backend,
+ r->connection,
+ r->server)) != OK)
+ goto cleanup;
/* Step Four: Process the Request */
if (((status = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK)
diff --git a/modules/proxy/mod_proxy_wstunnel.c b/modules/proxy/mod_proxy_wstunnel.c
index 9dda010..30ba1b4 100644
--- a/modules/proxy/mod_proxy_wstunnel.c
+++ b/modules/proxy/mod_proxy_wstunnel.c
@@ -15,9 +15,43 @@
*/
#include "mod_proxy.h"
+#include "http_config.h"
module AP_MODULE_DECLARE_DATA proxy_wstunnel_module;
+typedef struct {
+ unsigned int fallback_to_proxy_http :1,
+ fallback_to_proxy_http_set :1;
+} proxyws_dir_conf;
+
+static int can_fallback_to_proxy_http;
+
+static int proxy_wstunnel_check_trans(request_rec *r, const char *url)
+{
+ proxyws_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
+ &proxy_wstunnel_module);
+
+ if (can_fallback_to_proxy_http && dconf->fallback_to_proxy_http) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "check_trans fallback");
+ return DECLINED;
+ }
+
+ if (ap_cstr_casecmpn(url, "ws:", 3) != 0
+ && ap_cstr_casecmpn(url, "wss:", 4) != 0) {
+ return DECLINED;
+ }
+
+ if (!apr_table_get(r->headers_in, "Upgrade")) {
+ /* No Upgrade, let mod_proxy_http handle it (for instance).
+ * Note: anything but OK/DECLINED will do (i.e. bypass wstunnel w/o
+ * aborting the request), HTTP_UPGRADE_REQUIRED is documentary...
+ */
+ return HTTP_UPGRADE_REQUIRED;
+ }
+
+ return OK;
+}
+
/*
* Canonicalise http-like URLs.
* scheme is the scheme for the URL
@@ -26,19 +60,26 @@ module AP_MODULE_DECLARE_DATA proxy_wstunnel_module;
*/
static int proxy_wstunnel_canon(request_rec *r, char *url)
{
+ proxyws_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
+ &proxy_wstunnel_module);
char *host, *path, sport[7];
char *search = NULL;
const char *err;
char *scheme;
apr_port_t port, def_port;
+ if (can_fallback_to_proxy_http && dconf->fallback_to_proxy_http) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "canon fallback");
+ return DECLINED;
+ }
+
/* ap_port_of_scheme() */
- if (strncasecmp(url, "ws:", 3) == 0) {
+ if (ap_cstr_casecmpn(url, "ws:", 3) == 0) {
url += 3;
scheme = "ws:";
def_port = apr_uri_port_of_scheme("http");
}
- else if (strncasecmp(url, "wss:", 4) == 0) {
+ else if (ap_cstr_casecmpn(url, "wss:", 4) == 0) {
url += 4;
scheme = "wss:";
def_port = apr_uri_port_of_scheme("https");
@@ -69,15 +110,42 @@ static int proxy_wstunnel_canon(request_rec *r, char *url)
if (apr_table_get(r->notes, "proxy-nocanon")) {
path = url; /* this is the raw path */
}
+ else if (apr_table_get(r->notes, "proxy-noencode")) {
+ path = url; /* this is the encoded path already */
+ search = r->args;
+ }
else {
- path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
- r->proxyreq);
+ core_dir_config *d = ap_get_core_module_config(r->per_dir_config);
+ int flags = d->allow_encoded_slashes && !d->decode_encoded_slashes ? PROXY_CANONENC_NOENCODEDSLASHENCODING : 0;
+
+ path = ap_proxy_canonenc_ex(r->pool, url, strlen(url), enc_path, flags,
+ r->proxyreq);
+ if (!path) {
+ return HTTP_BAD_REQUEST;
+ }
search = r->args;
}
- if (path == NULL)
- return HTTP_BAD_REQUEST;
+ /*
+ * If we have a raw control character or a ' ' in nocanon path or
+ * r->args, correct encoding was missed.
+ */
+ if (path == url && *ap_scan_vchar_obstext(path)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10419)
+ "To be forwarded path contains control "
+ "characters or spaces");
+ return HTTP_FORBIDDEN;
+ }
+ if (search && *ap_scan_vchar_obstext(search)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10409)
+ "To be forwarded query string contains control "
+ "characters or spaces");
+ return HTTP_FORBIDDEN;
+ }
- apr_snprintf(sport, sizeof(sport), ":%d", port);
+ if (port != def_port)
+ apr_snprintf(sport, sizeof(sport), ":%d", port);
+ else
+ sport[0] = '\0';
if (ap_strchr_c(host, ':')) {
/* if literal IPv6 address */
@@ -280,112 +348,166 @@ static int proxy_wstunnel_handler(request_rec *r, proxy_worker *worker,
char *url, const char *proxyname,
apr_port_t proxyport)
{
+ proxyws_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
+ &proxy_wstunnel_module);
int status;
char server_portstr[32];
proxy_conn_rec *backend = NULL;
+ const char *upgrade;
char *scheme;
- int retry;
apr_pool_t *p = r->pool;
+ char *locurl = url;
apr_uri_t *uri;
int is_ssl = 0;
- const char *upgrade_method = *worker->s->upgrade ? worker->s->upgrade : "WebSocket";
- if (strncasecmp(url, "wss:", 4) == 0) {
+ if (can_fallback_to_proxy_http && dconf->fallback_to_proxy_http) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "handler fallback");
+ return DECLINED;
+ }
+
+ if (ap_cstr_casecmpn(url, "wss:", 4) == 0) {
scheme = "WSS";
is_ssl = 1;
}
- else if (strncasecmp(url, "ws:", 3) == 0) {
+ else if (ap_cstr_casecmpn(url, "ws:", 3) == 0) {
scheme = "WS";
}
else {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02450) "declining URL %s", url);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02450)
+ "declining URL %s", url);
return DECLINED;
}
-
- if (ap_cstr_casecmp(upgrade_method, "NONE") != 0) {
- const char *upgrade;
- upgrade = apr_table_get(r->headers_in, "Upgrade");
- if (!upgrade || (ap_cstr_casecmp(upgrade, upgrade_method) != 0 &&
- ap_cstr_casecmp(upgrade_method, "ANY") !=0)) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02900)
- "declining URL %s (not %s, Upgrade: header is %s)",
- url, upgrade_method, upgrade ? upgrade : "missing");
- return DECLINED;
- }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "serving URL %s", url);
+
+ upgrade = apr_table_get(r->headers_in, "Upgrade");
+ if (!upgrade || !ap_proxy_worker_can_upgrade(p, worker, upgrade,
+ "WebSocket")) {
+ const char *worker_upgrade = *worker->s->upgrade ? worker->s->upgrade
+ : "WebSocket";
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02900)
+ "require upgrade for URL %s "
+ "(Upgrade header is %s, expecting %s)",
+ url, upgrade ? upgrade : "missing", worker_upgrade);
+ apr_table_setn(r->err_headers_out, "Connection", "Upgrade");
+ apr_table_setn(r->err_headers_out, "Upgrade", worker_upgrade);
+ return HTTP_UPGRADE_REQUIRED;
}
uri = apr_palloc(p, sizeof(*uri));
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02451) "serving URL %s", url);
/* create space for state information */
- status = ap_proxy_acquire_connection(scheme, &backend, worker,
- r->server);
+ status = ap_proxy_acquire_connection(scheme, &backend, worker, r->server);
if (status != OK) {
- if (backend) {
- backend->close = 1;
- ap_proxy_release_connection(scheme, backend, r->server);
- }
- return status;
+ goto cleanup;
}
backend->is_ssl = is_ssl;
backend->close = 0;
- retry = 0;
- while (retry < 2) {
- char *locurl = url;
- /* Step One: Determine Who To Connect To */
- status = ap_proxy_determine_connection(p, r, conf, worker, backend,
- uri, &locurl, proxyname, proxyport,
- server_portstr,
- sizeof(server_portstr));
-
- if (status != OK)
- break;
-
- /* Step Two: Make the Connection */
- if (ap_proxy_connect_backend(scheme, backend, worker, r->server)) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02452)
- "failed to make connection to backend: %s",
- backend->hostname);
- status = HTTP_SERVICE_UNAVAILABLE;
- break;
- }
-
- /* Step Three: Create conn_rec */
- if (!backend->connection) {
- status = ap_proxy_connection_create_ex(scheme, backend, r);
- if (status != OK) {
- break;
- }
- }
-
- backend->close = 1; /* must be after ap_proxy_determine_connection */
+ /* Step One: Determine Who To Connect To */
+ status = ap_proxy_determine_connection(p, r, conf, worker, backend,
+ uri, &locurl, proxyname, proxyport,
+ server_portstr,
+ sizeof(server_portstr));
+ if (status != OK) {
+ goto cleanup;
+ }
+ /* Step Two: Make the Connection */
+ if (ap_proxy_connect_backend(scheme, backend, worker, r->server)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02452)
+ "failed to make connection to backend: %s",
+ backend->hostname);
+ status = HTTP_SERVICE_UNAVAILABLE;
+ goto cleanup;
+ }
- /* Step Three: Process the Request */
- status = proxy_wstunnel_request(p, r, backend, worker, conf, uri, locurl,
- server_portstr);
- break;
+ /* Step Three: Create conn_rec */
+ status = ap_proxy_connection_create_ex(scheme, backend, r);
+ if (status != OK) {
+ goto cleanup;
}
+ /* Step Four: Process the Request */
+ status = proxy_wstunnel_request(p, r, backend, worker, conf, uri, locurl,
+ server_portstr);
+
+cleanup:
/* Do not close the socket */
- ap_proxy_release_connection(scheme, backend, r->server);
+ if (backend) {
+ backend->close = 1;
+ ap_proxy_release_connection(scheme, backend, r->server);
+ }
return status;
}
-static void ap_proxy_http_register_hook(apr_pool_t *p)
+static void *create_proxyws_dir_config(apr_pool_t *p, char *dummy)
+{
+ proxyws_dir_conf *new =
+ (proxyws_dir_conf *) apr_pcalloc(p, sizeof(proxyws_dir_conf));
+
+ new->fallback_to_proxy_http = 1;
+
+ return (void *) new;
+}
+
+static void *merge_proxyws_dir_config(apr_pool_t *p, void *vbase, void *vadd)
+{
+ proxyws_dir_conf *new = apr_pcalloc(p, sizeof(proxyws_dir_conf)),
+ *add = vadd, *base = vbase;
+
+ new->fallback_to_proxy_http = (add->fallback_to_proxy_http_set)
+ ? add->fallback_to_proxy_http
+ : base->fallback_to_proxy_http;
+ new->fallback_to_proxy_http_set = (add->fallback_to_proxy_http_set
+ || base->fallback_to_proxy_http_set);
+
+ return new;
+}
+
+static const char * proxyws_fallback_to_proxy_http(cmd_parms *cmd, void *conf, int arg)
+{
+ proxyws_dir_conf *dconf = conf;
+ dconf->fallback_to_proxy_http = !!arg;
+ dconf->fallback_to_proxy_http_set = 1;
+ return NULL;
+}
+
+static int proxy_wstunnel_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ can_fallback_to_proxy_http =
+ (ap_find_linked_module("mod_proxy_http.c") != NULL);
+
+ return OK;
+}
+
+static const command_rec ws_proxy_cmds[] =
+{
+ AP_INIT_FLAG("ProxyWebsocketFallbackToProxyHttp",
+ proxyws_fallback_to_proxy_http, NULL, RSRC_CONF|ACCESS_CONF,
+ "whether to let mod_proxy_http handle the upgrade and tunneling, "
+ "On by default"),
+
+ {NULL}
+};
+
+static void ws_proxy_hooks(apr_pool_t *p)
{
- proxy_hook_scheme_handler(proxy_wstunnel_handler, NULL, NULL, APR_HOOK_FIRST);
- proxy_hook_canon_handler(proxy_wstunnel_canon, NULL, NULL, APR_HOOK_FIRST);
+ static const char * const aszSucc[] = { "mod_proxy_http.c", NULL};
+ ap_hook_post_config(proxy_wstunnel_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ proxy_hook_scheme_handler(proxy_wstunnel_handler, NULL, aszSucc, APR_HOOK_FIRST);
+ proxy_hook_check_trans(proxy_wstunnel_check_trans, NULL, aszSucc, APR_HOOK_MIDDLE);
+ proxy_hook_canon_handler(proxy_wstunnel_canon, NULL, aszSucc, APR_HOOK_FIRST);
}
AP_DECLARE_MODULE(proxy_wstunnel) = {
STANDARD20_MODULE_STUFF,
- NULL, /* create per-directory config structure */
- NULL, /* merge per-directory config structures */
+ create_proxyws_dir_config, /* create per-directory config structure */
+ merge_proxyws_dir_config, /* merge per-directory config structures */
NULL, /* create per-server config structure */
NULL, /* merge per-server config structures */
- NULL, /* command apr_table_t */
- ap_proxy_http_register_hook /* register hooks */
+ ws_proxy_cmds, /* command apr_table_t */
+ ws_proxy_hooks /* register hooks */
};
diff --git a/modules/proxy/proxy_util.c b/modules/proxy/proxy_util.c
index cbf8826..a54a4fa 100644
--- a/modules/proxy/proxy_util.c
+++ b/modules/proxy/proxy_util.c
@@ -19,22 +19,22 @@
#include "ap_mpm.h"
#include "scoreboard.h"
#include "apr_version.h"
+#include "apr_strings.h"
#include "apr_hash.h"
+#include "apr_atomic.h"
+#include "http_core.h"
#include "proxy_util.h"
#include "ajp.h"
#include "scgi.h"
+#include "mpm_common.h" /* for ap_max_mem_free */
+
#include "mod_http2.h" /* for http2_get_num_workers() */
#if APR_HAVE_UNISTD_H
#include <unistd.h> /* for getpid() */
#endif
-#if (APR_MAJOR_VERSION < 1)
-#undef apr_socket_create
-#define apr_socket_create apr_socket_create_ex
-#endif
-
#if APR_HAVE_SYS_UN_H
#include <sys/un.h>
#endif
@@ -47,7 +47,7 @@ APLOG_USE_MODULE(proxy);
/*
* Opaque structure containing target server info when
* using a forward proxy.
- * Up to now only used in combination with HTTP CONNECT.
+ * Up to now only used in combination with HTTP CONNECT to ProxyRemote
*/
typedef struct {
int use_http_connect; /* Use SSL Tunneling via HTTP CONNECT */
@@ -56,6 +56,17 @@ typedef struct {
const char *proxy_auth; /* Proxy authorization */
} forward_info;
+/*
+ * Opaque structure containing a refcounted and TTL'ed address.
+ */
+typedef struct proxy_address {
+ apr_sockaddr_t *addr; /* Remote address info */
+ const char *hostname; /* Remote host name */
+ apr_port_t hostport; /* Remote host port */
+ apr_uint32_t refcount; /* Number of conns and/or worker using it */
+ apr_uint32_t expiry; /* Expiry timestamp (seconds to proxy_start_time) */
+} proxy_address;
+
/* Global balancer counter */
int PROXY_DECLARE_DATA proxy_lb_workers = 0;
static int lb_workers_limit = 0;
@@ -64,6 +75,8 @@ const apr_strmatch_pattern PROXY_DECLARE_DATA *ap_proxy_strmatch_domain;
extern apr_global_mutex_t *proxy_mutex;
+static const apr_time_t *proxy_start_time; /* epoch for expiring addresses */
+
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);
@@ -204,14 +217,16 @@ PROXY_DECLARE(void) ap_proxy_c2hex(int ch, char *x)
* 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 forcedec,
- int proxyreq)
+PROXY_DECLARE(char *)ap_proxy_canonenc_ex(apr_pool_t *p, const char *x, int len,
+ enum enctype t, int flags,
+ int proxyreq)
{
int i, j, ch;
char *y;
char *allowed; /* characters which should not be encoded */
char *reserved; /* characters which much not be en/de-coded */
+ int forcedec = flags & PROXY_CANONENC_FORCEDEC;
+ int noencslashesenc = flags & PROXY_CANONENC_NOENCODEDSLASHENCODING;
/*
* N.B. in addition to :@&=, this allows ';' in an http path
@@ -260,17 +275,29 @@ PROXY_DECLARE(char *)ap_proxy_canonenc(apr_pool_t *p, const char *x, int len,
* decode it if not already done. do not decode reverse proxied URLs
* unless specifically forced
*/
- if ((forcedec || (proxyreq && proxyreq != PROXYREQ_REVERSE)) && ch == '%') {
+ if ((forcedec || noencslashesenc
+ || (proxyreq && proxyreq != PROXYREQ_REVERSE)) && 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;
+ y[j++] = x[i++];
+ y[j++] = x[i++];
+ y[j] = x[i];
continue;
}
+ if (noencslashesenc && !forcedec && (proxyreq == PROXYREQ_REVERSE)) {
+ /*
+ * In the reverse proxy case when we only want to keep encoded
+ * slashes untouched revert back to '%' which will cause
+ * '%' to be encoded in the following.
+ */
+ ch = '%';
+ }
+ else {
+ i += 2;
+ }
}
/* recode it, if necessary */
if (!apr_isalnum(ch) && !strchr(allowed, ch)) {
@@ -286,6 +313,22 @@ PROXY_DECLARE(char *)ap_proxy_canonenc(apr_pool_t *p, const char *x, int len,
}
/*
+ * 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 forcedec,
+ int proxyreq)
+{
+ int flags;
+
+ flags = forcedec ? PROXY_CANONENC_FORCEDEC : 0;
+ return ap_proxy_canonenc_ex(p, x, len, t, flags, proxyreq);
+}
+
+/*
* Parses network-location.
* urlp on input the URL; on output the path, after the leading /
* user NULL if no user/password permitted
@@ -366,14 +409,15 @@ PROXY_DECLARE(char *)
return NULL;
}
-PROXY_DECLARE(int) ap_proxyerror(request_rec *r, int statuscode, const char *message)
+static int proxyerror_core(request_rec *r, int statuscode, const char *message,
+ apr_status_t rv)
{
- const char *uri = ap_escape_html(r->pool, r->uri);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00898)
+ "%s returned by %s", message, r->uri);
+
apr_table_setn(r->notes, "error-notes",
apr_pstrcat(r->pool,
- "The proxy server could not handle the request <em><a href=\"",
- uri, "\">", ap_escape_html(r->pool, r->method), "&nbsp;", uri,
- "</a></em>.<p>\n"
+ "The proxy server could not handle the request<p>"
"Reason: <strong>", ap_escape_html(r->pool, message),
"</strong></p>",
NULL));
@@ -382,11 +426,14 @@ PROXY_DECLARE(int) ap_proxyerror(request_rec *r, int statuscode, const char *mes
apr_table_setn(r->notes, "verbose-error-to", "*");
r->status_line = apr_psprintf(r->pool, "%3.3u Proxy Error", statuscode);
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00898) "%s returned by %s", message,
- r->uri);
return statuscode;
}
+PROXY_DECLARE(int) ap_proxyerror(request_rec *r, int statuscode, const char *message)
+{
+ return proxyerror_core(r, statuscode, message, 0);
+}
+
static const char *
proxy_get_host_of_request(request_rec *r)
{
@@ -890,20 +937,20 @@ PROXY_DECLARE(const char *) ap_proxy_location_reverse_map(request_rec *r,
* translate url http://example.com/foo/bar/that to /bash/that
*/
for (n = 0; n < balancer->workers->nelts; n++) {
- l2 = strlen((*worker)->s->name);
+ l2 = strlen((*worker)->s->name_ex);
if (urlpart) {
/* urlpart (l3) assuredly starts with its own '/' */
- if ((*worker)->s->name[l2 - 1] == '/')
+ if ((*worker)->s->name_ex[l2 - 1] == '/')
--l2;
if (l1 >= l2 + l3
- && strncasecmp((*worker)->s->name, url, l2) == 0
+ && strncasecmp((*worker)->s->name_ex, url, l2) == 0
&& strncmp(urlpart, url + l2, l3) == 0) {
u = apr_pstrcat(r->pool, ent[i].fake, &url[l2 + l3],
NULL);
return ap_is_url(u) ? u : ap_construct_url(r->pool, u, r);
}
}
- else if (l1 >= l2 && strncasecmp((*worker)->s->name, url, l2) == 0) {
+ else if (l1 >= l2 && strncasecmp((*worker)->s->name_ex, url, l2) == 0) {
/* edge case where fake is just "/"... avoid double slash */
if ((ent[i].fake[0] == '/') && (ent[i].fake[1] == 0) && (url[l2] == '/')) {
u = apr_pstrdup(r->pool, &url[l2]);
@@ -1080,7 +1127,7 @@ PROXY_DECLARE(int) ap_proxy_valid_balancer_name(char *name, int i)
{
if (!i)
i = sizeof(BALANCER_PREFIX)-1;
- return (!strncasecmp(name, BALANCER_PREFIX, i));
+ return (!ap_cstr_casecmpn(name, BALANCER_PREFIX, i));
}
@@ -1172,11 +1219,13 @@ PROXY_DECLARE(char *) ap_proxy_define_balancer(apr_pool_t *p,
* exist, that's OK at this time. We check when we share and sync
*/
lbmethod = ap_lookup_provider(PROXY_LBMETHOD, "byrequests", "0");
-
+ (*balancer)->lbmethod = lbmethod;
+
(*balancer)->workers = apr_array_make(p, 5, sizeof(proxy_worker *));
+#if APR_HAS_THREADS
(*balancer)->gmutex = NULL;
(*balancer)->tmutex = NULL;
- (*balancer)->lbmethod = lbmethod;
+#endif
if (do_malloc)
bshared = ap_malloc(sizeof(proxy_balancer_shared));
@@ -1188,8 +1237,11 @@ PROXY_DECLARE(char *) ap_proxy_define_balancer(apr_pool_t *p,
bshared->was_malloced = (do_malloc != 0);
PROXY_STRNCPY(bshared->lbpname, "byrequests");
if (PROXY_STRNCPY(bshared->name, uri) != APR_SUCCESS) {
+ if (do_malloc) free(bshared);
return apr_psprintf(p, "balancer name (%s) too long", uri);
}
+ (*balancer)->lbmethod_set = 1;
+
/*
* We do the below for verification. The real sname will be
* done post_config
@@ -1198,6 +1250,7 @@ PROXY_DECLARE(char *) ap_proxy_define_balancer(apr_pool_t *p,
&sname);
sname = apr_pstrcat(p, conf->id, "_", sname, NULL);
if (PROXY_STRNCPY(bshared->sname, sname) != APR_SUCCESS) {
+ if (do_malloc) free(bshared);
return apr_psprintf(p, "balancer safe-name (%s) too long", sname);
}
bshared->hash.def = ap_proxy_hashfunc(bshared->name, PROXY_HASHFUNC_DEFAULT);
@@ -1244,6 +1297,7 @@ PROXY_DECLARE(apr_status_t) ap_proxy_share_balancer(proxy_balancer *balancer,
lbmethod = ap_lookup_provider(PROXY_LBMETHOD, balancer->s->lbpname, "0");
if (lbmethod) {
balancer->lbmethod = lbmethod;
+ balancer->lbmethod_set = 1;
} else {
ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, APLOGNO(02432)
"Cannot find LB Method: %s", balancer->s->lbpname);
@@ -1252,10 +1306,11 @@ PROXY_DECLARE(apr_status_t) ap_proxy_share_balancer(proxy_balancer *balancer,
if (*balancer->s->nonce == PROXY_UNSET_NONCE) {
char nonce[APR_UUID_FORMATTED_LENGTH + 1];
apr_uuid_t uuid;
- /* Retrieve a UUID and store the nonce for the lifetime of
- * the process.
- */
- apr_uuid_get(&uuid);
+
+ /* Generate a pseudo-UUID from the PRNG to use as a nonce for
+ * the lifetime of the process. uuid.data is a char array so
+ * this is an adequate substitute for apr_uuid_get(). */
+ ap_random_insecure_bytes(uuid.data, sizeof uuid.data);
apr_uuid_format(nonce, &uuid);
rv = PROXY_STRNCPY(balancer->s->nonce, nonce);
}
@@ -1264,7 +1319,9 @@ PROXY_DECLARE(apr_status_t) ap_proxy_share_balancer(proxy_balancer *balancer,
PROXY_DECLARE(apr_status_t) ap_proxy_initialize_balancer(proxy_balancer *balancer, server_rec *s, apr_pool_t *p)
{
+#if APR_HAS_THREADS
apr_status_t rv = APR_SUCCESS;
+#endif
ap_slotmem_provider_t *storage = balancer->storage;
apr_size_t size;
unsigned int num;
@@ -1304,6 +1361,7 @@ PROXY_DECLARE(apr_status_t) ap_proxy_initialize_balancer(proxy_balancer *balance
if (balancer->lbmethod && balancer->lbmethod->reset)
balancer->lbmethod->reset(balancer, s);
+#if APR_HAS_THREADS
if (balancer->tmutex == NULL) {
rv = apr_thread_mutex_create(&(balancer->tmutex), APR_THREAD_MUTEX_DEFAULT, p);
if (rv != APR_SUCCESS) {
@@ -1312,6 +1370,7 @@ PROXY_DECLARE(apr_status_t) ap_proxy_initialize_balancer(proxy_balancer *balance
return rv;
}
}
+#endif
return APR_SUCCESS;
}
@@ -1335,6 +1394,7 @@ static proxy_worker *proxy_balancer_get_best_worker(proxy_balancer *balancer,
balancer->lbmethod->name, balancer->s->name);
apr_pool_create(&tpool, r->pool);
+ apr_pool_tag(tpool, "proxy_lb_best");
spares = apr_array_make(tpool, 1, sizeof(proxy_worker*));
standbys = apr_array_make(tpool, 1, sizeof(proxy_worker*));
@@ -1424,7 +1484,8 @@ static proxy_worker *proxy_balancer_get_best_worker(proxy_balancer *balancer,
if (best_worker) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(10123)
"proxy: %s selected worker \"%s\" : busy %" APR_SIZE_T_FMT " : lbstatus %d",
- balancer->lbmethod->name, best_worker->s->name, best_worker->s->busy, best_worker->s->lbstatus);
+ balancer->lbmethod->name, best_worker->s->name_ex,
+ best_worker->s->busy, best_worker->s->lbstatus);
}
return best_worker;
@@ -1451,61 +1512,136 @@ static void socket_cleanup(proxy_conn_rec *conn)
apr_pool_clear(conn->scpool);
}
-static apr_status_t conn_pool_cleanup(void *theworker)
+static void address_cleanup(proxy_conn_rec *conn)
{
- proxy_worker *worker = (proxy_worker *)theworker;
- if (worker->cp->res) {
- worker->cp->pool = NULL;
+ conn->address = NULL;
+ conn->addr = NULL;
+ conn->hostname = NULL;
+ conn->port = 0;
+ conn->uds_path = NULL;
+ if (conn->uds_pool) {
+ apr_pool_clear(conn->uds_pool);
+ }
+ if (conn->sock) {
+ socket_cleanup(conn);
}
+}
+
+static apr_status_t conn_pool_cleanup(void *theworker)
+{
+ ((proxy_worker *)theworker)->cp = NULL;
return APR_SUCCESS;
}
-static void init_conn_pool(apr_pool_t *p, proxy_worker *worker)
+static apr_pool_t *make_conn_subpool(apr_pool_t *p, const char *tag,
+ server_rec *s)
+{
+ apr_pool_t *sp = NULL;
+ apr_allocator_t *alloc;
+ apr_thread_mutex_t *mutex;
+ apr_status_t rv;
+
+ rv = apr_allocator_create(&alloc);
+ if (rv == APR_SUCCESS) {
+ rv = apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT, p);
+ if (rv == APR_SUCCESS) {
+ apr_allocator_mutex_set(alloc, mutex);
+ apr_allocator_max_free_set(alloc, ap_max_mem_free);
+ rv = apr_pool_create_ex(&sp, p, NULL, alloc);
+ }
+ else {
+ apr_allocator_destroy(alloc);
+ }
+ }
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(10474)
+ "failed to create %s pool", tag);
+ ap_abort_on_oom();
+ return NULL; /* not reached */
+ }
+ apr_allocator_owner_set(alloc, sp);
+ apr_pool_tag(sp, tag);
+
+ return sp;
+}
+
+static void init_conn_pool(apr_pool_t *p, proxy_worker *worker, server_rec *s)
{
- apr_pool_t *pool;
proxy_conn_pool *cp;
/*
- * Create a connection pool's subpool.
- * This pool is used for connection recycling.
- * Once the worker is added it is never removed but
- * it can be disabled.
- */
- apr_pool_create(&pool, p);
- apr_pool_tag(pool, "proxy_worker_cp");
- /*
* Alloc from the same pool as worker.
* proxy_conn_pool is permanently attached to the worker.
*/
cp = (proxy_conn_pool *)apr_pcalloc(p, sizeof(proxy_conn_pool));
- cp->pool = pool;
worker->cp = cp;
+
+ /*
+ * We need a first pool (cp->pool) to maintain the connections attached to
+ * the worker and a second one (cp->dns_pool) to maintain the DNS addresses
+ * in use (TTL'ed, refcounted). New connections are created as/on a subpool
+ * of cp->pool and new addresses as/on a subpool of cp->dns_pool, such that
+ * both leaks (the subpools can be destroyed when the connections and/or
+ * addresses are over) and race conditions (the creation/destruction of
+ * subpools is protected by the parent pool's mutex) can be avoided.
+ *
+ * cp->dns_pool is created before cp->pool because when a connection on the
+ * latter is destroyed it might destroy an address on the former, so when
+ * the base pools are destroyed (e.g. child exit) we thusly make sure that
+ * cp->dns_pool and its subpools are still alive when cp->pool gets killed.
+ *
+ * Both cp->dns_pool and cp->pool have their own allocator/mutex too since
+ * acquiring connections and addresses don't need to contend.
+ */
+ cp->dns_pool = make_conn_subpool(p, "proxy_worker_dns", s);
+ cp->pool = make_conn_subpool(p, "proxy_worker_cp", s);
+
+ /* When p is cleaning up the child is exiting, signal that to e.g. avoid
+ * destroying the subpools explicitely in connection_destructor() when
+ * they have been destroyed already by the reslist cleanup.
+ */
+ apr_pool_pre_cleanup_register(p, worker, conn_pool_cleanup);
}
PROXY_DECLARE(int) ap_proxy_connection_reusable(proxy_conn_rec *conn)
{
proxy_worker *worker = conn->worker;
- return ! (conn->close || !worker->s->is_address_reusable || worker->s->disablereuse);
+ return !(conn->close
+ || conn->forward
+ || worker->s->disablereuse
+ || !worker->s->is_address_reusable);
}
-static apr_status_t connection_cleanup(void *theconn)
+static proxy_conn_rec *connection_make(apr_pool_t *p, proxy_worker *worker)
{
- proxy_conn_rec *conn = (proxy_conn_rec *)theconn;
- proxy_worker *worker = conn->worker;
+ proxy_conn_rec *conn;
+
+ conn = apr_pcalloc(p, sizeof(proxy_conn_rec));
+ conn->pool = p;
+ conn->worker = worker;
/*
- * If the connection pool is NULL the worker
- * cleanup has been run. Just return.
+ * Create another subpool that manages the data for the
+ * socket and the connection member of the proxy_conn_rec struct as we
+ * destroy this data more frequently than other data in the proxy_conn_rec
+ * struct like hostname and addr (at least in the case where we have
+ * keepalive connections that timed out).
+ *
+ * XXX: this is really needed only when worker->s->is_address_reusable,
+ * otherwise conn->scpool = conn->pool would be fine. For now we
+ * can't change it since it's (kind of) part of the API.
*/
- if (!worker->cp->pool) {
- return APR_SUCCESS;
- }
+ apr_pool_create(&conn->scpool, p);
+ apr_pool_tag(conn->scpool, "proxy_conn_scpool");
- if (conn->r) {
- apr_pool_destroy(conn->r->pool);
- conn->r = NULL;
- }
+ return conn;
+}
+
+static void connection_cleanup(void *theconn)
+{
+ proxy_conn_rec *conn = (proxy_conn_rec *)theconn;
+ proxy_worker *worker = conn->worker;
/* Sanity check: Did we already return the pooled connection? */
if (conn->inreslist) {
@@ -1513,37 +1649,43 @@ static apr_status_t connection_cleanup(void *theconn)
"Pooled connection 0x%pp for worker %s has been"
" already returned to the connection pool.", conn,
ap_proxy_worker_name(conn->pool, worker));
- return APR_SUCCESS;
+ return;
+ }
+
+ if (conn->r) {
+ apr_pool_destroy(conn->r->pool);
+ conn->r = NULL;
}
- /* determine if the connection need to be closed */
- if (!worker->s->is_address_reusable || worker->s->disablereuse) {
+ /* determine if the connection should be cleared, closed or reused */
+ if (!worker->s->is_address_reusable) {
apr_pool_t *p = conn->pool;
apr_pool_clear(p);
- conn = apr_pcalloc(p, sizeof(proxy_conn_rec));
- conn->pool = p;
- conn->worker = worker;
- apr_pool_create(&(conn->scpool), p);
- apr_pool_tag(conn->scpool, "proxy_conn_scpool");
+ conn = connection_make(p, worker);
}
else if (conn->close
- || (conn->connection
- && conn->connection->keepalive == AP_CONN_CLOSE)) {
+ || conn->forward
+ || (conn->connection
+ && conn->connection->keepalive == AP_CONN_CLOSE)
+ || worker->s->disablereuse) {
socket_cleanup(conn);
conn->close = 0;
}
+ else if (conn->is_ssl) {
+ /* Unbind/reset the SSL connection dir config (sslconn->dc) from
+ * r->per_dir_config, r will likely get destroyed before this proxy
+ * conn is reused.
+ */
+ ap_proxy_ssl_engine(conn->connection, worker->section_config, 1);
+ }
if (worker->s->hmax && worker->cp->res) {
conn->inreslist = 1;
apr_reslist_release(worker->cp->res, (void *)conn);
}
- else
- {
+ else {
worker->cp->conn = conn;
}
-
- /* Always return the SUCCESS */
- return APR_SUCCESS;
}
/* DEPRECATED */
@@ -1584,35 +1726,21 @@ PROXY_DECLARE(apr_status_t) ap_proxy_ssl_connection_cleanup(proxy_conn_rec *conn
static apr_status_t connection_constructor(void **resource, void *params,
apr_pool_t *pool)
{
- apr_pool_t *ctx;
- apr_pool_t *scpool;
+ apr_pool_t *p;
proxy_conn_rec *conn;
proxy_worker *worker = (proxy_worker *)params;
/*
- * Create the subpool for each connection
+ * Create a subpool for each connection
* This keeps the memory consumption constant
- * when disconnecting from backend.
- */
- apr_pool_create(&ctx, pool);
- apr_pool_tag(ctx, "proxy_conn_pool");
- /*
- * Create another subpool that manages the data for the
- * socket and the connection member of the proxy_conn_rec struct as we
- * destroy this data more frequently than other data in the proxy_conn_rec
- * struct like hostname and addr (at least in the case where we have
- * keepalive connections that timed out).
+ * when it's recycled or destroyed.
*/
- apr_pool_create(&scpool, ctx);
- apr_pool_tag(scpool, "proxy_conn_scpool");
- conn = apr_pcalloc(ctx, sizeof(proxy_conn_rec));
-
- conn->pool = ctx;
- conn->scpool = scpool;
- conn->worker = worker;
+ apr_pool_create(&p, pool);
+ apr_pool_tag(p, "proxy_conn_pool");
+ conn = connection_make(p, worker);
conn->inreslist = 1;
- *resource = conn;
+ *resource = conn;
return APR_SUCCESS;
}
@@ -1623,7 +1751,7 @@ static apr_status_t connection_destructor(void *resource, void *params,
proxy_worker *worker = params;
/* Destroy the pool only if not called from reslist_destroy */
- if (worker->cp->pool) {
+ if (worker->cp) {
proxy_conn_rec *conn = resource;
apr_pool_destroy(conn->pool);
}
@@ -1640,15 +1768,73 @@ PROXY_DECLARE(char *) ap_proxy_worker_name(apr_pool_t *p,
{
if (!(*worker->s->uds_path) || !p) {
/* just in case */
- return worker->s->name;
+ return worker->s->name_ex;
}
- return apr_pstrcat(p, "unix:", worker->s->uds_path, "|", worker->s->name, NULL);
+ return apr_pstrcat(p, "unix:", worker->s->uds_path, "|", worker->s->name_ex, NULL);
}
-PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker(apr_pool_t *p,
- proxy_balancer *balancer,
- proxy_server_conf *conf,
- const char *url)
+PROXY_DECLARE(int) ap_proxy_worker_can_upgrade(apr_pool_t *p,
+ const proxy_worker *worker,
+ const char *upgrade,
+ const char *dflt)
+{
+ /* Find in worker->s->upgrade list (if any) */
+ const char *worker_upgrade = worker->s->upgrade;
+ if (*worker_upgrade) {
+ return (strcmp(worker_upgrade, "*") == 0
+ || ap_cstr_casecmp(worker_upgrade, upgrade) == 0
+ || ap_find_token(p, worker_upgrade, upgrade));
+ }
+
+ /* Compare to the provided default (if any) */
+ return (dflt && ap_cstr_casecmp(dflt, upgrade) == 0);
+}
+
+/*
+ * Taken from ap_strcmp_match() :
+ * Match = 0, NoMatch = 1, Abort = -1, Inval = -2
+ * Based loosely on sections of wildmat.c by Rich Salz
+ * Hmmm... shouldn't this really go component by component?
+ *
+ * Adds handling of the "\<any>" => "<any>" unescaping.
+ */
+static int ap_proxy_strcmp_ematch(const char *str, const char *expected)
+{
+ apr_size_t x, y;
+
+ for (x = 0, y = 0; expected[y]; ++y, ++x) {
+ if (expected[y] == '$' && apr_isdigit(expected[y + 1])) {
+ do {
+ y += 2;
+ } while (expected[y] == '$' && apr_isdigit(expected[y + 1]));
+ if (!expected[y])
+ return 0;
+ while (str[x]) {
+ int ret;
+ if ((ret = ap_proxy_strcmp_ematch(&str[x++], &expected[y])) != 1)
+ return ret;
+ }
+ return -1;
+ }
+ else if (!str[x]) {
+ return -1;
+ }
+ else if (expected[y] == '\\' && !expected[++y]) {
+ /* NUL is an invalid char! */
+ return -2;
+ }
+ if (str[x] != expected[y])
+ return 1;
+ }
+ /* We got all the way through the worker path without a difference */
+ return 0;
+}
+
+PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker_ex(apr_pool_t *p,
+ proxy_balancer *balancer,
+ proxy_server_conf *conf,
+ const char *url,
+ unsigned int mask)
{
proxy_worker *worker;
proxy_worker *max_worker = NULL;
@@ -1664,7 +1850,12 @@ PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker(apr_pool_t *p,
return NULL;
}
- url = ap_proxy_de_socketfy(p, url);
+ if (!(mask & AP_PROXY_WORKER_NO_UDS)) {
+ url = ap_proxy_de_socketfy(p, url);
+ if (!url) {
+ return NULL;
+ }
+ }
c = ap_strchr_c(url, ':');
if (c == NULL || c[1] != '/' || c[2] != '/' || c[3] == '\0') {
@@ -1674,6 +1865,11 @@ PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker(apr_pool_t *p,
url_length = strlen(url);
url_copy = apr_pstrmemdup(p, url, url_length);
+ /* Default to lookup for both _PREFIX and _MATCH workers */
+ if (!(mask & (AP_PROXY_WORKER_IS_PREFIX | AP_PROXY_WORKER_IS_MATCH))) {
+ mask |= AP_PROXY_WORKER_IS_PREFIX | AP_PROXY_WORKER_IS_MATCH;
+ }
+
/*
* We need to find the start of the path and
* therefore we know the length of the scheme://hostname/
@@ -1704,22 +1900,35 @@ PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker(apr_pool_t *p,
proxy_worker **workers = (proxy_worker **)balancer->workers->elts;
for (i = 0; i < balancer->workers->nelts; i++, workers++) {
worker = *workers;
- if ( ((worker_name_length = strlen(worker->s->name)) <= url_length)
+ if ( ((worker_name_length = strlen(worker->s->name_ex)) <= url_length)
&& (worker_name_length >= min_match)
&& (worker_name_length > max_match)
- && (strncmp(url_copy, worker->s->name, worker_name_length) == 0) ) {
+ && (worker->s->is_name_matchable
+ || ((mask & AP_PROXY_WORKER_IS_PREFIX)
+ && strncmp(url_copy, worker->s->name_ex,
+ worker_name_length) == 0))
+ && (!worker->s->is_name_matchable
+ || ((mask & AP_PROXY_WORKER_IS_MATCH)
+ && ap_proxy_strcmp_ematch(url_copy,
+ worker->s->name_ex) == 0)) ) {
max_worker = worker;
max_match = worker_name_length;
}
-
}
} else {
worker = (proxy_worker *)conf->workers->elts;
for (i = 0; i < conf->workers->nelts; i++, worker++) {
- if ( ((worker_name_length = strlen(worker->s->name)) <= url_length)
+ if ( ((worker_name_length = strlen(worker->s->name_ex)) <= url_length)
&& (worker_name_length >= min_match)
&& (worker_name_length > max_match)
- && (strncmp(url_copy, worker->s->name, worker_name_length) == 0) ) {
+ && (worker->s->is_name_matchable
+ || ((mask & AP_PROXY_WORKER_IS_PREFIX)
+ && strncmp(url_copy, worker->s->name_ex,
+ worker_name_length) == 0))
+ && (!worker->s->is_name_matchable
+ || ((mask & AP_PROXY_WORKER_IS_MATCH)
+ && ap_proxy_strcmp_ematch(url_copy,
+ worker->s->name_ex) == 0)) ) {
max_worker = worker;
max_match = worker_name_length;
}
@@ -1729,6 +1938,14 @@ PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker(apr_pool_t *p,
return max_worker;
}
+PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker(apr_pool_t *p,
+ proxy_balancer *balancer,
+ proxy_server_conf *conf,
+ const char *url)
+{
+ return ap_proxy_get_worker_ex(p, balancer, conf, url, 0);
+}
+
/*
* To create a worker from scratch first we define the
* specifics of the worker; this is all local data.
@@ -1736,46 +1953,98 @@ PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker(apr_pool_t *p,
* shared. This allows for dynamic addition during
* config and runtime.
*/
-PROXY_DECLARE(char *) ap_proxy_define_worker(apr_pool_t *p,
+PROXY_DECLARE(char *) ap_proxy_define_worker_ex(apr_pool_t *p,
proxy_worker **worker,
proxy_balancer *balancer,
proxy_server_conf *conf,
const char *url,
- int do_malloc)
+ unsigned int mask)
{
- int rv;
- apr_uri_t uri, urisock;
+ apr_status_t rv;
proxy_worker_shared *wshared;
- char *ptr, *sockpath = NULL;
+ const char *ptr = NULL, *sockpath = NULL, *pdollars = NULL;
+ apr_port_t port_of_scheme;
+ int address_not_reusable = 0;
+ apr_uri_t uri;
/*
* Look to see if we are using UDS:
* require format: unix:/path/foo/bar.sock|http://ignored/path2/
* This results in talking http to the socket at /path/foo/bar.sock
*/
- ptr = ap_strchr((char *)url, '|');
- if (ptr) {
- *ptr = '\0';
- rv = apr_uri_parse(p, url, &urisock);
- if (rv == APR_SUCCESS && !strcasecmp(urisock.scheme, "unix")) {
- sockpath = ap_runtime_dir_relative(p, urisock.path);;
- url = ptr+1; /* so we get the scheme for the uds */
+ if (!ap_cstr_casecmpn(url, "unix:", 5)
+ && (ptr = ap_strchr_c(url + 5, '|'))) {
+ rv = apr_uri_parse(p, apr_pstrmemdup(p, url, ptr - url), &uri);
+ if (rv == APR_SUCCESS) {
+ sockpath = ap_runtime_dir_relative(p, uri.path);;
+ ptr++; /* so we get the scheme for the uds */
}
else {
- *ptr = '|';
+ ptr = url;
}
}
- rv = apr_uri_parse(p, url, &uri);
+ else {
+ ptr = url;
+ }
+
+ if (mask & AP_PROXY_WORKER_IS_MATCH) {
+ /* apr_uri_parse() will accept the '$' sign anywhere in the URL but
+ * in the :port part, and we don't want scheme://host:port$1$2/path
+ * to fail (e.g. "ProxyPassMatch ^/(a|b)(/.*)? http://host:port$2").
+ * So we trim all the $n from the :port and prepend them in uri.path
+ * afterward for apr_uri_unparse() to restore the original URL below.
+ * If a dollar substitution is found in the hostname[:port] part of
+ * the URL, reusing address and connections in the same worker is not
+ * possible (the current implementation of active connections cache
+ * handles/assumes a single origin server:port per worker only), so
+ * we set address_not_reusable here during parsing to take that into
+ * account in the worker settings below.
+ */
+#define IS_REF(x) (x[0] == '$' && apr_isdigit(x[1]))
+ const char *pos = ap_strstr_c(ptr, "://");
+ if (pos) {
+ pos += 3;
+ while (*pos && *pos != ':' && *pos != '/') {
+ if (*pos == '$') {
+ address_not_reusable = 1;
+ }
+ pos++;
+ }
+ if (*pos == ':') {
+ pos++;
+ while (*pos && !IS_REF(pos) && *pos != '/') {
+ pos++;
+ }
+ if (IS_REF(pos)) {
+ struct iovec vec[2];
+ const char *path = pos + 2;
+ while (*path && *path != '/') {
+ path++;
+ }
+ pdollars = apr_pstrmemdup(p, pos, path - pos);
+ vec[0].iov_base = (void *)ptr;
+ vec[0].iov_len = pos - ptr;
+ vec[1].iov_base = (void *)path;
+ vec[1].iov_len = strlen(path);
+ ptr = apr_pstrcatv(p, vec, 2, NULL);
+ address_not_reusable = 1;
+ }
+ }
+ }
+#undef IS_REF
+ }
+ /* Normalize the url (worker name) */
+ rv = apr_uri_parse(p, ptr, &uri);
if (rv != APR_SUCCESS) {
return apr_pstrcat(p, "Unable to parse URL: ", url, NULL);
}
if (!uri.scheme) {
return apr_pstrcat(p, "URL must be absolute!: ", url, NULL);
}
- /* allow for unix:/path|http: */
if (!uri.hostname) {
if (sockpath) {
+ /* allow for unix:/path|http: */
uri.hostname = "localhost";
}
else {
@@ -1786,6 +2055,16 @@ PROXY_DECLARE(char *) ap_proxy_define_worker(apr_pool_t *p,
ap_str_tolower(uri.hostname);
}
ap_str_tolower(uri.scheme);
+ port_of_scheme = ap_proxy_port_of_scheme(uri.scheme);
+ if (uri.port && uri.port == port_of_scheme) {
+ uri.port = 0;
+ }
+ if (pdollars) {
+ /* Restore/prepend pdollars into the path. */
+ uri.path = apr_pstrcat(p, pdollars, uri.path, NULL);
+ }
+ ptr = apr_uri_unparse(p, &uri, APR_URI_UNP_REVEALPASSWORD);
+
/*
* Workers can be associated w/ balancers or on their
* own; ie: the generic reverse-proxy or a worker
@@ -1809,26 +2088,25 @@ PROXY_DECLARE(char *) ap_proxy_define_worker(apr_pool_t *p,
/* we need to allocate space here */
*worker = apr_palloc(p, sizeof(proxy_worker));
}
-
memset(*worker, 0, sizeof(proxy_worker));
+
/* right here we just want to tuck away the worker info.
* if called during config, we don't have shm setup yet,
* so just note the info for later. */
- if (do_malloc)
+ if (mask & AP_PROXY_WORKER_IS_MALLOCED)
wshared = ap_malloc(sizeof(proxy_worker_shared)); /* will be freed ap_proxy_share_worker */
else
wshared = apr_palloc(p, sizeof(proxy_worker_shared));
-
memset(wshared, 0, sizeof(proxy_worker_shared));
- wshared->port = (uri.port ? uri.port : ap_proxy_port_of_scheme(uri.scheme));
- if (uri.port && uri.port == ap_proxy_port_of_scheme(uri.scheme)) {
- uri.port = 0;
+ if (PROXY_STRNCPY(wshared->name_ex, ptr) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(10366)
+ "Alert! worker name (%s) too long; truncated to: %s", ptr, wshared->name_ex);
}
- ptr = apr_uri_unparse(p, &uri, APR_URI_UNP_REVEALPASSWORD);
if (PROXY_STRNCPY(wshared->name, ptr) != APR_SUCCESS) {
- ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(02808)
- "Alert! worker name (%s) too long; truncated to: %s", ptr, wshared->name);
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(010118)
+ "worker name (%s) too long; truncated for legacy modules that do not use "
+ "proxy_worker_shared->name_ex: %s", ptr, wshared->name);
}
if (PROXY_STRNCPY(wshared->scheme, uri.scheme) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(010117)
@@ -1842,17 +2120,44 @@ PROXY_DECLARE(char *) ap_proxy_define_worker(apr_pool_t *p,
"worker hostname (%s) too long; truncated for legacy modules that do not use "
"proxy_worker_shared->hostname_ex: %s", uri.hostname, wshared->hostname);
}
+ wshared->port = (uri.port) ? uri.port : port_of_scheme;
wshared->flush_packets = flush_off;
wshared->flush_wait = PROXY_FLUSH_WAIT;
- wshared->is_address_reusable = 1;
+ wshared->address_ttl = (address_not_reusable) ? 0 : -1;
+ wshared->is_address_reusable = (address_not_reusable == 0);
+ wshared->disablereuse = (address_not_reusable != 0);
wshared->lbfactor = 100;
wshared->passes = 1;
wshared->fails = 1;
wshared->interval = apr_time_from_sec(HCHECK_WATHCHDOG_DEFAULT_INTERVAL);
wshared->smax = -1;
- wshared->hash.def = ap_proxy_hashfunc(wshared->name, PROXY_HASHFUNC_DEFAULT);
- wshared->hash.fnv = ap_proxy_hashfunc(wshared->name, PROXY_HASHFUNC_FNV);
- wshared->was_malloced = (do_malloc != 0);
+ wshared->hash.def = ap_proxy_hashfunc(wshared->name_ex, PROXY_HASHFUNC_DEFAULT);
+ wshared->hash.fnv = ap_proxy_hashfunc(wshared->name_ex, PROXY_HASHFUNC_FNV);
+ wshared->was_malloced = (mask & AP_PROXY_WORKER_IS_MALLOCED) != 0;
+ if (mask & AP_PROXY_WORKER_IS_MATCH) {
+ wshared->is_name_matchable = 1;
+
+ /* Before AP_PROXY_WORKER_IS_MATCH (< 2.4.47), a regex worker with
+ * dollar substitution was never matched against any actual URL, thus
+ * the requests fell through the generic worker. Now if a ProyPassMatch
+ * matches, a worker (and its parameters) is always used to determine
+ * the properties of the connection with the origin server. So for
+ * instance the same "timeout=" will be enforced for all the requests
+ * matched by the same ProyPassMatch worker, which is an improvement
+ * compared to the global/vhost [Proxy]Timeout applied by the generic
+ * worker. Likewise, address and connection reuse is the default for
+ * a ProyPassMatch worker with no dollar substitution, just like a
+ * "normal" worker. However to avoid DNS and connection reuse compat
+ * issues, connection reuse is disabled by default if there is any
+ * substitution in the uri-path (an explicit enablereuse=on can still
+ * opt-in), and reuse is even disabled definitively for substitutions
+ * happening in the hostname[:port] (is_address_reusable was unset
+ * above so it will prevent enablereuse=on to apply anyway).
+ */
+ if (ap_strchr_c(wshared->name, '$')) {
+ wshared->disablereuse = 1;
+ }
+ }
if (sockpath) {
if (PROXY_STRNCPY(wshared->uds_path, sockpath) != APR_SUCCESS) {
return apr_psprintf(p, "worker uds path (%s) too long", sockpath);
@@ -1875,6 +2180,33 @@ PROXY_DECLARE(char *) ap_proxy_define_worker(apr_pool_t *p,
return NULL;
}
+PROXY_DECLARE(char *) ap_proxy_define_worker(apr_pool_t *p,
+ proxy_worker **worker,
+ proxy_balancer *balancer,
+ proxy_server_conf *conf,
+ const char *url,
+ int do_malloc)
+{
+ return ap_proxy_define_worker_ex(p, worker, balancer, conf, url,
+ AP_PROXY_WORKER_IS_PREFIX |
+ (do_malloc ? AP_PROXY_WORKER_IS_MALLOCED
+ : 0));
+}
+
+/* DEPRECATED */
+PROXY_DECLARE(char *) ap_proxy_define_match_worker(apr_pool_t *p,
+ proxy_worker **worker,
+ proxy_balancer *balancer,
+ proxy_server_conf *conf,
+ const char *url,
+ int do_malloc)
+{
+ return ap_proxy_define_worker_ex(p, worker, balancer, conf, url,
+ AP_PROXY_WORKER_IS_MATCH |
+ (do_malloc ? AP_PROXY_WORKER_IS_MALLOCED
+ : 0));
+}
+
/*
* Create an already defined worker and free up memory
*/
@@ -1899,6 +2231,7 @@ PROXY_DECLARE(apr_status_t) ap_proxy_share_worker(proxy_worker *worker, proxy_wo
if (APLOGdebug(ap_server_conf)) {
apr_pool_t *pool;
apr_pool_create(&pool, ap_server_conf->process->pool);
+ apr_pool_tag(pool, "proxy_worker_name");
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02338)
"%s shm[%d] (0x%pp) for worker: %s", action, i, (void *)shm,
ap_proxy_worker_name(pool, worker));
@@ -1929,12 +2262,23 @@ PROXY_DECLARE(apr_status_t) ap_proxy_initialize_worker(proxy_worker *worker, ser
if (!worker->s->retry_set) {
worker->s->retry = apr_time_from_sec(PROXY_WORKER_DEFAULT_RETRY);
}
- /* By default address is reusable unless DisableReuse is set */
- if (worker->s->disablereuse) {
+ /* Consistently set address and connection reusabilty: when reuse
+ * is disabled by configuration, or when the address is known already
+ * to not be reusable for this worker (in any case, thus ignore/force
+ * DisableReuse).
+ */
+ if (!worker->s->address_ttl || (!worker->s->address_ttl_set
+ && worker->s->disablereuse)) {
worker->s->is_address_reusable = 0;
}
- else {
- worker->s->is_address_reusable = 1;
+ if (!worker->s->is_address_reusable && !worker->s->disablereuse) {
+ /* Explicit enablereuse=on can't work in this case, warn user. */
+ if (worker->s->disablereuse_set) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10400)
+ "enablereuse/disablereuse ignored for worker %s",
+ ap_proxy_worker_name(p, worker));
+ }
+ worker->s->disablereuse = 1;
}
/*
@@ -1979,67 +2323,71 @@ PROXY_DECLARE(apr_status_t) ap_proxy_initialize_worker(proxy_worker *worker, ser
ap_proxy_worker_name(p, worker));
}
else {
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00927)
- "initializing worker %s local",
- ap_proxy_worker_name(p, worker));
apr_global_mutex_lock(proxy_mutex);
- /* Now init local worker data */
- if (worker->tmutex == NULL) {
- rv = apr_thread_mutex_create(&(worker->tmutex), APR_THREAD_MUTEX_DEFAULT, p);
- if (rv != APR_SUCCESS) {
- ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00928)
- "can not create worker thread mutex");
+ /* Check again after we got the lock if we are still uninitialized */
+ if (!(AP_VOLATILIZE_T(unsigned int, worker->local_status) & PROXY_WORKER_INITIALIZED)) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00927)
+ "initializing worker %s local",
+ ap_proxy_worker_name(p, worker));
+ /* Now init local worker data */
+#if APR_HAS_THREADS
+ if (worker->tmutex == NULL) {
+ rv = apr_thread_mutex_create(&(worker->tmutex), APR_THREAD_MUTEX_DEFAULT, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00928)
+ "can not create worker thread mutex");
+ apr_global_mutex_unlock(proxy_mutex);
+ return rv;
+ }
+ }
+#endif
+ if (worker->cp == NULL)
+ init_conn_pool(p, worker, s);
+ if (worker->cp == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00929)
+ "can not create connection pool");
apr_global_mutex_unlock(proxy_mutex);
- return rv;
+ return APR_EGENERAL;
}
- }
- if (worker->cp == NULL)
- init_conn_pool(p, worker);
- if (worker->cp == NULL) {
- ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00929)
- "can not create connection pool");
- apr_global_mutex_unlock(proxy_mutex);
- return APR_EGENERAL;
- }
- if (worker->s->hmax) {
- rv = apr_reslist_create(&(worker->cp->res),
- worker->s->min, worker->s->smax,
- worker->s->hmax, worker->s->ttl,
- connection_constructor, connection_destructor,
- worker, worker->cp->pool);
-
- apr_pool_cleanup_register(worker->cp->pool, (void *)worker,
- conn_pool_cleanup,
- apr_pool_cleanup_null);
-
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00930)
- "initialized pool in child %" APR_PID_T_FMT " for (%s) min=%d max=%d smax=%d",
- getpid(), worker->s->hostname_ex, worker->s->min,
- worker->s->hmax, worker->s->smax);
+ if (worker->s->hmax) {
+ rv = apr_reslist_create(&(worker->cp->res),
+ worker->s->min, worker->s->smax,
+ worker->s->hmax, worker->s->ttl,
+ connection_constructor, connection_destructor,
+ worker, worker->cp->pool);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00930)
+ "initialized pool in child %" APR_PID_T_FMT " for (%s:%d) min=%d max=%d smax=%d",
+ getpid(), worker->s->hostname_ex, (int)worker->s->port,
+ worker->s->min, worker->s->hmax, worker->s->smax);
+
+ /* Set the acquire timeout */
+ if (rv == APR_SUCCESS && worker->s->acquire_set) {
+ apr_reslist_timeout_set(worker->cp->res, worker->s->acquire);
+ }
- /* Set the acquire timeout */
- if (rv == APR_SUCCESS && worker->s->acquire_set) {
- apr_reslist_timeout_set(worker->cp->res, worker->s->acquire);
}
+ else {
+ void *conn;
- }
- else {
- void *conn;
-
- rv = connection_constructor(&conn, worker, worker->cp->pool);
- worker->cp->conn = conn;
+ rv = connection_constructor(&conn, worker, worker->cp->pool);
+ worker->cp->conn = conn;
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00931)
- "initialized single connection worker in child %" APR_PID_T_FMT " for (%s)",
- getpid(), worker->s->hostname_ex);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(00931)
+ "initialized single connection worker in child %" APR_PID_T_FMT " for (%s:%d)",
+ getpid(), worker->s->hostname_ex,
+ (int)worker->s->port);
+ }
+ if (rv == APR_SUCCESS) {
+ worker->local_status |= (PROXY_WORKER_INITIALIZED);
+ }
}
apr_global_mutex_unlock(proxy_mutex);
}
if (rv == APR_SUCCESS) {
worker->s->status |= (PROXY_WORKER_INITIALIZED);
- worker->local_status |= (PROXY_WORKER_INITIALIZED);
}
return rv;
}
@@ -2050,8 +2398,9 @@ static int ap_proxy_retry_worker(const char *proxy_function, proxy_worker *worke
if (worker->s->status & PROXY_WORKER_IN_ERROR) {
if (PROXY_WORKER_IS(worker, PROXY_WORKER_STOPPED)) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(3305)
- "%s: Won't retry worker (%s): stopped",
- proxy_function, worker->s->hostname_ex);
+ "%s: Won't retry worker (%s:%d): stopped",
+ proxy_function, worker->s->hostname_ex,
+ (int)worker->s->port);
return DECLINED;
}
if ((worker->s->status & PROXY_WORKER_IGNORE_ERRORS)
@@ -2059,14 +2408,16 @@ static int ap_proxy_retry_worker(const char *proxy_function, proxy_worker *worke
++worker->s->retries;
worker->s->status &= ~PROXY_WORKER_IN_ERROR;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00932)
- "%s: worker for (%s) has been marked for retry",
- proxy_function, worker->s->hostname_ex);
+ "%s: worker for (%s:%d) has been marked for retry",
+ proxy_function, worker->s->hostname_ex,
+ (int)worker->s->port);
return OK;
}
else {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00933)
- "%s: too soon to retry worker for (%s)",
- proxy_function, worker->s->hostname_ex);
+ "%s: too soon to retry worker for (%s:%d)",
+ proxy_function, worker->s->hostname_ex,
+ (int)worker->s->port);
return DECLINED;
}
}
@@ -2080,33 +2431,43 @@ static int ap_proxy_retry_worker(const char *proxy_function, proxy_worker *worke
* were passed a UDS url (eg: from mod_proxy) and adjust uds_path
* as required.
*/
-static void fix_uds_filename(request_rec *r, char **url)
+static int fix_uds_filename(request_rec *r, char **url)
{
- char *ptr, *ptr2;
- if (!r || !r->filename) return;
+ char *uds_url = r->filename + 6, *origin_url;
if (!strncmp(r->filename, "proxy:", 6) &&
- (ptr2 = ap_strcasestr(r->filename, "unix:")) &&
- (ptr = ap_strchr(ptr2, '|'))) {
+ !ap_cstr_casecmpn(uds_url, "unix:", 5) &&
+ (origin_url = ap_strchr(uds_url + 5, '|'))) {
+ char *uds_path = NULL;
+ apr_size_t url_len;
apr_uri_t urisock;
apr_status_t rv;
- *ptr = '\0';
- rv = apr_uri_parse(r->pool, ptr2, &urisock);
- if (rv == APR_SUCCESS) {
- char *rurl = ptr+1;
- char *sockpath = ap_runtime_dir_relative(r->pool, urisock.path);
- apr_table_setn(r->notes, "uds_path", sockpath);
- *url = apr_pstrdup(r->pool, rurl); /* so we get the scheme for the uds */
- /* r->filename starts w/ "proxy:", so add after that */
- memmove(r->filename+6, rurl, strlen(rurl)+1);
- ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
- "*: rewrite of url due to UDS(%s): %s (%s)",
- sockpath, *url, r->filename);
+
+ *origin_url = '\0';
+ rv = apr_uri_parse(r->pool, uds_url, &urisock);
+ *origin_url++ = '|';
+
+ if (rv == APR_SUCCESS && urisock.path && (!urisock.hostname
+ || !urisock.hostname[0])) {
+ uds_path = ap_runtime_dir_relative(r->pool, urisock.path);
}
- else {
- *ptr = '|';
+ if (!uds_path) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10292)
+ "Invalid proxy UDS filename (%s)", r->filename);
+ return 0;
}
+ apr_table_setn(r->notes, "uds_path", uds_path);
+
+ /* Remove the UDS path from *url and r->filename */
+ url_len = strlen(origin_url);
+ *url = apr_pstrmemdup(r->pool, origin_url, url_len);
+ memcpy(uds_url, *url, url_len + 1);
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "*: rewrite of url due to UDS(%s): %s (%s)",
+ uds_path, *url, r->filename);
}
+ return 1;
}
PROXY_DECLARE(int) ap_proxy_pre_request(proxy_worker **worker,
@@ -2118,20 +2479,22 @@ PROXY_DECLARE(int) ap_proxy_pre_request(proxy_worker **worker,
access_status = proxy_run_pre_request(worker, balancer, r, conf, url);
if (access_status == DECLINED && *balancer == NULL) {
- *worker = ap_proxy_get_worker(r->pool, NULL, conf, *url);
+ const int forward = (r->proxyreq == PROXYREQ_PROXY);
+ *worker = ap_proxy_get_worker_ex(r->pool, NULL, conf, *url,
+ forward ? AP_PROXY_WORKER_NO_UDS : 0);
if (*worker) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
"%s: found worker %s for %s",
- (*worker)->s->scheme, (*worker)->s->name, *url);
- *balancer = NULL;
- fix_uds_filename(r, url);
+ (*worker)->s->scheme, (*worker)->s->name_ex, *url);
+ if (!forward && !fix_uds_filename(r, url)) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
access_status = OK;
}
- else if (r->proxyreq == PROXYREQ_PROXY) {
+ else if (forward) {
if (conf->forward) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
"*: found forward proxy worker for %s", *url);
- *balancer = NULL;
*worker = conf->forward;
access_status = OK;
/*
@@ -2145,8 +2508,8 @@ PROXY_DECLARE(int) ap_proxy_pre_request(proxy_worker **worker,
else if (r->proxyreq == PROXYREQ_REVERSE) {
if (conf->reverse) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
- "*: using default reverse proxy worker for %s (no keepalive)", *url);
- *balancer = NULL;
+ "*: using default reverse proxy worker for %s "
+ "(no keepalive)", *url);
*worker = conf->reverse;
access_status = OK;
/*
@@ -2155,7 +2518,9 @@ PROXY_DECLARE(int) ap_proxy_pre_request(proxy_worker **worker,
* regarding the Connection header in the request.
*/
apr_table_setn(r->subprocess_env, "proxy-nokeepalive", "1");
- fix_uds_filename(r, url);
+ if (!fix_uds_filename(r, url)) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
}
}
}
@@ -2287,8 +2652,9 @@ PROXY_DECLARE(int) ap_proxy_acquire_connection(const char *proxy_function,
if (!PROXY_WORKER_IS_USABLE(worker)) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00940)
- "%s: disabled connection for (%s)",
- proxy_function, worker->s->hostname_ex);
+ "%s: disabled connection for (%s:%d)",
+ proxy_function, worker->s->hostname_ex,
+ (int)worker->s->port);
return HTTP_SERVICE_UNAVAILABLE;
}
}
@@ -2299,24 +2665,26 @@ PROXY_DECLARE(int) ap_proxy_acquire_connection(const char *proxy_function,
else {
/* create the new connection if the previous was destroyed */
if (!worker->cp->conn) {
- connection_constructor((void **)conn, worker, worker->cp->pool);
+ rv = connection_constructor((void **)conn, worker, worker->cp->pool);
}
else {
*conn = worker->cp->conn;
worker->cp->conn = NULL;
+ rv = APR_SUCCESS;
}
- rv = APR_SUCCESS;
}
if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00941)
- "%s: failed to acquire connection for (%s)",
- proxy_function, worker->s->hostname_ex);
+ "%s: failed to acquire connection for (%s:%d)",
+ proxy_function, worker->s->hostname_ex,
+ (int)worker->s->port);
return HTTP_SERVICE_UNAVAILABLE;
}
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00942)
- "%s: has acquired connection for (%s)",
- proxy_function, worker->s->hostname_ex);
+ "%s: has acquired connection for (%s:%d)",
+ proxy_function, worker->s->hostname_ex,
+ (int)worker->s->port);
(*conn)->worker = worker;
(*conn)->close = 0;
@@ -2330,13 +2698,362 @@ PROXY_DECLARE(int) ap_proxy_release_connection(const char *proxy_function,
server_rec *s)
{
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00943)
- "%s: has released connection for (%s)",
- proxy_function, conn->worker->s->hostname_ex);
+ "%s: has released connection for (%s:%d)",
+ proxy_function, conn->worker->s->hostname_ex,
+ (int)conn->worker->s->port);
connection_cleanup(conn);
return OK;
}
+static APR_INLINE void proxy_address_inc(proxy_address *address)
+{
+ apr_uint32_t old = apr_atomic_inc32(&address->refcount);
+ ap_assert(old > 0 && old < APR_UINT32_MAX);
+}
+
+static APR_INLINE void proxy_address_dec(proxy_address *address)
+{
+ /* Use _add32(, -1) since _dec32()'s returned value does not help */
+ apr_uint32_t old = apr_atomic_add32(&address->refcount, -1);
+ ap_assert(old > 0);
+ if (old == 1) {
+ apr_pool_destroy(address->addr->pool);
+ }
+}
+
+static apr_status_t proxy_address_cleanup(void *address)
+{
+ proxy_address_dec(address);
+ return APR_SUCCESS;
+}
+
+static APR_INLINE proxy_address *worker_address_get(proxy_worker *worker)
+{
+ /* No _readptr() so let's _casptr(, NULL, NULL) instead */
+ return apr_atomic_casptr((void *)&worker->address, NULL, NULL);
+}
+
+/* XXX: Call when PROXY_THREAD_LOCK()ed only! */
+static APR_INLINE void worker_address_set(proxy_worker *worker,
+ proxy_address *to)
+{
+ proxy_address *old = apr_atomic_xchgptr((void *)&worker->address, to);
+ if (old && old != to) {
+ proxy_address_dec(old);
+ }
+}
+
+static apr_status_t worker_address_resolve(proxy_worker *worker,
+ apr_sockaddr_t **paddr,
+ const char *hostname,
+ apr_port_t hostport,
+ const char *proxy_function,
+ request_rec *r, server_rec *s)
+{
+ apr_status_t rv;
+ apr_pool_t *pool = NULL;
+
+ apr_pool_create(&pool, worker->cp->dns_pool);
+ rv = apr_sockaddr_info_get(paddr, hostname, APR_UNSPEC,
+ hostport, 0, pool);
+ if (rv != APR_SUCCESS) {
+ if (r && !s) {
+ proxyerror_core(r, HTTP_INTERNAL_SERVER_ERROR,
+ apr_pstrcat(pool,
+ "DNS lookup failure for: ",
+ hostname, NULL),
+ rv);
+ }
+ else if (r) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(10477)
+ "%s: resolving worker %s address",
+ proxy_function, hostname);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10478)
+ "%s: resolving worker %s address",
+ proxy_function, hostname);
+ }
+ apr_pool_destroy(pool);
+ return rv;
+ }
+
+ if (r ? APLOGrdebug(r) : APLOGdebug(s)) {
+ char *addrs = NULL;
+ apr_sockaddr_t *addr = *paddr;
+ for (; addr; addr = addr->next) {
+ addrs = apr_psprintf(pool, "%s%s%pI",
+ addrs ? ", " : "",
+ addrs ? addrs : "",
+ addr);
+ }
+ if (r) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10479)
+ "%s: %s resolved to %s",
+ proxy_function, hostname, addrs);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10480)
+ "%s: %s resolved to %s",
+ proxy_function, hostname, addrs);
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+static int proxy_addrs_equal(const apr_sockaddr_t *addr1,
+ const apr_sockaddr_t *addr2)
+{
+ const apr_sockaddr_t *base2 = addr2, *pos2;
+ while (addr1 && addr2) {
+ for (pos2 = base2; pos2; pos2 = pos2->next) {
+ if (apr_sockaddr_equal(pos2, addr1)) {
+ break;
+ }
+ }
+ if (!pos2) {
+ return 0;
+ }
+ addr1 = addr1->next;
+ addr2 = addr2->next;
+ }
+ if (addr1 || addr2) {
+ return 0;
+ }
+ return 1;
+}
+
+PROXY_DECLARE(apr_status_t) ap_proxy_determine_address(const char *proxy_function,
+ proxy_conn_rec *conn,
+ const char *hostname,
+ apr_port_t hostport,
+ unsigned int flags,
+ request_rec *r,
+ server_rec *s)
+{
+ proxy_worker *worker = conn->worker;
+ apr_status_t rv;
+
+ /*
+ * Worker can have the single constant backend adress.
+ * The single DNS lookup is used once per worker.
+ * If dynamic change is needed then set the addr to NULL
+ * inside dynamic config to force the lookup.
+ * The worker's addressTTL parameter may also be configured
+ * to perform the DNS lookups only when the TTL expires,
+ * or each time if that TTL is zero.
+ */
+ if (!worker->s->is_address_reusable) {
+ conn->hostname = apr_pstrdup(conn->pool, hostname);
+ conn->port = hostport;
+
+ rv = apr_sockaddr_info_get(&conn->addr, hostname, APR_UNSPEC,
+ hostport, 0, conn->pool);
+ if (rv != APR_SUCCESS) {
+ if (r && !s) {
+ proxyerror_core(r, HTTP_INTERNAL_SERVER_ERROR,
+ apr_pstrcat(r->pool, "DNS lookup failure for: ",
+ hostname, NULL), rv);
+ }
+ else if (r) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(10475)
+ "%s: resolving backend %s address",
+ proxy_function, hostname);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10476)
+ "%s: resolving backend %s address",
+ proxy_function, hostname);
+ }
+ return rv;
+ }
+ }
+ else {
+ apr_sockaddr_t *addr = NULL;
+ proxy_address *address = NULL;
+ apr_int32_t ttl = worker->s->address_ttl;
+ apr_uint32_t now = 0;
+
+ if (flags & PROXY_DETERMINE_ADDRESS_CHECK) {
+ /* The caller wants to check if the address changed, return
+ * APR_EEXIST if not, otherwise fall through to update the
+ * worker's for everyone to switch.
+ */
+ if (!conn->addr) {
+ /* Need something to compare with */
+ return APR_EINVAL;
+ }
+ rv = worker_address_resolve(worker, &addr,
+ hostname, hostport,
+ proxy_function, r, s);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ if (proxy_addrs_equal(conn->addr, addr)) {
+ apr_pool_destroy(addr->pool);
+ return APR_EEXIST;
+ }
+ }
+
+ AP_DEBUG_ASSERT(ttl != 0);
+ if (ttl > 0) {
+ /* TODO: use a monotonic clock here */
+ now = apr_time_sec(apr_time_now() - *proxy_start_time);
+ }
+
+ /* Addresses are refcounted, destroyed when their refcount reaches 0.
+ *
+ * One ref is taken by worker->address as the worker's current/latest
+ * address, it's dropped when that address expires/changes (see below).
+ * The other refs are taken by the connections when using/switching to
+ * the current worker address (also below), they are dropped when the
+ * conns are destroyed (by the reslist though it should never happen
+ * if hmax is greater than the number of threads) OR for an expired
+ * conn->address when it's replaced by the new worker->address below.
+ *
+ * Dereferencing worker->address requires holding the worker mutex or
+ * some concurrent connection processing might change/destroy it at any
+ * time. So only conn->address is safe to dereference anywhere (unless
+ * NULL..) since it has at least the lifetime of the connection.
+ */
+ if (!addr) {
+ address = worker_address_get(worker);
+ }
+ if (!address
+ || conn->address != address
+ || apr_atomic_read32(&address->expiry) <= now) {
+ PROXY_THREAD_LOCK(worker);
+
+ /* Re-check while locked, might be a new address already */
+ if (!addr) {
+ address = worker_address_get(worker);
+ }
+ if (!address || apr_atomic_read32(&address->expiry) <= now) {
+ if (!addr) {
+ rv = worker_address_resolve(worker, &addr,
+ hostname, hostport,
+ proxy_function, r, s);
+ if (rv != APR_SUCCESS) {
+ PROXY_THREAD_UNLOCK(worker);
+ return rv;
+ }
+
+ /* Recompute "now" should the DNS be slow
+ * TODO: use a monotonic clock here
+ */
+ now = apr_time_sec(apr_time_now() - *proxy_start_time);
+ }
+
+ address = apr_pcalloc(addr->pool, sizeof(*address));
+ address->hostname = apr_pstrdup(addr->pool, hostname);
+ address->hostport = hostport;
+ address->addr = addr;
+
+ if (ttl > 0) {
+ /* We keep each worker's expiry date shared accross all the
+ * children so that they update their address at the same
+ * time, regardless of whether a specific child forced an
+ * address to expire at some point (for connect() issues).
+ */
+ address->expiry = apr_atomic_read32(&worker->s->address_expiry);
+ if (address->expiry <= now) {
+ apr_uint32_t new_expiry = address->expiry + ttl;
+ while (new_expiry <= now) {
+ new_expiry += ttl;
+ }
+ new_expiry = apr_atomic_cas32(&worker->s->address_expiry,
+ new_expiry, address->expiry);
+ /* race lost? well the expiry should grow anyway.. */
+ AP_DEBUG_ASSERT(new_expiry > now);
+ address->expiry = new_expiry;
+ }
+ }
+ else {
+ /* Never expires */
+ address->expiry = APR_UINT32_MAX;
+ }
+
+ /* One ref is for worker->address in any case */
+ if (worker->address || worker->cp->addr) {
+ apr_atomic_set32(&address->refcount, 1);
+ }
+ else {
+ /* Set worker->cp->addr once for compat with third-party
+ * modules. This addr never changed before and can't change
+ * underneath users now because of some TTL configuration.
+ * So we take one more ref for worker->cp->addr to remain
+ * allocated forever (though it might not be up to date..).
+ * Modules should use conn->addr instead of worker->cp-addr
+ * to get the actual address used by each conn, determined
+ * at connect() time.
+ */
+ apr_atomic_set32(&address->refcount, 2);
+ worker->cp->addr = address->addr;
+ }
+
+ /* Publish the changes. The old worker address (if any) is no
+ * longer used by this worker, it will be destroyed now if the
+ * worker is the last user (refcount == 1) or by the last conn
+ * using it (refcount > 1).
+ */
+ worker_address_set(worker, address);
+ }
+
+ /* Take the ref for conn->address (before dropping the mutex so to
+ * let no chance for this address be killed before it's used!)
+ */
+ proxy_address_inc(address);
+
+ PROXY_THREAD_UNLOCK(worker);
+
+ /* Kill any socket using the old address */
+ if (conn->sock) {
+ if (r ? APLOGrdebug(r) : APLOGdebug(s)) {
+ /* XXX: this requires the old conn->addr[ess] to still
+ * be alive since it's not copied by apr_socket_connect()
+ * in ap_proxy_connect_backend().
+ */
+ apr_sockaddr_t *local_addr = NULL;
+ apr_sockaddr_t *remote_addr = NULL;
+ apr_socket_addr_get(&local_addr, APR_LOCAL, conn->sock);
+ apr_socket_addr_get(&remote_addr, APR_REMOTE, conn->sock);
+ if (r) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10481)
+ "%s: closing connection to %s (%pI<>%pI) on "
+ "address change", proxy_function, hostname,
+ local_addr, remote_addr);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10482)
+ "%s: closing connection to %s (%pI<>%pI) on "
+ "address change", proxy_function, hostname,
+ local_addr, remote_addr);
+ }
+ }
+ socket_cleanup(conn);
+ }
+
+ /* Kill the old address (if any) and use the new one */
+ if (conn->address) {
+ apr_pool_cleanup_run(conn->pool, conn->address,
+ proxy_address_cleanup);
+ }
+ apr_pool_cleanup_register(conn->pool, address,
+ proxy_address_cleanup,
+ apr_pool_cleanup_null);
+ address_cleanup(conn);
+ conn->address = address;
+ conn->hostname = address->hostname;
+ conn->port = address->hostport;
+ conn->addr = address->addr;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
PROXY_DECLARE(int)
ap_proxy_determine_connection(apr_pool_t *p, request_rec *r,
proxy_server_conf *conf,
@@ -2350,8 +3067,6 @@ ap_proxy_determine_connection(apr_pool_t *p, request_rec *r,
int server_portstr_size)
{
int server_port;
- apr_status_t err = APR_SUCCESS;
- apr_status_t uerr = APR_SUCCESS;
const char *uds_path;
/*
@@ -2371,6 +3086,12 @@ ap_proxy_determine_connection(apr_pool_t *p, request_rec *r,
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00944)
"connecting %s to %s:%d", *url, uri->hostname, uri->port);
+ /* Close a possible existing socket if we are told to do so */
+ if (conn->close) {
+ socket_cleanup(conn);
+ conn->close = 0;
+ }
+
/*
* allocate these out of the specified connection pool
* The scheme handler decides if this is permanent or
@@ -2397,129 +3118,122 @@ ap_proxy_determine_connection(apr_pool_t *p, request_rec *r,
* to check host and port on the conn and be careful about
* spilling the cached addr from the worker.
*/
- uds_path = (*worker->s->uds_path ? worker->s->uds_path : apr_table_get(r->notes, "uds_path"));
+ uds_path = (*worker->s->uds_path
+ ? worker->s->uds_path
+ : apr_table_get(r->notes, "uds_path"));
if (uds_path) {
- if (conn->uds_path == NULL) {
- /* use (*conn)->pool instead of worker->cp->pool to match lifetime */
- conn->uds_path = apr_pstrdup(conn->pool, uds_path);
- }
- if (conn->uds_path) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02545)
- "%s: has determined UDS as %s",
- uri->scheme, conn->uds_path);
- }
- else {
- /* should never happen */
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02546)
- "%s: cannot determine UDS (%s)",
- uri->scheme, uds_path);
-
- }
- /*
- * In UDS cases, some structs are NULL. Protect from de-refs
- * and provide info for logging at the same time.
- */
- if (!conn->addr) {
- apr_sockaddr_t *sa;
- apr_sockaddr_info_get(&sa, NULL, APR_UNSPEC, 0, 0, conn->pool);
- conn->addr = sa;
+ if (!conn->uds_path || strcmp(conn->uds_path, uds_path) != 0) {
+ apr_pool_t *pool = conn->pool;
+ if (conn->uds_path) {
+ address_cleanup(conn);
+ if (!conn->uds_pool) {
+ apr_pool_create(&conn->uds_pool, worker->cp->dns_pool);
+ }
+ pool = conn->uds_pool;
+ }
+ /*
+ * In UDS cases, some structs are NULL. Protect from de-refs
+ * and provide info for logging at the same time.
+ */
+#if APR_HAVE_SOCKADDR_UN
+ apr_sockaddr_info_get(&conn->addr, uds_path, APR_UNIX, 0, 0, pool);
+ if (conn->addr && conn->addr->hostname) {
+ conn->uds_path = conn->addr->hostname;
+ }
+ else {
+ conn->uds_path = apr_pstrdup(pool, uds_path);
+ }
+#else
+ apr_sockaddr_info_get(&conn->addr, NULL, APR_UNSPEC, 0, 0, pool);
+ conn->uds_path = apr_pstrdup(pool, uds_path);
+#endif
+ conn->hostname = apr_pstrdup(pool, uri->hostname);
+ conn->port = uri->port;
}
- conn->hostname = "httpd-UDS";
- conn->port = 0;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02545)
+ "%s: has determined UDS as %s (for %s:%hu)",
+ uri->scheme, conn->uds_path, conn->hostname, conn->port);
}
else {
- int will_reuse = worker->s->is_address_reusable && !worker->s->disablereuse;
- if (!conn->hostname || !will_reuse) {
- if (proxyname) {
- conn->hostname = apr_pstrdup(conn->pool, proxyname);
- conn->port = proxyport;
- /*
- * If we have a forward proxy and the protocol is HTTPS,
- * then we need to prepend a HTTP CONNECT request before
- * sending our actual HTTPS requests.
- * Save our real backend data for using it later during HTTP CONNECT.
+ const char *hostname = uri->hostname;
+ apr_port_t hostport = uri->port;
+
+ /* Not a remote CONNECT until further notice */
+ conn->forward = NULL;
+
+ if (proxyname) {
+ hostname = proxyname;
+ hostport = proxyport;
+
+ /*
+ * If we have a remote proxy and the protocol is HTTPS,
+ * then we need to prepend a HTTP CONNECT request before
+ * sending our actual HTTPS requests.
+ */
+ if (conn->is_ssl) {
+ forward_info *forward;
+ const char *proxy_auth;
+
+ /* Do we want to pass Proxy-Authorization along?
+ * If we haven't used it, then YES
+ * If we have used it then MAYBE: RFC2616 says we MAY propagate it.
+ * So let's make it configurable by env.
+ * The logic here is the same used in mod_proxy_http.
*/
- if (conn->is_ssl) {
- const char *proxy_auth;
+ proxy_auth = apr_table_get(r->notes, "proxy-basic-creds");
+ if (proxy_auth == NULL
+ && (r->user == NULL /* we haven't yet authenticated */
+ || apr_table_get(r->subprocess_env, "Proxy-Chain-Auth"))) {
+ proxy_auth = apr_table_get(r->headers_in, "Proxy-Authorization");
+ }
+ if (proxy_auth != NULL && proxy_auth[0] == '\0') {
+ proxy_auth = NULL;
+ }
- forward_info *forward = apr_pcalloc(conn->pool, sizeof(forward_info));
+ /* Reset forward info if they changed */
+ if (!(forward = conn->forward)
+ || forward->target_port != uri->port
+ || ap_cstr_casecmp(forward->target_host, uri->hostname) != 0
+ || (forward->proxy_auth != NULL) != (proxy_auth != NULL)
+ || (forward->proxy_auth != NULL && proxy_auth != NULL &&
+ strcmp(forward->proxy_auth, proxy_auth) != 0)) {
+ apr_pool_t *fwd_pool = conn->pool;
+ if (worker->s->is_address_reusable) {
+ if (conn->fwd_pool) {
+ apr_pool_clear(conn->fwd_pool);
+ }
+ else {
+ apr_pool_create(&conn->fwd_pool, conn->pool);
+ }
+ fwd_pool = conn->fwd_pool;
+ }
+ forward = apr_pcalloc(fwd_pool, sizeof(forward_info));
conn->forward = forward;
+
+ /*
+ * Save our real backend data for using it later during HTTP CONNECT.
+ */
forward->use_http_connect = 1;
- forward->target_host = apr_pstrdup(conn->pool, uri->hostname);
+ forward->target_host = apr_pstrdup(fwd_pool, uri->hostname);
forward->target_port = uri->port;
- /* Do we want to pass Proxy-Authorization along?
- * If we haven't used it, then YES
- * If we have used it then MAYBE: RFC2616 says we MAY propagate it.
- * So let's make it configurable by env.
- * The logic here is the same used in mod_proxy_http.
- */
- proxy_auth = apr_table_get(r->headers_in, "Proxy-Authorization");
- if (proxy_auth != NULL &&
- proxy_auth[0] != '\0' &&
- r->user == NULL && /* we haven't yet authenticated */
- apr_table_get(r->subprocess_env, "Proxy-Chain-Auth")) {
- forward->proxy_auth = apr_pstrdup(conn->pool, proxy_auth);
+ if (proxy_auth) {
+ forward->proxy_auth = apr_pstrdup(fwd_pool, proxy_auth);
}
}
}
- else {
- conn->hostname = apr_pstrdup(conn->pool, uri->hostname);
- conn->port = uri->port;
- }
- if (!will_reuse) {
- /*
- * Only do a lookup if we should not reuse the backend address.
- * Otherwise we will look it up once for the worker.
- */
- err = apr_sockaddr_info_get(&(conn->addr),
- conn->hostname, APR_UNSPEC,
- conn->port, 0,
- conn->pool);
- }
- socket_cleanup(conn);
- conn->close = 0;
}
- if (will_reuse) {
- /*
- * Looking up the backend address for the worker only makes sense if
- * we can reuse the address.
- */
- if (!worker->cp->addr) {
- if ((err = PROXY_THREAD_LOCK(worker)) != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, err, r, APLOGNO(00945) "lock");
- return HTTP_INTERNAL_SERVER_ERROR;
- }
- /*
- * Worker can have the single constant backend address.
- * The single DNS lookup is used once per worker.
- * If dynamic change is needed then set the addr to NULL
- * inside dynamic config to force the lookup.
- */
- err = apr_sockaddr_info_get(&(worker->cp->addr),
- conn->hostname, APR_UNSPEC,
- conn->port, 0,
- worker->cp->pool);
- conn->addr = worker->cp->addr;
- if ((uerr = PROXY_THREAD_UNLOCK(worker)) != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, uerr, r, APLOGNO(00946) "unlock");
- }
- }
- else {
- conn->addr = worker->cp->addr;
- }
+ if (conn->hostname
+ && (conn->port != hostport
+ || ap_cstr_casecmp(conn->hostname, hostname) != 0)) {
+ address_cleanup(conn);
}
- }
- /* Close a possible existing socket if we are told to do so */
- if (conn->close) {
- socket_cleanup(conn);
- conn->close = 0;
- }
- if (err != APR_SUCCESS) {
- return ap_proxyerror(r, HTTP_BAD_GATEWAY,
- apr_pstrcat(p, "DNS lookup failure for: ",
- conn->hostname, NULL));
+ /* Resolve the connection address with the determined hostname/port */
+ if (ap_proxy_determine_address(uri->scheme, conn, hostname, hostport,
+ 0, r, NULL)) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
}
/* Get the server port for the Via headers */
@@ -2578,7 +3292,8 @@ ap_proxy_determine_connection(apr_pool_t *p, request_rec *r,
}
}
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00947)
- "connected %s to %s:%d", *url, conn->hostname, conn->port);
+ "connecting %s to %pI (%s:%hu)", *url,
+ conn->addr, conn->hostname, conn->port);
return OK;
}
@@ -2679,7 +3394,8 @@ static apr_status_t send_http_connect(proxy_conn_rec *backend,
nbytes = apr_snprintf(buffer, sizeof(buffer),
"CONNECT %s:%d HTTP/1.0" CRLF,
forward->target_host, forward->target_port);
- /* Add proxy authorization from the initial request if necessary */
+ /* Add proxy authorization from the configuration, or initial
+ * request if necessary */
if (forward->proxy_auth != NULL) {
nbytes += apr_snprintf(buffer + nbytes, sizeof(buffer) - nbytes,
"Proxy-Authorization: %s" CRLF,
@@ -2835,7 +3551,7 @@ PROXY_DECLARE(apr_status_t) ap_proxy_check_connection(const char *scheme,
/* Filter chain is OK and empty, yet we can't determine from
* ap_check_pipeline (actually ap_core_input_filter) whether
* an empty non-blocking read is EAGAIN or EOF on the socket
- * side (it's always SUCCESS), so check it explicitely here.
+ * side (it's always SUCCESS), so check it explicitly here.
*/
if (ap_proxy_is_socket_connected(conn->sock)) {
rv = APR_SUCCESS;
@@ -2882,7 +3598,7 @@ PROXY_DECLARE(apr_status_t) ap_proxy_check_connection(const char *scheme,
"%s: backend socket is disconnected.", scheme);
}
else {
- ap_log_error(APLOG_MARK, APLOG_WARNING, 0, server, APLOGNO(03408)
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, server, APLOGNO(03408)
"%s: reusable backend connection is not empty: "
"forcibly closed", scheme);
}
@@ -2902,11 +3618,14 @@ PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function,
{
apr_status_t rv;
int loglevel;
- apr_sockaddr_t *backend_addr = conn->addr;
+ forward_info *forward = conn->forward;
+ apr_sockaddr_t *backend_addr;
/* the local address to use for the outgoing connection */
apr_sockaddr_t *local_addr;
apr_socket_t *newsock;
void *sconf = s->module_config;
+ int address_reusable = worker->s->is_address_reusable;
+ int did_dns_lookup = 0;
proxy_server_conf *conf =
(proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
@@ -2915,6 +3634,16 @@ PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function,
return DECLINED;
}
+ /* We'll set conn->addr to the address actually connect()ed, so if the
+ * network connection is not reused (per ap_proxy_check_connection()
+ * above) we need to reset conn->addr to the first resolved address
+ * and try to connect it first.
+ */
+ if (conn->address && rv != APR_SUCCESS) {
+ conn->addr = conn->address->addr;
+ }
+ backend_addr = conn->addr;
+
while (rv != APR_SUCCESS && (backend_addr || conn->uds_path)) {
#if APR_HAVE_SYS_UN_H
if (conn->uds_path)
@@ -2924,10 +3653,11 @@ PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function,
if (rv != APR_SUCCESS) {
loglevel = APLOG_ERR;
ap_log_error(APLOG_MARK, loglevel, rv, s, APLOGNO(02453)
- "%s: error creating Unix domain socket for "
- "target %s",
+ "%s: error creating Unix domain socket "
+ "%s (%s:%hu)",
proxy_function,
- worker->s->hostname_ex);
+ conn->uds_path,
+ conn->hostname, conn->port);
break;
}
conn->connection = NULL;
@@ -2937,19 +3667,18 @@ PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function,
apr_socket_close(newsock);
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02454)
"%s: attempt to connect to Unix domain socket "
- "%s (%s) failed",
- proxy_function,
- conn->uds_path,
- worker->s->hostname_ex);
+ "%s (%s:%hu) failed",
+ proxy_function, conn->uds_path,
+ conn->hostname, conn->port);
break;
}
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02823)
"%s: connection established with Unix domain socket "
- "%s (%s)",
+ "%s (%s:%hu)",
proxy_function,
conn->uds_path,
- worker->s->hostname_ex);
+ conn->hostname, conn->port);
}
else
#endif
@@ -2959,11 +3688,11 @@ PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function,
conn->scpool)) != APR_SUCCESS) {
loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
ap_log_error(APLOG_MARK, loglevel, rv, s, APLOGNO(00952)
- "%s: error creating fam %d socket for "
- "target %s",
+ "%s: error creating fam %d socket to %pI for "
+ "(%s:%hu)",
proxy_function,
- backend_addr->family,
- worker->s->hostname_ex);
+ backend_addr->family, backend_addr,
+ conn->hostname, conn->port);
/*
* this could be an IPv6 address from the DNS but the
* local machine won't give us an IPv6 socket; hopefully the
@@ -3012,8 +3741,9 @@ PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function,
}
}
ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s,
- "%s: fam %d socket created to connect to %s",
- proxy_function, backend_addr->family, worker->s->hostname_ex);
+ "%s: fam %d socket created for %pI (%s:%hu)",
+ proxy_function, backend_addr->family, backend_addr,
+ conn->hostname, conn->port);
if (conf->source_address_set) {
local_addr = apr_pmemdup(conn->scpool, conf->source_address,
@@ -3035,19 +3765,45 @@ PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function,
apr_socket_close(newsock);
loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
ap_log_error(APLOG_MARK, loglevel, rv, s, APLOGNO(00957)
- "%s: attempt to connect to %pI (%s) failed",
- proxy_function,
- backend_addr,
- worker->s->hostname_ex);
+ "%s: attempt to connect to %pI (%s:%hu) failed",
+ proxy_function, backend_addr,
+ conn->hostname, conn->port);
backend_addr = backend_addr->next;
+ /*
+ * If we run out of resolved IP's when connecting and if
+ * we cache the resolution in the worker the resolution
+ * might have changed. Hence try a DNS lookup to see if this
+ * helps.
+ */
+ if (!backend_addr && address_reusable && !did_dns_lookup) {
+ /* Issue a new DNS lookup to check if the address changed,
+ * in which case (SUCCESS) restart the loop with the new
+ * one(s), otherwise leave (nothing we can do about it).
+ */
+ if (ap_proxy_determine_address(proxy_function, conn,
+ conn->hostname, conn->port,
+ PROXY_DETERMINE_ADDRESS_CHECK,
+ NULL, s) == APR_SUCCESS) {
+ backend_addr = conn->addr;
+ }
+
+ /*
+ * In case of an error backend_addr will be NULL which
+ * is enough to leave the loop. If successful we'll retry
+ * the new addresses only once.
+ */
+ did_dns_lookup = 1;
+ }
continue;
}
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02824)
- "%s: connection established with %pI (%s)",
- proxy_function,
- backend_addr,
- worker->s->hostname_ex);
+ "%s: connection established with %pI (%s:%hu)",
+ proxy_function, backend_addr,
+ conn->hostname, conn->port);
+
+ /* Set the actual sockaddr we are connected to */
+ conn->addr = backend_addr;
}
/* Set a timeout on the socket */
@@ -3063,13 +3819,12 @@ PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function,
conn->sock = newsock;
- if (!conn->uds_path && conn->forward) {
- forward_info *forward = (forward_info *)conn->forward;
+ if (forward && forward->use_http_connect) {
/*
* For HTTP CONNECT we need to prepend CONNECT request before
* sending our actual HTTPS requests.
*/
- if (forward->use_http_connect) {
+ {
rv = send_http_connect(conn, s);
/* If an error occurred, loop round and try again */
if (rv != APR_SUCCESS) {
@@ -3077,11 +3832,11 @@ PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function,
apr_socket_close(newsock);
loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
ap_log_error(APLOG_MARK, loglevel, rv, s, APLOGNO(00958)
- "%s: attempt to connect to %s:%d "
- "via http CONNECT through %pI (%s) failed",
+ "%s: attempt to connect to %s:%hu "
+ "via http CONNECT through %pI (%s:%hu) failed",
proxy_function,
forward->target_host, forward->target_port,
- backend_addr, worker->s->hostname_ex);
+ backend_addr, conn->hostname, conn->port);
backend_addr = backend_addr->next;
continue;
}
@@ -3101,9 +3856,10 @@ PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function,
worker->s->error_time = apr_time_now();
worker->s->status |= PROXY_WORKER_IN_ERROR;
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00959)
- "ap_proxy_connect_backend disabling worker for (%s) for %"
- APR_TIME_T_FMT "s",
- worker->s->hostname_ex, apr_time_sec(worker->s->retry));
+ "ap_proxy_connect_backend disabling worker for (%s:%hu) "
+ "for %" APR_TIME_T_FMT "s",
+ worker->s->hostname_ex, (int)worker->s->port,
+ apr_time_sec(worker->s->retry));
}
}
else {
@@ -3172,6 +3928,12 @@ static int proxy_connection_create(const char *proxy_function,
apr_bucket_alloc_t *bucket_alloc;
if (conn->connection) {
+ if (conn->is_ssl) {
+ /* on reuse, reinit the SSL connection dir config with the current
+ * r->per_dir_config, the previous one was reset on release.
+ */
+ ap_proxy_ssl_engine(conn->connection, per_dir_config, 1);
+ }
return OK;
}
@@ -3189,7 +3951,7 @@ static int proxy_connection_create(const char *proxy_function,
* the peer reset the connection already; ap_run_create_connection()
* closed the socket
*/
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0,
s, APLOGNO(00960) "%s: an error occurred creating a "
"new connection to %pI (%s)", proxy_function,
backend_addr, conn->hostname);
@@ -3207,6 +3969,16 @@ static int proxy_connection_create(const char *proxy_function,
backend_addr, conn->hostname);
return HTTP_INTERNAL_SERVER_ERROR;
}
+ if (conn->ssl_hostname) {
+ /* Set a note on the connection about what CN is requested,
+ * such that mod_ssl can check if it is requested to do so.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, conn->connection,
+ "%s: set SNI to %s for (%s)", proxy_function,
+ conn->ssl_hostname, conn->hostname);
+ apr_table_setn(conn->connection->notes, "proxy-request-hostname",
+ conn->ssl_hostname);
+ }
}
else {
/* TODO: See if this will break FTP */
@@ -3269,6 +4041,45 @@ int ap_proxy_lb_workers(void)
return lb_workers_limit;
}
+static APR_INLINE int error_code_overridden(const int *elts, int nelts,
+ int code)
+{
+ int min = 0;
+ int max = nelts - 1;
+ AP_DEBUG_ASSERT(max >= 0);
+
+ while (min < max) {
+ int mid = (min + max) / 2;
+ int val = elts[mid];
+
+ if (val < code) {
+ min = mid + 1;
+ }
+ else if (val > code) {
+ max = mid - 1;
+ }
+ else {
+ return 1;
+ }
+ }
+
+ return elts[min] == code;
+}
+
+PROXY_DECLARE(int) ap_proxy_should_override(proxy_dir_conf *conf, int code)
+{
+ if (!conf->error_override)
+ return 0;
+
+ if (apr_is_empty_array(conf->error_override_codes))
+ return ap_is_HTTP_ERROR(code);
+
+ /* Since error_override_codes is sorted, apply binary search. */
+ return error_code_overridden((int *)conf->error_override_codes->elts,
+ conf->error_override_codes->nelts,
+ code);
+}
+
PROXY_DECLARE(void) ap_proxy_backend_broke(request_rec *r,
apr_bucket_brigade *brigade)
{
@@ -3416,16 +4227,15 @@ PROXY_DECLARE(apr_status_t) ap_proxy_sync_balancer(proxy_balancer *b, server_rec
}
if (!found) {
proxy_worker **runtime;
+ /* XXX: a thread mutex is maybe enough here */
apr_global_mutex_lock(proxy_mutex);
runtime = apr_array_push(b->workers);
- *runtime = apr_palloc(conf->pool, sizeof(proxy_worker));
+ *runtime = apr_pcalloc(conf->pool, sizeof(proxy_worker));
apr_global_mutex_unlock(proxy_mutex);
(*runtime)->hash = shm->hash;
- (*runtime)->context = NULL;
- (*runtime)->cp = NULL;
(*runtime)->balancer = b;
(*runtime)->s = shm;
- (*runtime)->tmutex = NULL;
+
rv = ap_proxy_initialize_worker(*runtime, s, conf->pool);
if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(00966) "Cannot init worker");
@@ -3433,7 +4243,7 @@ PROXY_DECLARE(apr_status_t) ap_proxy_sync_balancer(proxy_balancer *b, server_rec
}
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02403)
"grabbing shm[%d] (0x%pp) for worker: %s", i, (void *)shm,
- (*runtime)->s->name);
+ (*runtime)->s->name_ex);
}
}
if (b->s->need_reset) {
@@ -3565,97 +4375,125 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p,
char **old_cl_val,
char **old_te_val)
{
+ int rc = OK;
conn_rec *c = r->connection;
int counter;
char *buf;
+ apr_table_t *saved_headers_in = r->headers_in;
+ const char *saved_host = apr_table_get(saved_headers_in, "Host");
const apr_array_header_t *headers_in_array;
const apr_table_entry_t *headers_in;
- apr_table_t *saved_headers_in;
apr_bucket *e;
- int do_100_continue;
+ int force10 = 0, do_100_continue = 0;
conn_rec *origin = p_conn->connection;
+ const char *host, *creds, *val;
proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
/*
+ * HTTP "Ping" test? Easiest is 100-Continue. However:
* To be compliant, we only use 100-Continue for requests with bodies.
* We also make sure we won't be talking HTTP/1.0 as well.
*/
- do_100_continue = (worker->s->ping_timeout_set
- && ap_request_has_body(r)
- && (PROXYREQ_REVERSE == r->proxyreq)
- && !(apr_table_get(r->subprocess_env, "force-proxy-request-1.0")));
-
if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) {
- /*
- * According to RFC 2616 8.2.3 we are not allowed to forward an
- * Expect: 100-continue to an HTTP/1.0 server. Instead we MUST return
- * a HTTP_EXPECTATION_FAILED
- */
- if (r->expecting_100) {
- return HTTP_EXPECTATION_FAILED;
+ force10 = 1;
+ }
+ else if (apr_table_get(r->notes, "proxy-100-continue")
+ || PROXY_SHOULD_PING_100_CONTINUE(worker, r)) {
+ do_100_continue = 1;
+ }
+ if (force10 || apr_table_get(r->subprocess_env, "proxy-nokeepalive")) {
+ if (origin) {
+ origin->keepalive = AP_CONN_CLOSE;
}
- buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.0" CRLF, NULL);
p_conn->close = 1;
- } else {
- buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.1" CRLF, NULL);
}
- if (apr_table_get(r->subprocess_env, "proxy-nokeepalive")) {
- origin->keepalive = AP_CONN_CLOSE;
- p_conn->close = 1;
+
+ if (force10) {
+ buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.0" CRLF, NULL);
+ }
+ else {
+ buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.1" CRLF, NULL);
}
ap_xlate_proto_to_ascii(buf, strlen(buf));
e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+
+ /*
+ * Make a copy on r->headers_in for the request we make to the backend,
+ * modify the copy in place according to our configuration and connection
+ * handling, use it to fill in the forwarded headers' brigade, and finally
+ * restore the saved/original ones in r->headers_in.
+ *
+ * Note: We need to take r->pool for apr_table_copy as the key / value
+ * pairs in r->headers_in have been created out of r->pool and
+ * p might be (and actually is) a longer living pool.
+ * This would trigger the bad pool ancestry abort in apr_table_copy if
+ * apr is compiled with APR_POOL_DEBUG.
+ *
+ * icing: if p indeed lives longer than r->pool, we should allocate
+ * all new header values from r->pool as well and avoid leakage.
+ */
+ r->headers_in = apr_table_copy(r->pool, saved_headers_in);
+
+ /* Return the original Transfer-Encoding and/or Content-Length values
+ * then drop the headers, they must be set by the proxy handler based
+ * on the actual body being forwarded.
+ */
+ if ((*old_te_val = (char *)apr_table_get(r->headers_in,
+ "Transfer-Encoding"))) {
+ apr_table_unset(r->headers_in, "Transfer-Encoding");
+ }
+ if ((*old_cl_val = (char *)apr_table_get(r->headers_in,
+ "Content-Length"))) {
+ apr_table_unset(r->headers_in, "Content-Length");
+ }
+
+ /* Clear out hop-by-hop request headers not to forward */
+ if (ap_proxy_clear_connection(r, r->headers_in) < 0) {
+ rc = HTTP_BAD_REQUEST;
+ goto cleanup;
+ }
+
+ /* RFC2616 13.5.1 says we should strip these */
+ apr_table_unset(r->headers_in, "Keep-Alive");
+ apr_table_unset(r->headers_in, "Upgrade");
+ apr_table_unset(r->headers_in, "Trailer");
+ apr_table_unset(r->headers_in, "TE");
+
+ /* Compute Host header */
if (dconf->preserve_host == 0) {
if (ap_strchr_c(uri->hostname, ':')) { /* if literal IPv6 address */
if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) {
- buf = apr_pstrcat(p, "Host: [", uri->hostname, "]:",
- uri->port_str, CRLF, NULL);
+ host = apr_pstrcat(r->pool, "[", uri->hostname, "]:",
+ uri->port_str, NULL);
} else {
- buf = apr_pstrcat(p, "Host: [", uri->hostname, "]", CRLF, NULL);
+ host = apr_pstrcat(r->pool, "[", uri->hostname, "]", NULL);
}
} else {
if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) {
- buf = apr_pstrcat(p, "Host: ", uri->hostname, ":",
- uri->port_str, CRLF, NULL);
+ host = apr_pstrcat(r->pool, uri->hostname, ":",
+ uri->port_str, NULL);
} else {
- buf = apr_pstrcat(p, "Host: ", uri->hostname, CRLF, NULL);
+ host = uri->hostname;
}
}
+ apr_table_setn(r->headers_in, "Host", host);
}
else {
- /* don't want to use r->hostname, as the incoming header might have a
- * port attached
+ /* don't want to use r->hostname as the incoming header might have a
+ * port attached, let's use the original header.
*/
- const char* hostname = apr_table_get(r->headers_in,"Host");
- if (!hostname) {
- hostname = r->server->server_hostname;
+ host = saved_host;
+ if (!host) {
+ host = r->server->server_hostname;
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01092)
"no HTTP 0.9 request (with no host line) "
"on incoming request and preserve host set "
"forcing hostname to be %s for uri %s",
- hostname, r->uri);
+ host, r->uri);
+ apr_table_setn(r->headers_in, "Host", host);
}
- buf = apr_pstrcat(p, "Host: ", hostname, CRLF, NULL);
}
- ap_xlate_proto_to_ascii(buf, strlen(buf));
- e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(header_brigade, e);
-
- /*
- * Save the original headers in here and restore them when leaving, since
- * we will apply proxy purpose only modifications (eg. clearing hop-by-hop
- * headers, add Via or X-Forwarded-* or Expect...), whereas the originals
- * will be needed later to prepare the correct response and logging.
- *
- * Note: We need to take r->pool for apr_table_copy as the key / value
- * pairs in r->headers_in have been created out of r->pool and
- * p might be (and actually is) a longer living pool.
- * This would trigger the bad pool ancestry abort in apr_table_copy if
- * apr is compiled with APR_POOL_DEBUG.
- */
- saved_headers_in = r->headers_in;
- r->headers_in = apr_table_copy(r->pool, saved_headers_in);
/* handle Via */
if (conf->viaopt == via_block) {
@@ -3690,23 +4528,19 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p,
* to backend
*/
if (do_100_continue) {
- const char *val;
-
- if (!r->expecting_100) {
- /* Don't forward any "100 Continue" response if the client is
- * not expecting it.
- */
- apr_table_setn(r->subprocess_env, "proxy-interim-response",
- "Suppress");
- }
-
/* Add the Expect header if not already there. */
- if (((val = apr_table_get(r->headers_in, "Expect")) == NULL)
- || (strcasecmp(val, "100-Continue") != 0 /* fast path */
- && !ap_find_token(r->pool, val, "100-Continue"))) {
+ if (!(val = apr_table_get(r->headers_in, "Expect"))
+ || (ap_cstr_casecmp(val, "100-Continue") != 0 /* fast path */
+ && !ap_find_token(r->pool, val, "100-Continue"))) {
apr_table_mergen(r->headers_in, "Expect", "100-Continue");
}
}
+ else {
+ /* XXX: we should strip the 100-continue token only from the
+ * Expect header, but are there others actually used anywhere?
+ */
+ apr_table_unset(r->headers_in, "Expect");
+ }
/* X-Forwarded-*: handling
*
@@ -3730,8 +4564,6 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p,
*/
if (dconf->add_forwarded_headers) {
if (PROXYREQ_REVERSE == r->proxyreq) {
- const char *buf;
-
/* Add X-Forwarded-For: so that the upstream has a chance to
* determine, where the original request came from.
*/
@@ -3741,8 +4573,9 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p,
/* Add X-Forwarded-Host: so that upstream knows what the
* original request hostname was.
*/
- if ((buf = apr_table_get(r->headers_in, "Host"))) {
- apr_table_mergen(r->headers_in, "X-Forwarded-Host", buf);
+ if (saved_host) {
+ apr_table_mergen(r->headers_in, "X-Forwarded-Host",
+ saved_host);
}
/* Add X-Forwarded-Server: so that upstream knows what the
@@ -3754,79 +4587,315 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p,
}
}
+ /* Do we want to strip Proxy-Authorization ?
+ * If we haven't used it, then NO
+ * If we have used it then MAYBE: RFC2616 says we MAY propagate it.
+ * So let's make it configurable by env.
+ */
+ if (r->user != NULL /* we've authenticated */
+ && !apr_table_get(r->subprocess_env, "Proxy-Chain-Auth")) {
+ apr_table_unset(r->headers_in, "Proxy-Authorization");
+ }
+
+ /* for sub-requests, ignore freshness/expiry headers */
+ if (r->main) {
+ apr_table_unset(r->headers_in, "If-Match");
+ apr_table_unset(r->headers_in, "If-Modified-Since");
+ apr_table_unset(r->headers_in, "If-Range");
+ apr_table_unset(r->headers_in, "If-Unmodified-Since");
+ apr_table_unset(r->headers_in, "If-None-Match");
+ }
+
+ creds = apr_table_get(r->notes, "proxy-basic-creds");
+ if (creds) {
+ apr_table_mergen(r->headers_in, "Proxy-Authorization", creds);
+ }
+
+ /* run hook to fixup the request we are about to send */
proxy_run_fixups(r);
- if (ap_proxy_clear_connection(r, r->headers_in) < 0) {
- return HTTP_BAD_REQUEST;
+
+ /* We used to send `Host: ` always first, so let's keep it that
+ * way. No telling which legacy backend is relying on this.
+ * If proxy_run_fixups() changed the value, use it (though removal
+ * is ignored).
+ */
+ val = apr_table_get(r->headers_in, "Host");
+ if (val) {
+ apr_table_unset(r->headers_in, "Host");
+ host = val;
}
+ buf = apr_pstrcat(p, "Host: ", host, CRLF, NULL);
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(header_brigade, e);
- /* send request headers */
+ /* Append the (remaining) headers to the brigade */
headers_in_array = apr_table_elts(r->headers_in);
headers_in = (const apr_table_entry_t *) headers_in_array->elts;
for (counter = 0; counter < headers_in_array->nelts; counter++) {
if (headers_in[counter].key == NULL
- || headers_in[counter].val == NULL
+ || headers_in[counter].val == NULL) {
+ continue;
+ }
- /* Already sent */
- || !strcasecmp(headers_in[counter].key, "Host")
+ buf = apr_pstrcat(p, headers_in[counter].key, ": ",
+ headers_in[counter].val, CRLF,
+ NULL);
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+ }
- /* Clear out hop-by-hop request headers not to send
- * RFC2616 13.5.1 says we should strip these headers
- */
- || !strcasecmp(headers_in[counter].key, "Keep-Alive")
- || !strcasecmp(headers_in[counter].key, "TE")
- || !strcasecmp(headers_in[counter].key, "Trailer")
- || !strcasecmp(headers_in[counter].key, "Upgrade")
+cleanup:
+ r->headers_in = saved_headers_in;
+ return rc;
+}
- ) {
- continue;
+PROXY_DECLARE(int) ap_proxy_prefetch_input(request_rec *r,
+ proxy_conn_rec *backend,
+ apr_bucket_brigade *input_brigade,
+ apr_read_type_e block,
+ apr_off_t *bytes_read,
+ apr_off_t max_read)
+{
+ apr_pool_t *p = r->pool;
+ conn_rec *c = r->connection;
+ apr_bucket_brigade *temp_brigade;
+ apr_status_t status;
+ apr_off_t bytes;
+
+ *bytes_read = 0;
+ if (max_read < APR_BUCKET_BUFF_SIZE) {
+ max_read = APR_BUCKET_BUFF_SIZE;
+ }
+
+ /* Prefetch max_read bytes
+ *
+ * This helps us avoid any election of C-L v.s. T-E
+ * request bodies, since we are willing to keep in
+ * memory this much data, in any case. This gives
+ * us an instant C-L election if the body is of some
+ * reasonable size.
+ */
+ temp_brigade = apr_brigade_create(p, input_brigade->bucket_alloc);
+
+ /* Account for saved input, if any. */
+ apr_brigade_length(input_brigade, 0, bytes_read);
+
+ /* Ensure we don't hit a wall where we have a buffer too small for
+ * ap_get_brigade's filters to fetch us another bucket, surrender
+ * once we hit 80 bytes (an arbitrary value) less than max_read.
+ */
+ while (*bytes_read < max_read - 80
+ && (APR_BRIGADE_EMPTY(input_brigade)
+ || !APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade)))) {
+ status = ap_get_brigade(r->input_filters, temp_brigade,
+ AP_MODE_READBYTES, block,
+ max_read - *bytes_read);
+ /* ap_get_brigade may return success with an empty brigade
+ * for a non-blocking read which would block
+ */
+ if (block == APR_NONBLOCK_READ
+ && ((status == APR_SUCCESS && APR_BRIGADE_EMPTY(temp_brigade))
+ || APR_STATUS_IS_EAGAIN(status))) {
+ break;
}
- /* Do we want to strip Proxy-Authorization ?
- * If we haven't used it, then NO
- * If we have used it then MAYBE: RFC2616 says we MAY propagate it.
- * So let's make it configurable by env.
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01095)
+ "prefetch request body failed to %pI (%s)"
+ " from %s (%s)", backend->addr,
+ backend->hostname ? backend->hostname : "",
+ c->client_ip, c->remote_host ? c->remote_host : "");
+ return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
+ }
+
+ apr_brigade_length(temp_brigade, 1, &bytes);
+ *bytes_read += bytes;
+
+ /*
+ * Save temp_brigade in input_brigade. (At least) in the SSL case
+ * temp_brigade contains transient buckets whose data would get
+ * overwritten during the next call of ap_get_brigade in the loop.
+ * ap_save_brigade ensures these buckets to be set aside.
+ * Calling ap_save_brigade with NULL as filter is OK, because
+ * input_brigade already has been created and does not need to get
+ * created by ap_save_brigade.
*/
- if (!strcasecmp(headers_in[counter].key,"Proxy-Authorization")) {
- if (r->user != NULL) { /* we've authenticated */
- if (!apr_table_get(r->subprocess_env, "Proxy-Chain-Auth")) {
- continue;
- }
+ status = ap_save_brigade(NULL, &input_brigade, &temp_brigade, p);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01096)
+ "processing prefetched request body failed"
+ " to %pI (%s) from %s (%s)", backend->addr,
+ backend->hostname ? backend->hostname : "",
+ c->client_ip, c->remote_host ? c->remote_host : "");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ return OK;
+}
+
+PROXY_DECLARE(int) ap_proxy_read_input(request_rec *r,
+ proxy_conn_rec *backend,
+ apr_bucket_brigade *bb,
+ apr_off_t max_read)
+{
+ apr_bucket_alloc_t *bucket_alloc = bb->bucket_alloc;
+ apr_read_type_e block = (backend->connection) ? APR_NONBLOCK_READ
+ : APR_BLOCK_READ;
+ apr_status_t status;
+ int rv;
+
+ for (;;) {
+ apr_brigade_cleanup(bb);
+ status = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
+ block, max_read);
+ if (block == APR_BLOCK_READ
+ || (!(status == APR_SUCCESS && APR_BRIGADE_EMPTY(bb))
+ && !APR_STATUS_IS_EAGAIN(status))) {
+ break;
+ }
+
+ /* Flush and retry (blocking) */
+ apr_brigade_cleanup(bb);
+ rv = ap_proxy_pass_brigade(bucket_alloc, r, backend,
+ backend->connection, bb, 1);
+ if (rv != OK) {
+ return rv;
+ }
+ block = APR_BLOCK_READ;
+ }
+
+ if (status != APR_SUCCESS) {
+ conn_rec *c = r->connection;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02608)
+ "read request body failed to %pI (%s)"
+ " from %s (%s)", backend->addr,
+ backend->hostname ? backend->hostname : "",
+ c->client_ip, c->remote_host ? c->remote_host : "");
+ return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
+ }
+
+ return OK;
+}
+
+PROXY_DECLARE(int) ap_proxy_spool_input(request_rec *r,
+ proxy_conn_rec *backend,
+ apr_bucket_brigade *input_brigade,
+ apr_off_t *bytes_spooled,
+ apr_off_t max_mem_spool)
+{
+ apr_pool_t *p = r->pool;
+ int seen_eos = 0, rv = OK;
+ apr_status_t status = APR_SUCCESS;
+ apr_bucket_alloc_t *bucket_alloc = input_brigade->bucket_alloc;
+ apr_bucket_brigade *body_brigade;
+ apr_bucket *e;
+ apr_off_t bytes, fsize = 0;
+ apr_file_t *tmpfile = NULL;
+
+ *bytes_spooled = 0;
+ body_brigade = apr_brigade_create(p, bucket_alloc);
+
+ do {
+ if (APR_BRIGADE_EMPTY(input_brigade)) {
+ rv = ap_proxy_read_input(r, backend, input_brigade,
+ HUGE_STRING_LEN);
+ if (rv != OK) {
+ return rv;
}
}
- /* Skip Transfer-Encoding and Content-Length for now.
- */
- if (!strcasecmp(headers_in[counter].key, "Transfer-Encoding")) {
- *old_te_val = headers_in[counter].val;
- continue;
+ /* If this brigade contains EOS, either stop or remove it. */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+ seen_eos = 1;
}
- if (!strcasecmp(headers_in[counter].key, "Content-Length")) {
- *old_cl_val = headers_in[counter].val;
- continue;
+
+ apr_brigade_length(input_brigade, 1, &bytes);
+
+ if (*bytes_spooled + bytes > max_mem_spool) {
+ /* can't spool any more in memory; write latest brigade to disk */
+ if (tmpfile == NULL) {
+ const char *temp_dir;
+ char *template;
+
+ status = apr_temp_dir_get(&temp_dir, p);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01089)
+ "search for temporary directory failed");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ apr_filepath_merge(&template, temp_dir,
+ "modproxy.tmp.XXXXXX",
+ APR_FILEPATH_NATIVE, p);
+ status = apr_file_mktemp(&tmpfile, template, 0, p);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01090)
+ "creation of temporary file in directory "
+ "%s failed", temp_dir);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ for (e = APR_BRIGADE_FIRST(input_brigade);
+ e != APR_BRIGADE_SENTINEL(input_brigade);
+ e = APR_BUCKET_NEXT(e)) {
+ const char *data;
+ apr_size_t bytes_read, bytes_written;
+
+ apr_bucket_read(e, &data, &bytes_read, APR_BLOCK_READ);
+ status = apr_file_write_full(tmpfile, data, bytes_read, &bytes_written);
+ if (status != APR_SUCCESS) {
+ const char *tmpfile_name;
+
+ if (apr_file_name_get(&tmpfile_name, tmpfile) != APR_SUCCESS) {
+ tmpfile_name = "(unknown)";
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01091)
+ "write to temporary file %s failed",
+ tmpfile_name);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ AP_DEBUG_ASSERT(bytes_read == bytes_written);
+ fsize += bytes_written;
+ }
+ apr_brigade_cleanup(input_brigade);
}
+ else {
- /* for sub-requests, ignore freshness/expiry headers */
- if (r->main) {
- if ( !strcasecmp(headers_in[counter].key, "If-Match")
- || !strcasecmp(headers_in[counter].key, "If-Modified-Since")
- || !strcasecmp(headers_in[counter].key, "If-Range")
- || !strcasecmp(headers_in[counter].key, "If-Unmodified-Since")
- || !strcasecmp(headers_in[counter].key, "If-None-Match")) {
- continue;
+ /*
+ * Save input_brigade in body_brigade. (At least) in the SSL case
+ * input_brigade contains transient buckets whose data would get
+ * overwritten during the next call of ap_get_brigade in the loop.
+ * ap_save_brigade ensures these buckets to be set aside.
+ * Calling ap_save_brigade with NULL as filter is OK, because
+ * body_brigade already has been created and does not need to get
+ * created by ap_save_brigade.
+ */
+ status = ap_save_brigade(NULL, &body_brigade, &input_brigade, p);
+ if (status != APR_SUCCESS) {
+ return HTTP_INTERNAL_SERVER_ERROR;
}
+
}
- buf = apr_pstrcat(p, headers_in[counter].key, ": ",
- headers_in[counter].val, CRLF,
- NULL);
- ap_xlate_proto_to_ascii(buf, strlen(buf));
- e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(header_brigade, e);
- }
+ *bytes_spooled += bytes;
+ } while (!seen_eos);
- /* Restore the original headers in (see comment above),
- * we won't modify them anymore.
- */
- r->headers_in = saved_headers_in;
+ APR_BRIGADE_CONCAT(input_brigade, body_brigade);
+ if (tmpfile) {
+ apr_brigade_insert_file(input_brigade, tmpfile, 0, fsize, p);
+ }
+ if (apr_table_get(r->subprocess_env, "proxy-sendextracrlf")) {
+ e = apr_bucket_immortal_create(CRLF_ASCII, 2, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(input_brigade, e);
+ }
+ if (tmpfile) {
+ /* We dropped metadata buckets when spooling to tmpfile,
+ * terminate with EOS to allow for flushing in a one go.
+ */
+ e = apr_bucket_eos_create(bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(input_brigade, e);
+ }
return OK;
}
@@ -3899,7 +4968,7 @@ PROXY_DECLARE(apr_port_t) ap_proxy_port_of_scheme(const char *scheme)
} else {
proxy_schemes_t *pscheme;
for (pscheme = pschemes; pscheme->name != NULL; ++pscheme) {
- if (strcasecmp(scheme, pscheme->name) == 0) {
+ if (ap_cstr_casecmp(scheme, pscheme->name) == 0) {
return pscheme->default_port;
}
}
@@ -3908,6 +4977,23 @@ PROXY_DECLARE(apr_port_t) ap_proxy_port_of_scheme(const char *scheme)
return 0;
}
+static APR_INLINE int ap_filter_should_yield(ap_filter_t *f)
+{
+ return f->c->data_in_output_filters;
+}
+
+static APR_INLINE int ap_filter_output_pending(conn_rec *c)
+{
+ ap_filter_t *f = c->output_filters;
+ while (f->next) {
+ f = f->next;
+ }
+ if (f->frec->filter_func.out_func(f, NULL)) {
+ return AP_FILTER_ERROR;
+ }
+ return c->data_in_output_filters ? OK : DECLINED;
+}
+
PROXY_DECLARE(apr_status_t) ap_proxy_buckets_lifetime_transform(request_rec *r,
apr_bucket_brigade *from,
apr_bucket_brigade *to)
@@ -3946,6 +5032,16 @@ PROXY_DECLARE(apr_status_t) ap_proxy_buckets_lifetime_transform(request_rec *r,
return rv;
}
+/* An arbitrary large value to address pathological case where we keep
+ * reading from one side only, without scheduling the other direction for
+ * too long. This can happen with large MTU and small read buffers, like
+ * micro-benchmarking huge files bidirectional transfer with client, proxy
+ * and backend on localhost for instance. Though we could just ignore the
+ * case and let the sender stop by itself at some point when/if it needs to
+ * receive data, or the receiver stop when/if it needs to send...
+ */
+#define PROXY_TRANSFER_MAX_READS 10000
+
PROXY_DECLARE(apr_status_t) ap_proxy_transfer_between_connections(
request_rec *r,
conn_rec *c_i,
@@ -3955,81 +5051,576 @@ PROXY_DECLARE(apr_status_t) ap_proxy_transfer_between_connections(
const char *name,
int *sent,
apr_off_t bsize,
- int after)
+ int flags)
{
apr_status_t rv;
+ int flush_each = 0;
+ unsigned int num_reads = 0;
#ifdef DEBUGGING
apr_off_t len;
#endif
- do {
+ /*
+ * Compat: since FLUSH_EACH is default (and zero) for legacy reasons, we
+ * pretend it's no FLUSH_AFTER nor YIELD_PENDING flags, the latter because
+ * flushing would defeat the purpose of checking for pending data (hence
+ * determine whether or not the output chain/stack is full for stopping).
+ */
+ if (!(flags & (AP_PROXY_TRANSFER_FLUSH_AFTER |
+ AP_PROXY_TRANSFER_YIELD_PENDING))) {
+ flush_each = 1;
+ }
+
+ for (;;) {
apr_brigade_cleanup(bb_i);
rv = ap_get_brigade(c_i->input_filters, bb_i, AP_MODE_READBYTES,
APR_NONBLOCK_READ, bsize);
- if (rv == APR_SUCCESS) {
- if (c_o->aborted) {
- return APR_EPIPE;
- }
- if (APR_BRIGADE_EMPTY(bb_i)) {
- break;
+ if (rv != APR_SUCCESS) {
+ if (!APR_STATUS_IS_EAGAIN(rv) && !APR_STATUS_IS_EOF(rv)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03308)
+ "ap_proxy_transfer_between_connections: "
+ "error on %s - ap_get_brigade",
+ name);
+ if (rv == APR_INCOMPLETE) {
+ /* Don't return APR_INCOMPLETE, it'd mean "should yield"
+ * for the caller, while it means "incomplete body" here
+ * from ap_http_filter(), which is an error.
+ */
+ rv = APR_EGENERAL;
+ }
}
+ break;
+ }
+
+ if (c_o->aborted) {
+ apr_brigade_cleanup(bb_i);
+ flags &= ~AP_PROXY_TRANSFER_FLUSH_AFTER;
+ rv = APR_EPIPE;
+ break;
+ }
+ if (APR_BRIGADE_EMPTY(bb_i)) {
+ break;
+ }
#ifdef DEBUGGING
- len = -1;
- apr_brigade_length(bb_i, 0, &len);
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03306)
- "ap_proxy_transfer_between_connections: "
- "read %" APR_OFF_T_FMT
- " bytes from %s", len, name);
+ len = -1;
+ apr_brigade_length(bb_i, 0, &len);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03306)
+ "ap_proxy_transfer_between_connections: "
+ "read %" APR_OFF_T_FMT
+ " bytes from %s", len, name);
#endif
- if (sent) {
- *sent = 1;
- }
- ap_proxy_buckets_lifetime_transform(r, bb_i, bb_o);
- if (!after) {
- apr_bucket *b;
+ if (sent) {
+ *sent = 1;
+ }
+ ap_proxy_buckets_lifetime_transform(r, bb_i, bb_o);
+ if (flush_each) {
+ apr_bucket *b;
+ /*
+ * Do not use ap_fflush here since this would cause the flush
+ * bucket to be sent in a separate brigade afterwards which
+ * causes some filters to set aside the buckets from the first
+ * brigade and process them when FLUSH arrives in the second
+ * brigade. As set asides of our transformed buckets involve
+ * memory copying we try to avoid this. If we have the flush
+ * bucket in the first brigade they directly process the
+ * buckets without setting them aside.
+ */
+ b = apr_bucket_flush_create(bb_o->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb_o, b);
+ }
+ rv = ap_pass_brigade(c_o->output_filters, bb_o);
+ apr_brigade_cleanup(bb_o);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(03307)
+ "ap_proxy_transfer_between_connections: "
+ "error on %s - ap_pass_brigade",
+ name);
+ flags &= ~AP_PROXY_TRANSFER_FLUSH_AFTER;
+ break;
+ }
- /*
- * Do not use ap_fflush here since this would cause the flush
- * bucket to be sent in a separate brigade afterwards which
- * causes some filters to set aside the buckets from the first
- * brigade and process them when the flush arrives in the second
- * brigade. As set asides of our transformed buckets involve
- * memory copying we try to avoid this. If we have the flush
- * bucket in the first brigade they directly process the
- * buckets without setting them aside.
- */
- b = apr_bucket_flush_create(bb_o->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb_o, b);
- }
- rv = ap_pass_brigade(c_o->output_filters, bb_o);
- if (rv != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(03307)
+ /* Yield if the output filters stack is full? This is to avoid
+ * blocking and give the caller a chance to POLLOUT async.
+ */
+ if ((flags & AP_PROXY_TRANSFER_YIELD_PENDING)
+ && ap_filter_should_yield(c_o->output_filters)) {
+ int rc = ap_filter_output_pending(c_o);
+ if (rc == OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
"ap_proxy_transfer_between_connections: "
- "error on %s - ap_pass_brigade",
- name);
+ "yield (output pending)");
+ rv = APR_INCOMPLETE;
+ break;
}
- } else if (!APR_STATUS_IS_EAGAIN(rv) && !APR_STATUS_IS_EOF(rv)) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03308)
+ if (rc != DECLINED) {
+ rv = AP_FILTER_ERROR;
+ break;
+ }
+ }
+
+ /* Yield if we keep hold of the thread for too long? This gives
+ * the caller a chance to schedule the other direction too.
+ */
+ if ((flags & AP_PROXY_TRANSFER_YIELD_MAX_READS)
+ && ++num_reads > PROXY_TRANSFER_MAX_READS) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
"ap_proxy_transfer_between_connections: "
- "error on %s - ap_get_brigade",
- name);
+ "yield (max reads)");
+ rv = APR_SUCCESS;
+ break;
}
- } while (rv == APR_SUCCESS);
+ }
- if (after) {
+ if (flags & AP_PROXY_TRANSFER_FLUSH_AFTER) {
ap_fflush(c_o->output_filters, bb_o);
+ apr_brigade_cleanup(bb_o);
}
+ apr_brigade_cleanup(bb_i);
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, rv, r,
- "ap_proxy_transfer_between_connections complete");
+ "ap_proxy_transfer_between_connections complete (%s %pI)",
+ (c_i == r->connection) ? "to" : "from",
+ (c_i == r->connection) ? c_o->client_addr
+ : c_i->client_addr);
if (APR_STATUS_IS_EAGAIN(rv)) {
rv = APR_SUCCESS;
}
-
return rv;
}
+struct proxy_tunnel_conn {
+ /* the other side of the tunnel */
+ struct proxy_tunnel_conn *other;
+
+ conn_rec *c;
+ const char *name;
+
+ apr_pollfd_t *pfd;
+ apr_bucket_brigade *bb;
+
+ unsigned int down_in:1,
+ down_out:1;
+};
+
+PROXY_DECLARE(apr_status_t) ap_proxy_tunnel_create(proxy_tunnel_rec **ptunnel,
+ request_rec *r, conn_rec *c_o,
+ const char *scheme)
+{
+ apr_status_t rv;
+ conn_rec *c_i = r->connection;
+ apr_interval_time_t client_timeout = -1, origin_timeout = -1;
+ proxy_tunnel_rec *tunnel;
+
+ *ptunnel = NULL;
+
+ tunnel = apr_pcalloc(r->pool, sizeof(*tunnel));
+
+ rv = apr_pollset_create(&tunnel->pollset, 2, r->pool, APR_POLLSET_NOCOPY);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ tunnel->r = r;
+ tunnel->scheme = apr_pstrdup(r->pool, scheme);
+ tunnel->client = apr_pcalloc(r->pool, sizeof(struct proxy_tunnel_conn));
+ tunnel->origin = apr_pcalloc(r->pool, sizeof(struct proxy_tunnel_conn));
+ tunnel->pfds = apr_array_make(r->pool, 2, sizeof(apr_pollfd_t));
+ tunnel->read_buf_size = ap_get_read_buf_size(r);
+ tunnel->client->other = tunnel->origin;
+ tunnel->origin->other = tunnel->client;
+ tunnel->timeout = -1;
+
+ tunnel->client->c = c_i;
+ tunnel->client->name = "client";
+ tunnel->client->bb = apr_brigade_create(c_i->pool, c_i->bucket_alloc);
+ tunnel->client->pfd = &APR_ARRAY_PUSH(tunnel->pfds, apr_pollfd_t);
+ tunnel->client->pfd->p = r->pool;
+ tunnel->client->pfd->desc_type = APR_NO_DESC;
+ rv = ap_get_pollfd_from_conn(tunnel->client->c,
+ tunnel->client->pfd, &client_timeout);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ tunnel->client->pfd->client_data = tunnel->client;
+ if (tunnel->client->pfd->desc_type == APR_POLL_SOCKET) {
+ apr_socket_opt_set(tunnel->client->pfd->desc.s, APR_SO_NONBLOCK, 1);
+ }
+
+ tunnel->origin->c = c_o;
+ tunnel->origin->name = "origin";
+ tunnel->origin->bb = apr_brigade_create(c_o->pool, c_o->bucket_alloc);
+ tunnel->origin->pfd = &APR_ARRAY_PUSH(tunnel->pfds, apr_pollfd_t);
+ tunnel->origin->pfd->p = r->pool;
+ tunnel->origin->pfd->desc_type = APR_POLL_SOCKET;
+ tunnel->origin->pfd->desc.s = ap_get_conn_socket(c_o);
+ tunnel->origin->pfd->client_data = tunnel->origin;
+ apr_socket_timeout_get(tunnel->origin->pfd->desc.s, &origin_timeout);
+ apr_socket_opt_set(tunnel->origin->pfd->desc.s, APR_SO_NONBLOCK, 1);
+
+ /* Defaults to the largest timeout of both connections */
+ tunnel->timeout = (client_timeout >= 0 && client_timeout > origin_timeout ?
+ client_timeout : origin_timeout);
+
+ /* No coalescing filters */
+ ap_remove_output_filter_byhandle(c_i->output_filters,
+ "SSL/TLS Coalescing Filter");
+ ap_remove_output_filter_byhandle(c_o->output_filters,
+ "SSL/TLS Coalescing Filter");
+
+ /* Bidirectional non-HTTP stream will confuse mod_reqtimeoout */
+ ap_remove_input_filter_byhandle(c_i->input_filters, "reqtimeout");
+
+ /* The input/output filter stacks should contain connection filters only */
+ r->input_filters = r->proto_input_filters = c_i->input_filters;
+ r->output_filters = r->proto_output_filters = c_i->output_filters;
+
+ /* Won't be reused after tunneling */
+ c_i->keepalive = AP_CONN_CLOSE;
+ c_o->keepalive = AP_CONN_CLOSE;
+
+ /* Disable half-close forwarding for this request? */
+ if (apr_table_get(r->subprocess_env, "proxy-nohalfclose")) {
+ tunnel->nohalfclose = 1;
+ }
+
+ if (tunnel->client->pfd->desc_type == APR_POLL_SOCKET) {
+ /* Both ends are sockets, the poll strategy is:
+ * - poll both sides POLLOUT
+ * - when one side is writable, remove the POLLOUT
+ * and add POLLIN to the other side.
+ * - tunnel arriving data, remove POLLIN from the source
+ * again and add POLLOUT to the receiving side
+ * - on EOF on read, remove the POLLIN from that side
+ * Repeat until both sides are down */
+ tunnel->client->pfd->reqevents = APR_POLLOUT | APR_POLLERR;
+ tunnel->origin->pfd->reqevents = APR_POLLOUT | APR_POLLERR;
+ if ((rv = apr_pollset_add(tunnel->pollset, tunnel->origin->pfd)) ||
+ (rv = apr_pollset_add(tunnel->pollset, tunnel->client->pfd))) {
+ return rv;
+ }
+ }
+ else if (tunnel->client->pfd->desc_type == APR_POLL_FILE) {
+ /* Input is a PIPE fd, the poll strategy is:
+ * - always POLLIN on origin
+ * - use socket strategy described above for client only
+ * otherwise the same
+ */
+ tunnel->client->pfd->reqevents = 0;
+ tunnel->origin->pfd->reqevents = APR_POLLIN | APR_POLLHUP |
+ APR_POLLOUT | APR_POLLERR;
+ if ((rv = apr_pollset_add(tunnel->pollset, tunnel->origin->pfd))) {
+ return rv;
+ }
+ }
+ else {
+ /* input is already closed, unsual, but we know nothing about
+ * the tunneled protocol. */
+ tunnel->client->down_in = 1;
+ tunnel->origin->pfd->reqevents = APR_POLLIN | APR_POLLHUP;
+ if ((rv = apr_pollset_add(tunnel->pollset, tunnel->origin->pfd))) {
+ return rv;
+ }
+ }
+
+ *ptunnel = tunnel;
+ return APR_SUCCESS;
+}
+
+static void add_pollset(apr_pollset_t *pollset, apr_pollfd_t *pfd,
+ apr_int16_t events)
+{
+ apr_status_t rv;
+
+ AP_DEBUG_ASSERT((pfd->reqevents & events) == 0);
+
+ if (pfd->reqevents) {
+ rv = apr_pollset_remove(pollset, pfd);
+ if (rv != APR_SUCCESS) {
+ AP_DEBUG_ASSERT(1);
+ }
+ }
+
+ if (events & APR_POLLIN) {
+ events |= APR_POLLHUP;
+ }
+ pfd->reqevents |= events | APR_POLLERR;
+ rv = apr_pollset_add(pollset, pfd);
+ if (rv != APR_SUCCESS) {
+ AP_DEBUG_ASSERT(1);
+ }
+}
+
+static void del_pollset(apr_pollset_t *pollset, apr_pollfd_t *pfd,
+ apr_int16_t events)
+{
+ apr_status_t rv;
+
+ AP_DEBUG_ASSERT((pfd->reqevents & events) != 0);
+
+ rv = apr_pollset_remove(pollset, pfd);
+ if (rv != APR_SUCCESS) {
+ AP_DEBUG_ASSERT(0);
+ return;
+ }
+
+ if (events & APR_POLLIN) {
+ events |= APR_POLLHUP;
+ }
+ if (pfd->reqevents & ~(events | APR_POLLERR)) {
+ pfd->reqevents &= ~events;
+ rv = apr_pollset_add(pollset, pfd);
+ if (rv != APR_SUCCESS) {
+ AP_DEBUG_ASSERT(0);
+ return;
+ }
+ }
+ else {
+ pfd->reqevents = 0;
+ }
+}
+
+static int proxy_tunnel_forward(proxy_tunnel_rec *tunnel,
+ struct proxy_tunnel_conn *in)
+{
+ struct proxy_tunnel_conn *out = in->other;
+ apr_status_t rv;
+ int sent = 0;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, tunnel->r,
+ "proxy: %s: %s input ready",
+ tunnel->scheme, in->name);
+
+ rv = ap_proxy_transfer_between_connections(tunnel->r,
+ in->c, out->c,
+ in->bb, out->bb,
+ in->name, &sent,
+ tunnel->read_buf_size,
+ AP_PROXY_TRANSFER_YIELD_PENDING |
+ AP_PROXY_TRANSFER_YIELD_MAX_READS);
+ if (sent && out == tunnel->client) {
+ tunnel->replied = 1;
+ }
+ if (rv != APR_SUCCESS) {
+ if (APR_STATUS_IS_INCOMPLETE(rv)) {
+ /* Pause POLLIN while waiting for POLLOUT on the other
+ * side, hence avoid filling the output filters even
+ * more to avoid blocking there.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, tunnel->r,
+ "proxy: %s: %s wait writable",
+ tunnel->scheme, out->name);
+ }
+ else if (APR_STATUS_IS_EOF(rv)) {
+ /* Stop POLLIN and wait for POLLOUT (flush) on the
+ * other side to shut it down.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, tunnel->r,
+ "proxy: %s: %s read shutdown",
+ tunnel->scheme, in->name);
+ if (tunnel->nohalfclose) {
+ /* No half-close forwarding, we are done both ways as
+ * soon as one side shuts down.
+ */
+ return DONE;
+ }
+ in->down_in = 1;
+ }
+ else {
+ /* Real failure, bail out */
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ del_pollset(tunnel->pollset, in->pfd, APR_POLLIN);
+ if (out->pfd->desc_type == APR_POLL_SOCKET) {
+ /* if the output is a SOCKET, we can stop polling the input
+ * until the output signals POLLOUT again. */
+ add_pollset(tunnel->pollset, out->pfd, APR_POLLOUT);
+ }
+ else {
+ /* We can't use POLLOUT in this direction for the only
+ * APR_POLL_FILE case we have so far (mod_h2's "signal" pipe),
+ * we assume that the client's ouput filters chain will block/flush
+ * if necessary (i.e. no pending data), hence that the origin
+ * is EOF when reaching here. This direction is over. */
+ ap_assert(in->down_in && APR_STATUS_IS_EOF(rv));
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, tunnel->r,
+ "proxy: %s: %s write shutdown",
+ tunnel->scheme, out->name);
+ out->down_out = 1;
+ }
+ }
+
+ return OK;
+}
+
+PROXY_DECLARE(int) ap_proxy_tunnel_run(proxy_tunnel_rec *tunnel)
+{
+ int status = OK, rc;
+ request_rec *r = tunnel->r;
+ apr_pollset_t *pollset = tunnel->pollset;
+ struct proxy_tunnel_conn *client = tunnel->client,
+ *origin = tunnel->origin;
+ apr_interval_time_t timeout = tunnel->timeout >= 0 ? tunnel->timeout : -1;
+ const char *scheme = tunnel->scheme;
+ apr_status_t rv;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(10212)
+ "proxy: %s: tunnel running (timeout %lf)",
+ scheme, timeout >= 0 ? (double)timeout / APR_USEC_PER_SEC
+ : (double)-1.0);
+
+ /* Loop until both directions of the connection are closed,
+ * or a failure occurs.
+ */
+ do {
+ const apr_pollfd_t *results;
+ apr_int32_t nresults, i;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r,
+ "proxy: %s: polling (client=%hx, origin=%hx)",
+ scheme, client->pfd->reqevents, origin->pfd->reqevents);
+ do {
+ rv = apr_pollset_poll(pollset, timeout, &nresults, &results);
+ } while (APR_STATUS_IS_EINTR(rv));
+
+ if (rv != APR_SUCCESS) {
+ if (APR_STATUS_IS_TIMEUP(rv)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, APLOGNO(10213)
+ "proxy: %s: polling timed out "
+ "(client=%hx, origin=%hx)",
+ scheme, client->pfd->reqevents,
+ origin->pfd->reqevents);
+ status = HTTP_GATEWAY_TIME_OUT;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10214)
+ "proxy: %s: polling failed", scheme);
+ status = HTTP_INTERNAL_SERVER_ERROR;
+ }
+ goto done;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r, APLOGNO(10215)
+ "proxy: %s: woken up, %i result(s)", scheme, nresults);
+
+ for (i = 0; i < nresults; i++) {
+ const apr_pollfd_t *pfd = &results[i];
+ struct proxy_tunnel_conn *tc = pfd->client_data;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r,
+ "proxy: %s: #%i: %s: %hx/%hx", scheme, i,
+ tc->name, pfd->rtnevents, tc->pfd->reqevents);
+
+ /* sanity check */
+ if (pfd->desc.s != client->pfd->desc.s
+ && pfd->desc.s != origin->pfd->desc.s) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10222)
+ "proxy: %s: unknown socket in pollset", scheme);
+ status = HTTP_INTERNAL_SERVER_ERROR;
+ goto done;
+ }
+
+ if (!(pfd->rtnevents & (APR_POLLIN | APR_POLLOUT |
+ APR_POLLHUP | APR_POLLERR))) {
+ /* this catches POLLNVAL etc.. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10220)
+ "proxy: %s: polling events error (%x)",
+ scheme, pfd->rtnevents);
+ status = HTTP_INTERNAL_SERVER_ERROR;
+ goto done;
+ }
+
+ /* We want to write if we asked for POLLOUT and got:
+ * - POLLOUT: the socket is ready for write;
+ * - !POLLIN: the socket is in error state (POLLERR) so we let
+ * the user know by failing the write and log, OR the socket
+ * is shutdown for read already (POLLHUP) so we have to
+ * shutdown for write.
+ */
+ if ((tc->pfd->reqevents & APR_POLLOUT)
+ && ((pfd->rtnevents & APR_POLLOUT)
+ || !(tc->pfd->reqevents & APR_POLLIN)
+ || !(pfd->rtnevents & (APR_POLLIN | APR_POLLHUP)))) {
+ struct proxy_tunnel_conn *out = tc, *in = tc->other;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r,
+ "proxy: %s: %s output ready",
+ scheme, out->name);
+
+ rc = ap_filter_output_pending(out->c);
+ if (rc == OK) {
+ /* Keep polling out (only) */
+ continue;
+ }
+ if (rc != DECLINED) {
+ /* Real failure, bail out */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10221)
+ "proxy: %s: %s flushing failed (%i)",
+ scheme, out->name, rc);
+ status = rc;
+ goto done;
+ }
+
+ /* No more pending data. If the other side is not readable
+ * anymore it's time to shutdown for write (this direction
+ * is over). Otherwise back to normal business.
+ */
+ del_pollset(pollset, out->pfd, APR_POLLOUT);
+ if (in->down_in) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "proxy: %s: %s write shutdown",
+ scheme, out->name);
+ apr_socket_shutdown(out->pfd->desc.s, 1);
+ out->down_out = 1;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r,
+ "proxy: %s: %s resume writable",
+ scheme, out->name);
+ add_pollset(pollset, in->pfd, APR_POLLIN);
+
+ /* Flush any pending input data now, we don't know when
+ * the next POLLIN will trigger and retaining data might
+ * deadlock the underlying protocol. We don't check for
+ * pending data first with ap_filter_input_pending() since
+ * the read from proxy_tunnel_forward() is nonblocking
+ * anyway and returning OK if there's no data.
+ */
+ rc = proxy_tunnel_forward(tunnel, in);
+ if (rc != OK) {
+ status = rc;
+ goto done;
+ }
+ }
+ }
+
+ /* We want to read if we asked for POLLIN|HUP and got:
+ * - POLLIN|HUP: the socket is ready for read or EOF (POLLHUP);
+ * - !POLLOUT: the socket is in error state (POLLERR) so we let
+ * the user know by failing the read and log.
+ */
+ if ((tc->pfd->reqevents & APR_POLLIN)
+ && ((pfd->rtnevents & (APR_POLLIN | APR_POLLHUP))
+ || !(pfd->rtnevents & APR_POLLOUT))) {
+ rc = proxy_tunnel_forward(tunnel, tc);
+ if (rc != OK) {
+ status = rc;
+ goto done;
+ }
+ }
+ }
+ } while (!client->down_out || !origin->down_out);
+
+done:
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(10223)
+ "proxy: %s: tunneling returns (%i)", scheme, status);
+ if (status == DONE) {
+ status = OK;
+ }
+ return status;
+}
+
PROXY_DECLARE (const char *) ap_proxy_show_hcmethod(hcmethod_t method)
{
proxy_hcmethods_t *m = proxy_hcmethods;
@@ -4046,4 +5637,14 @@ void proxy_util_register_hooks(apr_pool_t *p)
APR_REGISTER_OPTIONAL_FN(ap_proxy_retry_worker);
APR_REGISTER_OPTIONAL_FN(ap_proxy_clear_connection);
APR_REGISTER_OPTIONAL_FN(proxy_balancer_get_best_worker);
+
+ {
+ apr_time_t *start_time = ap_retained_data_get("proxy_start_time");
+ if (start_time == NULL) {
+ start_time = ap_retained_data_create("proxy_start_time",
+ sizeof(*start_time));
+ *start_time = apr_time_now();
+ }
+ proxy_start_time = start_time;
+ }
}
diff --git a/modules/proxy/proxy_util.h b/modules/proxy/proxy_util.h
index 202be8d..bc131da 100644
--- a/modules/proxy/proxy_util.h
+++ b/modules/proxy/proxy_util.h
@@ -31,9 +31,9 @@ PROXY_DECLARE(int) ap_proxy_is_domainname(struct dirconn_entry *This, apr_pool_t
PROXY_DECLARE(int) ap_proxy_is_hostname(struct dirconn_entry *This, apr_pool_t *p);
PROXY_DECLARE(int) ap_proxy_is_word(struct dirconn_entry *This, apr_pool_t *p);
-PROXY_DECLARE_DATA extern int proxy_lb_workers;
-PROXY_DECLARE_DATA extern const apr_strmatch_pattern *ap_proxy_strmatch_path;
-PROXY_DECLARE_DATA extern const apr_strmatch_pattern *ap_proxy_strmatch_domain;
+extern PROXY_DECLARE_DATA int proxy_lb_workers;
+extern PROXY_DECLARE_DATA const apr_strmatch_pattern *ap_proxy_strmatch_path;
+extern PROXY_DECLARE_DATA const apr_strmatch_pattern *ap_proxy_strmatch_domain;
/**
* Register optional functions declared within proxy_util.c.