]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-http: http-client: Added support for tunneling SSL conntections through proxy.
authorStephan Bosch <stephan@rename-it.nl>
Sat, 12 Oct 2013 08:11:04 +0000 (11:11 +0300)
committerStephan Bosch <stephan@rename-it.nl>
Sat, 12 Oct 2013 08:11:04 +0000 (11:11 +0300)
src/lib-http/http-client-connection.c
src/lib-http/http-client-host.c
src/lib-http/http-client-peer.c
src/lib-http/http-client-private.h
src/lib-http/http-client-request.c
src/lib-http/http-client.c
src/lib-http/http-client.h

index eff8e341f91727018937552053451cdf055eba09..ce151452e279f715a46cbb17276f62b6f66af055 100644 (file)
@@ -4,6 +4,7 @@
 #include "net.h"
 #include "str.h"
 #include "hash.h"
+#include "llist.h"
 #include "array.h"
 #include "ioloop.h"
 #include "istream.h"
@@ -912,7 +913,8 @@ static void http_client_connect_timeout(struct http_client_connection *conn)
        http_client_connection_destroy(&conn->conn);
 }
 
-static void http_client_connection_connect(struct http_client_connection *conn)
+static void
+http_client_connection_connect(struct http_client_connection *conn)
 {
        unsigned int msecs;
 
@@ -936,6 +938,94 @@ static void http_client_connection_connect(struct http_client_connection *conn)
        }
 }
 
+static void
+http_client_connect_tunnel_timeout(struct http_client_connection *conn)
+{
+       http_client_connection_unref(&conn);
+}
+
+// FIXME: put something like this in lib/connection.c
+static void
+_connection_init_from_streams(struct connection_list *list,
+                           struct connection *conn, const char *name,
+                           struct istream *input, struct ostream *output)
+{
+       i_assert(name != NULL);
+
+       conn->list = list;
+       conn->name = i_strdup(name);
+       conn->fd_in = i_stream_get_fd(input);
+       conn->fd_out = o_stream_get_fd(output);
+
+       i_assert(conn->fd_in >= 0);
+       i_assert(conn->fd_out >= 0);
+       i_assert(conn->io == NULL);
+       i_assert(conn->input == NULL);
+       i_assert(conn->output == NULL);
+       i_assert(conn->to == NULL);
+
+       conn->input = input;
+       i_stream_set_name(conn->input, conn->name);
+
+       conn->output = output;
+       o_stream_set_no_error_handling(conn->output, TRUE);
+       o_stream_set_name(conn->output, conn->name);
+
+       conn->io = io_add(conn->fd_in, IO_READ, *list->v.input, conn);
+       
+       DLLIST_PREPEND(&list->connections, conn);
+       list->connections_count++;
+
+       if (list->v.client_connected != NULL)
+               list->v.client_connected(conn, TRUE);
+}
+
+static void
+http_client_connection_tunnel_response(const struct http_response *response,
+                              struct http_client_connection *conn)
+{
+       struct http_client_tunnel tunnel;
+       const char *name = http_client_peer_addr2str(&conn->peer->addr);
+
+       if (response->status != 200) {
+               http_client_peer_connection_failure(conn->peer, t_strdup_printf(
+                       "tunnel connect(%s) failed: %d %s", name,
+                               response->status, response->reason));
+               conn->connect_request = NULL;
+               return;
+       }
+
+       http_client_request_start_tunnel(conn->connect_request, &tunnel);
+       conn->connect_request = NULL;
+
+       _connection_init_from_streams
+               (conn->client->conn_list, &conn->conn, name, tunnel.input, tunnel.output);
+}
+
+static void
+http_client_connection_connect_tunnel(struct http_client_connection *conn,
+       const struct ip_addr *ip, unsigned int port)
+{
+       unsigned int msecs;
+
+       conn->connect_start_timestamp = ioloop_timeval;
+
+       conn->connect_request = http_client_request_connect_ip
+               (conn->client, ip, port, http_client_connection_tunnel_response, conn);
+       http_client_request_set_urgent(conn->connect_request);
+       http_client_request_submit(conn->connect_request);
+
+       /* don't use connection.h timeout because we want this timeout
+          to include also the SSL handshake */
+       msecs = conn->client->set.connect_timeout_msecs;
+       if (msecs == 0)
+               msecs = conn->client->set.request_timeout_msecs;
+       if (msecs > 0) {
+               conn->to_connect =
+                       timeout_add(msecs, http_client_connect_tunnel_timeout, conn);
+       }
+}
+
 struct http_client_connection *
 http_client_connection_create(struct http_client_peer *peer)
 {
@@ -951,6 +1041,9 @@ http_client_connection_create(struct http_client_peer *peer)
        case HTTP_CLIENT_PEER_ADDR_HTTPS:
                conn_type = "HTTPS";
                break;
+       case HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL:
+               conn_type = "Tunneled HTTPS";
+               break;
        case HTTP_CLIENT_PEER_ADDR_RAW:
                conn_type = "Raw";
                break;
@@ -964,9 +1057,13 @@ http_client_connection_create(struct http_client_peer *peer)
        if (peer->addr.type != HTTP_CLIENT_PEER_ADDR_RAW)
                i_array_init(&conn->request_wait_list, 16);
 
-       connection_init_client_ip
-               (peer->client->conn_list, &conn->conn, &addr->ip, addr->port);
-       http_client_connection_connect(conn);
+       if (peer->addr.type == HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL) {
+               http_client_connection_connect_tunnel(conn, &addr->ip, addr->port);
+       } else {
+               connection_init_client_ip
+                       (peer->client->conn_list, &conn->conn, &addr->ip, addr->port);
+               http_client_connection_connect(conn);
+       }
 
        array_append(&peer->conns, &conn, 1);
 
@@ -1000,6 +1097,9 @@ void http_client_connection_unref(struct http_client_connection **_conn)
        conn->closing = TRUE;
        conn->connected = FALSE;
 
+       if (conn->connect_request != NULL)
+               http_client_request_abort(&conn->connect_request);
+
        if (conn->incoming_payload != NULL) {
                /* the stream is still accessed by lib-http caller. */
                i_stream_remove_destroy_callback(conn->incoming_payload,
index 57c37d676dc10d110f532dd157692245592fa55e..bc20ba78ff1eeabc19391eb74d9e416fa11e2362 100644 (file)
@@ -428,9 +428,10 @@ static void http_client_host_lookup
 }
 
 struct http_client_host *http_client_host_get
-(struct http_client *client, const char *hostname)
+(struct http_client *client, const struct http_url *host_url)
 {
        struct http_client_host *host;
+       const char *hostname = host_url->host_name;
 
        host = hash_table_lookup(client->hosts, hostname);
        if (host == NULL) {
@@ -445,6 +446,12 @@ struct http_client_host *http_client_host_get
                hash_table_insert(client->hosts, hostname, host);
                DLLIST_PREPEND(&client->hosts_list, host);
 
+               if (host_url->have_host_ip) {
+                       host->ips_count = 1;
+                       host->ips = i_new(struct ip_addr, host->ips_count);
+                       host->ips[0] = host_url->host_ip;
+               }
+
                http_client_host_debug(host, "Host created");
        }
        return host;
index 1c35aba301ed8885c4fa3b44c007d5e6d1dea62d..8b1978f3fb97addd8f49629499776eef02361c9b 100644 (file)
@@ -48,6 +48,7 @@ unsigned int http_client_peer_addr_hash
        case HTTP_CLIENT_PEER_ADDR_HTTP:
                return net_ip_hash(&peer->ip) + peer->port;
        case HTTP_CLIENT_PEER_ADDR_HTTPS:
+       case HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL:
                return net_ip_hash(&peer->ip) + peer->port +
                        (peer->https_name == NULL ? 0 : str_hash(peer->https_name));
        }
index 023f1b1fcf9e073d7bb19270d92c043def038fee..db74ec3dd84ce2db64e4230d559eeb10aa2db312 100644 (file)
@@ -35,6 +35,7 @@ HASH_TABLE_DEFINE_TYPE(http_client_peer, const struct http_client_peer_addr *,
 enum http_client_peer_addr_type {
        HTTP_CLIENT_PEER_ADDR_HTTP = 0,
        HTTP_CLIENT_PEER_ADDR_HTTPS,
+       HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL,
        HTTP_CLIENT_PEER_ADDR_RAW
 };
 
@@ -97,6 +98,7 @@ struct http_client_request {
        unsigned int submitted:1;
        unsigned int connect_tunnel:1;
        unsigned int connect_direct:1;
+       unsigned int ssl_tunnel:1;
 };
 
 struct http_client_host_port {
@@ -178,6 +180,7 @@ struct http_client_connection {
        int connect_errno;
        struct timeval connect_start_timestamp;
        struct timeval connected_timestamp;
+       struct http_client_request *connect_request;
 
        struct ssl_iostream *ssl_iostream;
        struct http_response_parser *http_parser;
@@ -248,7 +251,10 @@ http_client_request_get_peer_addr(const struct http_client_request *req,
                addr->type = HTTP_CLIENT_PEER_ADDR_RAW;
                addr->port = (host_url->have_port ? host_url->port : HTTPS_DEFAULT_PORT);
        } else if (host_url->have_ssl) {
-               addr->type = HTTP_CLIENT_PEER_ADDR_HTTPS;
+               if (req->ssl_tunnel)
+                       addr->type = HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL;
+               else
+                       addr->type = HTTP_CLIENT_PEER_ADDR_HTTPS;
                addr->https_name = host_url->host_name;
                addr->port = (host_url->have_port ? host_url->port : HTTPS_DEFAULT_PORT);
        } else {
@@ -260,13 +266,19 @@ http_client_request_get_peer_addr(const struct http_client_request *req,
 static inline const char *
 http_client_connection_label(struct http_client_connection *conn)
 {
-       return t_strdup_printf("%s [%d]",
-                http_client_peer_addr2str(&conn->peer->addr), conn->id);
+       return t_strdup_printf("%s%s [%d]",
+               http_client_peer_addr2str(&conn->peer->addr),
+               (conn->peer->addr.type == HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL ?
+                       " (tunnel)" : ""), conn->id);
 }
 
 static inline const char *
 http_client_peer_label(struct http_client_peer *peer)
 {
+       if (peer->addr.type == HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL) {
+               return t_strconcat
+                       (http_client_peer_addr2str(&peer->addr), " (tunnel)", NULL);
+       }
        return http_client_peer_addr2str(&peer->addr);
 }
 
@@ -344,7 +356,8 @@ unsigned int http_client_peer_idle_connections(struct http_client_peer *peer);
 void http_client_peer_switch_ioloop(struct http_client_peer *peer);
 
 struct http_client_host *
-       http_client_host_get(struct http_client *client, const char *hostname);
+http_client_host_get(struct http_client *client,
+       const struct http_url *host_url);
 void http_client_host_free(struct http_client_host **_host);
 void http_client_host_submit_request(struct http_client_host *host,
        struct http_client_request *req);
index 2fe6964f3d70b3269fead68e3e1b41a537535d4f..8ca4b8153788377a09fd7375dbd1b5bb2aadf080 100644 (file)
@@ -115,7 +115,24 @@ http_client_request_connect(struct http_client *client,
        req->origin_url.port = port;
        req->origin_url.have_port = TRUE;
        req->connect_tunnel = TRUE;
-       req->target = "";
+       req->target = req->origin_url.host_name;
+       return req;
+}
+
+#undef http_client_request_connect_ip
+struct http_client_request *
+http_client_request_connect_ip(struct http_client *client,
+                   const struct ip_addr *ip, in_port_t port,
+                   http_client_request_callback_t *callback,
+                               void *context)
+{
+       struct http_client_request *req;
+       const char *hostname = net_ip2addr(ip);
+
+       req = http_client_request_connect
+               (client, hostname, port, callback, context);
+       req->origin_url.host_ip = *ip;
+       req->origin_url.have_host_ip = TRUE;
        return req;
 }
 
@@ -300,9 +317,15 @@ static void http_client_request_do_submit(struct http_client_request *req)
 
        /* determine what host to contact to submit this request */
        if (proxy_url != NULL) {
-               req->host_url = proxy_url;         /* proxy server */
+               if (req->origin_url.have_ssl && !client->set.no_ssl_tunnel &&
+                       !req->connect_tunnel) {
+                       req->host_url = &req->origin_url;  /* tunnel to origin server */
+                       req->ssl_tunnel = TRUE;
+               } else {
+                       req->host_url = proxy_url;         /* proxy server */
+               }
        } else {
-               req->host_url = &req->origin_url;  /* origin server */
+               req->host_url = &req->origin_url;    /* origin server */
        }
 
        /* use submission date if no date is set explicitly */
@@ -327,7 +350,7 @@ static void http_client_request_do_submit(struct http_client_request *req)
                        req->urgent = TRUE;
        }
 
-       host = http_client_host_get(req->client, req->host_url->host_name);
+       host = http_client_host_get(req->client, req->host_url);
        req->state = HTTP_REQUEST_STATE_QUEUED;
 
        http_client_host_submit_request(host, req);
index c3699fb1ddb11b093d839db042ccef6709148ffe..09c56d96c52ecb92003f913f38835db3cac0601a 100644 (file)
@@ -103,6 +103,7 @@ struct http_client *http_client_init(const struct http_client_settings *set)
                (set->max_pipelined_requests > 0 ? set->max_pipelined_requests : 1);
        client->set.max_attempts = set->max_attempts;
        client->set.no_auto_redirect = set->no_auto_redirect;
+       client->set.no_ssl_tunnel = set->no_ssl_tunnel;
        client->set.max_redirects = set->max_redirects;
        client->set.response_hdr_limits = set->response_hdr_limits;
        client->set.request_timeout_msecs = set->request_timeout_msecs;
index 0fe7959336764e2b9b1cda342596706f1f6ce651..e21ec2b43bfdb73370abd955ec2a8dab4ad7cc5d 100644 (file)
@@ -64,6 +64,10 @@ struct http_client_settings {
        /* don't automatically act upon redirect responses */
        bool no_auto_redirect;
 
+       /* if we use a proxy, delegate SSL negotiation to proxy, rather than
+          creating a CONNECT tunnel through the proxy for the SSL link */
+       bool no_ssl_tunnel;
+
        /* maximum number of redirects for a request
           (default = 0; redirects refused) 
    */
@@ -138,6 +142,16 @@ http_client_request_connect(struct http_client *client,
                CALLBACK_TYPECHECK(callback, void (*)( \
                        const struct http_response *response, typeof(context))), \
                (http_client_request_callback_t *)callback, context)
+struct http_client_request *
+http_client_request_connect_ip(struct http_client *client,
+                   const struct ip_addr *ip, in_port_t port,
+                   http_client_request_callback_t *callback,
+                   void *context);
+#define http_client_request_connect_ip(client, ip, port, callback, context) \
+       http_client_request_connect_ip(client, ip, port + \
+               CALLBACK_TYPECHECK(callback, void (*)( \
+                       const struct http_response *response, typeof(context))), \
+               (http_client_request_callback_t *)callback, context)
 
 void http_client_request_set_port(struct http_client_request *req,
        in_port_t port);