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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
diff --git a/src/transports/auth_ntlm.h b/src/transports/auth_ntlm.h
index 5b42b2b..a7cd6d7 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 c535f2a..e63827c 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;