Add Windows SSPI Negotiate#147
Conversation
|
Hello @micha102 , thanks, this is a nice addition. I cannot test it myself right now, but it looks well written and documented. I added a couple of comments in proxy.c. Also, since now sspi supports both ntlm and negotiate, I would add few changes in the sspi code:
Thanks |
|
Thanks. |
2d4975b to
23f404c
Compare
|
I did the renaming of the old functions which assumed SSPI is only NTLM to explicitly say they are NTLM. |
|
Hello @micha102 I guess you can still view the comments if you go to the "Files changed" tab. There you can see my comments in main.c and proxy.c. Are |
There was a problem hiding this comment.
Pull request overview
This PR extends CNTLM’s Windows (Cygwin) SSPI authentication support by adding an SSPI Negotiate mode alongside the existing SSPI NTLM mode, enabling Kerberos/Negotiate-based proxy authentication using the current Windows logon session.
Changes:
- Added SSPI Negotiate mode helpers/APIs and scheme reporting.
- Updated proxy authentication flow to emit
Proxy-Authorization: Negotiate …using a per-thread Negotiate state. - Updated documentation to mention both NTLM and Negotiate SSPI modes.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
sspi.h |
Adds Negotiate state struct and new SSPI API declarations. |
sspi.c |
Adds mode checks (NTLM/Negotiate), scheme getter, Negotiate token generation, and renames SSPI NTLM functions. |
proxy.c |
Adds thread-local Negotiate state and uses SSPI Negotiate in proxy_authenticate(). |
ntlm.c |
Updates SSPI NTLM function call names and gates response path on sspi_is_ntlm(). |
main.c |
Adjusts password/hashes requirement logic for SSPI Negotiate mode. |
doc/cntlm.conf |
Updates SSPI comment to reflect Negotiate support. |
doc/cntlm.1 |
Updates manpage to document SSPI ... Negotiate. |
Comments suppressed due to low confidence (1)
sspi.c:223
- In UNICODE builds,
sspi_modeallocation/conversion does not leave room for the terminating NUL (zmalloc(sizeof(wchar_t) * strlen(mode))andmbstowcs(..., strlen(mode))). This can leavesspi_modeunterminated and make laterwcscmpread past the buffer. Allocatestrlen(mode)+1and convert/copy including the terminator.
if ((!strcasecmp("NTLM", mode) || !strcasecmp("Negotiate", mode)) && sspi_dll) // Only NTLM and Negotiate supported for now
{
#ifdef UNICODE
sspi_mode = zmalloc(sizeof(wchar_t) * strlen(mode));
mbstowcs(sspi_mode, mode, strlen(mode));
#else
| #ifdef UNICODE | ||
| return (wcscmp(sspi_mode, L"Negotiate") == 0); | ||
| #else | ||
| return (strcasecmp(sspi_mode, "Negotiate") == 0); | ||
| #endif | ||
| } | ||
|
|
||
| int sspi_is_ntlm(void) { | ||
| #ifdef UNICODE | ||
| return (wcscmp(sspi_mode, L"NTLM") == 0); | ||
| #else | ||
| return (strcasecmp(sspi_mode, "NTLM") == 0); |
| // Step 1: Acquire credentials handle with Negotiate package | ||
| status = _AcquireCredentialsHandle( | ||
| NULL, | ||
| TEXT("Negotiate"), | ||
| SECPKG_CRED_OUTBOUND, | ||
| NULL, NULL, NULL, NULL, | ||
| &state->credentials, | ||
| &expiry); | ||
|
|
||
| if (status != SEC_E_OK) { | ||
| syslog(LOG_ERR, "AcquireCredentialsHandle failed: 0x%08x", status); | ||
| return 0; | ||
| } | ||
|
|
||
| // Build SPN from proxy hostname | ||
| char hostname_only[256]; | ||
| strcpy(hostname_only, proxy_hostname ? proxy_hostname : ""); | ||
| char *colon = strchr(hostname_only, ':'); | ||
| if (colon) *colon = '\0'; | ||
|
|
||
| // Step 2: Initialize security context | ||
| output_desc.ulVersion = SECBUFFER_VERSION; | ||
| output_desc.cBuffers = 1; | ||
| output_desc.pBuffers = &output_buffer; | ||
| output_buffer.cbBuffer = TOKEN_BUFSIZE; | ||
| output_buffer.BufferType = SECBUFFER_TOKEN; | ||
| output_buffer.pvBuffer = zmalloc(TOKEN_BUFSIZE); | ||
|
|
||
| #ifdef UNICODE | ||
| wchar_t target_name[261]; // 256 + 5 chars | ||
| wchar_t whostname[256]; | ||
| mbstowcs(whostname, hostname_only, 256); | ||
| swprintf(target_name, 256, L"HTTP/%s", whostname); | ||
| #else | ||
| char target_name[261]; // 256 + 5 chars | ||
| snprintf(target_name, sizeof(target_name), "HTTP/%s", hostname_only); | ||
| #endif | ||
|
|
||
| status = _InitializeSecurityContext( | ||
| &state->credentials, | ||
| NULL, | ||
| target_name, | ||
| ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONNECTION, | ||
| 0, | ||
| SECURITY_NETWORK_DREP, | ||
| NULL, | ||
| 0, | ||
| &state->context, | ||
| &output_desc, | ||
| &context_attr, | ||
| &expiry); | ||
|
|
||
| syslog(LOG_ERR, "InitializeSecurityContext status: 0x%08x", status); | ||
|
|
||
| if (status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { | ||
| syslog(LOG_ERR, "InitializeSecurityContext failed: 0x%08x", status); | ||
| _FreeCredentialsHandle(&state->credentials); | ||
| free(output_buffer.pvBuffer); | ||
| return 0; | ||
| } | ||
| *dst = output_buffer.pvBuffer; | ||
|
|
||
| return output_buffer.cbBuffer; | ||
| } |
| #ifdef __CYGWIN__ | ||
| if (sspi_is_negotiate()) { | ||
| // Get scheme from SSPI (returns "NTLM" or "Negotiate") | ||
| snprintf(buf, bufsize, "%s ", sspi_get_scheme()); | ||
| int prefix_len = strlen(buf); | ||
|
|
||
| pthread_once(&key_once, make_negotiate_key); | ||
| struct sspi_negotiate_state *negotiate_state = pthread_getspecific(negotiate_state_key); | ||
| if (!negotiate_state) { | ||
| negotiate_state = zmalloc(sizeof(struct sspi_negotiate_state)); | ||
| pthread_setspecific(negotiate_state_key, negotiate_state); | ||
| } | ||
| len = sspi_negotiate_request(&tmp, negotiate_state, curr_proxy->hostname); |
| if (sspi_is_negotiate()) { | ||
| // Get scheme from SSPI (returns "NTLM" or "Negotiate") | ||
| snprintf(buf, bufsize, "%s ", sspi_get_scheme()); | ||
| int prefix_len = strlen(buf); | ||
|
|
||
| pthread_once(&key_once, make_negotiate_key); | ||
| struct sspi_negotiate_state *negotiate_state = pthread_getspecific(negotiate_state_key); | ||
| if (!negotiate_state) { | ||
| negotiate_state = zmalloc(sizeof(struct sspi_negotiate_state)); | ||
| memset(negotiate_state, 0, sizeof(struct sspi_negotiate_state)); | ||
| pthread_setspecific(negotiate_state_key, negotiate_state); | ||
| } | ||
| len = sspi_negotiate_request(&tmp, negotiate_state, curr_proxy->hostname); | ||
| if (len) { |
| @@ -224,7 +224,7 @@ int ntlm_request(char **dst, struct auth_s *creds) { | |||
| #ifdef __CYGWIN__ | |||
| if (sspi_enabled()) | |||
| status = _InitializeSecurityContext( | ||
| &state->credentials, | ||
| NULL, | ||
| target_name, | ||
| ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONNECTION, | ||
| 0, | ||
| SECURITY_NETWORK_DREP, | ||
| NULL, | ||
| 0, | ||
| &state->context, | ||
| &output_desc, | ||
| &context_attr, | ||
| &expiry); | ||
|
|
||
| syslog(LOG_ERR, "InitializeSecurityContext status: 0x%08x", status); | ||
|
|
||
| if (status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { | ||
| syslog(LOG_ERR, "InitializeSecurityContext failed: 0x%08x", status); | ||
| _FreeCredentialsHandle(&state->credentials); | ||
| free(output_buffer.pvBuffer); | ||
| return 0; | ||
| } |
| syslog(LOG_ERR, "InitializeSecurityContext status: 0x%08x", status); | ||
|
|
|
|
||
| // Helper function to create the key | ||
| static void make_negotiate_key(void) { | ||
| pthread_key_create(&negotiate_state_key, NULL); | ||
| } |
| if (sspi_is_negotiate()) { | ||
| // Get scheme from SSPI (returns "NTLM" or "Negotiate") | ||
| snprintf(buf, bufsize, "%s ", sspi_get_scheme()); | ||
| int prefix_len = strlen(buf); | ||
|
|
| #endif | ||
| #ifdef __CYGWIN__ | ||
| !sspi_enabled() && | ||
| (!sspi_is_negotiate() && sspi_enabled()) && |





In this PR, I add support of SSPI Negotiate for Windows. (Previously only NTLM is supported)
SSPI Negotiate mode directly generates a Kerberos ticket to inherit the user session. No login or password are needed.
Also, the authenticate is done on one-shot (no 407 Proxy Authentication Required received but directly 200 Connection Established)
I tested it with our corporate proxy successfully.
Kindly review it.
Note: I didn't integrate it to the -M switch (automatic configuration detection)