diff --git a/CMakeLists.txt b/CMakeLists.txt
index 611388024f..159743fdd8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -434,6 +434,8 @@ option(CURL_DISABLE_KERBEROS_AUTH "Disable Kerberos authentication" OFF)
mark_as_advanced(CURL_DISABLE_KERBEROS_AUTH)
option(CURL_DISABLE_NEGOTIATE_AUTH "Disable negotiate authentication" OFF)
mark_as_advanced(CURL_DISABLE_NEGOTIATE_AUTH)
+option(CURL_DISABLE_NEGOTIATE_NTLM "Block NTLM within SPNEGO negotiation" OFF)
+mark_as_advanced(CURL_DISABLE_NEGOTIATE_NTLM)
option(CURL_DISABLE_AWS "Disable aws-sigv4" OFF)
mark_as_advanced(CURL_DISABLE_AWS)
option(CURL_DISABLE_DICT "Disable DICT" OFF)
@@ -1339,6 +1341,11 @@ if(CURL_USE_GSSAPI)
elseif(GSS_VERSION) # MIT
set(CURL_KRB5_VERSION "\"${GSS_VERSION}\"")
endif()
+
+ cmake_push_check_state()
+ list(APPEND CMAKE_REQUIRED_LIBRARIES CURL::gss)
+ check_function_exists("gss_set_neg_mechs" HAVE_GSS_SET_NEG_MECHS)
+ cmake_pop_check_state()
else()
message(WARNING "GSSAPI has been requested, but no supporting libraries found. Skipping.")
endif()
diff --git a/configure.ac b/configure.ac
index d06024bc10..7a014e6fba 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2016,6 +2016,7 @@ if test "$want_gss" = "yes"; then
AC_MSG_RESULT([no])
AC_MSG_ERROR([--with-gssapi was specified, but a GSS-API library was not found.])
])
+ AC_CHECK_FUNCS([gss_set_neg_mechs])
fi
build_libstubgss=no
@@ -4459,6 +4460,26 @@ AS_HELP_STRING([--disable-negotiate-auth],[Disable negotiate authentication]),
AC_MSG_RESULT(yes)
)
+dnl ************************************************************
+dnl disable NTLM within SPNEGO negotiation
+dnl
+AC_MSG_CHECKING([whether to allow NTLM within SPNEGO])
+AC_ARG_ENABLE(negotiate-ntlm,
+AS_HELP_STRING([--enable-negotiate-ntlm],[Allow NTLM within SPNEGO (default)])
+AS_HELP_STRING([--disable-negotiate-ntlm],[Block NTLM within SPNEGO]),
+[ case "$enableval" in
+ no)
+ AC_MSG_RESULT(no)
+ AC_DEFINE(CURL_DISABLE_NEGOTIATE_NTLM, 1, [to block NTLM within SPNEGO])
+ CURL_DISABLE_NEGOTIATE_NTLM=1
+ ;;
+ *)
+ AC_MSG_RESULT(yes)
+ ;;
+ esac ],
+ AC_MSG_RESULT(yes)
+)
+
dnl ************************************************************
dnl disable aws
dnl
diff --git a/docs/CURL-DISABLE.md b/docs/CURL-DISABLE.md
index a6a1ea1661..0b2596e21b 100644
--- a/docs/CURL-DISABLE.md
+++ b/docs/CURL-DISABLE.md
@@ -38,6 +38,10 @@ Disable support for the Kerberos authentication methods.
Disable support for the negotiate authentication methods.
+## `CURL_DISABLE_NEGOTIATE_NTLM`
+
+Block NTLM authentication within SPNEGO negotiation.
+
## `CURL_DISABLE_AWS`
Disable **aws-sigv4** support.
diff --git a/docs/libcurl/curl_version_info.md b/docs/libcurl/curl_version_info.md
index fd589a834c..c6aad83135 100644
--- a/docs/libcurl/curl_version_info.md
+++ b/docs/libcurl/curl_version_info.md
@@ -312,6 +312,12 @@ libcurl ignore cookies with a domain that is on the list.
libcurl was built with support for SPNEGO authentication (Simple and Protected
GSS-API Negotiation Mechanism, defined in RFC 2478.)
+## `SPNEGO-no-NTLM`
+
+*features* mask bit: none
+
+NTLM authentication is blocked within SPNEGO negotiation.
+
## `SSL`
*features* mask bit: CURL_VERSION_SSL
diff --git a/lib/curl_config-cmake.h.in b/lib/curl_config-cmake.h.in
index 41b0ddf073..36620d7e0e 100644
--- a/lib/curl_config-cmake.h.in
+++ b/lib/curl_config-cmake.h.in
@@ -58,6 +58,9 @@
/* disables negotiate authentication */
#cmakedefine CURL_DISABLE_NEGOTIATE_AUTH 1
+/* blocks NTLM within SPNEGO negotiation */
+#cmakedefine CURL_DISABLE_NEGOTIATE_NTLM 1
+
/* disables aws-sigv4 */
#cmakedefine CURL_DISABLE_AWS 1
diff --git a/lib/curl_gssapi.c b/lib/curl_gssapi.c
index 650d1908d0..85102ee355 100644
--- a/lib/curl_gssapi.c
+++ b/lib/curl_gssapi.c
@@ -81,7 +81,6 @@ enum min_err_code {
/* libcurl is also passing this struct to these functions, which are not yet
* stubbed:
- * gss_inquire_context()
* gss_unwrap()
* gss_wrap()
*/
@@ -308,6 +307,48 @@ static OM_uint32 stub_gss_delete_sec_context(
return GSS_S_COMPLETE;
}
+
+/* NTLMSSP OID: 1.3.6.1.4.1.311.2.2.10 */
+static gss_OID_desc stub_ntlmssp_oid = {
+ 10, CURL_UNCONST("\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a")
+};
+
+static OM_uint32
+stub_gss_inquire_context(OM_uint32 *min,
+ struct stub_gss_ctx_id_t_desc *context,
+ gss_name_t *src_name,
+ gss_name_t *targ_name,
+ OM_uint32 *lifetime_rec,
+ gss_OID *mech_type,
+ OM_uint32 *ctx_flags,
+ int *locally_initiated,
+ int *open_context)
+{
+ (void)src_name;
+ (void)targ_name;
+ (void)lifetime_rec;
+ (void)ctx_flags;
+ (void)locally_initiated;
+ (void)open_context;
+
+ if(!min)
+ return GSS_S_FAILURE;
+
+ if(!context) {
+ *min = STUB_GSS_INVALID_CTX;
+ return GSS_S_FAILURE;
+ }
+
+ *min = 0;
+ if(mech_type) {
+ if(context->have_ntlm && !context->have_krb5)
+ *mech_type = &stub_ntlmssp_oid;
+ else
+ *mech_type = (gss_OID)&Curl_krb5_mech_oid;
+ }
+
+ return GSS_S_COMPLETE;
+}
#endif /* CURL_GSS_STUB */
OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data,
@@ -319,7 +360,8 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data,
gss_buffer_t input_token,
gss_buffer_t output_token,
const bool mutual_auth,
- OM_uint32 *ret_flags)
+ OM_uint32 *ret_flags,
+ gss_cred_id_t cred_handle)
{
OM_uint32 req_flags = GSS_C_REPLAY_FLAG;
@@ -341,7 +383,7 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data,
#ifdef CURL_GSS_STUB
if(getenv("CURL_STUB_GSS_CREDS"))
return stub_gss_init_sec_context(minor_status,
- GSS_C_NO_CREDENTIAL, /* cred_handle */
+ cred_handle,
(struct stub_gss_ctx_id_t_desc **)context,
target_name,
mech_type,
@@ -356,7 +398,7 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data,
#endif /* CURL_GSS_STUB */
return gss_init_sec_context(minor_status,
- GSS_C_NO_CREDENTIAL, /* cred_handle */
+ cred_handle,
context,
target_name,
mech_type,
@@ -384,6 +426,23 @@ OM_uint32 Curl_gss_delete_sec_context(OM_uint32 *min,
return gss_delete_sec_context(min, context, output_token);
}
+OM_uint32 Curl_gss_inquire_context(OM_uint32 *minor_status,
+ gss_ctx_id_t context,
+ gss_OID *mech_type)
+{
+#ifdef CURL_GSS_STUB
+ if(getenv("CURL_STUB_GSS_CREDS"))
+ return stub_gss_inquire_context(minor_status,
+ (struct stub_gss_ctx_id_t_desc *)context,
+ NULL, NULL, NULL, mech_type,
+ NULL, NULL, NULL);
+#endif /* CURL_GSS_STUB */
+
+ return gss_inquire_context(minor_status, context,
+ NULL, NULL, NULL, mech_type,
+ NULL, NULL, NULL);
+}
+
#ifdef CURLVERBOSE
#define GSS_LOG_BUFFER_LEN 1024
static size_t display_gss_error(OM_uint32 status, int type,
diff --git a/lib/curl_gssapi.h b/lib/curl_gssapi.h
index fc3759ebcb..e7f649e114 100644
--- a/lib/curl_gssapi.h
+++ b/lib/curl_gssapi.h
@@ -41,12 +41,17 @@ OM_uint32 Curl_gss_init_sec_context(struct Curl_easy *data,
gss_buffer_t input_token,
gss_buffer_t output_token,
const bool mutual_auth,
- OM_uint32 *ret_flags);
+ OM_uint32 *ret_flags,
+ gss_cred_id_t cred_handle);
OM_uint32 Curl_gss_delete_sec_context(OM_uint32 *min,
gss_ctx_id_t *context,
gss_buffer_t output_token);
+OM_uint32 Curl_gss_inquire_context(OM_uint32 *minor_status,
+ gss_ctx_id_t context,
+ gss_OID *mech_type);
+
#ifdef CURLVERBOSE
/* Helper to log a GSS-API error status */
void Curl_gss_log_error(struct Curl_easy *data, const char *prefix,
diff --git a/lib/curl_sspi.c b/lib/curl_sspi.c
index 3ea17621b1..4180643507 100644
--- a/lib/curl_sspi.c
+++ b/lib/curl_sspi.c
@@ -93,7 +93,7 @@ void Curl_sspi_global_cleanup(void)
* Returns CURLE_OK on success.
*/
CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp,
- SEC_WINNT_AUTH_IDENTITY *identity)
+ SEC_WINNT_AUTH_IDENTITY_EX *identity)
{
xcharp_u useranddomain;
xcharp_u user, dup_user;
@@ -105,6 +105,8 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp,
/* Initialize the identity */
memset(identity, 0, sizeof(*identity));
+ identity->Version = SEC_WINNT_AUTH_IDENTITY_VERSION;
+ identity->Length = sizeof(*identity);
useranddomain.tchar_ptr = curlx_convert_UTF8_to_tchar(userp);
if(!useranddomain.tchar_ptr)
@@ -195,7 +197,7 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp,
*
* identity [in/out] - The identity structure.
*/
-void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY *identity)
+void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY_EX *identity)
{
if(identity) {
curlx_safefree(identity->User);
diff --git a/lib/curl_sspi.h b/lib/curl_sspi.h
index 3779d51753..ea405f53a7 100644
--- a/lib/curl_sspi.h
+++ b/lib/curl_sspi.h
@@ -34,14 +34,14 @@ void Curl_sspi_global_cleanup(void);
/* This is used to populate the domain in an SSPI identity structure */
CURLcode Curl_override_sspi_http_realm(const char *chlg,
- SEC_WINNT_AUTH_IDENTITY *identity);
+ SEC_WINNT_AUTH_IDENTITY_EX *identity);
/* This is used to generate an SSPI identity structure */
CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp,
- SEC_WINNT_AUTH_IDENTITY *identity);
+ SEC_WINNT_AUTH_IDENTITY_EX *identity);
/* This is used to free an SSPI identity structure */
-void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY *identity);
+void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY_EX *identity);
/* Forward-declaration of global variables defined in curl_sspi.c */
extern PSecurityFunctionTable Curl_pSecFn;
diff --git a/lib/ldap.c b/lib/ldap.c
index f8e7ff6a70..ab2abc9fda 100644
--- a/lib/ldap.c
+++ b/lib/ldap.c
@@ -157,7 +157,7 @@ static ULONG ldap_win_bind_auth(LDAP *server, const char *user,
const char *passwd, unsigned long authflags)
{
ULONG method = 0;
- SEC_WINNT_AUTH_IDENTITY cred;
+ SEC_WINNT_AUTH_IDENTITY_EX cred;
ULONG rc = LDAP_AUTH_METHOD_NOT_SUPPORTED;
memset(&cred, 0, sizeof(cred));
diff --git a/lib/socks_gssapi.c b/lib/socks_gssapi.c
index 32db07044a..f7f0cbceba 100644
--- a/lib/socks_gssapi.c
+++ b/lib/socks_gssapi.c
@@ -180,7 +180,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf,
gss_token,
&gss_send_token,
TRUE,
- &gss_ret_flags);
+ &gss_ret_flags,
+ GSS_C_NO_CREDENTIAL);
if(gss_token != GSS_C_NO_BUFFER) {
curlx_safefree(gss_recv_token.value);
diff --git a/lib/vauth/digest_sspi.c b/lib/vauth/digest_sspi.c
index f0b6780fca..b7703b84fa 100644
--- a/lib/vauth/digest_sspi.c
+++ b/lib/vauth/digest_sspi.c
@@ -95,8 +95,8 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data,
CredHandle credentials;
CtxtHandle context;
PSecPkgInfo SecurityPackage;
- SEC_WINNT_AUTH_IDENTITY identity;
- SEC_WINNT_AUTH_IDENTITY *p_identity;
+ SEC_WINNT_AUTH_IDENTITY_EX identity;
+ SEC_WINNT_AUTH_IDENTITY_EX *p_identity;
SecBuffer chlg_buf;
SecBuffer resp_buf;
SecBufferDesc chlg_desc;
@@ -240,7 +240,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data,
* Returns CURLE_OK on success.
*/
CURLcode Curl_override_sspi_http_realm(const char *chlg,
- SEC_WINNT_AUTH_IDENTITY *identity)
+ SEC_WINNT_AUTH_IDENTITY_EX *identity)
{
xcharp_u domain, dup_domain;
@@ -466,8 +466,8 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
if(!digest->http_context) {
CredHandle credentials;
- SEC_WINNT_AUTH_IDENTITY identity;
- SEC_WINNT_AUTH_IDENTITY *p_identity;
+ SEC_WINNT_AUTH_IDENTITY_EX identity;
+ SEC_WINNT_AUTH_IDENTITY_EX *p_identity;
SecBuffer resp_buf;
SecBufferDesc resp_desc;
unsigned long attrs;
diff --git a/lib/vauth/krb5_gssapi.c b/lib/vauth/krb5_gssapi.c
index 64c735be58..92c283f877 100644
--- a/lib/vauth/krb5_gssapi.c
+++ b/lib/vauth/krb5_gssapi.c
@@ -138,7 +138,8 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data,
&input_token,
&output_token,
mutual_auth,
- NULL);
+ NULL,
+ GSS_C_NO_CREDENTIAL);
if(GSS_ERROR(major_status)) {
if(output_token.value)
diff --git a/lib/vauth/spnego_gssapi.c b/lib/vauth/spnego_gssapi.c
index 38bb4c1422..61e293327f 100644
--- a/lib/vauth/spnego_gssapi.c
+++ b/lib/vauth/spnego_gssapi.c
@@ -37,6 +37,7 @@
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
+
/*
* Curl_auth_is_spnego_supported()
*
@@ -158,6 +159,54 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data,
}
#endif
+#ifdef HAVE_GSS_SET_NEG_MECHS
+#ifdef CURL_DISABLE_NEGOTIATE_NTLM
+ /* Acquire explicit credentials and restrict SPNEGO sub-mechanisms to
+ * exclude NTLM. We enumerate all available mechanisms and filter out
+ * the NTLMSSP OID, matching SSPI's "!ntlm". */
+ if(nego->cred == GSS_C_NO_CREDENTIAL) {
+ /* OID 1.3.6.1.4.1.311.2.2.10 (NTLMSSP) */
+ static const gss_OID_desc ntlmssp_oid = {
+ 10, CURL_UNCONST("\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a")
+ };
+ gss_OID_set available_mechs = GSS_C_NO_OID_SET;
+ gss_OID_set filtered_mechs = GSS_C_NO_OID_SET;
+
+ /* Acquire default credentials for SPNEGO */
+ major_status = gss_acquire_cred(&minor_status, GSS_C_NO_NAME,
+ GSS_C_INDEFINITE, GSS_C_NO_OID_SET,
+ GSS_C_INITIATE, &nego->cred, NULL, NULL);
+ if(GSS_ERROR(major_status)) {
+ Curl_gss_log_error(data, "gss_acquire_cred() failed: ",
+ major_status, minor_status);
+ Curl_safefree(input_token.value);
+ return CURLE_AUTH_ERROR;
+ }
+
+ /* Get all available mechanisms */
+ major_status = gss_indicate_mechs(&minor_status, &available_mechs);
+ if(!GSS_ERROR(major_status)) {
+ /* Build a set excluding NTLMSSP */
+ major_status = gss_create_empty_oid_set(&minor_status, &filtered_mechs);
+ if(!GSS_ERROR(major_status)) {
+ size_t i;
+ for(i = 0; i < available_mechs->count; i++) {
+ gss_OID oid = &available_mechs->elements[i];
+ if(oid->length != ntlmssp_oid.length ||
+ memcmp(oid->elements, ntlmssp_oid.elements, oid->length)) {
+ gss_add_oid_set_member(&minor_status, oid, &filtered_mechs);
+ }
+ }
+ /* Restrict SPNEGO to only use non-NTLM mechanisms */
+ gss_set_neg_mechs(&minor_status, nego->cred, filtered_mechs);
+ gss_release_oid_set(&minor_status, &filtered_mechs);
+ }
+ gss_release_oid_set(&minor_status, &available_mechs);
+ }
+ }
+#endif /* CURL_DISABLE_NEGOTIATE_NTLM */
+#endif /* HAVE_GSS_SET_NEG_MECHS */
+
/* Generate our challenge-response message */
major_status = Curl_gss_init_sec_context(data,
&minor_status,
@@ -168,7 +217,8 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data,
&input_token,
&output_token,
TRUE,
- NULL);
+ NULL,
+ nego->cred);
/* Free the decoded challenge as it is not required anymore */
curlx_safefree(input_token.value);
@@ -191,6 +241,31 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data,
return CURLE_AUTH_ERROR;
}
+ /* Check if NTLM was selected and is disallowed */
+#ifdef CURL_DISABLE_NEGOTIATE_NTLM
+ if(nego->context != GSS_C_NO_CONTEXT) {
+ /* OID 1.3.6.1.4.1.311.2.2.10 (NTLMSSP) */
+ static const gss_OID_desc ntlmssp_oid = {
+ 10, CURL_UNCONST("\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a")
+ };
+ OM_uint32 inquire_major, inquire_minor;
+ gss_OID mech_type = GSS_C_NO_OID;
+
+ inquire_major = Curl_gss_inquire_context(&inquire_minor,
+ nego->context,
+ &mech_type);
+ if(!GSS_ERROR(inquire_major) && mech_type &&
+ mech_type->length == ntlmssp_oid.length &&
+ !memcmp(mech_type->elements, ntlmssp_oid.elements,
+ ntlmssp_oid.length)) {
+ infof(data, "SPNEGO chose NTLM, but NTLM is not allowed");
+ gss_release_buffer(&unused_status, &output_token);
+ Curl_auth_cleanup_spnego(nego);
+ return CURLE_AUTH_ERROR;
+ }
+ }
+#endif /* CURL_DISABLE_NEGOTIATE_NTLM */
+
/* Free previous token */
if(nego->output_token.length && nego->output_token.value)
gss_release_buffer(&unused_status, &nego->output_token);
@@ -280,6 +355,12 @@ void Curl_auth_cleanup_spnego(struct negotiatedata *nego)
nego->spn = GSS_C_NO_NAME;
}
+ /* Free our credentials */
+ if(nego->cred != GSS_C_NO_CREDENTIAL) {
+ gss_release_cred(&minor_status, &nego->cred);
+ nego->cred = GSS_C_NO_CREDENTIAL;
+ }
+
/* Reset any variables */
nego->status = 0;
nego->noauthpersist = FALSE;
diff --git a/lib/vauth/spnego_sspi.c b/lib/vauth/spnego_sspi.c
index 1baf59320a..ce65a93f1e 100644
--- a/lib/vauth/spnego_sspi.c
+++ b/lib/vauth/spnego_sspi.c
@@ -146,6 +146,29 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data,
/* Use the current Windows user */
nego->p_identity = NULL;
+#ifdef CURL_DISABLE_NEGOTIATE_NTLM
+ /* Exclude NTLM from SPNEGO negotiation via the PackageList field */
+ if(!nego->p_identity) {
+ memset(&nego->identity, 0, sizeof(nego->identity));
+ nego->identity.Version = SEC_WINNT_AUTH_IDENTITY_VERSION;
+ nego->identity.Length = sizeof(nego->identity);
+ nego->identity.Flags =
+#ifdef UNICODE
+ SEC_WINNT_AUTH_IDENTITY_UNICODE;
+#else
+ SEC_WINNT_AUTH_IDENTITY_ANSI;
+#endif
+ nego->p_identity = &nego->identity;
+ }
+
+ /* Use the special name "!ntlm" to prevent NTLM from being used:
+ * https://learn.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-sec_winnt_auth_identity_exa
+ */
+ nego->identity.PackageList =
+ (unsigned TCHAR *)CURL_UNCONST(TEXT("!ntlm"));
+ nego->identity.PackageListLength = 5;
+#endif /* CURL_DISABLE_NEGOTIATE_NTLM */
+
/* Allocate our credentials handle */
nego->credentials = curlx_calloc(1, sizeof(CredHandle));
if(!nego->credentials)
diff --git a/lib/vauth/vauth.h b/lib/vauth/vauth.h
index b5ff31414f..3d4d7511a9 100644
--- a/lib/vauth/vauth.h
+++ b/lib/vauth/vauth.h
@@ -170,8 +170,8 @@ struct ntlmdata {
#endif
CredHandle *credentials;
CtxtHandle *context;
- SEC_WINNT_AUTH_IDENTITY identity;
- SEC_WINNT_AUTH_IDENTITY *p_identity;
+ SEC_WINNT_AUTH_IDENTITY_EX identity;
+ SEC_WINNT_AUTH_IDENTITY_EX *p_identity;
size_t token_max;
BYTE *output_token;
BYTE *input_token;
@@ -241,8 +241,8 @@ struct kerberos5data {
CredHandle *credentials;
CtxtHandle *context;
TCHAR *spn;
- SEC_WINNT_AUTH_IDENTITY identity;
- SEC_WINNT_AUTH_IDENTITY *p_identity;
+ SEC_WINNT_AUTH_IDENTITY_EX identity;
+ SEC_WINNT_AUTH_IDENTITY_EX *p_identity;
size_t token_max;
BYTE *output_token;
#else
@@ -297,6 +297,7 @@ struct negotiatedata {
OM_uint32 status;
gss_ctx_id_t context;
gss_name_t spn;
+ gss_cred_id_t cred;
gss_buffer_desc output_token;
#ifdef GSS_C_CHANNEL_BOUND_FLAG
struct dynbuf channel_binding_data;
@@ -309,8 +310,8 @@ struct negotiatedata {
SECURITY_STATUS status;
CredHandle *credentials;
CtxtHandle *context;
- SEC_WINNT_AUTH_IDENTITY identity;
- SEC_WINNT_AUTH_IDENTITY *p_identity;
+ SEC_WINNT_AUTH_IDENTITY_EX identity;
+ SEC_WINNT_AUTH_IDENTITY_EX *p_identity;
TCHAR *spn;
size_t token_max;
BYTE *output_token;
diff --git a/lib/version.c b/lib/version.c
index b3b0a46abb..b80da1f276 100644
--- a/lib/version.c
+++ b/lib/version.c
@@ -503,6 +503,9 @@ static const struct feat features_table[] = {
#endif /* USE_SSL */
#ifdef USE_SPNEGO
FEATURE("SPNEGO", NULL, CURL_VERSION_SPNEGO),
+#ifdef CURL_DISABLE_NEGOTIATE_NTLM
+ FEATURE("SPNEGO-no-NTLM", NULL, 0),
+#endif
#endif
#ifdef USE_SSL
FEATURE("SSL", NULL, CURL_VERSION_SSL),
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
index 3003dce03b..0390ae7e9d 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -255,7 +255,7 @@ test2056 test2057 test2058 test2059 test2060 test2061 test2062 test2063 \
test2064 test2065 test2066 test2067 test2068 test2069 test2070 test2071 \
test2072 test2073 test2074 test2075 test2076 test2077 test2078 test2079 \
test2080 test2081 test2082 test2083 test2084 test2085 test2086 test2087 \
-test2088 test2089 test2090 test2091 \
+test2088 test2089 test2090 test2091 test2092 test2093 \
test2100 test2101 test2102 test2103 test2104 \
\
test2200 test2201 test2202 test2203 test2204 test2205 \
diff --git a/tests/data/test2092 b/tests/data/test2092
new file mode 100644
index 0000000000..5423e15055
--- /dev/null
+++ b/tests/data/test2092
@@ -0,0 +1,59 @@
+
+
+
+
+HTTP
+HTTP GET
+HTTP Negotiate auth (stub ntlm)
+SPNEGO NTLM disallowed
+
+
+
+# Server-side
+
+
+HTTP/1.1 200 OK swsclose
+Content-Length: 23
+
+This IS the real page!
+
+
+
+# Client-side
+
+
+http
+
+
+SPNEGO skips auth when NTLM blocked by CURL_DISABLE_NEGOTIATE_NTLM
+
+
+GSS-API
+Debug
+negotiate-ntlm-disabled
+
+
+CURL_STUB_GSS_CREDS="NTLM_Alice"
+
+
+--negotiate http://%HOSTIP:%HTTPPORT/%TESTNUMBER
+
+
+
+# Verify data after the test has been "shot"
+
+
+0
+
+# When NTLM is the only available mechanism and is blocked,
+# negotiate auth silently fails and the request is sent without
+# any Authorization header.
+
+GET /%TESTNUMBER HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+User-Agent: curl/%VERSION
+Accept: */*
+
+
+
+
diff --git a/tests/data/test2093 b/tests/data/test2093
new file mode 100644
index 0000000000..95f84c2656
--- /dev/null
+++ b/tests/data/test2093
@@ -0,0 +1,69 @@
+
+
+
+
+HTTP
+HTTP GET
+HTTP Negotiate auth (stub krb5)
+SPNEGO NTLM disallowed
+
+
+
+# Server-side
+
+
+HTTP/1.1 200 Things are fine in server land
+Server: Microsoft-IIS/7.0
+Content-Type: text/html; charset=iso-8859-1
+WWW-Authenticate: Negotiate RA==
+Content-Length: 15
+
+Nice auth sir!
+
+
+HTTP/1.1 200 Things are fine in server land
+Server: Microsoft-IIS/7.0
+Content-Type: text/html; charset=iso-8859-1
+WWW-Authenticate: Negotiate RA==
+Content-Length: 15
+
+Nice auth sir!
+
+
+
+# Client-side
+
+
+http
+
+
+SPNEGO with Kerberos still works when built with CURL_DISABLE_NEGOTIATE_NTLM
+
+
+GSS-API
+Debug
+negotiate-ntlm-disabled
+
+
+CURL_STUB_GSS_CREDS="KRB5_Alice"
+
+
+--negotiate http://%HOSTIP:%HTTPPORT/%TESTNUMBER
+
+
+
+# Verify data after the test has been "shot"
+
+
+0
+
+
+GET /%TESTNUMBER HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+Authorization: Negotiate %b64["KRB5_Alice":HTTP@127.0.0.1:1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA]b64%
+User-Agent: curl/%VERSION
+Accept: */*
+
+
+
+
diff --git a/tests/runtests.pl b/tests/runtests.pl
index e9184fd47a..ebc27c5b87 100755
--- a/tests/runtests.pl
+++ b/tests/runtests.pl
@@ -694,6 +694,8 @@ sub checksystemfeatures {
$feature{"Kerberos"} = $feat =~ /Kerberos/i;
# SPNEGO enabled
$feature{"SPNEGO"} = $feat =~ /SPNEGO/i;
+ # SPNEGO NTLM disabled (compile-time)
+ $feature{"negotiate-ntlm-disabled"} = $feat =~ /SPNEGO-no-NTLM/i;
# TLS-SRP enabled
$feature{"TLS-SRP"} = $feat =~ /TLS-SRP/i;
# PSL enabled