#include "net.h"
#include "str.h"
#include "hash.h"
+#include "llist.h"
#include "array.h"
#include "ioloop.h"
#include "istream.h"
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;
}
}
+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)
{
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;
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);
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,
}
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) {
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;
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));
}
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
};
unsigned int submitted:1;
unsigned int connect_tunnel:1;
unsigned int connect_direct:1;
+ unsigned int ssl_tunnel:1;
};
struct http_client_host_port {
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;
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 {
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);
}
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);
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;
}
/* 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 */
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);
(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;
/* 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)
*/
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);