From b57962b77e77ae04021851efa5ed66d1b49ea041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D1=83=D1=81=D0=BB=D0=B0=D0=BD=20=D0=98=D0=B6=D0=B1?= =?UTF-8?q?=D1=83=D0=BB=D0=B0=D1=82=D0=BE=D0=B2?= Date: Tue, 6 Jan 2026 15:57:54 +0100 Subject: [PATCH 1/6] Support SSPI NTLM authentication mechanism TODO: add text Fixes #150 --- dbus/CMakeLists.txt | 4 +- dbus/dbus-auth.c | 430 +++++++++++++++++++++- dbus/dbus-backtrace-win.c | 2 +- dbus/dbus-sysdeps-win.c | 730 ++++++++++++++++++++++++++++++++++++-- dbus/dbus-sysdeps-win.h | 95 ++++- meson.build | 1 + 6 files changed, 1230 insertions(+), 32 deletions(-) diff --git a/dbus/CMakeLists.txt b/dbus/CMakeLists.txt index 04f9ca22..2f42c221 100644 --- a/dbus/CMakeLists.txt +++ b/dbus/CMakeLists.txt @@ -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}) diff --git a/dbus/dbus-auth.c b/dbus/dbus-auth.c index 3ce06704..56ccf461 100644 --- a/dbus/dbus-auth.c +++ b/dbus/dbus-auth.c @@ -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,407 @@ 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; + + default: + break; + } + + switch (auth->sspi_ntlm_state) + { + 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; + + 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; + } + +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; + + default: + break; + } + + switch (auth->sspi_ntlm_state) + { + 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; + + 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; + } + +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 +1808,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 +2909,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 +2920,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); } } diff --git a/dbus/dbus-backtrace-win.c b/dbus/dbus-backtrace-win.c index d9bc2dc4..51faabfa 100644 --- a/dbus/dbus-backtrace-win.c +++ b/dbus/dbus-backtrace-win.c @@ -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); } diff --git a/dbus/dbus-sysdeps-win.c b/dbus/dbus-sysdeps-win.c index e8fb78da..261a3734 100644 --- a/dbus/dbus-sysdeps-win.c +++ b/dbus/dbus-sysdeps-win.c @@ -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 #include @@ -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 */ diff --git a/dbus/dbus-sysdeps-win.h b/dbus/dbus-sysdeps-win.h index f7be2101..d9ee7bfa 100644 --- a/dbus/dbus-sysdeps-win.h +++ b/dbus/dbus-sysdeps-win.h @@ -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 #undef interface +#ifndef DBUS_WINCE +#include +#include +#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, diff --git a/meson.build b/meson.build index 7281b7ec..efb09cd0 100644 --- a/meson.build +++ b/meson.build @@ -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 = [] From f6e593b8963ee14f1a93ab8a5f64440ff1adfceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D1=83=D1=81=D0=BB=D0=B0=D0=BD=20=D0=98=D0=B6=D0=B1?= =?UTF-8?q?=D1=83=D0=BB=D0=B0=D1=82=D0=BE=D0=B2?= Date: Fri, 14 Jul 2017 18:02:20 +0000 Subject: [PATCH 2/6] DBUS_WINDOWS_SSPI_NTLM custom test This is a simple socket client that connects to a dbus test server daemon and tries to authenticate using the DBUS_WINDOWS_SSPI_NTLM mechanism. The code that does authentication is completely independent from libdbus, and is completely bare. The test is explicitly Windows-only, as it drops all platform-independent abstractions that libdbus would have provided (not to mention the fact that DBUS_WINDOWS_SSPI_NTLM itself is Windows-only...). The code will try to authenticate multiple times, changing a different byte in a different buffer each time, and then expecting the authentication to success or fail depending on what is changed (the test has limited, hardcoded awareness of the NTLM message structure for that purpose). https://bugs.freedesktop.org/show_bug.cgi?id=96577 --- test/CMakeLists.txt | 7 + .../debug-auth-sspi.conf.in | 15 + test/meson.build | 12 +- test/test-auth-windows-sspi-corrupt.c | 703 ++++++++++++++++++ test/test-utils-auth.c | 159 ++++ test/test-utils-auth.h | 13 + 6 files changed, 908 insertions(+), 1 deletion(-) create mode 100644 test/data/valid-config-files/debug-auth-sspi.conf.in create mode 100755 test/test-auth-windows-sspi-corrupt.c create mode 100755 test/test-utils-auth.c create mode 100755 test/test-utils-auth.h diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 032ba1f7..122d4b5f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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 diff --git a/test/data/valid-config-files/debug-auth-sspi.conf.in b/test/data/valid-config-files/debug-auth-sspi.conf.in new file mode 100644 index 00000000..98cba0b3 --- /dev/null +++ b/test/data/valid-config-files/debug-auth-sspi.conf.in @@ -0,0 +1,15 @@ + + + + + @TEST_LISTEN@ + @DBUS_TEST_DATA@/valid-service-files + DBUS_WINDOWS_SSPI_NTLM + + + + + + + diff --git a/test/meson.build b/test/meson.build index a60afac6..698800ac 100644 --- a/test/meson.build +++ b/test/meson.build @@ -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 diff --git a/test/test-auth-windows-sspi-corrupt.c b/test/test-auth-windows-sspi-corrupt.c new file mode 100755 index 00000000..e150dc9f --- /dev/null +++ b/test/test-auth-windows-sspi-corrupt.c @@ -0,0 +1,703 @@ +#include + +#include + +#include +#include + +#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); + + 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; +} diff --git a/test/test-utils-auth.c b/test/test-utils-auth.c new file mode 100755 index 00000000..6af7c3c1 --- /dev/null +++ b/test/test-utils-auth.c @@ -0,0 +1,159 @@ +#include +#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; +} diff --git a/test/test-utils-auth.h b/test/test-utils-auth.h new file mode 100755 index 00000000..31aeb249 --- /dev/null +++ b/test/test-utils-auth.h @@ -0,0 +1,13 @@ +#ifndef TEST_UTILS_AUTH_H +#define TEST_UTILS_AUTH_H + +#include +#include "test-utils.h" +#include +#include + +DBusSocket +test_connect_to_bus_simple (TestMainContext *ctx, + const gchar *address); + +#endif \ No newline at end of file From 47c48c6d2a1114364a33838962bafffb08e5fd2f Mon Sep 17 00:00:00 2001 From: Ralf Habacker Date: Wed, 7 Jan 2026 10:22:42 +0100 Subject: [PATCH 3/6] Fix code style for default case of switch statement In handle_server_data_windows_sspi_ntlm_mech() move default case to bottom of the switch statement, which is commonly used in dbus source code. --- dbus/dbus-auth.c | 52 ++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/dbus/dbus-auth.c b/dbus/dbus-auth.c index 56ccf461..f7f39b99 100644 --- a/dbus/dbus-auth.c +++ b/dbus/dbus-auth.c @@ -1444,19 +1444,6 @@ handle_server_data_windows_sspi_ntlm_mech (DBusAuth *auth, switch (auth->sspi_ntlm_state) { - 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; - case DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN: auth->outbound_message_count = 0; auth->inbound_message_count = 0; @@ -1594,6 +1581,19 @@ handle_server_data_windows_sspi_ntlm_mech (DBusAuth *auth, _dbus_verbose ("%s: authenticated client using DBUS_WINDOWS_SSPI_NTLM\n", DBUS_AUTH_NAME (auth)); return TRUE; + + 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: @@ -1682,19 +1682,6 @@ handle_client_data_windows_sspi_ntlm_mech (DBusAuth *auth, switch (auth->sspi_ntlm_state) { - 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; - case DBUS_SSPI_NTLM_INTERNAL_STATE_CLEAN: auth->inbound_message_count += 1; @@ -1769,6 +1756,19 @@ handle_client_data_windows_sspi_ntlm_mech (DBusAuth *auth, } return TRUE; + + 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: From 0e86cad27862283dfec83ab5759ac38be53b31e3 Mon Sep 17 00:00:00 2001 From: Ralf Habacker Date: Wed, 7 Jan 2026 10:49:01 +0100 Subject: [PATCH 4/6] Fixes for -Werror=switch-enum To fix the mentioned compile error missing switches cases has been added. --- CMakeLists.txt | 2 +- dbus/dbus-auth.c | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c6aa0a08..77ed218a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/dbus/dbus-auth.c b/dbus/dbus-auth.c index f7f39b99..9427d7a1 100644 --- a/dbus/dbus-auth.c +++ b/dbus/dbus-auth.c @@ -1438,6 +1438,16 @@ handle_server_data_windows_sspi_ntlm_mech (DBusAuth *auth, 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; } @@ -1582,6 +1592,13 @@ handle_server_data_windows_sspi_ntlm_mech (DBusAuth *auth, 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)); @@ -1676,6 +1693,17 @@ handle_client_data_windows_sspi_ntlm_mech (DBusAuth *auth, 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; } @@ -1757,6 +1785,16 @@ handle_client_data_windows_sspi_ntlm_mech (DBusAuth *auth, 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"); From 24bb8af9f559a94b586d17271c5c1045ea941cd6 Mon Sep 17 00:00:00 2001 From: Ralf Habacker Date: Wed, 7 Jan 2026 11:40:53 +0100 Subject: [PATCH 5/6] Add test_dump_* functions --- test/test-utils-glib.c | 18 ++++++++++++++++++ test/test-utils-glib.h | 3 +++ 2 files changed, 21 insertions(+) diff --git a/test/test-utils-glib.c b/test/test-utils-glib.c index cefd2f5d..c99feadd 100644 --- a/test/test-utils-glib.c +++ b/test/test-utils-glib.c @@ -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]); +} diff --git a/test/test-utils-glib.h b/test/test-utils-glib.h index 427f7d53..730ab253 100644 --- a/test/test-utils-glib.h +++ b/test/test-utils-glib.h @@ -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 From 16ff61b7e8cd85bc31375c0231e1d254fbc8c927 Mon Sep 17 00:00:00 2001 From: Ralf Habacker Date: Wed, 7 Jan 2026 12:02:27 +0100 Subject: [PATCH 6/6] Add message for starting new attempt to connect --- test/test-auth-windows-sspi-corrupt.c | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test-auth-windows-sspi-corrupt.c b/test/test-auth-windows-sspi-corrupt.c index e150dc9f..20116024 100755 --- a/test/test-auth-windows-sspi-corrupt.c +++ b/test/test-auth-windows-sspi-corrupt.c @@ -183,6 +183,7 @@ do_corrupt_sspi_auth (Fixture *f, 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,