Merge branch 'sspi-ntlm-auth' into 'main'

Draft: Support SSPI NTLM authentication mechanism

Closes #150

See merge request dbus/dbus!548
This commit is contained in:
Ralf Habacker 2026-01-07 11:14:46 +00:00
commit 9c09315e11
15 changed files with 2199 additions and 34 deletions

View file

@ -306,7 +306,6 @@ else()
strict-aliasing
strict-prototypes
switch-default
switch-enum
undef
unused-but-set-variable
write-strings
@ -325,6 +324,7 @@ else()
unused-parameter
)
set(WARNINGS_ERRORS
switch-enum
)
if(ENABLE_WERROR)
list(APPEND WARNINGS error)

View file

@ -280,7 +280,7 @@ if(WIN32)
if(WINCE)
target_link_libraries(dbus-1 ws2)
else(WINCE)
target_link_libraries(dbus-1 ws2_32 advapi32 netapi32 iphlpapi dbghelp)
target_link_libraries(dbus-1 ws2_32 advapi32 netapi32 iphlpapi dbghelp secur32)
endif()
else(WIN32)
if(DEFINED DBUS_LIBRARY_REVISION)
@ -325,7 +325,7 @@ if(WIN32)
if(WINCE)
target_link_libraries(dbus-internal ws2)
else(WINCE)
target_link_libraries(dbus-internal ws2_32 advapi32 netapi32 iphlpapi)
target_link_libraries(dbus-internal ws2_32 advapi32 netapi32 iphlpapi secur32)
endif()
else(WIN32)
target_link_libraries(dbus-internal ${CMAKE_THREAD_LIBS_INIT})

View file

@ -179,6 +179,16 @@ struct DBusAuth
DBusKeyring *keyring; /**< Keyring for cookie mechanism. */
int cookie_id; /**< ID of cookie to use */
DBusString challenge; /**< Challenge sent to client */
DBusString response; /**< Response sent to server */
#if defined (DBUS_WIN) && !defined (DBUS_WINCE)
DBusSSPINTLMInfo sspi_ntlm; /**< defined in sysdeps-win.c. */
unsigned int inbound_message_count; /**< Number of inbound messages. */
unsigned int outbound_message_count; /**< Number of outbound messages. */
DBusSSPINTLMInternalState sspi_ntlm_state; /**< defined in sysdeps-win.h */
DBusError sspi_ntlm_error; /**< error (might need to be sent repeatedly) */
dbus_bool_t sspi_ntlm_done; /**< whether SSPI finished the auth or not */
#endif
char **allowed_mechs; /**< Mechanisms we're allowed to use,
* or #NULL if we can use any
@ -391,9 +401,14 @@ _dbus_auth_new (int size)
auth->desired_identity = _dbus_credentials_new ();
if (auth->desired_identity == NULL)
goto enomem_8;
if (!_dbus_string_init (&auth->response))
goto enomem_10;
return auth;
enomem_10:
_dbus_string_free (&auth->response);
#if 0
enomem_9:
_dbus_credentials_unref (auth->desired_identity);
@ -1382,9 +1397,445 @@ handle_client_shutdown_anonymous_mech (DBusAuth *auth)
}
/*
* DBUS_WINDOWS_SSPI_NTLM mechanism
* Implementation is mostly in dbus-systeps-win.c
*/
#if defined (DBUS_WIN) && !defined (DBUS_WINCE)
#define NTLM_CLIENT_OUTBOUND_COUNT 2
#define NTLM_SERVER_OUTBOUND_COUNT 1
static dbus_bool_t
handle_server_data_windows_sspi_ntlm_mech (DBusAuth *auth,
const DBusString *data)
{
dbus_bool_t error_is_local = TRUE;
switch (auth->sspi_ntlm_state)
{
case DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_REJECTED:
if (!send_rejected (auth))
return FALSE;
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN;
return TRUE;
case DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_ERROR:
if (!send_error (auth, auth->sspi_ntlm_error.message))
return FALSE;
dbus_error_free (&auth->sspi_ntlm_error);
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN;
return TRUE;
case DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_CHALLENGE:
if (!send_data (auth, &auth->challenge))
return FALSE;
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_CHECK_IF_DONE;
return TRUE;
case DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN:
case DBUS_SSPI_NTLM_INTERNAL_STATE_CLIENT_DATA_GET_NEXT_RESPONSE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_CLIENT_DATA_MAYBE_SEND_RESPONSE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_INVALID:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_CHECK_IF_DONE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_GET_NEXT_CHALLENGE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_MAYBE_SEND_CHALLENGE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_FETCH_CREDENTIALS:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_SEND_OK:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_RESPONSE:
default:
break;
}
switch (auth->sspi_ntlm_state)
{
case DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN:
auth->outbound_message_count = 0;
auth->inbound_message_count = 0;
auth->inbound_message_count += 1;
if (auth->inbound_message_count > NTLM_CLIENT_OUTBOUND_COUNT)
goto ntlm_server_message_number_mismatch;
_dbus_string_set_length (&auth->challenge, 0);
auth->sspi_ntlm_done = FALSE;
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_GET_NEXT_CHALLENGE;
/* fallthrough */
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_GET_NEXT_CHALLENGE:
if (!_dbus_sspi_ntlm_next_challenge (&auth->sspi_ntlm,
&auth->sspi_ntlm_state,
data,
&auth->challenge,
&auth->sspi_ntlm_done,
&error_is_local,
&auth->sspi_ntlm_error))
{
_DBUS_ASSERT_ERROR_IS_SET (&auth->sspi_ntlm_error);
if (dbus_error_has_name (&auth->sspi_ntlm_error, DBUS_ERROR_NO_MEMORY))
{
dbus_error_free (&auth->sspi_ntlm_error);
/* sspi_ntlm_state was modified by the next_challenge(), don't touch it */
return FALSE;
}
_dbus_verbose ("%s: Error getting next challenge: %s\n",
DBUS_AUTH_NAME (auth), auth->sspi_ntlm_error.message);
dbus_error_free (&auth->sspi_ntlm_error);
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_REJECTED;
if (!send_rejected (auth))
return FALSE;
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN;
return TRUE;
}
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_MAYBE_SEND_CHALLENGE;
/* fallthrough */
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_MAYBE_SEND_CHALLENGE:
if (_dbus_string_get_length (&auth->challenge) > 0)
{
auth->outbound_message_count += 1;
if (auth->outbound_message_count > NTLM_SERVER_OUTBOUND_COUNT)
goto ntlm_server_message_number_mismatch;
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_CHALLENGE;
if (!send_data (auth, &auth->challenge))
return FALSE;
}
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_CHECK_IF_DONE;
/* fallthrough */
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_CHECK_IF_DONE:
if (!auth->sspi_ntlm_done)
{
if (_dbus_string_get_length (&auth->challenge) == 0)
{
_dbus_verbose ("%s: SSPI produced 0-length challenge, but authentication is not done yet\n",
DBUS_AUTH_NAME (auth));
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_REJECTED;
if (!send_rejected (auth))
return FALSE;
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN;
return TRUE;
}
goto_state (auth, &server_state_waiting_for_data);
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN;
return TRUE;
}
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_FETCH_CREDENTIALS;
/* fallthrough */
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_FETCH_CREDENTIALS:
error_is_local = TRUE;
if (!_dbus_sspi_ntlm_fetch_credentials (&auth->sspi_ntlm,
&auth->sspi_ntlm_state,
auth->authorized_identity,
&error_is_local,
&auth->sspi_ntlm_error))
{
_DBUS_ASSERT_ERROR_IS_SET (&auth->sspi_ntlm_error);
if (dbus_error_has_name (&auth->sspi_ntlm_error, DBUS_ERROR_NO_MEMORY))
{
dbus_error_free (&auth->sspi_ntlm_error);
return FALSE;
}
_dbus_verbose ("%s: Error fetching NTLM-negotiated credentials: %s\n",
DBUS_AUTH_NAME (auth), auth->sspi_ntlm_error.message);
dbus_error_free (&auth->sspi_ntlm_error);
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_REJECTED;
if (!send_rejected (auth))
return FALSE;
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN;
return TRUE;
}
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_SEND_OK;
/* fallthrough */
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_SEND_OK:
if (!send_ok (auth))
return FALSE;
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN;
_dbus_verbose ("%s: authenticated client using DBUS_WINDOWS_SSPI_NTLM\n",
DBUS_AUTH_NAME (auth));
return TRUE;
case DBUS_SSPI_NTLM_INTERNAL_STATE_CLIENT_DATA_GET_NEXT_RESPONSE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_CLIENT_DATA_MAYBE_SEND_RESPONSE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_INVALID:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_ERROR:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_REJECTED:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_CHALLENGE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_RESPONSE:
default:
_dbus_verbose ("%s: Wrong SSPI NTLM state\n",
DBUS_AUTH_NAME (auth));
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_REJECTED;
if (!send_rejected (auth))
return FALSE;
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN;
return TRUE;
}
ntlm_server_message_number_mismatch:
_dbus_verbose ("%s: Server expected to send/receive %u/%u SSPI buffers, but sent/received %u/%u instead",
DBUS_AUTH_NAME (auth),
NTLM_SERVER_OUTBOUND_COUNT, NTLM_CLIENT_OUTBOUND_COUNT,
auth->outbound_message_count, auth->inbound_message_count);
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_REJECTED;
if (send_rejected (auth))
{
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN;
return TRUE;
}
return FALSE;
}
static void
handle_server_shutdown_windows_sspi_ntlm_mech (DBusAuth *auth)
{
_dbus_sspi_ntlm_free_info (&auth->sspi_ntlm);
}
static dbus_bool_t
handle_client_initial_response_windows_sspi_ntlm_mech (DBusAuth *auth,
DBusString *response)
{
auth->outbound_message_count = 0;
auth->inbound_message_count = 0;
if (_dbus_sspi_ntlm_initial_response (&auth->sspi_ntlm,
&auth->sspi_ntlm_state,
response,
&auth->sspi_ntlm_error))
{
/* This is the first message we send, so no message count check here */
auth->outbound_message_count += 1;
return TRUE;
}
_DBUS_ASSERT_ERROR_IS_SET (&auth->sspi_ntlm_error);
if (!dbus_error_has_name (&auth->sspi_ntlm_error, DBUS_ERROR_NO_MEMORY))
_dbus_verbose ("%s: Error getting initial response: %s\n",
DBUS_AUTH_NAME (auth), auth->sspi_ntlm_error.message);
dbus_error_free (&auth->sspi_ntlm_error);
/* initial_response is stateless, so this is always OK */
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN;
/* This is not an OOM error, but we can't return TRUE */
return FALSE;
}
static dbus_bool_t
handle_client_data_windows_sspi_ntlm_mech (DBusAuth *auth,
const DBusString *data)
{
dbus_bool_t error_is_local = TRUE;
switch (auth->sspi_ntlm_state)
{
case DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_ERROR:
if (!send_error (auth, auth->sspi_ntlm_error.message))
return FALSE;
dbus_error_free (&auth->sspi_ntlm_error);
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN;
return TRUE;
case DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_RESPONSE:
if (!send_data (auth, &auth->response))
return FALSE;
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN;
return TRUE;
case DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN:
case DBUS_SSPI_NTLM_INTERNAL_STATE_CLIENT_DATA_GET_NEXT_RESPONSE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_CLIENT_DATA_MAYBE_SEND_RESPONSE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_INVALID:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_REJECTED:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_CHALLENGE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_CHECK_IF_DONE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_GET_NEXT_CHALLENGE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_MAYBE_SEND_CHALLENGE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_FETCH_CREDENTIALS:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_SEND_OK:
default:
break;
}
switch (auth->sspi_ntlm_state)
{
case DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN:
auth->inbound_message_count += 1;
if (auth->inbound_message_count > NTLM_SERVER_OUTBOUND_COUNT)
goto ntlm_client_message_number_mismatch;
_dbus_string_set_length (&auth->response, 0);
auth->sspi_ntlm_done = FALSE;
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_CLIENT_DATA_GET_NEXT_RESPONSE;
/* fallthrough */
case DBUS_SSPI_NTLM_INTERNAL_STATE_CLIENT_DATA_GET_NEXT_RESPONSE:
if (!_dbus_sspi_ntlm_next_response (&auth->sspi_ntlm,
&auth->sspi_ntlm_state,
data,
&auth->response,
&error_is_local,
&auth->sspi_ntlm_error))
{
_DBUS_ASSERT_ERROR_IS_SET (&auth->sspi_ntlm_error);
if (dbus_error_has_name (&auth->sspi_ntlm_error, DBUS_ERROR_NO_MEMORY))
{
dbus_error_free (&auth->sspi_ntlm_error);
/* sspi_ntlm_state was modified by the next_response(), don't touch it */
return FALSE;
}
if (error_is_local)
{
_dbus_verbose ("%s: Error getting response: %s\n",
DBUS_AUTH_NAME (auth), auth->sspi_ntlm_error.message);
dbus_error_free (&auth->sspi_ntlm_error);
dbus_set_error_const (&auth->sspi_ntlm_error, DBUS_ERROR_FAILED, "Failed to get next response due to internal error");
}
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_ERROR;
if (!send_error (auth, auth->sspi_ntlm_error.message))
return FALSE;
dbus_error_free (&auth->sspi_ntlm_error);
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN;
return TRUE;
}
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_CLIENT_DATA_MAYBE_SEND_RESPONSE;
/* fallthrough */
case DBUS_SSPI_NTLM_INTERNAL_STATE_CLIENT_DATA_MAYBE_SEND_RESPONSE:
if (_dbus_string_get_length (&auth->response) > 0)
{
auth->outbound_message_count += 1;
if (auth->outbound_message_count > NTLM_CLIENT_OUTBOUND_COUNT)
goto ntlm_client_message_number_mismatch;
if (!send_data (auth, &auth->response))
return FALSE;
}
else
{
/* SSPI gave us nothing to send to the server, so assume
* we're done and wait for OK.
*/
goto_state (auth, &client_state_waiting_for_ok);
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN;
}
return TRUE;
case DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_ERROR:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_REJECTED:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_CHALLENGE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_CHECK_IF_DONE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_GET_NEXT_CHALLENGE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_MAYBE_SEND_CHALLENGE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_FETCH_CREDENTIALS:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_SEND_OK:
case DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_RESPONSE:
case DBUS_SSPI_NTLM_INTERNAL_STATE_INVALID:
default:
dbus_error_free (&auth->sspi_ntlm_error);
dbus_set_error_const (&auth->sspi_ntlm_error, DBUS_ERROR_FAILED, "Wrong SSPI NTLM state");
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_ERROR;
if (!send_error (auth, auth->sspi_ntlm_error.message))
return FALSE;
dbus_error_free (&auth->sspi_ntlm_error);
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN;
return TRUE;
}
ntlm_client_message_number_mismatch:
dbus_set_error (&auth->sspi_ntlm_error, DBUS_ERROR_FAILED,
"Client expected to send/receive %u/%u SSPI buffers, but sent/received %u/%u instead",
NTLM_CLIENT_OUTBOUND_COUNT, NTLM_SERVER_OUTBOUND_COUNT,
auth->outbound_message_count, auth->inbound_message_count);
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_ERROR;
if (!send_error (auth, auth->sspi_ntlm_error.message))
return FALSE;
dbus_error_free (&auth->sspi_ntlm_error);
auth->sspi_ntlm_state = DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN;
return TRUE;
}
static void
handle_client_shutdown_windows_sspi_ntlm_mech (DBusAuth *auth)
{
_dbus_sspi_ntlm_free_info (&auth->sspi_ntlm);
}
#endif
/* Put mechanisms here in order of preference.
* Right now we have:
*
* - DBUS_WINDOWS_SSPI_NTLM uses Windows SSPI API to perform NTLM authentication
* - EXTERNAL checks socket credentials (or in the future, other info from the OS)
* - DBUS_COOKIE_SHA1 uses a cookie in the home directory, like xauth or ICE
* - ANONYMOUS checks nothing but doesn't auth the person as a user
@ -1395,6 +1846,16 @@ handle_client_shutdown_anonymous_mech (DBusAuth *auth)
*/
static const DBusAuthMechanismHandler
all_mechanisms[] = {
#if defined (DBUS_WIN) && !defined (DBUS_WINCE)
{ "DBUS_WINDOWS_SSPI_NTLM",
handle_server_data_windows_sspi_ntlm_mech,
NULL, NULL,
handle_server_shutdown_windows_sspi_ntlm_mech,
handle_client_initial_response_windows_sspi_ntlm_mech,
handle_client_data_windows_sspi_ntlm_mech,
NULL, NULL,
handle_client_shutdown_windows_sspi_ntlm_mech },
#endif
{ "EXTERNAL",
handle_server_data_external_mech,
NULL, NULL,
@ -2486,6 +2947,7 @@ _dbus_auth_unref (DBusAuth *auth)
_dbus_keyring_unref (auth->keyring);
_dbus_string_free (&auth->context);
_dbus_string_free (&auth->response);
_dbus_string_free (&auth->challenge);
_dbus_string_free (&auth->identity);
_dbus_string_free (&auth->incoming);
@ -2496,7 +2958,11 @@ _dbus_auth_unref (DBusAuth *auth)
_dbus_credentials_unref (auth->credentials);
_dbus_credentials_unref (auth->authorized_identity);
_dbus_credentials_unref (auth->desired_identity);
#if defined (DBUS_WIN) && !defined (DBUS_WINCE)
dbus_error_free (&auth->sspi_ntlm_error);
#endif
dbus_free (auth);
}
}

View file

@ -150,7 +150,7 @@ static void dump_backtrace_for_thread (HANDLE hThread)
if (SymFromAddr (GetCurrentProcess (), sf.AddrPC.Offset, &displacement, pSymbol))
{
if (displacement)
DPRINTF ("%3d %s+0x%I64x", i++, pSymbol->Name, displacement);
DPRINTF ("%3d %s+0x%" PRIx64, i++, pSymbol->Name, displacement);
else
DPRINTF ("%3d %s", i++, pSymbol->Name);
}

View file

@ -54,8 +54,10 @@
#endif
/* Declarations missing in mingw's and windows sdk 7.0 headers */
#ifndef __MINGW64_VERSION_MAJOR
extern BOOL WINAPI ConvertStringSidToSidA (LPCSTR StringSid, PSID *Sid);
extern BOOL WINAPI ConvertSidToStringSidA (PSID Sid, LPSTR *StringSid);
#endif
#include <stdio.h>
#include <stdlib.h>
@ -981,6 +983,64 @@ is_winxp_sp3_or_lower (void)
dwlConditionMask);
}
/**
* Gets SID from a token
* @param sid points to sid buffer, need to be freed with LocalFree()
* @param token a token to get the SID from
* @param error an error to set in case of failure
* @returns TRUE on success, FALSE otherwise
*/
dbus_bool_t
_dbus_get_token_sid (char **sid,
HANDLE token,
DBusError *error)
{
TOKEN_USER *token_user = NULL;
DWORD n;
PSID psid;
int retval = FALSE;
if (!GetTokenInformation (token, TokenUser, NULL, 0, &n)
&& GetLastError () != ERROR_INSUFFICIENT_BUFFER)
{
_dbus_win_set_error_from_win_error (error, GetLastError ());
return FALSE;
}
token_user = alloca (n);
if (token_user == NULL)
{
/* Ran out of stack space. Try to treat it as being out of heap space. */
_DBUS_SET_OOM (error);
return FALSE;
}
if (!GetTokenInformation (token, TokenUser, token_user, n, &n))
{
_dbus_win_set_error_from_win_error (error, GetLastError ());
return FALSE;
}
psid = token_user->User.Sid;
if (!IsValidSid (psid))
{
dbus_set_error_const (error, DBUS_ERROR_FAILED, "Invalid token SID");
return FALSE;
}
if (!ConvertSidToStringSidA (psid, sid))
{
dbus_set_error_const (error, DBUS_ERROR_FAILED, "Can't convert token SID to string");
return FALSE;
}
_dbus_verbose ("_dbus_get_token_sid() got '%s' and returns %d\n", *sid, retval);
return TRUE;
}
/** Gets our SID
* @param sid points to sid buffer, need to be freed with LocalFree()
* @param process_id the process id for which the sid should be returned (use 0 for current process)
@ -989,13 +1049,11 @@ is_winxp_sp3_or_lower (void)
dbus_bool_t
_dbus_getsid(char **sid, dbus_pid_t process_id)
{
HANDLE process_handle;
HANDLE process_token = INVALID_HANDLE_VALUE;
TOKEN_USER *token_user = NULL;
DWORD n;
PSID psid;
DBusError error;
int retval = FALSE;
HANDLE process_handle;
if (process_id == 0)
process_handle = GetCurrentProcess();
else if (is_winxp_sp3_or_lower())
@ -1006,31 +1064,15 @@ _dbus_getsid(char **sid, dbus_pid_t process_id)
if (!OpenProcessToken (process_handle, TOKEN_QUERY, &process_token))
{
_dbus_win_warn_win_error ("OpenProcessToken failed", GetLastError ());
goto failed;
goto end;
}
if ((!GetTokenInformation (process_token, TokenUser, NULL, 0, &n)
&& GetLastError () != ERROR_INSUFFICIENT_BUFFER)
|| (token_user = alloca (n)) == NULL
|| !GetTokenInformation (process_token, TokenUser, token_user, n, &n))
{
_dbus_win_warn_win_error ("GetTokenInformation failed", GetLastError ());
goto failed;
}
psid = token_user->User.Sid;
if (!IsValidSid (psid))
{
_dbus_verbose("invalid sid\n");
goto failed;
}
if (!ConvertSidToStringSidA (psid, sid))
{
_dbus_verbose("invalid sid\n");
goto failed;
}
//okay:
retval = TRUE;
dbus_error_init (&error);
retval = _dbus_get_token_sid (sid, process_token, &error);
if (!retval)
_dbus_warn ("Failed to get process token SID: %s\n", error.message);
dbus_error_free (&error);
failed:
end:
CloseHandle (process_handle);
if (process_token != INVALID_HANDLE_VALUE)
CloseHandle (process_token);
@ -3952,6 +3994,153 @@ _dbus_win_warn_win_error (const char *message,
dbus_error_free (&error);
}
typedef struct DBusSSPIErrorEntry DBusSSPIErrorEntry;
struct DBusSSPIErrorEntry
{
SECURITY_STATUS value;
const char *literal;
};
#define def_sspi_err(literal) { literal, #literal },
static DBusSSPIErrorEntry dbus_sspi_error_map[] =
{
def_sspi_err (SEC_E_ALGORITHM_MISMATCH)
def_sspi_err (SEC_E_BAD_BINDINGS)
def_sspi_err (SEC_E_BAD_PKGID)
def_sspi_err (SEC_E_BUFFER_TOO_SMALL)
def_sspi_err (SEC_E_CANNOT_INSTALL)
def_sspi_err (SEC_E_CANNOT_PACK)
def_sspi_err (SEC_E_CERT_EXPIRED)
def_sspi_err (SEC_E_CERT_UNKNOWN)
def_sspi_err (SEC_E_CERT_WRONG_USAGE)
def_sspi_err (SEC_E_CONTEXT_EXPIRED)
def_sspi_err (SEC_E_CROSSREALM_DELEGATION_FAILURE)
def_sspi_err (SEC_E_CRYPTO_SYSTEM_INVALID)
def_sspi_err (SEC_E_DECRYPT_FAILURE)
def_sspi_err (SEC_E_DELEGATION_REQUIRED)
def_sspi_err (SEC_E_DOWNGRADE_DETECTED)
def_sspi_err (SEC_E_ENCRYPT_FAILURE)
def_sspi_err (SEC_E_ILLEGAL_MESSAGE)
def_sspi_err (SEC_E_INCOMPLETE_CREDENTIALS)
def_sspi_err (SEC_E_INCOMPLETE_MESSAGE)
def_sspi_err (SEC_E_INSUFFICIENT_MEMORY)
def_sspi_err (SEC_E_INTERNAL_ERROR)
def_sspi_err (SEC_E_INVALID_HANDLE)
def_sspi_err (SEC_E_INVALID_TOKEN)
def_sspi_err (SEC_E_ISSUING_CA_UNTRUSTED)
def_sspi_err (SEC_E_ISSUING_CA_UNTRUSTED_KDC)
def_sspi_err (SEC_E_KDC_CERT_EXPIRED)
def_sspi_err (SEC_E_KDC_CERT_REVOKED)
def_sspi_err (SEC_E_KDC_INVALID_REQUEST)
def_sspi_err (SEC_E_KDC_UNABLE_TO_REFER)
def_sspi_err (SEC_E_KDC_UNKNOWN_ETYPE)
def_sspi_err (SEC_E_LOGON_DENIED)
def_sspi_err (SEC_E_MAX_REFERRALS_EXCEEDED)
def_sspi_err (SEC_E_MESSAGE_ALTERED)
def_sspi_err (SEC_E_MULTIPLE_ACCOUNTS)
def_sspi_err (SEC_E_MUST_BE_KDC)
def_sspi_err (SEC_E_NO_AUTHENTICATING_AUTHORITY)
def_sspi_err (SEC_E_NO_CREDENTIALS)
def_sspi_err (SEC_E_NO_IMPERSONATION)
def_sspi_err (SEC_E_NO_IP_ADDRESSES)
def_sspi_err (SEC_E_NO_KERB_KEY)
def_sspi_err (SEC_E_NO_PA_DATA)
def_sspi_err (SEC_E_NO_S4U_PROT_SUPPORT)
def_sspi_err (SEC_E_NO_TGT_REPLY)
def_sspi_err (SEC_E_NOT_OWNER)
def_sspi_err (SEC_E_NOT_SUPPORTED)
def_sspi_err (SEC_E_OK)
def_sspi_err (SEC_E_OUT_OF_SEQUENCE)
def_sspi_err (SEC_E_PKINIT_CLIENT_FAILURE)
def_sspi_err (SEC_E_PKINIT_NAME_MISMATCH)
def_sspi_err (SEC_E_QOP_NOT_SUPPORTED)
def_sspi_err (SEC_E_REVOCATION_OFFLINE_C)
def_sspi_err (SEC_E_REVOCATION_OFFLINE_KDC)
def_sspi_err (SEC_E_SECPKG_NOT_FOUND)
def_sspi_err (SEC_E_SECURITY_QOS_FAILED)
def_sspi_err (SEC_E_SHUTDOWN_IN_PROGRESS)
def_sspi_err (SEC_E_SMARTCARD_CERT_EXPIRED)
def_sspi_err (SEC_E_SMARTCARD_CERT_REVOKED)
def_sspi_err (SEC_E_SMARTCARD_LOGON_REQUIRED)
def_sspi_err (SEC_E_STRONG_CRYPTO_NOT_SUPPORTED)
def_sspi_err (SEC_E_TARGET_UNKNOWN)
def_sspi_err (SEC_E_TIME_SKEW)
def_sspi_err (SEC_E_TOO_MANY_PRINCIPALS)
def_sspi_err (SEC_E_UNFINISHED_CONTEXT_DELETED)
def_sspi_err (SEC_E_UNKNOWN_CREDENTIALS)
def_sspi_err (SEC_E_UNSUPPORTED_FUNCTION)
def_sspi_err (SEC_E_UNSUPPORTED_PREAUTH)
def_sspi_err (SEC_E_UNTRUSTED_ROOT)
def_sspi_err (SEC_E_WRONG_CREDENTIAL_HANDLE)
def_sspi_err (SEC_E_WRONG_PRINCIPAL)
def_sspi_err (SEC_I_COMPLETE_AND_CONTINUE)
def_sspi_err (SEC_I_COMPLETE_NEEDED)
def_sspi_err (SEC_I_CONTEXT_EXPIRED)
def_sspi_err (SEC_I_CONTINUE_NEEDED)
def_sspi_err (SEC_I_INCOMPLETE_CREDENTIALS)
def_sspi_err (SEC_I_LOCAL_LOGON)
def_sspi_err (SEC_I_NO_LSA_CONTEXT)
def_sspi_err (SEC_I_RENEGOTIATE)
def_sspi_err (SEC_E_OK)
};
#undef def_sspi_err
/**
* Assigns an error name and message corresponding to SSPI return value
* code to a DBusError. Does nothing if error is #NULL.
* Note that this function can be slow-ish, so only call it when
* SSPI returns unexpected status code, which is treated as an error.
*
* @param error the error.
* @param func_call description of the function that produced the status. Can be NULL.
* @param status the value returned from a SSPI function
*/
void
_dbus_win_set_error_from_sspi_status (DBusError *error,
const char *func_call,
SECURITY_STATUS status)
{
int i;
const char *literal;
/* This is obviously suboptimal, but this function is no invoked when
* everything is going fine, so no big deal.
*/
for (literal = NULL, i = 0; dbus_sspi_error_map[i].value != SEC_E_OK; i++)
{
if (dbus_sspi_error_map[i].value != status)
continue;
literal = dbus_sspi_error_map[i].literal;
}
if (literal == NULL)
{
literal = "UNRECOGNIZED ERROR CODE";
}
if (func_call)
dbus_set_error (error, DBUS_ERROR_FAILED, "%s returned 0x%08lx (%s)", func_call, status, literal);
else
dbus_set_error (error, DBUS_ERROR_FAILED, "0x%08lx (%s)", status, literal);
}
void
_dbus_win_warn_sspi_status (const char *message,
const char *func_call,
SECURITY_STATUS status)
{
DBusError error;
dbus_error_init (&error);
_dbus_win_set_error_from_sspi_status (&error, func_call, status);
_dbus_warn ("%s: %s\n", message, error.message);
dbus_error_free (&error);
}
/**
* Removes a directory; Directory must be empty
*
@ -4517,5 +4706,492 @@ _dbus_listen_unix_socket (const char *path,
return s;
}
/*
* DBUS_WINDOWS_SSPI_NTLM mechanism
*/
#ifndef DBUS_WINCE
/* This is needed because SSPI function prototypes claim that they take
* non-const provider name, even though there's no reason to modify it.
*/
wchar_t ntlm_ssp_name[] = L"NTLM";
/**
* Feeds client response to SSPI, producing the next challenge.
*
* @param sspi_ntlm - SSPI NTLM context carried by DBusAuth
* @param state - SSPI NTLM state, for recovering from OOM errors
* @param data - inbound client response
* @param challenge - string to fill with the next challenge
* @param done - boolean to set when SSPI authentication succeeded
* @param error_is_local - boolean to set when @error is not to be shared
* @param error - an error to send back (optional)
*
* The @challenge is always valid (even if its length ends up being zero),
* as long as the function returns TRUE. The @challenge must be sent back
* to the client, then freed with _dbus_string_free().
*
* @returns #TRUE if everything is OK, @challenge and is @response are valid, #FALSE otherwise
*/
dbus_bool_t
_dbus_sspi_ntlm_next_challenge (DBusSSPINTLMInfo *sspi_ntlm,
DBusSSPINTLMInternalState *state,
const DBusString *data,
DBusString *challenge,
dbus_bool_t *done,
dbus_bool_t *error_is_local,
DBusError *error)
{
SECURITY_STATUS ss;
PSecPkgInfoW sec_package_info;
TimeStamp lifetime;
SecBuffer input_sec_buffer = { 0, SECBUFFER_TOKEN, NULL};
SecBufferDesc input_sec_buffers_descriptor = { SECBUFFER_VERSION, 1, &input_sec_buffer };
SecBuffer output_sec_buffer = { 0, SECBUFFER_TOKEN, NULL};
SecBufferDesc output_sec_buffers_descriptor = { SECBUFFER_VERSION, 1, &output_sec_buffer };
ULONG attributes;
dbus_bool_t is_done;
input_sec_buffer.cbBuffer = _dbus_string_get_length (data);
/* SSPI accepts non-const pointer, even though it doesn't write there */
input_sec_buffer.pvBuffer = (void *) _dbus_string_get_const_data (data);
if (*state != DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_GET_NEXT_CHALLENGE)
{
*error_is_local = TRUE;
dbus_set_error_const (error, DBUS_ERROR_FAILED, "Wrong SSPI NTLM state for getting next challenge");
return FALSE;
}
if (input_sec_buffer.cbBuffer == 0)
{
*error_is_local = FALSE;
dbus_set_error_const (error, DBUS_ERROR_FAILED, "Received 0-length DATA, which is unacceptable");
return FALSE;
}
if (sspi_ntlm->message_limit == 0)
{
ss = QuerySecurityPackageInfoW (ntlm_ssp_name, &sec_package_info);
if (ss != SEC_E_OK)
{
*error_is_local = TRUE;
_dbus_win_set_error_from_sspi_status (error, "QuerySecurityPackageInfoW (NTLM)", ss);
return FALSE;
}
sspi_ntlm->message_limit = sec_package_info->cbMaxToken;
ss = FreeContextBuffer (sec_package_info);
if (ss != SEC_E_OK)
_dbus_win_warn_sspi_status ("server", "FreeContextBuffer (PSecPkgInfoW)", ss);
}
if (!sspi_ntlm->free_credentials)
{
ss = AcquireCredentialsHandleW (NULL, ntlm_ssp_name, SECPKG_CRED_INBOUND, 0, 0, 0, 0,
&sspi_ntlm->credentials, &lifetime);
if (ss != SEC_E_OK)
{
*error_is_local = TRUE;
_dbus_win_set_error_from_sspi_status (error, "AcquireCredentialsHandleW (NTLM, INBOUND)", ss);
return FALSE;
}
sspi_ntlm->free_credentials = TRUE;
}
if (!_dbus_string_set_length (challenge, sspi_ntlm->message_limit))
return FALSE;
output_sec_buffer.cbBuffer = _dbus_string_get_length (challenge);
output_sec_buffer.pvBuffer = _dbus_string_get_data (challenge);
ss = AcceptSecurityContext (&sspi_ntlm->credentials,
sspi_ntlm->free_context ? &sspi_ntlm->context : NULL,
&input_sec_buffers_descriptor,
0,
SECURITY_NATIVE_DREP,
&sspi_ntlm->context,
&output_sec_buffers_descriptor,
&attributes,
&lifetime);
if (ss != SEC_E_OK &&
ss != SEC_I_COMPLETE_AND_CONTINUE &&
ss != SEC_I_COMPLETE_NEEDED &&
ss != SEC_I_CONTINUE_NEEDED)
{
*error_is_local = TRUE;
_dbus_win_set_error_from_sspi_status (error, "AcceptSecurityContext ()", ss);
return FALSE;
}
sspi_ntlm->free_context = TRUE;
is_done = !((SEC_I_CONTINUE_NEEDED == ss) || (SEC_I_COMPLETE_AND_CONTINUE == ss));
if ((SEC_I_COMPLETE_NEEDED == ss) || (SEC_I_COMPLETE_AND_CONTINUE == ss))
{
ss = CompleteAuthToken (&sspi_ntlm->context,
&output_sec_buffers_descriptor);
if (ss != SEC_E_OK)
{
*error_is_local = TRUE;
_dbus_win_set_error_from_sspi_status (error, "CompleteAuthToken ()", ss);
return FALSE;
}
}
/* Only shortens the string */
_dbus_string_set_length (challenge, output_sec_buffer.cbBuffer);
*done = is_done;
return TRUE;
}
/**
* Converts successfully authenticated SSPI security context
* attributes into credentials, adding them to DBusCredentials.
*
* @param sspi_ntlm - SSPI NTLM context carried by DBusAuth
* @param state - SSPI NTLM state, for recovering from OOM errors
* @param credentials - credentials object to modify
* @param error_is_local - boolean to set when @error is not to be shared
* @param error - an error to send back (optional)
*
* @returns #TRUE if everything is OK and @credentials were modified, #FALSE otherwise
*/
dbus_bool_t
_dbus_sspi_ntlm_fetch_credentials (DBusSSPINTLMInfo *sspi_ntlm,
DBusSSPINTLMInternalState *state,
DBusCredentials *credentials,
dbus_bool_t *error_is_local,
DBusError *error)
{
SECURITY_STATUS ss;
HANDLE client_token;
char *sid;
dbus_bool_t result;
if (*state != DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_FETCH_CREDENTIALS)
{
*error_is_local = TRUE;
dbus_set_error_const (error, DBUS_ERROR_FAILED, "Wrong SSPI NTLM state for fetching credentials");
return FALSE;
}
/* We might lose a bit of performance by re-querying and re-getting the token on every
* OOM-induced retry, but there's no state to worry about.
*/
ss = QuerySecurityContextToken (&sspi_ntlm->context, &client_token);
if (ss != SEC_E_OK)
{
*error_is_local = TRUE;
_dbus_win_set_error_from_sspi_status (error, "QuerySecurityContextToken ()", ss);
return FALSE;
}
result = _dbus_get_token_sid (&sid, client_token, error);
if (!result)
{
CloseHandle (client_token);
*error_is_local = TRUE;
return FALSE;
}
result = _dbus_credentials_add_windows_sid (credentials, sid);
if (!result)
{
*error_is_local = TRUE;
/* _dbus_credentials_add_windows_sid() currently only fails when OOM */
_DBUS_SET_OOM (error);
}
LocalFree (sid);
CloseHandle (client_token);
return result;
}
/**
* Cleans up any state initialized by _dbus_sspi_ntlm_*() functions.
*
* @param sspi_ntlm - SSPI NTLM context carried by DBusAuth
*/
void
_dbus_sspi_ntlm_free_info (DBusSSPINTLMInfo *sspi_ntlm)
{
if (sspi_ntlm->free_context)
DeleteSecurityContext (&sspi_ntlm->context);
sspi_ntlm->free_context = FALSE;
if (sspi_ntlm->free_credentials)
FreeCredentialHandle (&sspi_ntlm->credentials);
sspi_ntlm->free_credentials = FALSE;
}
/**
* Generates initial client response for SSPI NTLM authentication and
* appends it to @initial_response.
*
* @param sspi_ntlm - SSPI NTLM context carried by DBusAuth
* @param state - SSPI NTLM state, for recovering from OOM errors
* @param initial_response - outbound data to append the response to
* @param error - an error to send back (optional)
*
* @returns #TRUE if everything is OK and @initial_response was modified, #FALSE otherwise
*/
dbus_bool_t
_dbus_sspi_ntlm_initial_response (DBusSSPINTLMInfo *sspi_ntlm,
DBusSSPINTLMInternalState *state,
DBusString *initial_response,
DBusError *error)
{
SECURITY_STATUS ss;
PSecPkgInfoW sec_package_info;
TimeStamp lifetime;
SecBuffer output_sec_buffer = { 0, SECBUFFER_TOKEN, NULL};
SecBufferDesc output_sec_buffers_descriptor = { SECBUFFER_VERSION, 1, &output_sec_buffer };
ULONG attributes;
DBusString plaintext;
dbus_bool_t result;
if (*state != DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN)
{
dbus_set_error_const (error, DBUS_ERROR_FAILED, "Wrong SSPI NTLM state for getting initial challenge");
return FALSE;
}
if (sspi_ntlm->message_limit == 0)
{
ss = QuerySecurityPackageInfoW (ntlm_ssp_name, &sec_package_info);
if (ss != SEC_E_OK)
{
_dbus_win_set_error_from_sspi_status (error, "QuerySecurityPackageInfoW ()", ss);
return FALSE;
}
sspi_ntlm->message_limit = sec_package_info->cbMaxToken;
ss = FreeContextBuffer (sec_package_info);
if (ss != SEC_E_OK)
_dbus_win_warn_sspi_status ("client", "FreeContextBuffer (PSecPkgInfoW)", ss);
}
if (!sspi_ntlm->free_credentials)
{
ss = AcquireCredentialsHandleW (NULL, ntlm_ssp_name, SECPKG_CRED_OUTBOUND, 0, 0, 0, 0,
&sspi_ntlm->credentials, &lifetime);
if (ss != SEC_E_OK)
{
_dbus_win_set_error_from_sspi_status (error, "AcquireCredentialsHandleW (NTLM, OUTBOUND)", ss);
return FALSE;
}
sspi_ntlm->free_credentials = TRUE;
}
if (!_dbus_string_init_preallocated (&plaintext, sspi_ntlm->message_limit))
{
_DBUS_SET_OOM (error);
return FALSE;
}
_dbus_string_set_length (&plaintext, sspi_ntlm->message_limit);
output_sec_buffer.cbBuffer = _dbus_string_get_length (&plaintext);
output_sec_buffer.pvBuffer = _dbus_string_get_data (&plaintext);
/* We might lose some performance by re-initializing and the context on every
* OOM-induced retry, but if we clean it up on OOM, this function becomes stateless,
* which is nice.
*/
ss = InitializeSecurityContextW (&sspi_ntlm->credentials,
NULL,
NULL,
ISC_REQ_IDENTIFY,
0,
SECURITY_NATIVE_DREP,
NULL,
0,
&sspi_ntlm->context,
&output_sec_buffers_descriptor,
&attributes,
&lifetime);
if (ss != SEC_E_OK &&
ss != SEC_I_COMPLETE_AND_CONTINUE &&
ss != SEC_I_COMPLETE_NEEDED &&
ss != SEC_I_CONTINUE_NEEDED)
{
_dbus_win_set_error_from_sspi_status (error, "InitializeSecurityContextW ()", ss);
_dbus_string_free (&plaintext);
return FALSE;
}
sspi_ntlm->free_context = TRUE;
if ((SEC_I_COMPLETE_NEEDED == ss) || (SEC_I_COMPLETE_AND_CONTINUE == ss))
{
ss = CompleteAuthToken (&sspi_ntlm->context,
&output_sec_buffers_descriptor);
if (ss != SEC_E_OK)
{
_dbus_win_set_error_from_sspi_status (error, "CompleteAuthToken ()", ss);
_dbus_string_free (&plaintext);
DeleteSecurityContext (&sspi_ntlm->context);
sspi_ntlm->free_context = FALSE;
return FALSE;
}
}
/* Only shortens the string */
_dbus_string_set_length (&plaintext, output_sec_buffer.cbBuffer);
result = _dbus_string_hex_encode (&plaintext, 0, initial_response,
_dbus_string_get_length (initial_response));
if (!result)
{
DeleteSecurityContext (&sspi_ntlm->context);
sspi_ntlm->free_context = FALSE;
_DBUS_SET_OOM (error);
}
_dbus_string_free (&plaintext);
return result;
}
/**
* Feeds server challenge to SSPI, producing the next response.
*
* @param sspi_ntlm - SSPI NTLM context carried by DBusAuth
* @param state - SSPI NTLM state, for recovering from OOM errors
* @param data - inbound server challenge
* @param response - string to fill with the next response
* @param error_is_local - boolean to set when @error is not to be shared
* @param error - an error to send back (optional)
*
* @returns #TRUE if everything is OK and @response are valid, #FALSE otherwise
*/
dbus_bool_t
_dbus_sspi_ntlm_next_response (DBusSSPINTLMInfo *sspi_ntlm,
DBusSSPINTLMInternalState *state,
const DBusString *data,
DBusString *response,
dbus_bool_t *error_is_local,
DBusError *error)
{
SECURITY_STATUS ss;
TimeStamp lifetime;
SecBuffer input_sec_buffer = { 0, SECBUFFER_TOKEN, NULL};
SecBufferDesc input_sec_buffers_descriptor = { SECBUFFER_VERSION, 1, &input_sec_buffer };
SecBuffer output_sec_buffer = { 0, SECBUFFER_TOKEN, NULL};
SecBufferDesc output_sec_buffers_descriptor = { SECBUFFER_VERSION, 1, &output_sec_buffer };
ULONG attributes;
input_sec_buffer.cbBuffer = _dbus_string_get_length (data);
/* SSPI accepts non-const pointer, even though it doesn't write there */
input_sec_buffer.pvBuffer = (void *) _dbus_string_get_const_data (data);
if (*state != DBUS_SSPI_NTLM_INTERNAL_STATE_CLIENT_DATA_GET_NEXT_RESPONSE)
{
*error_is_local = TRUE;
dbus_set_error_const (error, DBUS_ERROR_FAILED, "Wrong SSPI NTLM state for getting next response");
return FALSE;
}
if (input_sec_buffer.cbBuffer == 0)
{
*error_is_local = FALSE;
dbus_set_error_const (error, DBUS_ERROR_FAILED, "Received 0-length DATA, which is unacceptable");
return FALSE;
}
if (!_dbus_string_set_length (response, sspi_ntlm->message_limit))
return FALSE;
output_sec_buffer.cbBuffer = _dbus_string_get_length (response);
output_sec_buffer.pvBuffer = _dbus_string_get_data (response);
ss = InitializeSecurityContextW (&sspi_ntlm->credentials,
&sspi_ntlm->context,
NULL,
ISC_REQ_IDENTIFY,
0,
SECURITY_NATIVE_DREP,
&input_sec_buffers_descriptor,
0,
&sspi_ntlm->context,
&output_sec_buffers_descriptor,
&attributes,
&lifetime);
if (ss != SEC_E_OK &&
ss != SEC_I_COMPLETE_AND_CONTINUE &&
ss != SEC_I_COMPLETE_NEEDED &&
ss != SEC_I_CONTINUE_NEEDED)
{
*error_is_local = TRUE;
_dbus_win_set_error_from_sspi_status (error, "InitializeSecurityContextW ()", ss);
return FALSE;
}
if ((SEC_I_COMPLETE_NEEDED == ss) || (SEC_I_COMPLETE_AND_CONTINUE == ss))
{
ss = CompleteAuthToken (&sspi_ntlm->context,
&output_sec_buffers_descriptor);
if (ss != SEC_E_OK)
{
*error_is_local = TRUE;
_dbus_win_set_error_from_sspi_status (error, "CompleteAuthToken ()", ss);
return FALSE;
}
}
_dbus_string_set_length (response, output_sec_buffer.cbBuffer);
return TRUE;
}
#endif
/** @} end of sysdeps-win */
/* tests in dbus-sysdeps-util.c */

View file

@ -28,6 +28,8 @@
#ifndef DBUS_SYSDEPS_WIN_H
#define DBUS_SYSDEPS_WIN_H
#define SECURITY_WIN32
extern void *_dbus_win_get_dll_hmodule (void);
#define WIN32_LEAN_AND_MEAN
@ -39,6 +41,11 @@ extern void *_dbus_win_get_dll_hmodule (void);
#include <windows.h>
#undef interface
#ifndef DBUS_WINCE
#include <sspi.h>
#include <sddl.h>
#endif
#define DBUS_CONSOLE_DIR "/var/run/console/"
@ -88,7 +95,93 @@ DBUS_PRIVATE_EXPORT
dbus_bool_t _dbus_get_install_root (DBusString *str);
DBUS_PRIVATE_EXPORT
dbus_bool_t _dbus_getsid(char **sid, dbus_pid_t process_id);
dbus_bool_t _dbus_getsid (char **sid,
dbus_pid_t process_id);
DBUS_PRIVATE_EXPORT
dbus_bool_t _dbus_get_token_sid (char **sid,
HANDLE token,
DBusError *error);
/** Information for SSPI NTLM authentication */
typedef struct DBusSSPINTLMInfo DBusSSPINTLMInfo;
/**
* Information for SSPI NTLM authentication
*/
struct DBusSSPINTLMInfo
{
#ifndef DBUS_WINCE
CredHandle credentials; /**< SSPI Credentials. */
CtxtHandle context; /**< SSPI Context. */
size_t message_limit; /**< Maximum size of an SSPI NTLM message. */
char *output_buffer; /**< Pre-allocated buffer for an SSPI NTLM message. */
unsigned int free_credentials : 1; /**< Must free credentials on shutdown */
unsigned int free_context : 1; /**< Must free context on shutdown */
#endif
};
/**
* Representation of the internal state of SSPI NTLM implementation.
* Because we have no control over the internal state of NTLM SSP,
* we can't just return FALSE in case of out-of-memory errors and
* then expect to re-run the function later. We need to remember
* which state the SSP is in, to avoid calling SSPI functions
* multiple times with the same arguments, which will lead to an error.
*/
typedef enum
{
DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN,
DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_ERROR,
DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_REJECTED,
DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_CHALLENGE,
DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_CHECK_IF_DONE,
DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_GET_NEXT_CHALLENGE,
DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_MAYBE_SEND_CHALLENGE,
DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_FETCH_CREDENTIALS,
DBUS_SSPI_NTLM_INTERNAL_STATE_SERVER_DATA_SEND_OK,
DBUS_SSPI_NTLM_INTERNAL_STATE_SEND_RESPONSE,
DBUS_SSPI_NTLM_INTERNAL_STATE_CLIENT_DATA_GET_NEXT_RESPONSE,
DBUS_SSPI_NTLM_INTERNAL_STATE_CLIENT_DATA_MAYBE_SEND_RESPONSE,
DBUS_SSPI_NTLM_INTERNAL_STATE_INVALID = -1
} DBusSSPINTLMInternalState;
void _dbus_win_set_error_from_sspi_status (DBusError *error,
const char *func_call,
SECURITY_STATUS status);
void _dbus_win_warn_sspi_status (const char *message,
const char *func_call,
SECURITY_STATUS status);
dbus_bool_t _dbus_sspi_ntlm_next_challenge (DBusSSPINTLMInfo *sspi_ntlm,
DBusSSPINTLMInternalState *state,
const DBusString *data,
DBusString *challenge,
dbus_bool_t *done,
dbus_bool_t *error_is_local,
DBusError *error);
dbus_bool_t _dbus_sspi_ntlm_fetch_credentials (DBusSSPINTLMInfo *sspi_ntlm,
DBusSSPINTLMInternalState *state,
DBusCredentials *credentials,
dbus_bool_t *error_is_local,
DBusError *error);
dbus_bool_t _dbus_sspi_ntlm_initial_response (DBusSSPINTLMInfo *sspi_ntlm,
DBusSSPINTLMInternalState *state,
DBusString *initial_response,
DBusError *error);
dbus_bool_t _dbus_sspi_ntlm_next_response (DBusSSPINTLMInfo *sspi_ntlm,
DBusSSPINTLMInternalState *state,
const DBusString *data,
DBusString *response,
dbus_bool_t *error_is_local,
DBusError *error);
void _dbus_sspi_ntlm_free_info (DBusSSPINTLMInfo *sspi_ntlm);
HANDLE _dbus_spawn_program (const char *name,
char **argv,

View file

@ -661,6 +661,7 @@ elif platform_windows
cc.find_library('ws2_32'),
cc.find_library('iphlpapi'),
cc.find_library('dbghelp'),
cc.find_library('secur32'),
]
else
network_libs = []

View file

@ -218,6 +218,13 @@ if(DBUS_WITH_GLIB)
add_test_executable(test-userdb internals/userdb.c ${TEST_LIBRARIES})
add_helper_executable(manual-authz manual-authz.c ${TEST_LIBRARIES})
add_helper_executable(manual-test-thread-blocking thread-blocking.c ${TEST_LIBRARIES})
if(WIN32)
set(auth_windows_sspi_corrupt_SOURCES
test-auth-windows-sspi-corrupt.c
test-utils-auth.c
)
add_test_executable(test-auth-windows-sspi-corrupt "${auth_windows_sspi_corrupt_SOURCES}" ${TEST_LIBRARIES})
endif()
endif()
### keep these in creation order, i.e. uppermost dirs first

View file

@ -0,0 +1,15 @@
<!-- Bus that listens on a debug pipe and requires SSPI auth, used to test SSPI -->
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<listen>@TEST_LISTEN@</listen>
<servicedir>@DBUS_TEST_DATA@/valid-service-files</servicedir>
<auth>DBUS_WINDOWS_SSPI_NTLM</auth>
<policy context="default">
<allow send_interface="*"/>
<allow receive_interface="*"/>
<allow own="*"/>
<allow user="*"/>
</policy>
</busconfig>

View file

@ -440,7 +440,17 @@ if platform_windows
'srcs': [ 'manual-paths.c' ],
'link': [ libdbus_testutils, ],
'test': false,
}
},
{
'name': 'test-auth-windows-sspi-corrupt',
'srcs': [
'test-auth-windows-sspi-corrupt.c',
'test-utils-auth.c'
],
'link': [ libdbus_testutils, ],
'deps': [ glib, gio, ],
'test': true,
},
]
endif

View file

@ -0,0 +1,704 @@
#include <config.h>
#include <dbus/dbus.h>
#include <stdlib.h>
#include <winsock2.h>
#include "test-utils-glib.h"
#include "test-utils-auth.h"
typedef struct {
gboolean skip;
DBusError e;
TestMainContext *ctx;
GPid daemon_pid;
DBusServer *server;
gchar *address;
DBusSocket sock;
size_t message_limit;
CredHandle credentials;
DBusString plaintext;
CtxtHandle context;
int free_context;
/* The moment when we start to screw with the data being sent.
* phase 0 - don't do anything, just a successful auth
* phase 1 - corrupt the initial client->server AUTH message
* phase 2 - corrupt the first server->client DATA message
* phase 3 - corrupt the first client->server DATA message
* There's no phase 4 (server responds with OK, but SSPI is not playing any part in that)
*/
int phase;
/* offset of the byte to corrupt. Increases by 1 with each iteration.
* Once it goes past the end of the data, advance to the next phase.
*/
unsigned int corrupt_offset;
DBusString outgoing;
int free_outgoing;
DBusString incoming;
int free_incoming;
DBusString decoded;
int free_decoded;
} Fixture;
typedef struct {
const char *bug_ref;
guint min_messages;
const char *config_file;
enum { SPECIFY_ADDRESS = 0, RELY_ON_DEFAULT } connect_mode;
} Config;
static void
setup (Fixture *f,
gconstpointer context)
{
const Config *config = context;
SECURITY_STATUS ss;
PSecPkgInfoW sec_package_info;
TimeStamp lifetime;
wchar_t ntlm_ssp_name[] = L"NTLM";
ss = QuerySecurityPackageInfoW (ntlm_ssp_name, &sec_package_info);
g_assert (ss == SEC_E_OK);
f->message_limit = sec_package_info->cbMaxToken;
ss = FreeContextBuffer (sec_package_info);
g_assert (ss == SEC_E_OK);
ss = AcquireCredentialsHandleW (NULL, ntlm_ssp_name, SECPKG_CRED_OUTBOUND, 0, 0, 0, 0,
&f->credentials, &lifetime);
g_assert (ss == SEC_E_OK);
g_assert (_dbus_string_init (&f->plaintext));
_dbus_string_set_length (&f->plaintext, f->message_limit);
f->ctx = test_main_context_get ();
dbus_error_init (&f->e);
f->address = test_get_dbus_daemon (config ? config->config_file : NULL,
TEST_USER_ME, NULL,
&f->daemon_pid);
if (f->address == NULL)
{
f->skip = TRUE;
return;
}
f->sock.sock = INVALID_SOCKET;
f->phase = 0;
f->corrupt_offset = 0;
f->free_context = 0;
f->free_outgoing = 0;
f->free_incoming = 0;
f->free_decoded = 0;
}
/* Returns TRUE to mean "keep calling me, i still have other ways
* to corrupt the data".
* Returns FALSE to mean "i'm done, don't call me again".
*/
static dbus_bool_t
do_corrupt_sspi_auth (Fixture *f,
gconstpointer context)
{
SECURITY_STATUS ss;
TimeStamp lifetime;
SecBuffer output_sec_buffer = { 0, SECBUFFER_TOKEN, NULL};
SecBufferDesc output_sec_buffers_descriptor = { SECBUFFER_VERSION, 1, &output_sec_buffer };
ULONG attributes;
volatile int bytes_written;
volatile int bytes_read;
char *buffer;
SecBuffer input_sec_buffer = { 0, SECBUFFER_TOKEN, NULL};
SecBufferDesc input_sec_buffers_descriptor = { SECBUFFER_VERSION, 1, &input_sec_buffer };
int end;
int to_read, total_read;
#define PHASES 4
char expect_error_in_phase[PHASES];
unsigned long bytes_available;
char zero;
int old_offset, old_phase;
old_offset = f->corrupt_offset;
old_phase = f->phase;
if (_dbus_socket_is_valid (f->sock))
{
/* Ensure that there's no leftover incoming data in the socket */
do
{
#define HUNDRED 100
char buf[HUNDRED];
bytes_available = 0;
ioctlsocket (f->sock.sock, FIONREAD, &bytes_available);
bytes_read = 0;
if (bytes_available > 0)
bytes_read = recv (f->sock.sock, buf, bytes_available > HUNDRED ? HUNDRED : bytes_available, 0);
#undef HUNDRED
} while (bytes_read > 0);
}
if (!_dbus_socket_is_valid (f->sock))
{
f->sock = test_connect_to_bus_simple (f->ctx, f->address);
g_assert (_dbus_socket_is_valid (f->sock));
zero = 0;
g_assert (send (f->sock.sock, &zero, 1, 0) == 1);
}
#define HANDLE_BAD_RECV \
if (bytes_read < 0) \
{ \
g_print ("disconnected unexpectedly\n"); \
f->corrupt_offset = old_offset; \
f->phase = old_phase; \
_dbus_close_socket (&f->sock, NULL); \
_dbus_socket_invalidate (&f->sock); \
\
return TRUE; \
}
#define HANDLE_BAD_SEND \
if (bytes_written < 0) \
{ \
g_print ("disconnected unexpectedly\n"); \
f->corrupt_offset = old_offset; \
f->phase = old_phase; \
_dbus_close_socket (&f->sock, NULL); \
_dbus_socket_invalidate (&f->sock); \
\
return TRUE; \
}
g_assert (_dbus_string_init (&f->outgoing));
f->free_outgoing = 1;
g_assert (_dbus_string_append (&f->outgoing, "AUTH DBUS_WINDOWS_SSPI_NTLM "));
output_sec_buffer.cbBuffer = _dbus_string_get_length (&f->plaintext);
output_sec_buffer.pvBuffer = _dbus_string_get_data (&f->plaintext);
g_print ("\nInit security context\n");
ss = InitializeSecurityContextW (&f->credentials,
NULL,
NULL,
ISC_REQ_IDENTIFY,
0,
SECURITY_NATIVE_DREP,
NULL,
0,
&f->context,
&output_sec_buffers_descriptor,
&attributes,
&lifetime);
f->free_context = 1;
g_assert (ss == SEC_E_OK ||
ss == SEC_I_COMPLETE_AND_CONTINUE ||
ss == SEC_I_COMPLETE_NEEDED ||
ss == SEC_I_CONTINUE_NEEDED);
if ((SEC_I_COMPLETE_NEEDED == ss) || (SEC_I_COMPLETE_AND_CONTINUE == ss))
{
ss = CompleteAuthToken (&f->context,
&output_sec_buffers_descriptor);
g_assert (ss == SEC_E_OK);
}
_dbus_string_set_length (&f->plaintext, output_sec_buffer.cbBuffer);
memset (expect_error_in_phase, 0, PHASES);
g_print ("phase 1\n");
if (f->phase == 1)
{
if (f->corrupt_offset < output_sec_buffer.cbBuffer && f->corrupt_offset < 32)
{
BYTE x = ((BYTE *) output_sec_buffer.pvBuffer)[f->corrupt_offset];
x = (x == 255 ? 0 : x + 1);
((BYTE *) output_sec_buffer.pvBuffer)[f->corrupt_offset] = x;
switch (f->corrupt_offset)
{
/* 4 bytes (12, 13, 14, 15) are flags, changing some of them makes no difference */
case 13:
case 14:
case 15:
/* 2 bytes (16, 17) is domain SB lengh */
/* 2 bytes (18, 19) is domain SB allocated space, changing it makes no difference */
case 18:
case 19:
g_print ("corrupted byte %d in phase %d, auth will succeed anyway\n", f->corrupt_offset, f->phase);
break;
/* 2 bytes (24, 25) is workstation SB length, changing it causes an error later on */
case 24:
g_print ("corrupted byte %d in phase %d, auth will fail in phase 3\n", f->corrupt_offset, f->phase);
expect_error_in_phase[3] = TRUE;
break;
/* 2 bytes (26, 27) is workstation SB allocated space, changing it makes no difference */
case 26:
case 27:
g_print ("corrupted byte %d in phase %d, auth will succeed anyway\n", f->corrupt_offset, f->phase);
break;
/* 4 bytes (28, 29, 30, 31) is workstation SB offset, changing it causes an error later on */
case 28:
g_print ("corrupted byte %d in phase %d, auth will fail in phase 3\n", f->corrupt_offset, f->phase);
expect_error_in_phase[3] = TRUE;
break;
/* 1 byte (32) is OS major version, changing it makes no difference */
case 32:
/* 1 byte (33) is OS minor version, changing it makes no difference */
case 33:
/* 2 byte (34, 35) is OS build number, changing it makes no difference */
case 34:
case 35:
/* 4 bytes (36, 37, 38, 39) are unknown/reserved, changing them makes no difference */
case 36:
case 37:
case 38:
case 39:
g_print ("corrupted byte %d in phase %d, auth will succeed anyway\n", f->corrupt_offset, f->phase);
break;
default:
if (f->corrupt_offset >= 40)
{
/* Data section starts at byte 40, its length depends on the argument we used, and changing it causes an error later on */
g_print ("corrupted byte %d in phase %d, auth will fail in phase 3\n", f->corrupt_offset, f->phase);
expect_error_in_phase[3] = TRUE;
}
else
{
g_print ("corrupted byte %d in phase %d, auth will fail in phase 1\n", f->corrupt_offset, f->phase);
expect_error_in_phase[1] = TRUE;
}
}
f->corrupt_offset += 1;
}
else
{
f->phase += 1;
f->corrupt_offset = 0;
g_print ("will begin corrupting phase %d\n", f->phase);
}
}
g_assert (_dbus_string_hex_encode (&f->plaintext, 0, &f->outgoing, _dbus_string_get_length (&f->outgoing)));
g_assert (_dbus_string_append (&f->outgoing, "\r\n"));
_dbus_verbose ("sending `%.*s`\n", _dbus_string_get_length (&f->outgoing), _dbus_string_get_data (&f->outgoing));
bytes_written = send (f->sock.sock,
_dbus_string_get_data (&f->outgoing), _dbus_string_get_length (&f->outgoing),
0);
HANDLE_BAD_SEND
g_assert (bytes_written == _dbus_string_get_length (&f->outgoing));
#define DATASTR "DATA "
#define DATASTRLEN strlen (DATASTR)
#define REJSTR "REJECTED"
#define REJSTRLEN strlen (REJSTR)
g_assert (_dbus_string_init (&f->incoming));
f->free_incoming = 1;
g_print ("receiving in phase 1\n");
g_assert (_dbus_string_set_length (&f->incoming, 100));
buffer = _dbus_string_get_data (&f->incoming);
bytes_read = recv (f->sock.sock, buffer, 100, MSG_PEEK);
HANDLE_BAD_RECV
g_print ("received %.*s\n", bytes_read, buffer);
if (f->phase <= 1 && (expect_error_in_phase[1] != FALSE))
{
g_assert (bytes_read >= (int) REJSTRLEN);
g_assert (strncmp (buffer, REJSTR, REJSTRLEN) == 0);
g_print ("auth failed in phase 1\n");
return TRUE;
}
g_assert (bytes_read >= (int) DATASTRLEN);
g_assert (strncmp (buffer, DATASTR, DATASTRLEN) == 0);
_dbus_string_free (&f->outgoing);
f->free_outgoing = 0;
to_read = 0;
total_read = 0;
do
{
int i, should_break;
for (i = 0, should_break = FALSE; i < total_read - 1; i++)
{
if (buffer[i] == '\r' && buffer[i + 1] == '\n')
{
should_break = TRUE;
total_read = i + 2;
}
}
if (should_break)
break;
to_read += 100;
g_assert (_dbus_string_set_length (&f->incoming, to_read));
buffer = _dbus_string_get_data (&f->incoming);
bytes_read = recv (f->sock.sock, &buffer[total_read], 100, 0);
HANDLE_BAD_RECV
g_assert (bytes_read > 0);
total_read += bytes_read;
} while (TRUE);
g_assert (buffer[total_read - 2] == '\r' &&
buffer[total_read - 1] == '\n');
buffer[total_read - 1] = buffer[total_read - 2] = '\0';
g_assert (_dbus_string_init (&f->decoded));
f->free_decoded = 1;
g_assert (_dbus_string_hex_decode (&f->incoming, DATASTRLEN, &end, &f->decoded, 0));
g_assert (end == total_read - 2);
_dbus_string_free (&f->incoming);
f->free_incoming = 0;
input_sec_buffer.cbBuffer = _dbus_string_get_length (&f->decoded);
input_sec_buffer.pvBuffer = (void *) _dbus_string_get_const_data (&f->decoded);
g_assert (input_sec_buffer.cbBuffer > 0);
g_print ("phase 2\n");
if (f->phase == 2)
{
/* 2 bytes (40, 41) is target info SB length, changing it makes no difference */
/* 2 bytes (42, 43) is target info SB allocated space, changing it makes no difference */
/* 4 bytes (44, 45, 46, 47) is target info SB offset, changing it makes no difference */
/* Data section starts at byte 48, its length depends on the argument we used, and changing it makes no difference
* because of local authentication.
*/
if (f->corrupt_offset < input_sec_buffer.cbBuffer && f->corrupt_offset < 40)
{
BYTE x = ((BYTE *) input_sec_buffer.pvBuffer)[f->corrupt_offset];
x = (x == 255 ? 0 : x + 1);
((BYTE *) input_sec_buffer.pvBuffer)[f->corrupt_offset] = x;
switch (f->corrupt_offset)
{
/* 8 bytes (32, 33, 34, 35, 36, 37, 38, 39) is the context, changing it causes an error later on */
case 32:
case 33:
case 34:
case 35:
case 36:
case 37:
case 38:
case 39:
g_print ("corrupted byte %d in phase %d, auth will fail in phase 3\n", f->corrupt_offset, f->phase);
expect_error_in_phase[3] = TRUE;
break;
default:
if (f->corrupt_offset >= 12 && f->corrupt_offset < 32)
{
/* 2 bytes (12, 13) is target name SB length, changing it makes no difference */
/* 2 bytes (14, 15) is target name SB allocated space, changing it makes no difference */
/* 4 bytes (16, 17, 18, 19) is target name SB offset, changing it makes no difference */
/* 4 bytes (20, 21, 22, 23) are flags, changing some of them makes no difference */
/* 8 bytes (24, 25, 26, 27, 28, 29, 30, 31) is the challenge, changing it makes no difference (yes! Because of local authentication) */
g_print ("corrupted byte %d in phase %d, auth will succeed anyway. Skipping to offset 31\n", f->corrupt_offset, f->phase);
f->corrupt_offset = 31;
}
else
{
g_print ("corrupted byte %d in phase %d, auth will fail in phase 2\n", f->corrupt_offset, f->phase);
expect_error_in_phase[2] = TRUE;
}
}
f->corrupt_offset += 1;
}
else
{
f->phase += 1;
f->corrupt_offset = 0;
g_print ("win begin corrupting phase %d", f->phase);
}
}
g_assert (_dbus_string_init (&f->outgoing));
f->free_outgoing = 1;
g_assert (_dbus_string_append (&f->outgoing, "DATA "));
_dbus_string_set_length (&f->plaintext, f->message_limit);
output_sec_buffer.cbBuffer = _dbus_string_get_length (&f->plaintext);
output_sec_buffer.pvBuffer = _dbus_string_get_data (&f->plaintext);
ss = InitializeSecurityContextW (&f->credentials,
&f->context,
NULL,
ISC_REQ_IDENTIFY,
0,
SECURITY_NATIVE_DREP,
&input_sec_buffers_descriptor,
0,
&f->context,
&output_sec_buffers_descriptor,
&attributes,
&lifetime);
g_print ("getting ISC result\n");
if (f->phase <= 2 && expect_error_in_phase[2] != FALSE)
{
g_assert (ss != SEC_E_OK &&
ss != SEC_I_COMPLETE_AND_CONTINUE &&
ss != SEC_I_COMPLETE_NEEDED &&
ss != SEC_I_CONTINUE_NEEDED);
g_print ("ISC failed, aborting auth\n");
/* Server still thinks we're doing AUTH, make sure it fails */
#define FAKEAUTHDATA "DATA baadf00d\r\n"
#define FAKEAUTHDATALEN strlen (FAKEAUTHDATA)
bytes_written = send (f->sock.sock, FAKEAUTHDATA, FAKEAUTHDATALEN, 0);
HANDLE_BAD_SEND
g_assert (bytes_written == FAKEAUTHDATALEN);
g_assert (_dbus_string_init (&f->incoming));
f->free_incoming = 1;
g_assert (_dbus_string_set_length (&f->incoming, 100));
buffer = _dbus_string_get_data (&f->incoming);
bytes_read = recv (f->sock.sock, buffer, 100, MSG_PEEK);
HANDLE_BAD_RECV
g_print ("received %.*s\n", bytes_read, buffer);
g_assert (bytes_read >= (int) REJSTRLEN);
g_assert (strncmp (buffer, REJSTR, REJSTRLEN) == 0);
g_print ("auth failed in phase 2\n");
return TRUE;
}
g_assert (ss == SEC_E_OK ||
ss == SEC_I_COMPLETE_AND_CONTINUE ||
ss == SEC_I_COMPLETE_NEEDED ||
ss == SEC_I_CONTINUE_NEEDED);
if ((SEC_I_COMPLETE_NEEDED == ss) || (SEC_I_COMPLETE_AND_CONTINUE == ss))
{
ss = CompleteAuthToken (&f->context,
&output_sec_buffers_descriptor);
g_assert (ss == SEC_E_OK);
}
_dbus_string_set_length (&f->plaintext, output_sec_buffer.cbBuffer);
g_print ("phase 3\n");
if (f->phase == 3)
{
/* Session key starts at byte 52, everything from that point is irrelevant */
if (f->corrupt_offset < output_sec_buffer.cbBuffer && f->corrupt_offset < 52)
{
BYTE x = ((BYTE *)output_sec_buffer.pvBuffer)[f->corrupt_offset];
x = (x == 255 ? 0 : x + 1);
((BYTE *)output_sec_buffer.pvBuffer)[f->corrupt_offset] = x;
switch (f->corrupt_offset)
{
/* 2 bytes (12, 13) is LM Response SB length, changing it causes an error */
case 12:
case 13:
/* 2 bytes (20, 21) is NTLM Response SB length, changing it causes an error */
case 20:
case 21:
/* 2 bytes (28, 29) is Target Name SB length, changing it causes an error */
case 28:
case 29:
/* 2 bytes (36, 37) is User Name SB length, changing it causes an error */
case 36:
case 37:
/* 2 bytes (44, 45) is Workstation Name SB length, changing it causes an error */
case 44:
case 45:
g_print ("corrupted byte %d in phase %d, auth will fail in phase 3\n", f->corrupt_offset, f->phase);
expect_error_in_phase[3] = TRUE;
break;
default:
if ((f->corrupt_offset > 13 && f->corrupt_offset < 20) ||
(f->corrupt_offset > 21 && f->corrupt_offset < 28) ||
(f->corrupt_offset > 29 && f->corrupt_offset < 36) ||
(f->corrupt_offset > 37 && f->corrupt_offset < 44) ||
(f->corrupt_offset > 45 && f->corrupt_offset < 52))
{
/* 2 bytes (14, 15) is LM Response SB allocated space, changing it makes no difference */
/* 4 bytes (16, 17, 18, 19) is LM Response SB offset, changing it makes no difference */
/* 2 bytes (22, 23) is NTLM Response SB allocated space, changing it makes no difference */
/* 4 bytes (24, 25, 26, 27) is NTLM Response SB offset, changing it makes no difference */
/* and so on for Target Name, User name and Workstation Name */
g_print ("corrupted byte %d in phase %d, auth will succeed anyway\n", f->corrupt_offset, f->phase);
}
else
{
g_print ("corrupted byte %d in phase %d, auth will fail in phase 3\n", f->corrupt_offset, f->phase);
expect_error_in_phase[3] = TRUE;
}
}
f->corrupt_offset += 1;
}
else
{
f->phase += 1;
f->corrupt_offset = 0;
g_print ("stopping corruption\n");
}
}
g_assert (_dbus_string_hex_encode (&f->plaintext, 0, &f->outgoing,
_dbus_string_get_length (&f->outgoing)));
g_assert (_dbus_string_append (&f->outgoing, "\r\n"));
bytes_written = send (f->sock.sock,
_dbus_string_get_data (&f->outgoing), _dbus_string_get_length (&f->outgoing),
0);
HANDLE_BAD_SEND
g_assert (bytes_written == _dbus_string_get_length (&f->outgoing));
#undef DATASTR
#undef DATASTRLEN
g_print ("receiving in phase 3\n");
g_assert (_dbus_string_init (&f->incoming));
f->free_incoming = 1;
g_assert (_dbus_string_set_length (&f->incoming, 100));
buffer = _dbus_string_get_data (&f->incoming);
bytes_read = recv (f->sock.sock, buffer, 100, MSG_PEEK);
HANDLE_BAD_RECV
g_print ("received %.*s\n", bytes_read, buffer);
if (f->phase <= 3 && expect_error_in_phase[3] != FALSE)
{
g_assert (bytes_read >= (int) REJSTRLEN);
g_assert (strncmp (buffer, REJSTR, REJSTRLEN) == 0);
g_print ("auth failed in phase 3\n");
return TRUE;
}
#undef REJSTR
#undef REJSTRLEN
#define OKSTR "OK "
#define OKSTRLEN strlen (OKSTR)
g_assert (bytes_read >= (int) OKSTRLEN);
g_assert (strncmp (buffer, OKSTR, OKSTRLEN) == 0);
g_print ("auth succeeded\n");
#undef OKSTR
#undef OKSTRLEN
/* Can't re-attempt AUTH after OK, must tear the connection down */
_dbus_close_socket (&f->sock, NULL);
_dbus_socket_invalidate (&f->sock);
_dbus_string_free (&f->outgoing);
f->free_outgoing = 0;
_dbus_string_free (&f->incoming);
f->free_incoming = 0;
DeleteSecurityContext (&f->context);
f->free_context = 0;
if (f->phase == 0)
{
f->phase += 1;
g_print ("will begin corrupting phase %d\n", f->phase);
}
else if (f->phase >= 4)
return FALSE;
return TRUE;
}
static void
test_sspi_corrupt (Fixture *f,
gconstpointer context)
{
while (TRUE)
{
/* clean up after previous iteration */
if (f->free_context)
{
DeleteSecurityContext (&f->context);
f->free_context = 0;
}
if (f->free_outgoing)
{
f->free_outgoing = 0;
_dbus_string_free (&f->outgoing);
}
if (f->free_incoming)
{
f->free_incoming = 0;
_dbus_string_free (&f->incoming);
}
if (f->free_decoded)
{
f->free_decoded = 0;
_dbus_string_free (&f->decoded);
}
if (!do_corrupt_sspi_auth (f, context))
break;
}
}
static void
teardown (Fixture *f,
gconstpointer context G_GNUC_UNUSED)
{
SECURITY_STATUS ss;
if (_dbus_socket_is_valid (f->sock))
{
_dbus_close_socket (&f->sock, NULL);
_dbus_socket_invalidate (&f->sock);
}
ss = FreeCredentialsHandle (&f->credentials);
g_assert (ss == SEC_E_OK);
_dbus_string_free (&f->plaintext);
dbus_error_free (&f->e);
if (f->daemon_pid != 0)
{
test_kill_pid (f->daemon_pid);
g_spawn_close_pid (f->daemon_pid);
f->daemon_pid = 0;
}
test_main_context_unref (f->ctx);
g_free (f->address);
}
static Config sspi_corrupt_config = {
"96577", 1, "valid-config-files/debug-auth-sspi.conf",
SPECIFY_ADDRESS
};
int
main (int argc,
char **argv)
{
test_init (&argc, &argv);
g_test_add ("/auth/sspi-corrupt", Fixture, &sspi_corrupt_config, setup, test_sspi_corrupt, teardown);
return g_test_run ();
return 0;
}

159
test/test-utils-auth.c Executable file
View file

@ -0,0 +1,159 @@
#include <config.h>
#include "test-utils-auth.h"
#include "test-utils-glib.h"
static dbus_bool_t
_dbus_win_simple_startup_winsock (void)
{
/* Straight from MSDN, deuglified */
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD (2, 0);
err = WSAStartup (wVersionRequested, &wsaData);
if (err != 0)
{
_dbus_assert_not_reached ("Could not initialize WinSock");
_dbus_abort ();
}
/* Confirm that the WinSock DLL supports 2.0. Note that if the DLL
* supports versions greater than 2.0 in addition to 2.0, it will
* still return 2.0 in wVersion since that is the version we
* requested.
*/
if (LOBYTE (wsaData.wVersion) != 2 ||
HIBYTE (wsaData.wVersion) != 0)
{
_dbus_assert_not_reached ("No usable WinSock found");
_dbus_abort ();
}
return TRUE;
}
static DBusSocket
_dbus_connect_simple_tcp_socket (const char *host,
const char *port,
const char *family)
{
DBusSocket fd = DBUS_SOCKET_INIT;
int res;
struct addrinfo hints;
struct addrinfo *ai, *tmp;
if (!_dbus_win_simple_startup_winsock ())
return _dbus_socket_get_invalid ();
_DBUS_ZERO (hints);
if (!family)
hints.ai_family = AF_UNSPEC;
else if (!strcmp(family, "ipv4"))
hints.ai_family = AF_INET;
else if (!strcmp(family, "ipv6"))
hints.ai_family = AF_INET6;
else
return _dbus_socket_get_invalid ();
hints.ai_protocol = IPPROTO_TCP;
hints.ai_socktype = SOCK_STREAM;
#ifdef AI_ADDRCONFIG
hints.ai_flags = AI_ADDRCONFIG;
#else
hints.ai_flags = 0;
#endif
if ((res = getaddrinfo (host, port, &hints, &ai)) != 0 || !ai)
return _dbus_socket_get_invalid ();
tmp = ai;
while (tmp)
{
if ((fd.sock = socket (tmp->ai_family, SOCK_STREAM, 0)) == INVALID_SOCKET)
{
freeaddrinfo(ai);
return _dbus_socket_get_invalid ();
}
if (connect (fd.sock, (struct sockaddr*) tmp->ai_addr, tmp->ai_addrlen) == SOCKET_ERROR)
{
closesocket (fd.sock);
fd.sock = INVALID_SOCKET;
tmp = tmp->ai_next;
continue;
}
break;
}
freeaddrinfo (ai);
if (!_dbus_socket_is_valid (fd))
return _dbus_socket_get_invalid ();
/* Every SOCKET is also a HANDLE. */
SetHandleInformation((HANDLE) fd.sock,
HANDLE_FLAG_INHERIT | HANDLE_FLAG_PROTECT_FROM_CLOSE,
0 /*disable both flags*/ );
return fd;
}
static DBusSocket
_dbus_simple_connection_open_internal (const char *address,
DBusError *error)
{
DBusSocket s = DBUS_SOCKET_INIT;
DBusAddressEntry **entries;
int len, i;
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
_dbus_verbose ("opening simple connection to: %s\n", address);
if (!dbus_parse_address (address, &entries, &len, error))
return s;
for (i = 0; i < len; i++)
{
const char *host = dbus_address_entry_get_value (entries[i], "host");
const char *port = dbus_address_entry_get_value (entries[i], "port");
const char *family = dbus_address_entry_get_value (entries[i], "family");
if (port == NULL)
continue;
if (host == NULL)
host = "localhost";
s = _dbus_connect_simple_tcp_socket (host, port, family);
if (!_dbus_socket_is_valid (s))
continue;
_dbus_verbose ("Successfully connected to tcp socket %s:%s\n",
host, port);
break;
}
dbus_address_entries_free (entries);
return s;
}
DBusSocket
test_connect_to_bus_simple (TestMainContext *ctx,
const gchar *address)
{
DBusSocket sock;
DBusError error = DBUS_ERROR_INIT;
sock = _dbus_simple_connection_open_internal (address, &error);
test_assert_no_error (&error);
g_assert (_dbus_socket_is_valid (sock));
return sock;
}

13
test/test-utils-auth.h Executable file
View file

@ -0,0 +1,13 @@
#ifndef TEST_UTILS_AUTH_H
#define TEST_UTILS_AUTH_H
#include <dbus/dbus.h>
#include "test-utils.h"
#include <dbus/dbus-transport.h>
#include <glib.h>
DBusSocket
test_connect_to_bus_simple (TestMainContext *ctx,
const gchar *address);
#endif

View file

@ -259,6 +259,9 @@ spawn_dbus_daemon (const gchar *binary,
g_ptr_array_add (argv, NULL);
test_dump_args(argv);
test_dump_env(envp);
g_spawn_async_with_pipes (NULL, /* working directory */
(gchar **) argv->pdata,
envp,
@ -1047,3 +1050,18 @@ test_get_helper_executable (const gchar *exe)
return g_build_filename (dbus_test_exec, exe, NULL);
}
void test_dump_args (const GPtrArray *argv)
{
for (guint i = 0; i < argv->len; i++)
{
gchar *str = g_ptr_array_index(argv, i);
g_print ("argv[%d] = %s\n", i, str);
}
}
void test_dump_env (gchar **env)
{
for (gint i = 0; env[i] != NULL; i++)
g_print ("env: %s\n", env[i]);
}

View file

@ -154,4 +154,7 @@ void test_incomplete (const gchar *message);
gchar *test_get_helper_executable (const gchar *exe);
void test_dump_args (const GPtrArray *argv);
void test_dump_env (gchar **env);
#endif