summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2018-10-28 21:27:56 +0000
committerEdward Thomson <ethomson@edwardthomson.com>2018-11-28 15:46:57 +0000
commit5d4e1e040f6c4530ac18f3ce82685e780192b2bb (patch)
tree0e2f8ebf93bc178be27959ba295e6196562020db
parent43b592ac84dbd3d649022ff9503f00ecc83d5278 (diff)
downloadlibgit2-5d4e1e040f6c4530ac18f3ce82685e780192b2bb.tar.gz
http: use CONNECT to talk to proxies
Natively support HTTPS connections through proxies by speaking CONNECT to the proxy and then adding a TLS connection on top of the socket.
-rw-r--r--src/transports/http.c237
1 files changed, 224 insertions, 13 deletions
diff --git a/src/transports/http.c b/src/transports/http.c
index 77e1f1be1..6d2aaf9cf 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -68,8 +68,7 @@ typedef struct {
unsigned chunk_buffer_len;
unsigned sent_request : 1,
received_response : 1,
- chunked : 1,
- replay_count : 3;
+ chunked : 1;
} http_stream;
typedef struct {
@@ -103,11 +102,13 @@ typedef struct {
git_buf parse_header_value;
char parse_buffer_data[NETIO_BUFSIZE];
char *content_type;
+ char *content_length;
char *location;
enum last_cb last_cb;
int parse_error;
int error;
- unsigned parse_finished : 1;
+ unsigned parse_finished : 1,
+ replay_count : 3;
} http_subtransport;
typedef struct {
@@ -260,8 +261,11 @@ static int gen_request(
}
/* Apply proxy and server credentials to the request */
- if (apply_credentials(buf, &t->proxy, AUTH_HEADER_PROXY) < 0 ||
- apply_credentials(buf, &t->server, AUTH_HEADER_SERVER) < 0)
+ if (t->proxy_opts.type != GIT_PROXY_NONE &&
+ apply_credentials(buf, &t->proxy, AUTH_HEADER_PROXY) < 0)
+ return -1;
+
+ if (apply_credentials(buf, &t->server, AUTH_HEADER_SERVER) < 0)
return -1;
git_buf_puts(buf, "\r\n");
@@ -308,6 +312,12 @@ static int on_header_ready(http_subtransport *t)
GITERR_CHECK_ALLOC(t->content_type);
}
}
+ else if (!strcasecmp("Content-Length", git_buf_cstr(name))) {
+ if (!t->content_length) {
+ t->content_length = git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(t->content_length);
+ }
+ }
else if (!strcasecmp("Proxy-Authenticate", git_buf_cstr(name))) {
char *dup = git__strdup(git_buf_cstr(value));
GITERR_CHECK_ALLOC(dup);
@@ -438,7 +448,7 @@ static int on_headers_complete(http_parser *parser)
int proxy_auth_types = 0, server_auth_types = 0;
/* Enforce a reasonable cap on the number of replays */
- if (s->replay_count++ >= GIT_HTTP_REPLAY_MAX) {
+ if (t->replay_count++ >= GIT_HTTP_REPLAY_MAX) {
giterr_set(GITERR_NET, "too many redirects or authentication replays");
return t->parse_error = PARSE_ERROR_GENERIC;
}
@@ -567,15 +577,19 @@ static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
if (t->parse_error == PARSE_ERROR_REPLAY)
return 0;
- if (ctx->buf_size < len) {
- giterr_set(GITERR_NET, "can't fit data in the buffer");
- return t->parse_error = PARSE_ERROR_GENERIC;
+ /* If there's no buffer set, we're explicitly ignoring the body. */
+ if (ctx->buffer) {
+ if (ctx->buf_size < len) {
+ giterr_set(GITERR_NET, "can't fit data in the buffer");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ memcpy(ctx->buffer, str, len);
+ ctx->buffer += len;
+ ctx->buf_size -= len;
}
- memcpy(ctx->buffer, str, len);
*(ctx->bytes_read) += len;
- ctx->buffer += len;
- ctx->buf_size -= len;
return 0;
}
@@ -601,6 +615,9 @@ static void clear_parser_state(http_subtransport *t)
git__free(t->content_type);
t->content_type = NULL;
+ git__free(t->content_length);
+ t->content_length = NULL;
+
git__free(t->location);
t->location = NULL;
@@ -734,6 +751,172 @@ static int stream_connect(
return error;
}
+static int gen_connect_req(git_buf *buf, http_subtransport *t)
+{
+ git_buf_printf(buf, "CONNECT %s:%s HTTP/1.1\r\n",
+ t->server.url.host, t->server.url.port);
+
+ git_buf_puts(buf, "User-Agent: ");
+ git_http__user_agent(buf);
+ git_buf_puts(buf, "\r\n");
+
+ git_buf_printf(buf, "Host: %s\r\n", t->proxy.url.host);
+
+ if (apply_credentials(buf, &t->proxy, AUTH_HEADER_PROXY) < 0)
+ return -1;
+
+ git_buf_puts(buf, "\r\n");
+
+ return git_buf_oom(buf) ? -1 : 0;
+}
+
+static int proxy_headers_complete(http_parser *parser)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+ int proxy_auth_types = 0;
+
+ /* Enforce a reasonable cap on the number of replays */
+ if (t->replay_count++ >= GIT_HTTP_REPLAY_MAX) {
+ giterr_set(GITERR_NET, "too many redirects or authentication replays");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ /* Both parse_header_name and parse_header_value are populated
+ * and ready for consumption. */
+ if (VALUE == t->last_cb)
+ if (on_header_ready(t) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ /*
+ * Capture authentication headers for the proxy or final endpoint,
+ * these may be 407/401 (authentication is not complete) or a 200
+ * (informing us that auth has completed).
+ */
+ if (parse_authenticate_response(&t->proxy, &proxy_auth_types) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ /* Check for a proxy authentication failure. */
+ if (parser->status_code == 407)
+ return on_auth_required(&t->proxy.cred,
+ parser,
+ t->proxy_opts.url,
+ SERVER_TYPE_PROXY,
+ t->proxy_opts.credentials,
+ t->proxy_opts.payload,
+ t->proxy.url.user,
+ proxy_auth_types);
+
+ if (parser->status_code != 200) {
+ giterr_set(GITERR_NET, "unexpected status code from proxy: %d",
+ parser->status_code);
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ if (!t->content_length || strcmp(t->content_length, "0") == 0)
+ t->parse_finished = 1;
+
+ return 0;
+}
+
+static int proxy_connect(
+ git_stream **out, git_stream *proxy_stream, http_subtransport *t)
+{
+ git_buf request = GIT_BUF_INIT;
+ static http_parser_settings proxy_parser_settings = {0};
+ size_t bytes_read = 0, bytes_parsed;
+ parser_context ctx;
+ int error;
+
+ /* Use the parser settings only to parser headers. */
+ proxy_parser_settings.on_header_field = on_header_field;
+ proxy_parser_settings.on_header_value = on_header_value;
+ proxy_parser_settings.on_headers_complete = proxy_headers_complete;
+ proxy_parser_settings.on_message_complete = on_message_complete;
+
+replay:
+ clear_parser_state(t);
+
+ gitno_buffer_setup_fromstream(proxy_stream,
+ &t->parse_buffer,
+ t->parse_buffer_data,
+ sizeof(t->parse_buffer_data));
+
+ if ((error = gen_connect_req(&request, t)) < 0)
+ goto done;
+
+ if ((error = git_stream_write(proxy_stream,
+ request.ptr, request.size, 0)) < 0)
+ goto done;
+
+ git_buf_dispose(&request);
+
+ while (!bytes_read && !t->parse_finished) {
+ t->parse_buffer.offset = 0;
+
+ if ((error = gitno_recv(&t->parse_buffer)) < 0)
+ goto done;
+
+ /*
+ * This call to http_parser_execute will invoke the on_*
+ * callbacks. Since we don't care about the body of the response,
+ * we can set our buffer to NULL.
+ */
+ ctx.t = t;
+ ctx.s = NULL;
+ ctx.buffer = NULL;
+ ctx.buf_size = 0;
+ ctx.bytes_read = &bytes_read;
+
+ /* Set the context, call the parser, then unset the context. */
+ t->parser.data = &ctx;
+
+ bytes_parsed = http_parser_execute(&t->parser,
+ &proxy_parser_settings, t->parse_buffer.data, t->parse_buffer.offset);
+
+ t->parser.data = NULL;
+
+ /* Ensure that we didn't get a redirect; unsupported. */
+ if (t->location) {
+ giterr_set(GITERR_NET, "proxy server sent unsupported redirect during CONNECT");
+ error = -1;
+ goto done;
+ }
+
+ /* Replay the request with authentication headers. */
+ if (PARSE_ERROR_REPLAY == t->parse_error)
+ goto replay;
+
+ if (t->parse_error < 0) {
+ error = t->parse_error == PARSE_ERROR_EXT ? PARSE_ERROR_EXT : -1;
+ goto done;
+ }
+
+ if (bytes_parsed != t->parse_buffer.offset) {
+ giterr_set(GITERR_NET,
+ "HTTP parser error: %s",
+ http_errno_description((enum http_errno)t->parser.http_errno));
+ error = -1;
+ goto done;
+ }
+ }
+
+ if ((error = git_tls_stream_wrap(out, proxy_stream, t->server.url.host)) == 0)
+ error = stream_connect(*out, &t->server.url,
+ t->owner->certificate_check_cb,
+ t->owner->message_cb_payload);
+
+ /*
+ * Since we've connected via a HTTPS proxy tunnel, we don't behave
+ * as if we have an HTTP proxy.
+ */
+ t->proxy_opts.type = GIT_PROXY_NONE;
+ t->replay_count = 0;
+
+done:
+ return error;
+}
+
static int http_connect(http_subtransport *t)
{
gitno_connection_data *url;
@@ -751,9 +934,16 @@ static int http_connect(http_subtransport *t)
git_stream_close(t->server.stream);
git_stream_free(t->server.stream);
t->server.stream = NULL;
- t->connected = 0;
}
+ if (t->proxy.stream) {
+ git_stream_close(t->proxy.stream);
+ git_stream_free(t->proxy.stream);
+ t->proxy.stream = NULL;
+ }
+
+ t->connected = 0;
+
if (t->proxy_opts.type == GIT_PROXY_SPECIFIED) {
url = &t->proxy.url;
cert_cb = t->proxy_opts.certificate_check;
@@ -786,6 +976,21 @@ static int http_connect(http_subtransport *t)
if ((error = stream_connect(stream, url, cert_cb, cb_payload)) < 0)
goto on_error;
+ /*
+ * At this point we have a connection to the remote server or to
+ * a proxy. If it's a proxy and the remote server is actually
+ * an HTTPS connection, then we need to build a CONNECT tunnel.
+ */
+ if (t->proxy_opts.type == GIT_PROXY_SPECIFIED &&
+ t->server.url.use_ssl) {
+ proxy_stream = stream;
+ stream = NULL;
+
+ if ((error = proxy_connect(&stream, proxy_stream, t)) < 0)
+ goto on_error;
+ }
+
+ t->proxy.stream = proxy_stream;
t->server.stream = stream;
t->connected = 1;
return 0;
@@ -1226,6 +1431,12 @@ static int http_close(git_smart_subtransport *subtransport)
t->server.stream = NULL;
}
+ if (t->proxy.stream) {
+ git_stream_close(t->proxy.stream);
+ git_stream_free(t->proxy.stream);
+ t->proxy.stream = NULL;
+ }
+
free_cred(&t->server.cred);
free_cred(&t->server.url_cred);
free_cred(&t->proxy.cred);