Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions http.c
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ static unsigned long empty_auth_useless =
CURLAUTH_BASIC
| CURLAUTH_DIGEST_IE
| CURLAUTH_DIGEST;
static int empty_auth_try_negotiate;

static struct curl_slist *pragma_header;
static struct string_list extra_http_headers = STRING_LIST_INIT_DUP;
Expand Down Expand Up @@ -704,6 +705,22 @@ static void init_curl_http_auth(CURL *result)
}
}

void http_reauth_prepare(int all_capabilities)
{
/*
* If we deferred stripping Negotiate to give empty auth a
* chance (auto mode), skip credential_fill on this retry so
* that init_curl_http_auth() sends empty credentials and
* libcurl can attempt Negotiate with the system ticket cache.
*/
if (empty_auth_try_negotiate &&
!http_auth.password && !http_auth.credential &&
(http_auth_methods & CURLAUTH_GSSNEGOTIATE))
return;

credential_fill(the_repository, &http_auth, all_capabilities);
}

/* *var must be free-able */
static void var_override(char **var, char *value)
{
Expand Down Expand Up @@ -1954,7 +1971,18 @@ static int handle_curl_result(struct slot_results *results)
}
return HTTP_NOAUTH;
} else {
http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
if (curl_empty_auth == -1 &&
!empty_auth_try_negotiate &&
(results->auth_avail & CURLAUTH_GSSNEGOTIATE)) {
/*
* In auto mode, give Negotiate a chance via
* empty auth before stripping it. If it fails,
* we will strip it on the next 401.
*/
empty_auth_try_negotiate = 1;
} else {
http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
}
if (results->auth_avail) {
http_auth_methods &= results->auth_avail;
http_auth_methods_restricted = 1;
Expand Down Expand Up @@ -2462,7 +2490,7 @@ static int http_request_recoverable(const char *url,
sleep(retry_delay);
}
} else if (ret == HTTP_REAUTH) {
credential_fill(the_repository, &http_auth, 1);
http_reauth_prepare(1);
}

/*
Expand Down
6 changes: 6 additions & 0 deletions http.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ extern int http_is_verbose;
extern ssize_t http_post_buffer;
extern struct credential http_auth;

/**
* Prepare for an HTTP re-authentication retry. This fills credentials
* via credential_fill() so the next request can include them.
*/
void http_reauth_prepare(int all_capabilities);

extern char curl_errorstr[CURL_ERROR_SIZE];

enum http_follow_config {
Expand Down
4 changes: 2 additions & 2 deletions remote-curl.c
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,7 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece
do {
err = probe_rpc(rpc, &results);
if (err == HTTP_REAUTH)
credential_fill(the_repository, &http_auth, 0);
http_reauth_prepare(0);
} while (err == HTTP_REAUTH);
if (err != HTTP_OK)
return -1;
Expand Down Expand Up @@ -1068,7 +1068,7 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece
rpc->any_written = 0;
err = run_slot(slot, NULL);
if (err == HTTP_REAUTH && !large_request) {
credential_fill(the_repository, &http_auth, 0);
http_reauth_prepare(0);
curl_slist_free_all(headers);
goto retry;
}
Expand Down
74 changes: 74 additions & 0 deletions t/t5563-simple-http-auth.sh
Original file line number Diff line number Diff line change
Expand Up @@ -748,4 +748,78 @@ test_expect_success NTLM 'access using NTLM auth' '
git ls-remote "$HTTPD_URL/ntlm_auth/repo.git"
'

test_lazy_prereq SPNEGO 'curl --version | grep -qi "SPNEGO\|GSS-API\|Kerberos\|negotiate"'

test_expect_success SPNEGO 'http.emptyAuth=auto attempts Negotiate before credential_fill' '
test_when_finished "per_test_cleanup" &&

set_credential_reply get <<-EOF &&
username=alice
password=secret-passwd
EOF

# Basic base64(alice:secret-passwd)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF

cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=200
id=default response=WWW-Authenticate: Negotiate
id=default response=WWW-Authenticate: Basic realm="example.com"
EOF

test_config_global credential.helper test-helper &&
GIT_TRACE_CURL="$TRASH_DIRECTORY/trace-auto" \
git -c http.emptyAuth=auto \
ls-remote "$HTTPD_URL/custom_auth/repo.git" &&

# In auto mode with a Negotiate+Basic server, there should be
# three 401 responses: (1) initial no-auth request, (2) empty-auth
# retry where Negotiate fails (no Kerberos ticket), (3) libcurl
# internal Negotiate retry. The fourth attempt uses Basic
# credentials from credential_fill and succeeds.
grep "HTTP/[0-9.]* 401" "$TRASH_DIRECTORY/trace-auto" >actual_401s &&
test_line_count = 3 actual_401s &&

expect_credential_query get <<-EOF
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
wwwauth[]=Negotiate
wwwauth[]=Basic realm="example.com"
EOF
'

test_expect_success SPNEGO 'http.emptyAuth=false skips Negotiate' '
test_when_finished "per_test_cleanup" &&

set_credential_reply get <<-EOF &&
username=alice
password=secret-passwd
EOF

# Basic base64(alice:secret-passwd)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF

cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=200
id=default response=WWW-Authenticate: Negotiate
id=default response=WWW-Authenticate: Basic realm="example.com"
EOF

test_config_global credential.helper test-helper &&
GIT_TRACE_CURL="$TRASH_DIRECTORY/trace-false" \
git -c http.emptyAuth=false \
ls-remote "$HTTPD_URL/custom_auth/repo.git" &&

# With emptyAuth=false, Negotiate is stripped immediately and
# credential_fill is called right away. Only one 401 response.
grep "HTTP/[0-9.]* 401" "$TRASH_DIRECTORY/trace-false" >actual_401s &&
test_line_count = 1 actual_401s
'

test_done
Loading