]> granicus.if.org Git - apache/commitdiff
Support remote https proxies by using HTTP CONNECT.
authorRainer Jung <rjung@apache.org>
Fri, 12 Feb 2010 09:58:48 +0000 (09:58 +0000)
committerRainer Jung <rjung@apache.org>
Fri, 12 Feb 2010 09:58:48 +0000 (09:58 +0000)
PR: 19188
Submitted by: Philippe Dutrueux <lilas evidian.com>
Reviewed by: rjung

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@909323 13f79535-47bb-0310-9956-ffa450edef68

CHANGES
docs/manual/mod/mod_proxy.xml
docs/manual/mod/mod_proxy_connect.xml
docs/manual/mod/mod_proxy_http.xml
include/ap_mmn.h
modules/proxy/mod_proxy.h
modules/proxy/proxy_util.c

diff --git a/CHANGES b/CHANGES
index 542d2fc435df421bf9bf8a41cb4b4f281f5418c8..a2aa7bb7907771c053247fbf0684ffb8d8cdc5ef 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,5 +1,11 @@
                                                         -*- coding: utf-8 -*-
 
+Changes with Apache 2.3.7
+
+  *) mod_proxy, mod_proxy_http: Support remote https proxies
+     by using HTTP CONNECT.
+     PR 19188.  [Philippe Dutrueux <lilas evidian.com>, Rainer Jung]
+
 Changes with Apache 2.3.6
 
   *) worker: Don't report server has reached MaxClients until it has.
index 16c539cbc42b2dfc33d6c76e31ff96532efb3e4f..f3e0c3d39c75d10900623eb1b7aa29fbe23e77c9 100644 (file)
@@ -465,8 +465,9 @@ request</description>
     </example>
 
     <p><var>scheme</var> is effectively the protocol that should be used to
-    communicate with the remote server; only <code>http</code> is supported by
-    this module.</p>
+    communicate with the remote server; only <code>http</code> and <code>https</code>
+    are supported by this module. When using <code>https</code>, the requests
+    are forwarded through the remote proxy using the HTTP CONNECT method.</p>
 
     <example><title>Example</title>
       ProxyRemote http://goodguys.example.com/ http://mirrorguys.example.com:8000<br />
index d021f1913d14e744df0f76728f828e8e04ae73d0..c683230dd9ac3523f0ec82d114b0921b5d7c96b4 100644 (file)
     requests, <module>mod_proxy</module> and
     <module>mod_proxy_connect</module> have to be present in the server.</p>
 
+    <p>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 <module>mod_proxy</module> and
+    <module>mod_proxy_connect</module> is not needed in this case.</p>
+
     <note type="warning"><title>Warning</title>
       <p>Do not enable proxying until you have <a
       href="mod_proxy.html#access">secured your server</a>. Open proxy
index cbd77651eaf9af90c199b94f745ab4726a8df366..92e16251601acfbbfe8954bac601ab3d044ed47c 100644 (file)
@@ -32,7 +32,7 @@
 <summary>
     <p>This module <em>requires</em> the service of <module
     >mod_proxy</module>. It provides the features used for
-    proxying HTTP requests. <module>mod_proxy_http</module>
+    proxying HTTP and HTTPS requests. <module>mod_proxy_http</module>
     supports HTTP/0.9, HTTP/1.0 and HTTP/1.1. It does <em>not</em>
     provide any caching abilities. If you want to set up a caching
     proxy, you might want to use the additional service of the
index 327a7f664149b3d979964652664a0ffbb93f59d1..b47ed321cba1bdd3cb2912cfd4b11d3acdb9de3d 100644 (file)
  * 20091230.3 (2.3.6-dev)  add ap_parse_log_level()
  * 20091230.4 (2.3.6-dev)  export ap_process_request_after_handler() for mod_serf
  * 20100208.0 (2.3.6-dev)  ap_socache_provider_t API changes to store and iterate
+ * 20100208.1 (2.3.6-dev)  Added forward member to proxy_conn_rec
  *
  */
 
 #ifndef MODULE_MAGIC_NUMBER_MAJOR
 #define MODULE_MAGIC_NUMBER_MAJOR 20100208
 #endif
-#define MODULE_MAGIC_NUMBER_MINOR 0                     /* 0...n */
+#define MODULE_MAGIC_NUMBER_MINOR 1                     /* 0...n */
 
 /**
  * Determine if the server's current MODULE_MAGIC_NUMBER is at least a
index a2da9419799e6fad0c1233265ff0837e75df7a5d..adf1376367c4e8a8645153539dd2ecfb65ac8b1e 100644 (file)
@@ -232,6 +232,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 {
index 85ab9d013993c25b007e6190bfa4e0e9e19f5317..91fdbc9a6ed959b68a3df272a9d750a36f8374d9 100644 (file)
 #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;
@@ -2109,6 +2121,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 = 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 = proxy_auth;
+                }
+            }
         }
         else {
             conn->hostname = apr_pstrdup(conn->pool, uri->hostname);
@@ -2250,6 +2290,83 @@ 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;
+    char 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 */
+    nbytes = sizeof(buffer) - 1;
+    status = apr_socket_recv(backend->sock, buffer, &nbytes);
+    while (status == APR_SUCCESS) {
+        len += nbytes;
+        buffer[len] = '\0';
+        if (strstr(buffer, "\r\n\r\n") != NULL) {
+            break;
+        }
+        nbytes = sizeof(buffer) - 1 - len;
+        status = apr_socket_recv(backend->sock, buffer + len, &nbytes);
+    }
+
+    /* Check for HTTP_OK response status */
+    if (status == APR_SUCCESS) {
+        int major, minor;
+        char code_str[10];
+
+        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 %s", &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,
@@ -2360,7 +2477,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;
     }
     /*
@@ -2547,4 +2690,3 @@ ap_proxy_buckets_lifetime_transform(request_rec *r, apr_bucket_brigade *from,
     }
     return rv;
 }
-