Skip to content

Commit 6f12598

Browse files
derrickstoleedscho
authored andcommitted
Merge pull request #265: gvfs-helper: fix support for NTLM
Fix NTLM support in gvfs-helper. NTLM is handled magically by libcurl when we CURLAUTH_ANY is enabled AND we passed empty username/password. This lets it negotiate with the server and choose the best authentication scheme.
2 parents 346d5fd + 1fe4e2e commit 6f12598

File tree

1 file changed

+88
-52
lines changed

1 file changed

+88
-52
lines changed

gvfs-helper.c

Lines changed: 88 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2729,6 +2729,84 @@ static void do_throttle_wait(struct gh__request_params *params,
27292729
}
27302730
}
27312731

2732+
static void set_main_creds_on_slot(struct active_request_slot *slot,
2733+
const struct credential *creds)
2734+
{
2735+
assert(creds == &gh__global.main_creds);
2736+
2737+
/*
2738+
* When talking to the main/origin server, we have 3 modes
2739+
* of operation:
2740+
*
2741+
* [1] The initial request is sent without loading creds
2742+
* and with ANY-AUTH set. (And the `":"` is a magic
2743+
* value.)
2744+
*
2745+
* This allows libcurl to negotiate for us if it can.
2746+
* For example, this allows NTLM to work by magic and
2747+
* we get 200s without ever seeing a 401. If libcurl
2748+
* cannot negotiate for us, it gives us a 401 (and all
2749+
* of the 401 code in this file responds to that).
2750+
*
2751+
* [2] A 401 retry will load the main creds and try again.
2752+
* This causes `creds->username`to be non-NULL (even
2753+
* if refers to a zero-length string). And we assume
2754+
* BASIC Authentication. (And a zero-length username
2755+
* is a convention for PATs, but then sometimes users
2756+
* put the PAT in their `username` field and leave the
2757+
* `password` field blank. And that works too.)
2758+
*
2759+
* [3] Subsequent requests on the same connection use
2760+
* whatever worked before.
2761+
*/
2762+
if (creds && creds->username) {
2763+
curl_easy_setopt(slot->curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
2764+
curl_easy_setopt(slot->curl, CURLOPT_USERNAME, creds->username);
2765+
curl_easy_setopt(slot->curl, CURLOPT_PASSWORD, creds->password);
2766+
} else {
2767+
curl_easy_setopt(slot->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
2768+
curl_easy_setopt(slot->curl, CURLOPT_USERPWD, ":");
2769+
}
2770+
}
2771+
2772+
static void set_cache_server_creds_on_slot(struct active_request_slot *slot,
2773+
const struct credential *creds)
2774+
{
2775+
assert(creds == &gh__global.cache_creds);
2776+
assert(creds->username);
2777+
2778+
/*
2779+
* Things are weird when talking to a cache-server:
2780+
*
2781+
* [1] They don't send 401s on an auth error, rather they send
2782+
* a 400 (with a nice human-readable string in the html body).
2783+
* This prevents libcurl from doing any negotiation for us.
2784+
*
2785+
* [2] Cache-servers don't manage their own passwords, but
2786+
* rather require us to send the Basic Authentication
2787+
* username & password that we would send to the main
2788+
* server. (So yes, we have to get creds validated
2789+
* against the main server creds and substitute them when
2790+
* talking to the cache-server.)
2791+
*
2792+
* This means that:
2793+
*
2794+
* [a] We cannot support cache-servers that want to use NTLM.
2795+
*
2796+
* [b] If we want to talk to a cache-server, we have get the
2797+
* Basic Auth creds for the main server. And this may be
2798+
* problematic if the libcurl and/or the credential manager
2799+
* insists on using NTML and prevents us from getting them.
2800+
*
2801+
* So we never try AUTH-ANY and force Basic Auth (if possible).
2802+
*/
2803+
if (creds && creds->username) {
2804+
curl_easy_setopt(slot->curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
2805+
curl_easy_setopt(slot->curl, CURLOPT_USERNAME, creds->username);
2806+
curl_easy_setopt(slot->curl, CURLOPT_PASSWORD, creds->password);
2807+
}
2808+
}
2809+
27322810
/*
27332811
* Do a single HTTP request WITHOUT robust-retry, auth-retry or fallback.
27342812
*/
@@ -2794,37 +2872,10 @@ static void do_req(const char *url_base,
27942872
curl_easy_setopt(slot->curl, CURLOPT_HEADERFUNCTION, parse_resp_hdr);
27952873
curl_easy_setopt(slot->curl, CURLOPT_HEADERDATA, params);
27962874

2797-
if (creds && creds->username) {
2798-
/*
2799-
* Force CURL to respect the username/password we provide by
2800-
* turning off the AUTH-ANY negotiation stuff.
2801-
*
2802-
* That is, CURLAUTH_ANY causes CURL to NOT send the creds
2803-
* on an initial request in order to force a 401 and let it
2804-
* negotiate the best auth scheme and then retry.
2805-
*
2806-
* This is problematic when talking to the cache-servers
2807-
* because they send a 400 (with a "A valid Basic Auth..."
2808-
* message body) rather than a 401. This means that the
2809-
* the automatic retry will never happen. And even if we
2810-
* do force a retry, CURL still won't send the creds.
2811-
*
2812-
* So we turn it off and force it use our creds.
2813-
*/
2814-
curl_easy_setopt(slot->curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
2815-
curl_easy_setopt(slot->curl, CURLOPT_USERNAME, creds->username);
2816-
curl_easy_setopt(slot->curl, CURLOPT_PASSWORD, creds->password);
2817-
} else {
2818-
/*
2819-
* Turn on the AUTH-ANY negotiation. This only works
2820-
* with the main Git server (because the cache-server
2821-
* doesn't handle 401s).
2822-
*
2823-
* TODO Think about if we really need to handle this case.
2824-
* TODO Guard with "if (params->sever_type == __MAIN)"
2825-
*/
2826-
curl_easy_setopt(slot->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
2827-
}
2875+
if (params->server_type == GH__SERVER_TYPE__MAIN)
2876+
set_main_creds_on_slot(slot, creds);
2877+
else
2878+
set_cache_server_creds_on_slot(slot, creds);
28282879

28292880
if (params->progress_base_phase2_msg.len ||
28302881
params->progress_base_phase3_msg.len) {
@@ -2923,27 +2974,8 @@ static void do_req__to_main(const char *url_component,
29232974
params->server_type = GH__SERVER_TYPE__MAIN;
29242975

29252976
/*
2926-
* When talking to the main Git server, we do allow non-auth'd
2927-
* initial requests (mainly for testing, since production servers
2928-
* will usually always want creds), so we DO NOT force load the
2929-
* user's creds here and let the normal 401 mechanism handle things.
2930-
* This has a slight perf penalty -- if we're bulk fetching 4000
2931-
* objects from a production server, we're going to do a 100K+ byte
2932-
* POST just to get a 401 and then repeat the 100K+ byte POST
2933-
* with the creds.
2934-
*
2935-
* TODO Consider making a test or runtime flag to alter this.
2936-
* TODO If set/not-set, add a call here to pre-populate the creds.
2937-
* TODO
2938-
* TODO lookup_main_creds();
2939-
* TODO
2940-
*
2941-
* TODO Testing with GIT_TRACE_CURL shows that curl will *SOMETIME*
2942-
* TODO send an "Expect: 100-continue" and silently handle/eat the
2943-
* TODO "HTTP/1.1 100 Continue" before the POST-body is sent,
2944-
* TODO so maybe this isn't an issue. (Other than the extra
2945-
* TODO roundtrip for the first set of headers.) More testing here
2946-
* TODO should be performed.
2977+
* When talking to the main Git server, we DO NOT preload the
2978+
* creds before the first request.
29472979
*/
29482980

29492981
do_req__with_robust_retry(gh__global.main_url, url_component,
@@ -2968,6 +3000,10 @@ static void do_req__to_cache_server(const char *url_component,
29683000
{
29693001
params->server_type = GH__SERVER_TYPE__CACHE;
29703002

3003+
/*
3004+
* When talking to a cache-server, DO force load the creds.
3005+
* This implicitly preloads the creds to the main server.
3006+
*/
29713007
synthesize_cache_server_creds();
29723008

29733009
do_req__with_robust_retry(gh__global.cache_server_url, url_component,

0 commit comments

Comments
 (0)