diff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc
index 7fd001206ded22..2d3ca702b63b4b 100644
--- a/Documentation/config/http.adoc
+++ b/Documentation/config/http.adoc
@@ -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",
diff --git a/credential.c b/credential.c
index 27cecb3128f72c..7748a0ab26d0d5 100644
--- a/credential.c
+++ b/credential.c
@@ -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")) {
@@ -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);
diff --git a/credential.h b/credential.h
index c78b72d110eaac..95244d5375dfe9 100644
--- a/credential.h
+++ b/credential.h
@@ -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;
diff --git a/http.c b/http.c
index 8ae0a28fbb129a..3a6872bb6612b4 100644
--- a/http.c
+++ b/http.c
@@ -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 =
@@ -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 =
@@ -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()) {
/*
@@ -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)
@@ -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) {
@@ -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();
}
@@ -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) {
@@ -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;
diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh
index 5091db949b7f99..f22ae2bf67e75b 100644
--- a/t/lib-httpd.sh
+++ b/t/lib-httpd.sh
@@ -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"
diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf
index e631ab0eb5ef05..0e11b0cd6ece33 100644
--- a/t/lib-httpd/apache.conf
+++ b/t/lib-httpd/apache.conf
@@ -151,6 +151,13 @@ SetEnv PERL_PATH ${PERL_PATH}
CGIPassAuth on
+
+ SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
+ SetEnv GIT_HTTP_EXPORT_ALL
+
+ CGIPassAuth on
+
+
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/
@@ -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
Options FollowSymlinks
diff --git a/t/lib-httpd/ntlm-handshake.sh b/t/lib-httpd/ntlm-handshake.sh
new file mode 100755
index 00000000000000..3cf1266e40f20a
--- /dev/null
+++ b/t/lib-httpd/ntlm-handshake.sh
@@ -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
diff --git a/t/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh
index c1febbae9d778b..12a9ef78bf53c9 100755
--- a/t/t5563-simple-http-auth.sh
+++ b/t/t5563-simple-http-auth.sh
@@ -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