summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2019-12-26 16:37:01 -0600
committerEdward Thomson <ethomson@edwardthomson.com>2020-01-24 10:39:56 -0600
commit76fd406a75c340fdda4c7a3a9dc56d206b2b0577 (patch)
treefb37a372a0a0f7bd6f60a7abd37d2f2d2128c3cc
parentb9c5b15a7958ab4ecb83504a6d858efd18610297 (diff)
downloadlibgit2-76fd406a75c340fdda4c7a3a9dc56d206b2b0577.tar.gz
http: send probe packets
When we're authenticating with a connection-based authentication scheme (NTLM, Negotiate), we need to make sure that we're still connected between the initial GET where we did the authentication and the POST that we're about to send. Our keep-alive session may have not kept alive, but more likely, some servers do not authenticate the entire keep-alive connection and may have "forgotten" that we were authenticated, namely Apache and nginx. Send a "probe" packet, that is an HTTP POST request to the upload-pack or receive-pack endpoint, that consists of an empty git pkt ("0000"). If we're authenticated, we'll get a 200 back. If we're not, we'll get a 401 back, and then we'll resend that probe packet with the first step of our authentication (asking to start authentication with the given scheme). We expect _yet another_ 401 back, with the authentication challenge. Finally, we will send our authentication response with the actual POST data. This will allow us to authenticate without draining the POST data in the initial request that gets us a 401.
-rw-r--r--src/transports/auth_ntlm.h3
-rw-r--r--src/transports/http.c69
2 files changed, 70 insertions, 2 deletions
diff --git a/src/transports/auth_ntlm.h b/src/transports/auth_ntlm.h
index 5b42b2b8e..a7cd6d795 100644
--- a/src/transports/auth_ntlm.h
+++ b/src/transports/auth_ntlm.h
@@ -11,6 +11,9 @@
#include "git2.h"
#include "auth.h"
+/* NTLM requires a full request/challenge/response */
+#define GIT_AUTH_STEPS_NTLM 2
+
#ifdef GIT_NTLM
#if defined(GIT_OPENSSL)
diff --git a/src/transports/http.c b/src/transports/http.c
index c535f2a35..e63827cdb 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -432,6 +432,56 @@ done:
return error;
}
+static bool needs_probe(http_stream *stream)
+{
+ http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
+
+ return (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM ||
+ transport->server.auth_schemetypes == GIT_HTTP_AUTH_NEGOTIATE);
+}
+
+static int send_probe(http_stream *stream)
+{
+ http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
+ git_http_client *client = transport->http_client;
+ const char *probe = "0000";
+ size_t len = 4;
+ git_net_url url = GIT_NET_URL_INIT;
+ git_http_request request = {0};
+ git_http_response response = {0};
+ bool complete = false;
+ size_t step, steps = 1;
+ int error;
+
+ /* NTLM requires a full challenge/response */
+ if (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM)
+ steps = GIT_AUTH_STEPS_NTLM;
+
+ /*
+ * Send at most two requests: one without any authentication to see
+ * if we get prompted to authenticate. If we do, send a second one
+ * with the first authentication message. The final authentication
+ * message with the response will occur with the *actual* POST data.
+ */
+ for (step = 0; step < steps && !complete; step++) {
+ git_net_url_dispose(&url);
+ git_http_response_dispose(&response);
+
+ if ((error = generate_request(&url, &request, stream, len)) < 0 ||
+ (error = git_http_client_send_request(client, &request)) < 0 ||
+ (error = git_http_client_send_body(client, probe, len)) < 0 ||
+ (error = git_http_client_read_response(&response, client)) < 0 ||
+ (error = git_http_client_skip_body(client)) < 0 ||
+ (error = handle_response(&complete, stream, &response, true)) < 0)
+ goto done;
+ }
+
+done:
+ git_http_response_dispose(&response);
+ git_net_url_dispose(&url);
+ return error;
+}
+
/*
* Write to an HTTP transport - for the first invocation of this function
* (ie, when stream->state == HTTP_STATE_NONE), we'll send a POST request
@@ -458,6 +508,20 @@ static int http_stream_write(
git_net_url_dispose(&url);
git_http_response_dispose(&response);
+ /*
+ * If we're authenticating with a connection-based mechanism
+ * (NTLM, Kerberos), send a "probe" packet. Servers SHOULD
+ * authenticate an entire keep-alive connection, so ideally
+ * we should not need to authenticate but some servers do
+ * not support this. By sending a probe packet, we'll be
+ * able to follow up with a second POST using the actual
+ * data (and, in the degenerate case, the authentication
+ * header as well).
+ */
+ if (needs_probe(stream) && (error = send_probe(stream)) < 0)
+ goto done;
+
+ /* Send the regular POST request. */
if ((error = generate_request(&url, &request, stream, len)) < 0 ||
(error = git_http_client_send_request(
transport->http_client, &request)) < 0)
@@ -511,6 +575,7 @@ static int http_stream_read_response(
{
http_stream *stream = (http_stream *)s;
http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
+ git_http_client *client = transport->http_client;
git_http_response response = {0};
bool complete;
int error;
@@ -518,7 +583,7 @@ static int http_stream_read_response(
*out_len = 0;
if (stream->state == HTTP_STATE_SENDING_REQUEST) {
- if ((error = git_http_client_read_response(&response, transport->http_client)) < 0 ||
+ if ((error = git_http_client_read_response(&response, client)) < 0 ||
(error = handle_response(&complete, stream, &response, false)) < 0)
goto done;
@@ -526,7 +591,7 @@ static int http_stream_read_response(
stream->state = HTTP_STATE_RECEIVING_RESPONSE;
}
- error = git_http_client_read_body(transport->http_client, buffer, buffer_size);
+ error = git_http_client_read_body(client, buffer, buffer_size);
if (error > 0) {
*out_len = error;