Skip to content

Commit 76fd406

Browse files
committed
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 parent b9c5b15 commit 76fd406

File tree

2 files changed

+70
-2
lines changed

2 files changed

+70
-2
lines changed

src/transports/auth_ntlm.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
#include "git2.h"
1212
#include "auth.h"
1313

14+
/* NTLM requires a full request/challenge/response */
15+
#define GIT_AUTH_STEPS_NTLM 2
16+
1417
#ifdef GIT_NTLM
1518

1619
#if defined(GIT_OPENSSL)

src/transports/http.c

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,56 @@ static int http_stream_read(
432432
return error;
433433
}
434434

435+
static bool needs_probe(http_stream *stream)
436+
{
437+
http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
438+
439+
return (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM ||
440+
transport->server.auth_schemetypes == GIT_HTTP_AUTH_NEGOTIATE);
441+
}
442+
443+
static int send_probe(http_stream *stream)
444+
{
445+
http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
446+
git_http_client *client = transport->http_client;
447+
const char *probe = "0000";
448+
size_t len = 4;
449+
git_net_url url = GIT_NET_URL_INIT;
450+
git_http_request request = {0};
451+
git_http_response response = {0};
452+
bool complete = false;
453+
size_t step, steps = 1;
454+
int error;
455+
456+
/* NTLM requires a full challenge/response */
457+
if (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM)
458+
steps = GIT_AUTH_STEPS_NTLM;
459+
460+
/*
461+
* Send at most two requests: one without any authentication to see
462+
* if we get prompted to authenticate. If we do, send a second one
463+
* with the first authentication message. The final authentication
464+
* message with the response will occur with the *actual* POST data.
465+
*/
466+
for (step = 0; step < steps && !complete; step++) {
467+
git_net_url_dispose(&url);
468+
git_http_response_dispose(&response);
469+
470+
if ((error = generate_request(&url, &request, stream, len)) < 0 ||
471+
(error = git_http_client_send_request(client, &request)) < 0 ||
472+
(error = git_http_client_send_body(client, probe, len)) < 0 ||
473+
(error = git_http_client_read_response(&response, client)) < 0 ||
474+
(error = git_http_client_skip_body(client)) < 0 ||
475+
(error = handle_response(&complete, stream, &response, true)) < 0)
476+
goto done;
477+
}
478+
479+
done:
480+
git_http_response_dispose(&response);
481+
git_net_url_dispose(&url);
482+
return error;
483+
}
484+
435485
/*
436486
* Write to an HTTP transport - for the first invocation of this function
437487
* (ie, when stream->state == HTTP_STATE_NONE), we'll send a POST request
@@ -458,6 +508,20 @@ static int http_stream_write(
458508
git_net_url_dispose(&url);
459509
git_http_response_dispose(&response);
460510

511+
/*
512+
* If we're authenticating with a connection-based mechanism
513+
* (NTLM, Kerberos), send a "probe" packet. Servers SHOULD
514+
* authenticate an entire keep-alive connection, so ideally
515+
* we should not need to authenticate but some servers do
516+
* not support this. By sending a probe packet, we'll be
517+
* able to follow up with a second POST using the actual
518+
* data (and, in the degenerate case, the authentication
519+
* header as well).
520+
*/
521+
if (needs_probe(stream) && (error = send_probe(stream)) < 0)
522+
goto done;
523+
524+
/* Send the regular POST request. */
461525
if ((error = generate_request(&url, &request, stream, len)) < 0 ||
462526
(error = git_http_client_send_request(
463527
transport->http_client, &request)) < 0)
@@ -511,22 +575,23 @@ static int http_stream_read_response(
511575
{
512576
http_stream *stream = (http_stream *)s;
513577
http_subtransport *transport = OWNING_SUBTRANSPORT(stream);
578+
git_http_client *client = transport->http_client;
514579
git_http_response response = {0};
515580
bool complete;
516581
int error;
517582

518583
*out_len = 0;
519584

520585
if (stream->state == HTTP_STATE_SENDING_REQUEST) {
521-
if ((error = git_http_client_read_response(&response, transport->http_client)) < 0 ||
586+
if ((error = git_http_client_read_response(&response, client)) < 0 ||
522587
(error = handle_response(&complete, stream, &response, false)) < 0)
523588
goto done;
524589

525590
assert(complete);
526591
stream->state = HTTP_STATE_RECEIVING_RESPONSE;
527592
}
528593

529-
error = git_http_client_read_body(transport->http_client, buffer, buffer_size);
594+
error = git_http_client_read_body(client, buffer, buffer_size);
530595

531596
if (error > 0) {
532597
*out_len = error;

0 commit comments

Comments
 (0)