From: Jim Jagielski Date: Mon, 15 Feb 2010 19:54:41 +0000 (+0000) Subject: mod_proxy, mod_proxy_http: Support remote https proxies X-Git-Tag: 2.2.15~80 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3bf36996902ec2e457fc4f95c44c91e32863b568;p=thirdparty%2Fapache%2Fhttpd.git mod_proxy, mod_proxy_http: Support remote https proxies by using HTTP CONNECT. PR 19188. [Philippe Dutrueux , Rainer Jung] Backport of r909323, r910079, r910081, r910124 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.2.x@910322 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index e02ba7dece3..10b118b792c 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,10 @@ Changes with Apache 2.2.15 access control is still vulnerable, unless using OpenSSL >= 0.9.8l. [Joe Orton, Ruediger Pluem, Hartmut Keil ] + *) mod_proxy, mod_proxy_http: Support remote https proxies + by using HTTP CONNECT. + PR 19188. [Philippe Dutrueux , Rainer Jung] + *) worker: Don't report server has reached MaxClients until it has. Add message when server gets within MinSpareThreads of MaxClients. PR 46996. [Dan Poirier] diff --git a/STATUS b/STATUS index 9603f84995e..a6b1afab38b 100644 --- a/STATUS +++ b/STATUS @@ -87,17 +87,6 @@ RELEASE SHOWSTOPPERS: PATCHES ACCEPTED TO BACKPORT FROM TRUNK: [ start all new proposals below, under PATCHES PROPOSED. ] - * mod_proxy: Allow https to a remote forward proxy by issuing an HTTP - CONNECT request. This adds a CONNECT client (sending a connect request). - It is not the same as mod_proxy_connect, which is a CONNECT server - (responding to connect requests). - PR: https://issues.apache.org/bugzilla/show_bug.cgi?id=19188 - Trunk patches: http://svn.apache.org/viewvc?rev=909323&view=rev - http://svn.apache.org/viewvc?rev=910079&view=rev - http://svn.apache.org/viewvc?rev=910081&view=rev - http://svn.apache.org/viewvc?rev=910124&view=rev - 2.2.x patch: http://people.apache.org/~rjung/patches/bz19188-proxyremote-https-v2.patch - +1: rjung, minfrin, jim PATCHES PROPOSED TO BACKPORT FROM TRUNK: diff --git a/docs/manual/mod/mod_proxy.xml b/docs/manual/mod/mod_proxy.xml index 2cbbc1e34aa..f683c0fc07e 100644 --- a/docs/manual/mod/mod_proxy.xml +++ b/docs/manual/mod/mod_proxy.xml @@ -475,8 +475,9 @@ request

scheme is effectively the protocol that should be used to - communicate with the remote server; only http is supported by - this module.

+ communicate with the remote server; only http and https + are supported by this module. When using https, the requests + are forwarded through the remote proxy using the HTTP CONNECT method.

Example ProxyRemote http://goodguys.example.com/ http://mirrorguys.example.com:8000
diff --git a/docs/manual/mod/mod_proxy_connect.xml b/docs/manual/mod/mod_proxy_connect.xml index ed8f07d8454..7a3aee6eeef 100644 --- a/docs/manual/mod/mod_proxy_connect.xml +++ b/docs/manual/mod/mod_proxy_connect.xml @@ -39,6 +39,11 @@ requests, mod_proxy and mod_proxy_connect have to be present in the server.

+

CONNECT is also used, when the server needs to send an HTTPS request + through a forward proxy. In this case the server acts as a CONNECT client. + This functionality is part of mod_proxy and + mod_proxy_connect is not needed in this case.

+ Warning

Do not enable proxying until you have secured your server. Open proxy diff --git a/docs/manual/mod/mod_proxy_http.xml b/docs/manual/mod/mod_proxy_http.xml index cbd77651eaf..92e16251601 100644 --- a/docs/manual/mod/mod_proxy_http.xml +++ b/docs/manual/mod/mod_proxy_http.xml @@ -32,7 +32,7 @@

This module requires the service of mod_proxy. It provides the features used for - proxying HTTP requests. mod_proxy_http + proxying HTTP and HTTPS requests. mod_proxy_http supports HTTP/0.9, HTTP/1.0 and HTTP/1.1. It does not provide any caching abilities. If you want to set up a caching proxy, you might want to use the additional service of the diff --git a/include/ap_mmn.h b/include/ap_mmn.h index c58660db48e..eef610ab671 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -138,6 +138,7 @@ * 20051115.22 (2.2.12) Add ap_escape_html2 API, with additional option * 20051115.23 (2.2.12) Add ap_open_piped_log_ex API, with cmdtype option, * and conditional cmdtype member of piped_log struct + * 20051115.24 (2.2.15) Add forward member to proxy_conn_rec */ #define MODULE_MAGIC_COOKIE 0x41503232UL /* "AP22" */ @@ -145,7 +146,7 @@ #ifndef MODULE_MAGIC_NUMBER_MAJOR #define MODULE_MAGIC_NUMBER_MAJOR 20051115 #endif -#define MODULE_MAGIC_NUMBER_MINOR 23 /* 0...n */ +#define MODULE_MAGIC_NUMBER_MINOR 24 /* 0...n */ /** * Determine if the server's current MODULE_MAGIC_NUMBER is at least a diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h index 04b710660aa..358248f1353 100644 --- a/modules/proxy/mod_proxy.h +++ b/modules/proxy/mod_proxy.h @@ -247,6 +247,7 @@ typedef struct { * which the backend currently answers. */ int need_flush;/* Flag to decide whether we need to flush the * filter chain or not */ + void *forward; /* opaque forward proxy data */ } proxy_conn_rec; typedef struct { diff --git a/modules/proxy/proxy_util.c b/modules/proxy/proxy_util.c index b835832d059..a70a8758b07 100644 --- a/modules/proxy/proxy_util.c +++ b/modules/proxy/proxy_util.c @@ -29,6 +29,18 @@ #define apr_socket_create apr_socket_create_ex #endif +/* + * Opaque structure containing target server info when + * using a forward proxy. + * Up to now only used in combination with HTTP CONNECT. + */ +typedef struct { + int use_http_connect; /* Use SSL Tunneling via HTTP CONNECT */ + const char *target_host; /* Target hostname */ + apr_port_t target_port; /* Target port */ + const char *proxy_auth; /* Proxy authorization */ +} forward_info; + /* Global balancer counter */ int PROXY_DECLARE_DATA proxy_lb_workers = 0; static int lb_workers_limit = 0; @@ -2085,6 +2097,34 @@ ap_proxy_determine_connection(apr_pool_t *p, request_rec *r, 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. + */ + if (conn->is_ssl) { + const char *proxy_auth; + + forward_info *forward = apr_pcalloc(conn->pool, sizeof(forward_info)); + conn->forward = forward; + forward->use_http_connect = 1; + forward->target_host = apr_pstrdup(conn->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); + } + } } else { conn->hostname = apr_pstrdup(conn->pool, uri->hostname); @@ -2224,6 +2264,102 @@ static int is_socket_connected(apr_socket_t *sock) } #endif /* USE_ALTERNATE_IS_CONNECTED */ + +/* + * Send a HTTP CONNECT request to a forward proxy. + * The proxy is given by "backend", the target server + * is contained in the "forward" member of "backend". + */ +static apr_status_t send_http_connect(proxy_conn_rec *backend, + server_rec *s) +{ + int status; + apr_size_t nbytes; + apr_size_t left; + int complete = 0; + char buffer[HUGE_STRING_LEN]; + char drain_buffer[HUGE_STRING_LEN]; + forward_info *forward = (forward_info *)backend->forward; + int len = 0; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "proxy: CONNECT: sending the CONNECT request for %s:%d " + "to the remote proxy %pI (%s)", + forward->target_host, forward->target_port, + backend->addr, backend->hostname); + /* Create the CONNECT request */ + 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 */ + if (forward->proxy_auth != NULL) { + nbytes += apr_snprintf(buffer + nbytes, sizeof(buffer) - nbytes, + "Proxy-Authorization: %s" CRLF, + forward->proxy_auth); + } + /* Set a reasonable agent and send everything */ + nbytes += apr_snprintf(buffer + nbytes, sizeof(buffer) - nbytes, + "Proxy-agent: %s" CRLF CRLF, + ap_get_server_banner()); + apr_socket_send(backend->sock, buffer, &nbytes); + + /* Receive the whole CONNECT response */ + left = sizeof(buffer) - 1; + /* Read until we find the end of the headers or run out of buffer */ + do { + nbytes = left; + status = apr_socket_recv(backend->sock, buffer + len, &nbytes); + len += nbytes; + left -= nbytes; + buffer[len] = '\0'; + if (strstr(buffer + len - nbytes, "\r\n\r\n") != NULL) { + complete = 1; + break; + } + } while (status == APR_SUCCESS && left > 0); + /* Drain what's left */ + if (!complete) { + nbytes = sizeof(drain_buffer) - 1; + while (status == APR_SUCCESS && nbytes) { + status = apr_socket_recv(backend->sock, drain_buffer, &nbytes); + buffer[nbytes] = '\0'; + nbytes = sizeof(drain_buffer) - 1; + if (strstr(drain_buffer, "\r\n\r\n") != NULL) { + complete = 1; + break; + } + } + } + + /* Check for HTTP_OK response status */ + if (status == APR_SUCCESS) { + int major, minor; + /* Only scan for three character status code */ + char code_str[4]; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "send_http_connect: response from the forward proxy: %s", + buffer); + + /* Extract the returned code */ + if (sscanf(buffer, "HTTP/%u.%u %3s", &major, &minor, code_str) == 3) { + status = atoi(code_str); + if (status == HTTP_OK) { + status = APR_SUCCESS; + } + else { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, + "send_http_connect: the forward proxy returned code is '%s'", + code_str); + status = APR_INCOMPLETE; + } + } + } + + return(status); +} + + PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function, proxy_conn_rec *conn, proxy_worker *worker, @@ -2336,7 +2472,33 @@ PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function, apr_socket_timeout_set(newsock, s->timeout); } - conn->sock = newsock; + conn->sock = newsock; + + if (conn->forward) { + forward_info *forward = (forward_info *)conn->forward; + /* + * 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) { + conn->sock = NULL; + apr_socket_close(newsock); + loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR; + ap_log_error(APLOG_MARK, loglevel, rv, s, + "proxy: %s: attempt to connect to %s:%d " + "via http CONNECT through %pI (%s) failed", + proxy_function, + forward->target_host, forward->target_port, + backend_addr, worker->hostname); + backend_addr = backend_addr->next; + continue; + } + } + } + connected = 1; } /* @@ -2516,4 +2678,3 @@ ap_proxy_buckets_lifetime_transform(request_rec *r, apr_bucket_brigade *from, } return rv; } -