diff options
Diffstat (limited to 'modules/proxy')
| -rw-r--r-- | modules/proxy/ajp.h | 1 | ||||
| -rw-r--r-- | modules/proxy/ajp_utils.c | 2 | ||||
| -rw-r--r-- | modules/proxy/mod_proxy.c | 209 | ||||
| -rw-r--r-- | modules/proxy/mod_proxy.h | 22 | ||||
| -rw-r--r-- | modules/proxy/mod_proxy_ajp.c | 91 | ||||
| -rw-r--r-- | modules/proxy/mod_proxy_balancer.c | 65 | ||||
| -rw-r--r-- | modules/proxy/mod_proxy_ftp.c | 7 | ||||
| -rw-r--r-- | modules/proxy/mod_proxy_http.c | 228 | ||||
| -rw-r--r-- | modules/proxy/proxy_util.c | 285 |
9 files changed, 711 insertions, 199 deletions
diff --git a/modules/proxy/ajp.h b/modules/proxy/ajp.h index 8c022fb3..8327e8d4 100644 --- a/modules/proxy/ajp.h +++ b/modules/proxy/ajp.h @@ -147,6 +147,7 @@ struct ajp_msg #define AJP_MSG_BUFFER_SZ 8192 #define AJP_MAX_BUFFER_SZ 65536 #define AJP13_MAX_SEND_BODY_SZ (AJP_MAX_BUFFER_SZ - AJP_HEADER_SZ) +#define AJP_PING_PONG_SZ 128 /** Send a request from web server to container*/ #define CMD_AJP13_FORWARD_REQUEST (unsigned char)2 diff --git a/modules/proxy/ajp_utils.c b/modules/proxy/ajp_utils.c index 5a0e8772..780aeb48 100644 --- a/modules/proxy/ajp_utils.c +++ b/modules/proxy/ajp_utils.c @@ -31,7 +31,7 @@ apr_status_t ajp_handle_cping_cpong(apr_socket_t *sock, ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "Into ajp_handle_cping_cpong"); - rc = ajp_msg_create(r->pool, AJP_HEADER_SZ_LEN+1, &msg); + rc = ajp_msg_create(r->pool, AJP_PING_PONG_SZ, &msg); if (rc != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "ajp_handle_cping_cpong: ajp_msg_create failed"); diff --git a/modules/proxy/mod_proxy.c b/modules/proxy/mod_proxy.c index de48638b..00bcfeba 100644 --- a/modules/proxy/mod_proxy.c +++ b/modules/proxy/mod_proxy.c @@ -168,6 +168,15 @@ static const char *set_worker_param(apr_pool_t *p, return "KeepAlive must be On|Off"; worker->keepalive_set = 1; } + else if (!strcasecmp(key, "disablereuse")) { + if (!strcasecmp(val, "on")) + worker->disablereuse = 1; + else if (!strcasecmp(val, "off")) + worker->disablereuse = 0; + else + return "DisableReuse must be On|Off"; + worker->disablereuse_set = 1; + } else if (!strcasecmp(key, "route")) { /* Worker route. */ @@ -432,6 +441,59 @@ static int proxy_detect(request_rec *r) return DECLINED; } +static const char *proxy_interpolate(request_rec *r, const char *str) +{ + /* Interpolate an env str in a configuration string + * Syntax ${var} --> value_of(var) + * Method: replace one var, and recurse on remainder of string + * Nothing clever here, and crap like nested vars may do silly things + * but we'll at least avoid sending the unwary into a loop + */ + const char *start; + const char *end; + const char *var; + const char *val; + const char *firstpart; + + start = ap_strstr_c(str, "${"); + if (start == NULL) { + return str; + } + end = ap_strchr_c(start+2, '}'); + if (end == NULL) { + return str; + } + /* OK, this is syntax we want to interpolate. Is there such a var ? */ + var = apr_pstrndup(r->pool, start+2, end-(start+2)); + val = apr_table_get(r->subprocess_env, var); + firstpart = apr_pstrndup(r->pool, str, (start-str)); + + if (val == NULL) { + return apr_pstrcat(r->pool, firstpart, + proxy_interpolate(r, end+1), NULL); + } + else { + return apr_pstrcat(r->pool, firstpart, val, + proxy_interpolate(r, end+1), NULL); + } +} +static apr_array_header_t *proxy_vars(request_rec *r, + apr_array_header_t *hdr) +{ + int i; + apr_array_header_t *ret = apr_array_make(r->pool, hdr->nelts, + sizeof (struct proxy_alias)); + struct proxy_alias *old = (struct proxy_alias *) hdr->elts; + + for (i = 0; i < hdr->nelts; ++i) { + struct proxy_alias *newcopy = apr_array_push(ret); + newcopy->fake = (old[i].flags & PROXYPASS_INTERPOLATE) + ? proxy_interpolate(r, old[i].fake) : old[i].fake; + newcopy->real = (old[i].flags & PROXYPASS_INTERPOLATE) + ? proxy_interpolate(r, old[i].real) : old[i].real; + } + return ret; +} static int proxy_trans(request_rec *r) { void *sconf = r->server->module_config; @@ -439,6 +501,10 @@ static int proxy_trans(request_rec *r) (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module); int i, len; struct proxy_alias *ent = (struct proxy_alias *) conf->aliases->elts; + proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config, + &proxy_module); + const char *fake; + const char *real; ap_regmatch_t regm[AP_MAX_REG_MATCH]; ap_regmatch_t reg1[AP_MAX_REG_MATCH]; char *found = NULL; @@ -459,9 +525,18 @@ static int proxy_trans(request_rec *r) for (i = 0; i < conf->aliases->nelts; i++) { unsigned int nocanon = ent[i].flags & PROXYPASS_NOCANON; const char *use_uri = nocanon ? r->unparsed_uri : r->uri; + if ((dconf->interpolate_env == 1) + && (ent[i].flags & PROXYPASS_INTERPOLATE)) { + fake = proxy_interpolate(r, ent[i].fake); + real = proxy_interpolate(r, ent[i].real); + } + else { + fake = ent[i].fake; + real = ent[i].real; + } if (ent[i].regex) { if (!ap_regexec(ent[i].regex, r->uri, AP_MAX_REG_MATCH, regm, 0)) { - if ((ent[i].real[0] == '!') && (ent[i].real[1] == '\0')) { + if ((real[0] == '!') && (real[1] == '\0')) { return DECLINED; } /* test that we haven't reduced the URI */ @@ -470,8 +545,7 @@ static int proxy_trans(request_rec *r) mismatch = 1; use_uri = r->uri; } - found = ap_pregsub(r->pool, ent[i].real, use_uri, - AP_MAX_REG_MATCH, + found = ap_pregsub(r->pool, real, use_uri, AP_MAX_REG_MATCH, (use_uri == r->uri) ? regm : reg1); /* Note: The strcmp() below catches cases where there * was no regex substitution. This is so cases like: @@ -486,20 +560,20 @@ static int proxy_trans(request_rec *r) * * which may be confusing. */ - if (found && strcmp(found, ent[i].real)) { + if (found && strcmp(found, real)) { found = apr_pstrcat(r->pool, "proxy:", found, NULL); } else { - found = apr_pstrcat(r->pool, "proxy:", ent[i].real, + found = apr_pstrcat(r->pool, "proxy:", real, use_uri, NULL); } } } else { - len = alias_match(r->uri, ent[i].fake); + len = alias_match(r->uri, fake); - if (len > 0) { - if ((ent[i].real[0] == '!') && (ent[i].real[1] == '\0')) { + if (len != 0) { + if ((real[0] == '!') && (real[1] == '\0')) { return DECLINED; } if (nocanon @@ -507,7 +581,7 @@ static int proxy_trans(request_rec *r) mismatch = 1; use_uri = r->uri; } - found = apr_pstrcat(r->pool, "proxy:", ent[i].real, + found = apr_pstrcat(r->pool, "proxy:", real, use_uri + len, NULL); } } @@ -591,6 +665,7 @@ static int proxy_map_location(request_rec *r) return OK; } + /* -------------------------------------------------------------- */ /* Fixup the filename */ @@ -601,6 +676,8 @@ static int proxy_fixup(request_rec *r) { char *url, *p; int access_status; + proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config, + &proxy_module); if (!r->proxyreq || !r->filename || strncmp(r->filename, "proxy:", 6) != 0) return DECLINED; @@ -608,6 +685,17 @@ static int proxy_fixup(request_rec *r) /* XXX: Shouldn't we try this before we run the proxy_walk? */ url = &r->filename[6]; + if ((dconf->interpolate_env == 1) && (r->proxyreq == PROXYREQ_REVERSE)) { + /* create per-request copy of reverse proxy conf, + * and interpolate vars in it + */ + proxy_req_conf *rconf = apr_palloc(r->pool, sizeof(proxy_req_conf)); + ap_set_module_config(r->request_config, &proxy_module, rconf); + rconf->raliases = proxy_vars(r, dconf->raliases); + rconf->cookie_paths = proxy_vars(r, dconf->cookie_paths); + rconf->cookie_domains = proxy_vars(r, dconf->cookie_domains); + } + /* canonicalise each specific scheme */ if ((access_status = proxy_run_canon_handler(r, url))) { return access_status; @@ -830,12 +918,41 @@ static int proxy_handler(request_rec *r) ents[i].hostname, ents[i].port); - /* an error or success */ - if (access_status != DECLINED && - access_status != HTTP_BAD_GATEWAY) { - goto cleanup; + /* Did the scheme handler process the request? */ + if (access_status != DECLINED) { + const char *cl_a; + char *end; + apr_off_t cl; + + /* + * An fatal error or success, so no point in + * retrying with a direct connection. + */ + if (access_status != HTTP_BAD_GATEWAY) { + goto cleanup; + } + cl_a = apr_table_get(r->headers_in, "Content-Length"); + if (cl_a) { + apr_strtoff(&cl, cl_a, &end, 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; + } + } + /* + * Transfer-Encoding was set as input header, so we had + * a request body. We cannot retry with a direct + * connection for the same reason as above. + */ + if (apr_table_get(r->headers_in, "Transfer-Encoding")) { + goto cleanup; + } } - /* we failed to talk to the upstream proxy */ } } } @@ -1002,6 +1119,7 @@ static void *create_proxy_dir_config(apr_pool_t *p, char *dummy) new->cookie_domains = apr_array_make(p, 10, sizeof(struct proxy_alias)); new->cookie_path_str = apr_strmatch_precompile(p, "path=", 0); new->cookie_domain_str = apr_strmatch_precompile(p, "domain=", 0); + new->interpolate_env = -1; /* unset */ return (void *) new; } @@ -1024,6 +1142,8 @@ static void *merge_proxy_dir_config(apr_pool_t *p, void *basev, void *addv) = apr_array_append(p, base->cookie_domains, add->cookie_domains); new->cookie_path_str = base->cookie_path_str; new->cookie_domain_str = base->cookie_domain_str; + new->interpolate_env = (add->interpolate_env == -1) ? base->interpolate_env + : add->interpolate_env; new->ftp_directory_charset = add->ftp_directory_charset ? add->ftp_directory_charset : base->ftp_directory_charset; @@ -1140,6 +1260,9 @@ static const char * else if (!strcasecmp(word,"nocanon")) { flags |= PROXYPASS_NOCANON; } + else if (!strcasecmp(word,"interpolate")) { + flags |= PROXYPASS_INTERPOLATE; + } else { char *val = strchr(word, '='); if (!val) { @@ -1237,31 +1360,41 @@ static const char * } -static const char * - add_pass_reverse(cmd_parms *cmd, void *dconf, const char *f, const char *r) +static const char * add_pass_reverse(cmd_parms *cmd, void *dconf, const char *f, + const char *r, const char *i) { proxy_dir_conf *conf = dconf; struct proxy_alias *new; - - if (r!=NULL && cmd->path == NULL ) { - new = apr_array_push(conf->raliases); - new->fake = f; - new->real = r; - } else if (r==NULL && cmd->path != NULL) { - new = apr_array_push(conf->raliases); - new->fake = cmd->path; - new->real = f; - } else { - if ( r == NULL) + const char *fake; + const char *real; + const char *interp; + + if (cmd->path == NULL) { + fake = f; + real = r; + interp = i; + if (r == NULL || !strcasecmp(r, "interpolate")) { return "ProxyPassReverse needs a path when not defined in a location"; - else + } + } + else { + fake = cmd->path; + real = f; + if (r && strcasecmp(r, "interpolate")) { return "ProxyPassReverse can not have a path when defined in a location"; + } + interp = r; } + new = apr_array_push(conf->raliases); + new->fake = fake; + new->real = real; + new->flags = interp ? PROXYPASS_INTERPOLATE : 0; + return NULL; } -static const char* - cookie_path(cmd_parms *cmd, void *dconf, const char *f, const char *r) +static const char* cookie_path(cmd_parms *cmd, void *dconf, const char *f, + const char *r, const char *interp) { proxy_dir_conf *conf = dconf; struct proxy_alias *new; @@ -1269,11 +1402,12 @@ static const char* new = apr_array_push(conf->cookie_paths); new->fake = f; new->real = r; + new->flags = interp ? PROXYPASS_INTERPOLATE : 0; return NULL; } -static const char* - cookie_domain(cmd_parms *cmd, void *dconf, const char *f, const char *r) +static const char* cookie_domain(cmd_parms *cmd, void *dconf, const char *f, + const char *r, const char *interp) { proxy_dir_conf *conf = dconf; struct proxy_alias *new; @@ -1281,7 +1415,7 @@ static const char* new = apr_array_push(conf->cookie_domains); new->fake = f; new->real = r; - + new->flags = interp ? PROXYPASS_INTERPOLATE : 0; return NULL; } @@ -1902,15 +2036,18 @@ static const command_rec proxy_cmds[] = "a scheme, partial URL or '*' and a proxy server"), AP_INIT_TAKE2("ProxyRemoteMatch", add_proxy_regex, NULL, RSRC_CONF, "a regex pattern and a proxy server"), + AP_INIT_FLAG("ProxyPassInterpolateEnv", ap_set_flag_slot, + (void*)APR_OFFSETOF(proxy_dir_conf, interpolate_env), + RSRC_CONF|ACCESS_CONF, "Interpolate Env Vars in reverse Proxy") , AP_INIT_RAW_ARGS("ProxyPass", add_pass_noregex, NULL, RSRC_CONF|ACCESS_CONF, "a virtual path and a URL"), AP_INIT_RAW_ARGS("ProxyPassMatch", add_pass_regex, NULL, RSRC_CONF|ACCESS_CONF, "a virtual path and a URL"), - AP_INIT_TAKE12("ProxyPassReverse", add_pass_reverse, NULL, RSRC_CONF|ACCESS_CONF, + AP_INIT_TAKE123("ProxyPassReverse", add_pass_reverse, NULL, RSRC_CONF|ACCESS_CONF, "a virtual path and a URL for reverse proxy behaviour"), - AP_INIT_TAKE2("ProxyPassReverseCookiePath", cookie_path, NULL, + AP_INIT_TAKE23("ProxyPassReverseCookiePath", cookie_path, NULL, RSRC_CONF|ACCESS_CONF, "Path rewrite rule for proxying cookies"), - AP_INIT_TAKE2("ProxyPassReverseCookieDomain", cookie_domain, NULL, + AP_INIT_TAKE23("ProxyPassReverseCookieDomain", cookie_domain, NULL, RSRC_CONF|ACCESS_CONF, "Domain rewrite rule for proxying cookies"), AP_INIT_ITERATE("ProxyBlock", set_proxy_exclude, NULL, RSRC_CONF, "A list of names, hosts or domains to which the proxy will not connect"), diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h index 3944104e..fdb48bf3 100644 --- a/modules/proxy/mod_proxy.h +++ b/modules/proxy/mod_proxy.h @@ -110,6 +110,7 @@ struct proxy_remote { }; #define PROXYPASS_NOCANON 0x01 +#define PROXYPASS_INTERPOLATE 0x02 struct proxy_alias { const char *real; const char *fake; @@ -213,14 +214,24 @@ typedef struct { const apr_strmatch_pattern* cookie_path_str; const apr_strmatch_pattern* cookie_domain_str; const char *ftp_directory_charset; + int interpolate_env; } proxy_dir_conf; +/* if we interpolate env vars per-request, we'll need a per-request + * copy of the reverse proxy config + */ +typedef struct { + apr_array_header_t *raliases; + apr_array_header_t* cookie_paths; + apr_array_header_t* cookie_domains; +} proxy_req_conf; + typedef struct { conn_rec *connection; const char *hostname; apr_port_t port; int is_ssl; - apr_pool_t *pool; /* Subpool used for creating socket */ + apr_pool_t *pool; /* Subpool for hostname and addr data */ apr_socket_t *sock; /* Connection socket */ apr_sockaddr_t *addr; /* Preparsed remote address info */ apr_uint32_t flags; /* Conection flags */ @@ -231,6 +242,11 @@ typedef struct { #if APR_HAS_THREADS int inreslist; /* connection in apr_reslist? */ #endif + apr_pool_t *scpool; /* Subpool used for socket and connection data */ + request_rec *r; /* Request record of the frontend request + * which the backend currently answers. */ + int need_flush;/* Flag to decide whether we need to flush the + * filter chain or not */ } proxy_conn_rec; typedef struct { @@ -337,6 +353,8 @@ struct proxy_worker { apr_interval_time_t ping_timeout; char ping_timeout_set; char retry_set; + char disablereuse; + char disablereuse_set; }; /* @@ -473,6 +491,8 @@ PROXY_DECLARE(apr_status_t) ap_proxy_string_read(conn_rec *c, apr_bucket_brigade PROXY_DECLARE(void) ap_proxy_table_unmerge(apr_pool_t *p, apr_table_t *t, char *key); /* DEPRECATED (will be replaced with ap_proxy_connect_backend */ PROXY_DECLARE(int) ap_proxy_connect_to_backend(apr_socket_t **, const char *, apr_sockaddr_t *, const char *, proxy_server_conf *, server_rec *, apr_pool_t *); +PROXY_DECLARE(apr_status_t) ap_proxy_ssl_connection_cleanup(proxy_conn_rec *conn, + request_rec *r); PROXY_DECLARE(int) ap_proxy_ssl_enable(conn_rec *c); PROXY_DECLARE(int) ap_proxy_ssl_disable(conn_rec *c); PROXY_DECLARE(int) ap_proxy_conn_is_https(conn_rec *c); diff --git a/modules/proxy/mod_proxy_ajp.c b/modules/proxy/mod_proxy_ajp.c index bad2b26e..80a6e0ab 100644 --- a/modules/proxy/mod_proxy_ajp.c +++ b/modules/proxy/mod_proxy_ajp.c @@ -29,7 +29,8 @@ module AP_MODULE_DECLARE_DATA proxy_ajp_module; */ static int proxy_ajp_canon(request_rec *r, char *url) { - char *host, *path, *search, sport[7]; + char *host, *path, sport[7]; + char *search = NULL; const char *err; apr_port_t port = AJP13_DEF_PORT; @@ -57,23 +58,18 @@ static int proxy_ajp_canon(request_rec *r, char *url) } /* - * now parse path/search args, according to rfc1738 - * - * N.B. if this isn't a true proxy request, then the URL _path_ - * has already been decoded. True proxy requests have - * r->uri == r->unparsed_uri, and no others have that property. + * now parse path/search args, according to rfc1738: + * process the path. With proxy-noncanon set (by + * mod_proxy) we use the raw, unparsed uri */ - if (r->uri == r->unparsed_uri) { - search = strchr(url, '?'); - if (search != NULL) - *(search++) = '\0'; + if (apr_table_get(r->notes, "proxy-nocanon")) { + path = url; /* this is the raw path */ } - else + else { + path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0, + r->proxyreq); search = r->args; - - /* process path */ - path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0, - r->proxyreq); + } if (path == NULL) return HTTP_BAD_REQUEST; @@ -89,6 +85,37 @@ static int proxy_ajp_canon(request_rec *r, char *url) return OK; } +#define METHOD_NON_IDEMPOTENT 0 +#define METHOD_IDEMPOTENT 1 +#define METHOD_IDEMPOTENT_WITH_ARGS 2 + +static int is_idempotent(request_rec *r) +{ + /* + * RFC2616 (9.1.2): GET, HEAD, PUT, DELETE, OPTIONS, TRACE are considered + * idempotent. Hint: HEAD requests use M_GET as method number as well. + */ + switch (r->method_number) { + case M_GET: + case M_DELETE: + case M_PUT: + case M_OPTIONS: + case M_TRACE: + /* + * If the request has arguments it might have side-effects and thus + * it might be undesirable to resent it to a backend again + * automatically. + */ + if (r->args) { + return METHOD_IDEMPOTENT_WITH_ARGS; + } + return METHOD_IDEMPOTENT; + /* Everything else is not considered idempotent. */ + default: + return METHOD_NON_IDEMPOTENT; + } +} + /* * XXX: AJP Auto Flushing * @@ -122,7 +149,7 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r, apr_bucket_brigade *input_brigade; apr_bucket_brigade *output_brigade; ajp_msg_t *msg; - apr_size_t bufsiz; + apr_size_t bufsiz = 0; char *buff; apr_uint16_t size; const char *tenc; @@ -138,6 +165,7 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r, proxy_server_conf *psf = ap_get_module_config(r->server->module_config, &proxy_module); apr_size_t maxsize = AJP_MSG_BUFFER_SZ; + int send_body = 0; if (psf->io_buffer_size_set) maxsize = psf->io_buffer_size; @@ -161,8 +189,17 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r, conn->worker->hostname); if (status == AJP_EOVERFLOW) return HTTP_BAD_REQUEST; - else - return HTTP_SERVICE_UNAVAILABLE; + else { + /* + * This is only non fatal when the method is idempotent. In this + * case we can dare to retry it with a different worker if we are + * a balancer member. + */ + if (is_idempotent(r) == METHOD_IDEMPOTENT) { + return HTTP_SERVICE_UNAVAILABLE; + } + return HTTP_INTERNAL_SERVER_ERROR; + } } /* allocate an AJP message to store the data of the buckets */ @@ -231,9 +268,14 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r, "proxy: send failed to %pI (%s)", conn->worker->cp->addr, conn->worker->hostname); - return HTTP_SERVICE_UNAVAILABLE; + /* + * It is fatal when we failed to send a (part) of the request + * body. + */ + return HTTP_INTERNAL_SERVER_ERROR; } conn->worker->s->transferred += bufsiz; + send_body = 1; } } @@ -249,7 +291,16 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r, "proxy: read response failed from %pI (%s)", conn->worker->cp->addr, conn->worker->hostname); - return HTTP_SERVICE_UNAVAILABLE; + /* + * This is only non fatal when we have not sent (parts) of a possible + * request body so far (we do not store it and thus cannot sent it + * again) and the method is idempotent. In this case we can dare to + * retry it with a different worker if we are a balancer member. + */ + if (!send_body && (is_idempotent(r) == METHOD_IDEMPOTENT)) { + return HTTP_SERVICE_UNAVAILABLE; + } + return HTTP_INTERNAL_SERVER_ERROR; } /* parse the reponse */ result = ajp_parse_type(r, conn->data); diff --git a/modules/proxy/mod_proxy_balancer.c b/modules/proxy/mod_proxy_balancer.c index d2ae88bb..bcc47cfc 100644 --- a/modules/proxy/mod_proxy_balancer.c +++ b/modules/proxy/mod_proxy_balancer.c @@ -23,12 +23,16 @@ #include "ap_mpm.h" #include "apr_version.h" #include "apr_hooks.h" +#include "apr_uuid.h" module AP_MODULE_DECLARE_DATA proxy_balancer_module; +static char balancer_nonce[APR_UUID_FORMATTED_LENGTH + 1]; + static int proxy_balancer_canon(request_rec *r, char *url) { - char *host, *path, *search; + char *host, *path; + char *search = NULL; const char *err; apr_port_t port = 0; @@ -52,21 +56,19 @@ static int proxy_balancer_canon(request_rec *r, char *url) url, err); return HTTP_BAD_REQUEST; } - /* now parse path/search args, according to rfc1738 */ - /* N.B. if this isn't a true proxy request, then the URL _path_ - * has already been decoded. True proxy requests have r->uri - * == r->unparsed_uri, and no others have that property. + /* + * now parse path/search args, according to rfc1738: + * process the path. With proxy-noncanon set (by + * mod_proxy) we use the raw, unparsed uri */ - if (r->uri == r->unparsed_uri) { - search = strchr(url, '?'); - if (search != NULL) - *(search++) = '\0'; + if (apr_table_get(r->notes, "proxy-nocanon")) { + path = url; /* this is the raw path */ } - else + else { + path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0, + r->proxyreq); search = r->args; - - /* process path */ - path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0, r->proxyreq); + } if (path == NULL) return HTTP_BAD_REQUEST; @@ -589,6 +591,31 @@ static void recalc_factors(proxy_balancer *balancer) } } +/* post_config hook: */ +static int balancer_init(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + void *data; + const char *userdata_key = "mod_proxy_balancer_init"; + apr_uuid_t uuid; + + /* balancer_init() will be called twice during startup. So, only + * set up the static data the second time through. */ + apr_pool_userdata_get(&data, userdata_key, s->process->pool); + if (!data) { + apr_pool_userdata_set((const void *)1, userdata_key, + apr_pool_cleanup_null, s->process->pool); + return OK; + } + + /* Retrieve a UUID and store the nonce for the lifetime of + * the process. */ + apr_uuid_get(&uuid); + apr_uuid_format(balancer_nonce, &uuid); + + return OK; +} + /* Manages the loadfactors and member status */ static int balancer_handler(request_rec *r) @@ -631,6 +658,14 @@ static int balancer_handler(request_rec *r) return HTTP_BAD_REQUEST; } } + + /* Check that the supplied nonce matches this server's nonce; + * otherwise ignore all parameters, to prevent a CSRF attack. */ + if ((name = apr_table_get(params, "nonce")) == NULL + || strcmp(balancer_nonce, name) != 0) { + apr_table_clear(params); + } + if ((name = apr_table_get(params, "b"))) bsel = ap_proxy_get_balancer(r->pool, conf, apr_pstrcat(r->pool, "balancer://", name, NULL)); @@ -762,6 +797,7 @@ static int balancer_handler(request_rec *r) ap_rvputs(r, "<tr>\n<td><a href=\"", r->uri, "?b=", balancer->name + sizeof("balancer://") - 1, "&w=", ap_escape_uri(r->pool, worker->name), + "&nonce=", balancer_nonce, "\">", NULL); ap_rvputs(r, worker->name, "</a></td>", NULL); ap_rvputs(r, "<td>", ap_escape_html(r->pool, worker->s->route), @@ -825,6 +861,8 @@ static int balancer_handler(request_rec *r) ap_rvputs(r, "<input type=hidden name=\"b\" ", NULL); ap_rvputs(r, "value=\"", bsel->name + sizeof("balancer://") - 1, "\">\n</form>\n", NULL); + ap_rvputs(r, "<input type=hidden name=\"nonce\" value=\"", + balancer_nonce, "\">\n", NULL); ap_rputs("<hr />\n", r); } ap_rputs(ap_psignature("",r), r); @@ -1063,6 +1101,7 @@ static void ap_proxy_balancer_register_hook(apr_pool_t *p) */ static const char *const aszPred[] = { "mpm_winnt.c", "mod_proxy.c", NULL}; /* manager handler */ + ap_hook_post_config(balancer_init, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(balancer_handler, NULL, NULL, APR_HOOK_FIRST); ap_hook_child_init(child_init, aszPred, NULL, APR_HOOK_MIDDLE); proxy_hook_pre_request(proxy_balancer_pre_request, NULL, NULL, APR_HOOK_FIRST); diff --git a/modules/proxy/mod_proxy_ftp.c b/modules/proxy/mod_proxy_ftp.c index 3cacac6e..75a2054e 100644 --- a/modules/proxy/mod_proxy_ftp.c +++ b/modules/proxy/mod_proxy_ftp.c @@ -314,6 +314,7 @@ static apr_status_t proxy_send_dir_filter(ap_filter_t *f, /* basedir is either "", or "/%2f" for the "squid %2f hack" */ const char *basedir = ""; /* By default, path is relative to the $HOME dir */ char *wildcard = NULL; + const char *escpath; /* Save "scheme://site" prefix without password */ site = apr_uri_unparse(p, &f->r->parsed_uri, APR_URI_UNP_OMITPASSWORD | APR_URI_UNP_OMITPATHINFO); @@ -350,13 +351,14 @@ static apr_status_t proxy_send_dir_filter(ap_filter_t *f, str = (basedir[0] != '\0') ? "<a href=\"/%2f/\">%2f</a>/" : ""; /* print "ftp://host/" */ + escpath = ap_escape_html(p, path); str = apr_psprintf(p, DOCTYPE_HTML_3_2 "<html>\n <head>\n <title>%s%s%s</title>\n" + "<base href=\"%s%s%s\">\n" " </head>\n" " <body>\n <h2>Directory of " "<a href=\"/\">%s</a>/%s", - site, basedir, ap_escape_html(p, path), - site, str); + site, basedir, escpath, site, basedir, escpath, site, str); APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str), p, c->bucket_alloc)); @@ -959,6 +961,7 @@ static int proxy_ftp_handler(request_rec *r, proxy_worker *worker, } /* TODO: see if ftp could use determine_connection */ backend->addr = connect_addr; + backend->r = r; ap_set_module_config(c->conn_config, &proxy_ftp_module, backend); } diff --git a/modules/proxy/mod_proxy_http.c b/modules/proxy/mod_proxy_http.c index e5f654bb..3ca21895 100644 --- a/modules/proxy/mod_proxy_http.c +++ b/modules/proxy/mod_proxy_http.c @@ -33,7 +33,8 @@ static apr_status_t ap_proxy_http_cleanup(const char *scheme, */ static int proxy_http_canon(request_rec *r, char *url) { - char *host, *path, *search, sport[7]; + char *host, *path, sport[7]; + char *search = NULL; const char *err; const char *scheme; apr_port_t port, def_port; @@ -67,21 +68,11 @@ static int proxy_http_canon(request_rec *r, char *url) return HTTP_BAD_REQUEST; } - /* now parse path/search args, according to rfc1738 */ - /* N.B. if this isn't a true proxy request, then the URL _path_ - * has already been decoded. True proxy requests have r->uri - * == r->unparsed_uri, and no others have that property. - */ - if (r->uri == r->unparsed_uri) { - search = strchr(url, '?'); - if (search != NULL) - *(search++) = '\0'; - } - else - search = r->args; - - /* process path */ - /* In a reverse proxy, our URL has been processed, so canonicalise + /* + * now parse path/search args, according to rfc1738: + * process the path. + * + * In a reverse proxy, our URL has been processed, so canonicalise * unless proxy-nocanon is set to say it's raw * In a forward proxy, we have and MUST NOT MANGLE the original. */ @@ -94,6 +85,7 @@ static int proxy_http_canon(request_rec *r, char *url) else { path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0, r->proxyreq); + search = r->args; } break; case PROXYREQ_PROXY: @@ -259,7 +251,7 @@ static void terminate_headers(apr_bucket_alloc_t *bucket_alloc, APR_BRIGADE_INSERT_TAIL(header_brigade, e); } -static apr_status_t pass_brigade(apr_bucket_alloc_t *bucket_alloc, +static int pass_brigade(apr_bucket_alloc_t *bucket_alloc, request_rec *r, proxy_conn_rec *conn, conn_rec *origin, apr_bucket_brigade *bb, int flush) @@ -279,22 +271,27 @@ static apr_status_t pass_brigade(apr_bucket_alloc_t *bucket_alloc, ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server, "proxy: pass request body failed to %pI (%s)", conn->addr, conn->hostname); - return status; + if (origin->aborted) { + return APR_STATUS_IS_TIMEUP(status) ? HTTP_GATEWAY_TIME_OUT : HTTP_BAD_GATEWAY; + } + else { + return HTTP_BAD_REQUEST; + } } apr_brigade_cleanup(bb); - return APR_SUCCESS; + return OK; } #define MAX_MEM_SPOOL 16384 -static apr_status_t stream_reqbody_chunked(apr_pool_t *p, +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; + int seen_eos = 0, rv = OK; apr_size_t hdr_len; apr_off_t bytes; apr_status_t status; @@ -352,7 +349,7 @@ static apr_status_t stream_reqbody_chunked(apr_pool_t *p, */ status = ap_save_brigade(NULL, &bb, &input_brigade, p); if (status != APR_SUCCESS) { - return status; + return HTTP_INTERNAL_SERVER_ERROR; } header_brigade = NULL; @@ -362,9 +359,9 @@ static apr_status_t stream_reqbody_chunked(apr_pool_t *p, } /* The request is flushed below this loop with chunk EOS header */ - status = pass_brigade(bucket_alloc, r, p_conn, origin, bb, 0); - if (status != APR_SUCCESS) { - return status; + rv = pass_brigade(bucket_alloc, r, p_conn, origin, bb, 0); + if (rv != OK) { + return rv; } if (seen_eos) { @@ -376,7 +373,7 @@ static apr_status_t stream_reqbody_chunked(apr_pool_t *p, HUGE_STRING_LEN); if (status != APR_SUCCESS) { - return status; + return HTTP_BAD_REQUEST; } } @@ -403,11 +400,11 @@ static apr_status_t stream_reqbody_chunked(apr_pool_t *p, APR_BRIGADE_INSERT_TAIL(bb, e); /* Now we have headers-only, or the chunk EOS mark; flush it */ - status = pass_brigade(bucket_alloc, r, p_conn, origin, bb, 1); - return status; + rv = pass_brigade(bucket_alloc, r, p_conn, origin, bb, 1); + return rv; } -static apr_status_t stream_reqbody_cl(apr_pool_t *p, +static int stream_reqbody_cl(apr_pool_t *p, request_rec *r, proxy_conn_rec *p_conn, conn_rec *origin, @@ -415,7 +412,7 @@ static apr_status_t stream_reqbody_cl(apr_pool_t *p, apr_bucket_brigade *input_brigade, const char *old_cl_val) { - int seen_eos = 0; + 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; @@ -428,7 +425,7 @@ static apr_status_t stream_reqbody_cl(apr_pool_t *p, add_cl(p, bucket_alloc, header_brigade, old_cl_val); if (APR_SUCCESS != (status = apr_strtoff(&cl_val, old_cl_val, NULL, 0))) { - return status; + return HTTP_INTERNAL_SERVER_ERROR; } } terminate_headers(bucket_alloc, header_brigade); @@ -476,7 +473,7 @@ static apr_status_t stream_reqbody_cl(apr_pool_t *p, */ status = ap_save_brigade(NULL, &bb, &input_brigade, p); if (status != APR_SUCCESS) { - return status; + return HTTP_INTERNAL_SERVER_ERROR; } header_brigade = NULL; @@ -486,9 +483,9 @@ static apr_status_t stream_reqbody_cl(apr_pool_t *p, } /* Once we hit EOS, we are ready to flush. */ - status = pass_brigade(bucket_alloc, r, p_conn, origin, bb, seen_eos); - if (status != APR_SUCCESS) { - return status; + rv = pass_brigade(bucket_alloc, r, p_conn, origin, bb, seen_eos); + if (rv != OK) { + return rv ; } if (seen_eos) { @@ -500,7 +497,7 @@ static apr_status_t stream_reqbody_cl(apr_pool_t *p, HUGE_STRING_LEN); if (status != APR_SUCCESS) { - return status; + return HTTP_BAD_REQUEST; } } @@ -508,7 +505,7 @@ static apr_status_t stream_reqbody_cl(apr_pool_t *p, ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "proxy: client %s given Content-Length did not match" " number of body bytes read", r->connection->remote_ip); - return APR_EOF; + return HTTP_BAD_REQUEST; } if (header_brigade) { @@ -516,12 +513,13 @@ static apr_status_t stream_reqbody_cl(apr_pool_t *p, * body; send it now with the flush flag */ bb = header_brigade; - status = pass_brigade(bucket_alloc, r, p_conn, origin, bb, 1); + return(pass_brigade(bucket_alloc, r, p_conn, origin, bb, 1)); } - return status; + + return OK; } -static apr_status_t spool_reqbody_cl(apr_pool_t *p, +static int spool_reqbody_cl(apr_pool_t *p, request_rec *r, proxy_conn_rec *p_conn, conn_rec *origin, @@ -562,7 +560,7 @@ static apr_status_t spool_reqbody_cl(apr_pool_t *p, if (status != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server, "proxy: search for temporary directory failed"); - return status; + return HTTP_INTERNAL_SERVER_ERROR; } apr_filepath_merge(&template, temp_dir, "modproxy.tmp.XXXXXX", @@ -572,7 +570,7 @@ static apr_status_t spool_reqbody_cl(apr_pool_t *p, ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server, "proxy: creation of temporary file in directory %s failed", temp_dir); - return status; + return HTTP_INTERNAL_SERVER_ERROR; } } for (e = APR_BRIGADE_FIRST(input_brigade); @@ -592,7 +590,7 @@ static apr_status_t spool_reqbody_cl(apr_pool_t *p, ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server, "proxy: write to temporary file %s failed", tmpfile_name); - return status; + return HTTP_INTERNAL_SERVER_ERROR; } AP_DEBUG_ASSERT(bytes_read == bytes_written); fsize += bytes_written; @@ -612,7 +610,7 @@ static apr_status_t spool_reqbody_cl(apr_pool_t *p, */ status = ap_save_brigade(NULL, &body_brigade, &input_brigade, p); if (status != APR_SUCCESS) { - return status; + return HTTP_INTERNAL_SERVER_ERROR; } } @@ -628,7 +626,7 @@ static apr_status_t spool_reqbody_cl(apr_pool_t *p, HUGE_STRING_LEN); if (status != APR_SUCCESS) { - return status; + return HTTP_BAD_REQUEST; } } @@ -662,12 +660,11 @@ static apr_status_t spool_reqbody_cl(apr_pool_t *p, APR_BRIGADE_INSERT_TAIL(header_brigade, e); } /* This is all a single brigade, pass with flush flagged */ - status = pass_brigade(bucket_alloc, r, p_conn, origin, header_brigade, 1); - return status; + return(pass_brigade(bucket_alloc, r, p_conn, origin, header_brigade, 1)); } static -apr_status_t ap_proxy_http_request(apr_pool_t *p, request_rec *r, +int ap_proxy_http_request(apr_pool_t *p, request_rec *r, proxy_conn_rec *p_conn, conn_rec *origin, proxy_server_conf *conf, apr_uri_t *uri, @@ -690,7 +687,7 @@ apr_status_t ap_proxy_http_request(apr_pool_t *p, request_rec *r, const char *old_te_val = NULL; apr_off_t bytes_read = 0; apr_off_t bytes; - int force10; + int force10, rv; apr_table_t *headers_in_copy; header_brigade = apr_brigade_create(p, origin->bucket_alloc); @@ -932,7 +929,7 @@ apr_status_t ap_proxy_http_request(apr_pool_t *p, request_rec *r, ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "proxy: %s Transfer-Encoding is not supported", old_te_val); - return APR_EINVAL; + return HTTP_INTERNAL_SERVER_ERROR; } if (old_cl_val && old_te_val) { @@ -965,7 +962,7 @@ apr_status_t ap_proxy_http_request(apr_pool_t *p, request_rec *r, " from %s (%s)", p_conn->addr, p_conn->hostname ? p_conn->hostname: "", c->remote_ip, c->remote_host ? c->remote_host: ""); - return status; + return HTTP_BAD_REQUEST; } apr_brigade_length(temp_brigade, 1, &bytes); @@ -987,7 +984,7 @@ apr_status_t ap_proxy_http_request(apr_pool_t *p, request_rec *r, " to %pI (%s) from %s (%s)", p_conn->addr, p_conn->hostname ? p_conn->hostname: "", c->remote_ip, c->remote_host ? c->remote_host: ""); - return status; + return HTTP_INTERNAL_SERVER_ERROR; } /* Ensure we don't hit a wall where we have a buffer too small @@ -1101,37 +1098,38 @@ skip_body: /* send the request body, if any. */ switch(rb_method) { case RB_STREAM_CHUNKED: - status = stream_reqbody_chunked(p, r, p_conn, origin, header_brigade, + rv = stream_reqbody_chunked(p, r, p_conn, origin, header_brigade, input_brigade); break; case RB_STREAM_CL: - status = stream_reqbody_cl(p, r, p_conn, origin, header_brigade, + rv = stream_reqbody_cl(p, r, p_conn, origin, header_brigade, input_brigade, old_cl_val); break; case RB_SPOOL_CL: - status = spool_reqbody_cl(p, r, p_conn, origin, header_brigade, + rv = spool_reqbody_cl(p, r, p_conn, origin, header_brigade, input_brigade, (old_cl_val != NULL) || (old_te_val != NULL) || (bytes_read > 0)); break; default: /* shouldn't be possible */ - status = APR_EINVAL; + rv = HTTP_INTERNAL_SERVER_ERROR ; break; } - if (status != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server, + if (rv != OK) { + /* apr_errno value has been logged in lower level method */ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "proxy: pass request body failed to %pI (%s)" " from %s (%s)", p_conn->addr, p_conn->hostname ? p_conn->hostname: "", c->remote_ip, c->remote_host ? c->remote_host: ""); - return status; + return rv; } - return APR_SUCCESS; + return OK; } static void process_proxy_header(request_rec* r, proxy_dir_conf* c, @@ -1309,6 +1307,16 @@ apr_status_t ap_proxygetline(apr_bucket_brigade *bb, char *s, int n, request_rec return rv; } +/* + * Limit the number of interim respones we sent back to the client. Otherwise + * we suffer from a memory build up. Besides there is NO sense in sending back + * an unlimited number of interim responses to the client. Thus if we cross + * this limit send back a 502 (Bad Gateway). + */ +#ifndef AP_MAX_INTERIM_RESPONSES +#define AP_MAX_INTERIM_RESPONSES 10 +#endif + static apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, proxy_conn_rec *backend, @@ -1323,14 +1331,15 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, apr_bucket *e; apr_bucket_brigade *bb, *tmp_bb; int len, backasswards; - int interim_response; /* non-zero whilst interim 1xx responses - * are being read. */ + int interim_response = 0; /* non-zero whilst interim 1xx responses + * are being read. */ int pread_len = 0; apr_table_t *save_table; int backend_broke = 0; static const char *hop_by_hop_hdrs[] = {"Keep-Alive", "Proxy-Authenticate", "TE", "Trailer", "Upgrade", NULL}; int i; + const char *te = NULL; bb = apr_brigade_create(p, c->bucket_alloc); @@ -1358,6 +1367,56 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, "proxy: error reading status line from remote " "server %s", backend->hostname); + /* + * If we are a reverse proxy request shutdown the connection + * WITHOUT ANY response to trigger a retry by the client + * if allowed (as for idempotent requests). + * BUT currently we should not do this if the request is the + * first request on a keepalive connection as browsers like + * seamonkey only display an empty page in this case and do + * not do a retry. + */ + if (r->proxyreq == PROXYREQ_REVERSE && c->keepalives) { + apr_bucket *eos; + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "proxy: Closing connection to client because" + " reading from backend server %s failed. Number" + " of keepalives %i", backend->hostname, + c->keepalives); + ap_proxy_backend_broke(r, bb); + /* + * Add an EOC bucket to signal the ap_http_header_filter + * that it should get out of our way, BUT ensure that the + * EOC bucket is inserted BEFORE an EOS bucket in bb as + * some resource filters like mod_deflate pass everything + * up to the EOS down the chain immediately and sent the + * remainder of the brigade later (or even never). But in + * this case the ap_http_header_filter does not get out of + * our way soon enough. + */ + e = ap_bucket_eoc_create(c->bucket_alloc); + eos = APR_BRIGADE_LAST(bb); + while ((APR_BRIGADE_SENTINEL(bb) != eos) + && !APR_BUCKET_IS_EOS(eos)) { + eos = APR_BUCKET_PREV(eos); + } + if (eos == APR_BRIGADE_SENTINEL(bb)) { + APR_BRIGADE_INSERT_TAIL(bb, e); + } + else { + APR_BUCKET_INSERT_BEFORE(eos, e); + } + ap_pass_brigade(r->output_filters, bb); + /* Need to return OK to avoid sending an error message */ + return OK; + } + else if (!c->keepalives) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "proxy: NOT Closing connection to client" + " although reading from backend server %s" + " failed.", backend->hostname); + } return ap_proxyerror(r, HTTP_BAD_GATEWAY, "Error reading from remote server"); } @@ -1461,6 +1520,11 @@ 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"); /* strip connection listed hop-by-hop headers from response */ backend->close += ap_proxy_liststr(apr_table_get(r->headers_out, "Connection"), @@ -1469,7 +1533,9 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, if ((buf = apr_table_get(r->headers_out, "Content-Type"))) { ap_set_content_type(r, apr_pstrdup(p, buf)); } - ap_proxy_pre_http_request(origin,rp); + if (!ap_is_HTTP_INFO(r->status)) { + ap_proxy_pre_http_request(origin, rp); + } /* Clear hop-by-hop headers */ for (i=0; hop_by_hop_hdrs[i]; ++i) { @@ -1518,7 +1584,12 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, backend->close += 1; } - interim_response = ap_is_HTTP_INFO(r->status); + if (ap_is_HTTP_INFO(r->status)) { + interim_response++; + } + else { + interim_response = 0; + } if (interim_response) { /* RFC2616 tells us to forward this. * @@ -1601,6 +1672,14 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, * ap_http_filter to know where to end. */ rp->headers_in = apr_table_copy(r->pool, r->headers_out); + /* + * Restore Transfer-Encoding header from response if we saved + * one before and there is none left. We need it for the + * ap_http_filter. See above. + */ + if (te && !apr_table_get(rp->headers_in, "Transfer-Encoding")) { + apr_table_add(rp->headers_in, "Transfer-Encoding", te); + } apr_table_unset(r->headers_out,"Transfer-Encoding"); @@ -1711,7 +1790,15 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, apr_brigade_cleanup(bb); } - } while (interim_response); + } while (interim_response && (interim_response < AP_MAX_INTERIM_RESPONSES)); + + /* See define of AP_MAX_INTERIM_RESPONSES for why */ + if (interim_response >= AP_MAX_INTERIM_RESPONSES) { + return ap_proxyerror(r, HTTP_BAD_GATEWAY, + apr_psprintf(p, + "Too many (%d) interim responses from origin server", + interim_response)); + } /* If our connection with the client is to be aborted, return DONE. */ if (c->aborted || backend_broke) { @@ -1830,14 +1917,9 @@ static int proxy_http_handler(request_rec *r, proxy_worker *worker, backend->is_ssl = is_ssl; - /* - * TODO: Currently we cannot handle persistent SSL backend connections, - * because we recreate backend->connection for each request and thus - * try to initialize an already existing SSL connection. This does - * not work. - */ - if (is_ssl) - backend->close_on_recycle = 1; + if (is_ssl) { + ap_proxy_ssl_connection_cleanup(backend, r); + } /* Step One: Determine Who To Connect To */ if ((status = ap_proxy_determine_connection(p, r, conf, worker, backend, diff --git a/modules/proxy/proxy_util.c b/modules/proxy/proxy_util.c index 910f3610..e8309e46 100644 --- a/modules/proxy/proxy_util.c +++ b/modules/proxy/proxy_util.c @@ -332,16 +332,16 @@ PROXY_DECLARE(const char *) PROXY_DECLARE(request_rec *)ap_proxy_make_fake_req(conn_rec *c, request_rec *r) { - request_rec *rp = apr_pcalloc(c->pool, sizeof(*r)); + request_rec *rp = apr_pcalloc(r->pool, sizeof(*r)); - rp->pool = c->pool; + rp->pool = r->pool; rp->status = HTTP_OK; - rp->headers_in = apr_table_make(c->pool, 50); - rp->subprocess_env = apr_table_make(c->pool, 50); - rp->headers_out = apr_table_make(c->pool, 12); - rp->err_headers_out = apr_table_make(c->pool, 5); - rp->notes = apr_table_make(c->pool, 5); + rp->headers_in = apr_table_make(r->pool, 50); + rp->subprocess_env = apr_table_make(r->pool, 50); + rp->headers_out = apr_table_make(r->pool, 12); + rp->err_headers_out = apr_table_make(r->pool, 5); + rp->notes = apr_table_make(r->pool, 5); rp->server = r->server; rp->proxyreq = r->proxyreq; @@ -352,7 +352,7 @@ PROXY_DECLARE(request_rec *)ap_proxy_make_fake_req(conn_rec *c, request_rec *r) rp->proto_output_filters = c->output_filters; rp->proto_input_filters = c->input_filters; - rp->request_config = ap_create_request_config(c->pool); + rp->request_config = ap_create_request_config(r->pool); proxy_run_create_req(r, rp); return rp; @@ -1041,6 +1041,7 @@ PROXY_DECLARE(void) ap_proxy_table_unmerge(apr_pool_t *p, apr_table_t *t, char * PROXY_DECLARE(const char *) ap_proxy_location_reverse_map(request_rec *r, proxy_dir_conf *conf, const char *url) { + proxy_req_conf *rconf; struct proxy_alias *ent; int i, l1, l2; char *u; @@ -1049,12 +1050,67 @@ PROXY_DECLARE(const char *) ap_proxy_location_reverse_map(request_rec *r, * XXX FIXME: Make sure this handled the ambiguous case of the :<PORT> * after the hostname */ + if (r->proxyreq != PROXYREQ_REVERSE) { + return url; + } l1 = strlen(url); - ent = (struct proxy_alias *)conf->raliases->elts; + if (conf->interpolate_env == 1) { + rconf = ap_get_module_config(r->request_config, &proxy_module); + ent = (struct proxy_alias *)rconf->raliases->elts; + } + else { + ent = (struct proxy_alias *)conf->raliases->elts; + } for (i = 0; i < conf->raliases->nelts; i++) { - l2 = strlen(ent[i].real); - if (l1 >= l2 && strncasecmp(ent[i].real, url, l2) == 0) { + proxy_server_conf *sconf = (proxy_server_conf *) + ap_get_module_config(r->server->module_config, &proxy_module); + proxy_balancer *balancer; + const char *real; + real = ent[i].real; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "ppr: real: %s", real); + /* + * First check if mapping against a balancer and see + * if we have such a entity. If so, then we need to + * find the particulars of the actual worker which may + * or may not be the right one... basically, we need + * to find which member actually handled this request. + */ + if ((strncasecmp(real, "balancer:", 9) == 0) && + (balancer = ap_proxy_get_balancer(r->pool, sconf, real))) { + int n; + proxy_worker *worker; + worker = (proxy_worker *)balancer->workers->elts; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "ppr: checking balancer: %s", + balancer->name); + for (n = 0; n < balancer->workers->nelts; n++) { + if (worker->port) { + u = apr_psprintf(r->pool, "%s://%s:%d/", worker->scheme, + worker->hostname, worker->port); + } + else { + u = apr_psprintf(r->pool, "%s://%s/", worker->scheme, + worker->hostname); + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "ppr: matching member (%s) and URL (%s)", + u, url); + + l2 = strlen(u); + if (l1 >= l2 && strncasecmp(u, url, l2) == 0) { + u = apr_pstrcat(r->pool, ent[i].fake, &url[l2], NULL); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "ppr: matched member (%s)", u); + return ap_construct_url(r->pool, u, r); + } + worker++; + } + } + + l2 = strlen(real); + if (l1 >= l2 && strncasecmp(real, url, l2) == 0) { u = apr_pstrcat(r->pool, ent[i].fake, &url[l2], NULL); return ap_construct_url(r->pool, u, r); } @@ -1073,6 +1129,8 @@ PROXY_DECLARE(const char *) ap_proxy_location_reverse_map(request_rec *r, PROXY_DECLARE(const char *) ap_proxy_cookie_reverse_map(request_rec *r, proxy_dir_conf *conf, const char *str) { + proxy_req_conf *rconf = ap_get_module_config(r->request_config, + &proxy_module); struct proxy_alias *ent; size_t len = strlen(str); const char *newpath = NULL; @@ -1087,6 +1145,10 @@ PROXY_DECLARE(const char *) ap_proxy_cookie_reverse_map(request_rec *r, int pdiff = 0; char *ret; + if (r->proxyreq != PROXYREQ_REVERSE) { + return str; + } + /* * Find the match and replacement, but save replacing until we've done * both path and domain so we know the new strlen @@ -1097,7 +1159,12 @@ PROXY_DECLARE(const char *) ap_proxy_cookie_reverse_map(request_rec *r, pathe = ap_strchr_c(pathp, ';'); l1 = pathe ? (pathe - pathp) : strlen(pathp); pathe = pathp + l1 ; - ent = (struct proxy_alias *)conf->cookie_paths->elts; + if (conf->interpolate_env == 1) { + ent = (struct proxy_alias *)rconf->cookie_paths->elts; + } + else { + ent = (struct proxy_alias *)conf->cookie_paths->elts; + } for (i = 0; i < conf->cookie_paths->nelts; i++) { l2 = strlen(ent[i].fake); if (l1 >= l2 && strncmp(ent[i].fake, pathp, l2) == 0) { @@ -1114,7 +1181,12 @@ PROXY_DECLARE(const char *) ap_proxy_cookie_reverse_map(request_rec *r, domaine = ap_strchr_c(domainp, ';'); l1 = domaine ? (domaine - domainp) : strlen(domainp); domaine = domainp + l1; - ent = (struct proxy_alias *)conf->cookie_domains->elts; + if (conf->interpolate_env == 1) { + ent = (struct proxy_alias *)rconf->cookie_domains->elts; + } + else { + ent = (struct proxy_alias *)conf->cookie_domains->elts; + } for (i = 0; i < conf->cookie_domains->nelts; i++) { l2 = strlen(ent[i].fake); if (l1 >= l2 && strncasecmp(ent[i].fake, domainp, l2) == 0) { @@ -1323,6 +1395,7 @@ static void init_conn_pool(apr_pool_t *p, proxy_worker *worker) * 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. @@ -1550,6 +1623,9 @@ static apr_status_t connection_cleanup(void *theconn) { proxy_conn_rec *conn = (proxy_conn_rec *)theconn; proxy_worker *worker = conn->worker; + apr_bucket_brigade *bb; + conn_rec *c; + request_rec *r; /* * If the connection pool is NULL the worker @@ -1570,13 +1646,67 @@ static apr_status_t connection_cleanup(void *theconn) } #endif + r = conn->r; + if (conn->need_flush && r && (r->bytes_sent || r->eos_sent)) { + /* + * We need to ensure that buckets that may have been buffered in the + * network filters get flushed to the network. This is needed since + * these buckets have been created with the bucket allocator of the + * backend connection. This allocator either gets destroyed if + * conn->close is set or the worker address is not reusable which + * causes the connection to the backend to be closed or it will be used + * again by another frontend connection that wants to recycle the + * backend connection. + * In this case we could run into nasty race conditions (e.g. if the + * next user of the backend connection destroys the allocator before we + * sent the buckets to the network). + * + * Remark 1: Only do this if buckets where sent down the chain before + * that could still be buffered in the network filter. This is the case + * if we have sent an EOS bucket or if we actually sent buckets with + * data down the chain. In all other cases we either have not sent any + * buckets at all down the chain or we only sent meta buckets that are + * not EOS buckets down the chain. The only meta bucket that remains in + * this case is the flush bucket which would have removed all possibly + * buffered buckets in the network filter. + * If we sent a flush bucket in the case where not ANY buckets were + * sent down the chain, we break error handling which happens AFTER us. + * + * Remark 2: Doing a setaside does not help here as the buckets remain + * created by the wrong allocator in this case. + * + * Remark 3: Yes, this creates a possible performance penalty in the case + * of pipelined requests as we may send only a small amount of data over + * the wire. + */ + c = r->connection; + bb = apr_brigade_create(r->pool, c->bucket_alloc); + if (r->eos_sent) { + /* + * If we have already sent an EOS bucket send directly to the + * connection based filters. We just want to flush the buckets + * if something hasn't been sent to the network yet. + */ + ap_fflush(c->output_filters, bb); + } + else { + ap_fflush(r->output_filters, bb); + } + apr_brigade_destroy(bb); + conn->r = NULL; + conn->need_flush = 0; + } + /* determine if the connection need to be closed */ - if (conn->close_on_recycle || conn->close) { + if (conn->close_on_recycle || conn->close || worker->disablereuse || + !worker->is_address_reusable) { apr_pool_t *p = conn->pool; - apr_pool_clear(conn->pool); - memset(conn, 0, sizeof(proxy_conn_rec)); + 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"); } #if APR_HAS_THREADS if (worker->hmax && worker->cp->res) { @@ -1593,11 +1723,54 @@ static apr_status_t connection_cleanup(void *theconn) return APR_SUCCESS; } +static void socket_cleanup(proxy_conn_rec *conn) +{ + conn->sock = NULL; + conn->connection = NULL; + apr_pool_clear(conn->scpool); +} + +PROXY_DECLARE(apr_status_t) ap_proxy_ssl_connection_cleanup(proxy_conn_rec *conn, + request_rec *r) +{ + apr_bucket_brigade *bb; + apr_status_t rv; + + /* + * If we have an existing SSL connection it might be possible that the + * server sent some SSL message we have not read so far (e.g. a SSL + * shutdown message if the server closed the keepalive connection while + * the connection was held unused in our pool). + * So ensure that if present (=> APR_NONBLOCK_READ) it is read and + * processed. We don't expect any data to be in the returned brigade. + */ + if (conn->sock && conn->connection) { + bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + rv = ap_get_brigade(conn->connection->input_filters, bb, + AP_MODE_READBYTES, APR_NONBLOCK_READ, + HUGE_STRING_LEN); + if ((rv != APR_SUCCESS) && !APR_STATUS_IS_EAGAIN(rv)) { + socket_cleanup(conn); + } + if (!APR_BRIGADE_EMPTY(bb)) { + apr_off_t len; + + rv = apr_brigade_length(bb, 0, &len); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, + "proxy: SSL cleanup brigade contained %" + APR_OFF_T_FMT " bytes of data.", len); + } + apr_brigade_destroy(bb); + } + return APR_SUCCESS; +} + /* reslist constructor */ static apr_status_t connection_constructor(void **resource, void *params, apr_pool_t *pool) { apr_pool_t *ctx; + apr_pool_t *scpool; proxy_conn_rec *conn; proxy_worker *worker = (proxy_worker *)params; @@ -1607,9 +1780,20 @@ static apr_status_t connection_constructor(void **resource, void *params, * when disconnecting from backend. */ apr_pool_create(&ctx, pool); - conn = apr_pcalloc(pool, sizeof(proxy_conn_rec)); + 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). + */ + 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; #if APR_HAS_THREADS conn->inreslist = 1; @@ -1725,8 +1909,13 @@ PROXY_DECLARE(apr_status_t) ap_proxy_initialize_worker(proxy_worker *worker, ser if (!worker->retry_set) { worker->retry = apr_time_from_sec(PROXY_WORKER_DEFAULT_RETRY); } - /* By default address is reusable */ - worker->is_address_reusable = 1; + /* By default address is reusable unless DisableReuse is set */ + if (worker->disablereuse) { + worker->is_address_reusable = 0; + } + else { + worker->is_address_reusable = 1; + } #if APR_HAS_THREADS ap_mpm_query(AP_MPMQ_MAX_THREADS, &mpm_threads); @@ -1873,11 +2062,6 @@ PROXY_DECLARE(int) ap_proxy_release_connection(const char *proxy_function, ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "proxy: %s: has released connection for (%s)", proxy_function, conn->worker->hostname); - /* If there is a connection kill it's cleanup */ - if (conn->connection) { - apr_pool_cleanup_kill(conn->connection->pool, conn, connection_cleanup); - conn->connection = NULL; - } connection_cleanup(conn); return OK; @@ -1899,6 +2083,8 @@ ap_proxy_determine_connection(apr_pool_t *p, request_rec *r, apr_status_t err = APR_SUCCESS; apr_status_t uerr = APR_SUCCESS; + conn->r = r; + /* * Break up the URL to determine the host to connect to */ @@ -1938,7 +2124,8 @@ ap_proxy_determine_connection(apr_pool_t *p, request_rec *r, * * TODO: Handle this much better... */ - if (!conn->hostname || !worker->is_address_reusable || + if (!conn->hostname || !worker->is_address_reusable || + worker->disablereuse || (r->connection->keepalives && (r->proxyreq == PROXYREQ_PROXY || r->proxyreq == PROXYREQ_REVERSE) && (strcasecmp(conn->hostname, uri->hostname) != 0) ) ) { @@ -1950,14 +2137,7 @@ ap_proxy_determine_connection(apr_pool_t *p, request_rec *r, conn->hostname = apr_pstrdup(conn->pool, uri->hostname); conn->port = uri->port; } - if (conn->sock) { - apr_socket_close(conn->sock); - conn->sock = NULL; - } - if (conn->connection) { - apr_pool_cleanup_kill(conn->connection->pool, conn, connection_cleanup); - conn->connection = NULL; - } + socket_cleanup(conn); err = apr_sockaddr_info_get(&(conn->addr), conn->hostname, APR_UNSPEC, conn->port, 0, @@ -2101,14 +2281,8 @@ PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function, (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module); if (conn->sock) { - /* - * This increases the connection pool size - * but the number of dropped connections is - * relatively small compared to connection lifetime - */ if (!(connected = is_socket_connected(conn->sock))) { - apr_socket_close(conn->sock); - conn->sock = NULL; + socket_cleanup(conn); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "proxy: %s: backend socket is disconnected.", proxy_function); @@ -2117,7 +2291,7 @@ PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function, while (backend_addr && !connected) { if ((rv = apr_socket_create(&newsock, backend_addr->family, SOCK_STREAM, APR_PROTO_TCP, - conn->pool)) != APR_SUCCESS) { + conn->scpool)) != APR_SUCCESS) { loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR; ap_log_error(APLOG_MARK, loglevel, rv, s, "proxy: %s: error creating fam %d socket for target %s", @@ -2132,6 +2306,7 @@ PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function, backend_addr = backend_addr->next; continue; } + conn->connection = NULL; #if !defined(TPF) && !defined(BEOS) if (worker->recv_buffer_size > 0 && @@ -2221,13 +2396,25 @@ PROXY_DECLARE(int) ap_proxy_connection_create(const char *proxy_function, apr_sockaddr_t *backend_addr = conn->addr; int rc; apr_interval_time_t current_timeout; + apr_bucket_alloc_t *bucket_alloc; + + if (conn->connection) { + return OK; + } /* + * We need to flush the buckets before we return the connection to the + * connection pool. See comment in connection_cleanup for why this is + * needed. + */ + conn->need_flush = 1; + bucket_alloc = apr_bucket_alloc_create(conn->scpool); + /* * The socket is now open, create a new backend server connection */ - conn->connection = ap_run_create_connection(c->pool, s, conn->sock, - c->id, c->sbh, - c->bucket_alloc); + conn->connection = ap_run_create_connection(conn->scpool, s, conn->sock, + 0, NULL, + bucket_alloc); if (!conn->connection) { /* @@ -2239,17 +2426,9 @@ PROXY_DECLARE(int) ap_proxy_connection_create(const char *proxy_function, "new connection to %pI (%s)", proxy_function, backend_addr, conn->hostname); /* XXX: Will be closed when proxy_conn is closed */ - apr_socket_close(conn->sock); - conn->sock = NULL; + socket_cleanup(conn); return HTTP_INTERNAL_SERVER_ERROR; } - /* - * register the connection cleanup to client connection - * so that the connection can be closed or reused - */ - apr_pool_cleanup_register(c->pool, (void *)conn, - connection_cleanup, - apr_pool_cleanup_null); /* For ssl connection to backend */ if (conn->is_ssl) { |
