Skip to content
Open
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
5 changes: 5 additions & 0 deletions Documentation/config/http.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,11 @@ http.sslKeyType::
See also libcurl `CURLOPT_SSLKEYTYPE`. Can be overridden by the
`GIT_SSL_KEY_TYPE` environment variable.

http.allowNTLMAuth::
Whether or not to allow NTLM authentication. While very convenient to set
up, and therefore still used in many on-prem scenarios, NTLM is a weak
authentication method and therefore deprecated. Defaults to "false".

http.schannelCheckRevoke::
Used to enforce or disable certificate revocation checks in cURL
when http.sslBackend is set to "schannel" via "true" and "false",
Expand Down
5 changes: 5 additions & 0 deletions credential.c
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,9 @@ int credential_read(struct credential *c, FILE *fp,
credential_set_capability(&c->capa_authtype, op_type);
else if (!strcmp(value, "state"))
credential_set_capability(&c->capa_state, op_type);
} else if (!strcmp(key, "ntlm")) {
if (!strcmp(value, "allow"))
c->ntlm_allow = 1;
} else if (!strcmp(key, "continue")) {
c->multistage = !!git_config_bool("continue", value);
} else if (!strcmp(key, "password_expiry_utc")) {
Expand Down Expand Up @@ -420,6 +423,8 @@ void credential_write(const struct credential *c, FILE *fp,
if (c->ephemeral)
credential_write_item(c, fp, "ephemeral", "1", 0);
}
if (c->ntlm_suppressed)
credential_write_item(c, fp, "ntlm", "suppressed", 0);
credential_write_item(c, fp, "protocol", c->protocol, 1);
credential_write_item(c, fp, "host", c->host, 1);
credential_write_item(c, fp, "path", c->path, 0);
Expand Down
3 changes: 3 additions & 0 deletions credential.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ struct credential {
struct credential_capability capa_authtype;
struct credential_capability capa_state;

unsigned ntlm_suppressed:1,
ntlm_allow:1;

char *username;
char *password;
char *credential;
Expand Down
41 changes: 37 additions & 4 deletions http.c
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ enum http_follow_config http_follow_config = HTTP_FOLLOW_INITIAL;

static struct credential cert_auth = CREDENTIAL_INIT;
static int ssl_cert_password_required;
static unsigned long http_auth_methods = CURLAUTH_ANY;
static unsigned long http_auth_any = CURLAUTH_ANY & ~CURLAUTH_NTLM;
static unsigned long http_auth_methods;
static int http_auth_methods_restricted;
/* Modes for which empty_auth cannot actually help us. */
static unsigned long empty_auth_useless =
Expand Down Expand Up @@ -430,6 +431,15 @@ static int http_options(const char *var, const char *value,
return 0;
}

if (!strcmp("http.allowntlmauth", var)) {
if (git_config_bool(var, value)) {
http_auth_any |= CURLAUTH_NTLM;
} else {
http_auth_any &= ~CURLAUTH_NTLM;
}
return 0;
}

if (!strcmp("http.schannelcheckrevoke", var)) {
if (value && !strcmp(value, "best-effort")) {
http_schannel_check_revoke_mode =
Expand Down Expand Up @@ -653,6 +663,11 @@ static void init_curl_http_auth(CURL *result)

credential_fill(the_repository, &http_auth, 1);

if (http_auth.ntlm_allow && !(http_auth_methods & CURLAUTH_NTLM)) {
http_auth_methods |= CURLAUTH_NTLM;
curl_easy_setopt(result, CURLOPT_HTTPAUTH, http_auth_methods);
}

if (http_auth.password) {
if (always_auth_proactively()) {
/*
Expand Down Expand Up @@ -712,11 +727,11 @@ static void init_curl_proxy_auth(CURL *result)
if (i == ARRAY_SIZE(proxy_authmethods)) {
warning("unsupported proxy authentication method %s: using anyauth",
http_proxy_authmethod);
curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
curl_easy_setopt(result, CURLOPT_PROXYAUTH, http_auth_any);
}
}
else
curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
curl_easy_setopt(result, CURLOPT_PROXYAUTH, http_auth_any);
}

static int has_cert_password(void)
Expand Down Expand Up @@ -1063,7 +1078,7 @@ static CURL *get_curl_handle(void)
}

curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_easy_setopt(result, CURLOPT_HTTPAUTH, http_auth_any);

#ifdef CURLGSSAPI_DELEGATION_FLAG
if (curl_deleg) {
Expand Down Expand Up @@ -1458,6 +1473,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
set_long_from_env(&curl_tcp_keepintvl, "GIT_TCP_KEEPINTVL");
set_long_from_env(&curl_tcp_keepcnt, "GIT_TCP_KEEPCNT");

http_auth_methods = http_auth_any;

curl_default = get_curl_handle();
}

Expand Down Expand Up @@ -1889,6 +1906,12 @@ static int handle_curl_result(struct slot_results *results)
} else if (missing_target(results))
return HTTP_MISSING_TARGET;
else if (results->http_code == 401) {
http_auth.ntlm_suppressed = (results->auth_avail & CURLAUTH_NTLM) &&
!(http_auth_any & CURLAUTH_NTLM);
if (http_auth.ntlm_suppressed && http_auth.ntlm_allow) {
http_auth_methods |= CURLAUTH_NTLM;
return HTTP_REAUTH;
}
if ((http_auth.username && http_auth.password) ||\
(http_auth.authtype && http_auth.credential)) {
if (http_auth.multistage) {
Expand All @@ -1898,6 +1921,16 @@ static int handle_curl_result(struct slot_results *results)
credential_reject(the_repository, &http_auth);
if (always_auth_proactively())
http_proactive_auth = PROACTIVE_AUTH_NONE;
if (http_auth.ntlm_suppressed) {
warning(_("Due to its cryptographic weaknesses, "
"NTLM authentication has been\n"
"disabled in Git by default. You can "
"re-enable it for trusted servers\n"
"by running:\n\n"
"git config set "
"http.%s://%s.allowNTLMAuth true"),
http_auth.protocol, http_auth.host);
}
return HTTP_NOAUTH;
} else {
http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
Expand Down
1 change: 1 addition & 0 deletions t/lib-httpd.sh
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ prepare_httpd() {
install_script error.sh
install_script apply-one-time-script.sh
install_script nph-custom-auth.sh
install_script ntlm-handshake.sh

ln -s "$LIB_HTTPD_MODULE_PATH" "$HTTPD_ROOT_PATH/modules"

Expand Down
8 changes: 8 additions & 0 deletions t/lib-httpd/apache.conf
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,13 @@ SetEnv PERL_PATH ${PERL_PATH}
CGIPassAuth on
</IfDefine>
</LocationMatch>
<LocationMatch /ntlm_auth/>
SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
SetEnv GIT_HTTP_EXPORT_ALL
<IfDefine USE_CGIPASSAUTH>
CGIPassAuth on
</IfDefine>
</LocationMatch>
ScriptAlias /smart/incomplete_length/git-upload-pack incomplete-length-upload-pack-v2-http.sh/
ScriptAlias /smart/incomplete_body/git-upload-pack incomplete-body-upload-pack-v2-http.sh/
ScriptAlias /smart/no_report/git-receive-pack error-no-report.sh/
Expand All @@ -161,6 +168,7 @@ ScriptAlias /error_smart/ error-smart-http.sh/
ScriptAlias /error/ error.sh/
ScriptAliasMatch /one_time_script/(.*) apply-one-time-script.sh/$1
ScriptAliasMatch /custom_auth/(.*) nph-custom-auth.sh/$1
ScriptAliasMatch /ntlm_auth/(.*) ntlm-handshake.sh/$1
<Directory ${GIT_EXEC_PATH}>
Options FollowSymlinks
</Directory>
Expand Down
38 changes: 38 additions & 0 deletions t/lib-httpd/ntlm-handshake.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/sh

case "$HTTP_AUTHORIZATION" in
'')
# No Authorization header -> send NTLM challenge
echo "Status: 401 Unauthorized"
echo "WWW-Authenticate: NTLM"
echo
;;
"NTLM TlRMTVNTUAAB"*)
# Type 1 -> respond with Type 2 challenge (hardcoded)
echo "Status: 401 Unauthorized"
# Base64-encoded version of the Type 2 challenge:
# signature: 'NTLMSSP\0'
# message_type: 2
# target_name: 'NTLM-GIT-SERVER'
# flags: 0xa2898205 =
# NEGOTIATE_UNICODE, REQUEST_TARGET, NEGOTIATE_NT_ONLY,
# TARGET_TYPE_SERVER, TARGET_TYPE_SHARE, REQUEST_NON_NT_SESSION_KEY,
# NEGOTIATE_VERSION, NEGOTIATE_128, NEGOTIATE_56
# challenge: 0xfa3dec518896295b
# context: '0000000000000000'
# target_info_present: true
# target_info_len: 128
# version: '10.0 (build 19041)'
echo "WWW-Authenticate: NTLM TlRMTVNTUAACAAAAHgAeADgAAAAFgomi+j3sUYiWKVsAAAAAAAAAAIAAgABWAAAACgBhSgAAAA9OAFQATABNAC0ARwBJAFQALQBTAEUAUgBWAEUAUgACABIAVwBPAFIASwBHAFIATwBVAFAAAQAeAE4AVABMAE0ALQBHAEkAVAAtAFMARQBSAFYARQBSAAQAEgBXAE8AUgBLAEcAUgBPAFUAUAADAB4ATgBUAEwATQAtAEcASQBUAC0AUwBFAFIAVgBFAFIABwAIAACfOcZKYNwBAAAAAA=="
echo
;;
"NTLM TlRMTVNTUAAD"*)
# Type 3 -> accept without validation
exec "$GIT_EXEC_PATH"/git-http-backend
;;
*)
echo "Status: 500 Unrecognized"
echo
echo "Unhandled auth: '$HTTP_AUTHORIZATION'"
;;
esac
29 changes: 29 additions & 0 deletions t/t5563-simple-http-auth.sh
Original file line number Diff line number Diff line change
Expand Up @@ -674,4 +674,33 @@ test_expect_success 'access using three-legged auth' '
EOF
'

test_lazy_prereq NTLM 'curl --version | grep -q NTLM'

test_expect_success NTLM 'access using NTLM auth' '
test_when_finished "per_test_cleanup" &&

set_credential_reply get <<-EOF &&
username=user
password=pwd
EOF

test_config_global credential.helper test-helper &&
test_must_fail env GIT_TRACE_CURL=1 git \
ls-remote "$HTTPD_URL/ntlm_auth/repo.git" 2>err &&
test_grep "allowNTLMAuth" err &&

# Can be enabled via config
GIT_TRACE_CURL=1 git -c http.$HTTPD_URL.allowNTLMAuth=true \
ls-remote "$HTTPD_URL/ntlm_auth/repo.git" &&

# Or via credential helper responding with ntlm=allow
set_credential_reply get <<-EOF &&
username=user
password=pwd
ntlm=allow
EOF

git ls-remote "$HTTPD_URL/ntlm_auth/repo.git"
'

test_done
Loading