From: Stephan Bosch Date: Sat, 12 Oct 2013 08:11:04 +0000 (+0300) Subject: lib-http: http-client: Added support for tunneling SSL conntections through proxy. X-Git-Tag: 2.2.7~73 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e47c2f17d8136c4d972d1074a3f84ba2ecef4fdc;p=thirdparty%2Fdovecot%2Fcore.git lib-http: http-client: Added support for tunneling SSL conntections through proxy. --- diff --git a/src/lib-http/http-client-connection.c b/src/lib-http/http-client-connection.c index eff8e341f9..ce151452e2 100644 --- a/src/lib-http/http-client-connection.c +++ b/src/lib-http/http-client-connection.c @@ -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, diff --git a/src/lib-http/http-client-host.c b/src/lib-http/http-client-host.c index 57c37d676d..bc20ba78ff 100644 --- a/src/lib-http/http-client-host.c +++ b/src/lib-http/http-client-host.c @@ -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; diff --git a/src/lib-http/http-client-peer.c b/src/lib-http/http-client-peer.c index 1c35aba301..8b1978f3fb 100644 --- a/src/lib-http/http-client-peer.c +++ b/src/lib-http/http-client-peer.c @@ -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)); } diff --git a/src/lib-http/http-client-private.h b/src/lib-http/http-client-private.h index 023f1b1fcf..db74ec3dd8 100644 --- a/src/lib-http/http-client-private.h +++ b/src/lib-http/http-client-private.h @@ -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); diff --git a/src/lib-http/http-client-request.c b/src/lib-http/http-client-request.c index 2fe6964f3d..8ca4b81537 100644 --- a/src/lib-http/http-client-request.c +++ b/src/lib-http/http-client-request.c @@ -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); diff --git a/src/lib-http/http-client.c b/src/lib-http/http-client.c index c3699fb1dd..09c56d96c5 100644 --- a/src/lib-http/http-client.c +++ b/src/lib-http/http-client.c @@ -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; diff --git a/src/lib-http/http-client.h b/src/lib-http/http-client.h index 0fe7959336..e21ec2b43b 100644 --- a/src/lib-http/http-client.h +++ b/src/lib-http/http-client.h @@ -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);