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/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..9427d7a1 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,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); } } 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 = [] 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..20116024 --- /dev/null +++ b/test/test-auth-windows-sspi-corrupt.c @@ -0,0 +1,704 @@ +#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); + + 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; +} 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 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