/*++ Copyright (c) 1996 Microsoft Corporation Module Name: nmutil.c Abstract: Miscellaneous utility routines for the Node Manager component. Author: Mike Massa (mikemas) 26-Oct-1996 Revision History: --*/ #define UNICODE 1 #include "service.h" #include "nmp.h" #include #include PVOID NmpClusterKey = NULL; DWORD NmpClusterKeyLength = 0; DWORD NmpQueryString( IN HDMKEY Key, IN LPCWSTR ValueName, IN DWORD ValueType, IN LPWSTR *StringBuffer, IN OUT LPDWORD StringBufferSize, OUT LPDWORD StringSize ) /*++ Routine Description: Reads a REG_SZ or REG_MULTI_SZ registry value. If the StringBuffer is not large enough to hold the data, it is reallocated. Arguments: Key - Open key for the value to be read. ValueName - Unicode name of the value to be read. ValueType - REG_SZ or REG_MULTI_SZ. StringBuffer - Buffer into which to place the value data. StringBufferSize - Pointer to the size of the StringBuffer. This parameter is updated if StringBuffer is reallocated. StringSize - The size of the data returned in StringBuffer, including the terminating null character. Return Value: The status of the registry query. Notes: To avoid deadlock with DM, must not be called with NM lock held. --*/ { DWORD status; DWORD valueType; WCHAR *temp; DWORD oldBufferSize = *StringBufferSize; BOOL noBuffer = FALSE; if (*StringBufferSize == 0) { noBuffer = TRUE; } *StringSize = *StringBufferSize; status = DmQueryValue( Key, ValueName, &valueType, (LPBYTE) *StringBuffer, StringSize ); if (status == NO_ERROR) { if (!noBuffer ) { if (valueType == ValueType) { return(NO_ERROR); } else { return(ERROR_INVALID_PARAMETER); } } status = ERROR_MORE_DATA; } if (status == ERROR_MORE_DATA) { temp = MIDL_user_allocate(*StringSize); if (temp == NULL) { *StringSize = 0; return(ERROR_NOT_ENOUGH_MEMORY); } if (!noBuffer) { MIDL_user_free(*StringBuffer); } *StringBuffer = temp; *StringBufferSize = *StringSize; status = DmQueryValue( Key, ValueName, &valueType, (LPBYTE) *StringBuffer, StringSize ); if (status == NO_ERROR) { if (valueType == ValueType) { return(NO_ERROR); } else { *StringSize = 0; return(ERROR_INVALID_PARAMETER); } } } return(status); } // NmpQueryString // // Routines to support the common network configuration code. // VOID ClNetPrint( IN ULONG LogLevel, IN PCHAR FormatString, ... ) { CHAR buffer[256]; DWORD bytes; va_list argList; va_start(argList, FormatString); bytes = FormatMessageA( FORMAT_MESSAGE_FROM_STRING, FormatString, 0, 0, buffer, sizeof(buffer), &argList ); va_end(argList); if (bytes != 0) { ClRtlLogPrint(LogLevel, "%1!hs!", buffer); } return; } // ClNetPrint VOID ClNetLogEvent( IN DWORD LogLevel, IN DWORD MessageId ) { CsLogEvent(LogLevel, MessageId); return; } // ClNetLogEvent VOID ClNetLogEvent1( IN DWORD LogLevel, IN DWORD MessageId, IN LPCWSTR Arg1 ) { CsLogEvent1(LogLevel, MessageId, Arg1); return; } // ClNetLogEvent1 VOID ClNetLogEvent2( IN DWORD LogLevel, IN DWORD MessageId, IN LPCWSTR Arg1, IN LPCWSTR Arg2 ) { CsLogEvent2(LogLevel, MessageId, Arg1, Arg2); return; } // ClNetLogEvent2 VOID ClNetLogEvent3( IN DWORD LogLevel, IN DWORD MessageId, IN LPCWSTR Arg1, IN LPCWSTR Arg2, IN LPCWSTR Arg3 ) { CsLogEvent3(LogLevel, MessageId, Arg1, Arg2, Arg3); return; } // ClNetLogEvent3 BOOLEAN NmpLockedEnterApi( NM_STATE RequiredState ) { if (NmpState >= RequiredState) { NmpActiveThreadCount++; CL_ASSERT(NmpActiveThreadCount != 0); return(TRUE); } return(FALSE); } // NmpLockedEnterApi BOOLEAN NmpEnterApi( NM_STATE RequiredState ) { BOOLEAN mayEnter; NmpAcquireLock(); mayEnter = NmpLockedEnterApi(RequiredState); NmpReleaseLock(); return(mayEnter); } // NmpEnterApi VOID NmpLockedLeaveApi( VOID ) { CL_ASSERT(NmpActiveThreadCount > 0); NmpActiveThreadCount--; if ((NmpActiveThreadCount == 0) && (NmpState == NmStateOfflinePending)) { SetEvent(NmpShutdownEvent); } return; } // NmpLockedLeaveApi VOID NmpLeaveApi( VOID ) { NmpAcquireLock(); NmpLockedLeaveApi(); NmpReleaseLock(); return; } // NmpLeaveApi // // Routines to provide a cluster shared key for signing and encrypting // data. // DWORD NmpGetLogonId( OUT LUID * LogonId ) { HANDLE tokenHandle = NULL; TOKEN_STATISTICS tokenInfo; DWORD bytesReturned; BOOL success = FALSE; DWORD status; if (LogonId == NULL) { status = STATUS_UNSUCCESSFUL; goto error_exit; } if (!OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &tokenHandle )) { status = GetLastError(); ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to open process token, status %1!u!.\n", status ); goto error_exit; } if (!GetTokenInformation( tokenHandle, TokenStatistics, &tokenInfo, sizeof(tokenInfo), &bytesReturned )) { status = GetLastError(); ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to get token information, status %1!u!.\n", status ); goto error_exit; } RtlCopyMemory(LogonId, &(tokenInfo.AuthenticationId), sizeof(LUID)); status = STATUS_SUCCESS; error_exit: if (tokenHandle != NULL) { CloseHandle(tokenHandle); } return(status); } // NmpGetLogonId DWORD NmpGenerateClusterKey( IN PVOID MixingBytes, IN DWORD MixingBytesSize, OUT PVOID * Key, OUT DWORD * KeyLength ) /*++ Routine Description: Generate the cluster key using the cluster instance id as mixing bytes. Allocate a buffer for the key and return it. Arguments: Key - set to buffer containing key KeyLength - length of resulting key --*/ { LUID logonId; BOOLEAN wasEnabled = FALSE; BOOLEAN trusted = FALSE; STRING name; HANDLE lsaHandle = NULL; DWORD ignore; DWORD packageId = 0; DWORD requestSize; PMSV1_0_DERIVECRED_REQUEST request = NULL; DWORD responseSize; PMSV1_0_DERIVECRED_RESPONSE response = NULL; PUCHAR key; DWORD keyLength; DWORD status = STATUS_SUCCESS; DWORD subStatus = STATUS_SUCCESS; status = NmpGetLogonId(&logonId); if (!NT_SUCCESS(status)) { ClRtlLogPrint( LOG_UNUSUAL, "[NM] Failed to determine logon ID, status %1!u!.\n", status ); goto error_exit; } // // Try to turn on TCB privilege if running in console mode. // // ISSUE-2001/04/30-daviddio // In normal operation, there is no need to enable the TCB privilege. // In fact, enabling the TCB privilege should usually fail since the // cluster service account is not given the TCB privilege during setup. // The fix for bug 337751 allows the cluster service account to issue // a MSV1_0_DERIVECRED_REQUEST even if it does not have a trusted // connection to LSA. The reason this code is left in is for // trouble-shooting. If the cluster service is run from the command // line (e.g. not via the SCM), then the fix for bug 337751 will not // apply, and a trusted connection to LSA will be required to generate // the cluster key. // if (CsRunningAsService) { status = RtlAdjustPrivilege(SE_TCB_PRIVILEGE, TRUE, FALSE, &wasEnabled); if (!NT_SUCCESS(status)) { #if CLUSTER_BETA ClRtlLogPrint(LOG_NOISE, "[NM] Failed to turn on TCB privilege, status %1!u!.\n", LsaNtStatusToWinError(status) ); #endif // CLUSTER_BETA trusted = FALSE; } else { #if CLUSTER_BETA ClRtlLogPrint(LOG_NOISE, "[NM] Turned on TCB privilege, wasEnabled = %1!ws!.\n", (wasEnabled) ? L"TRUE" : L"FALSE" ); #endif // CLUSTER_BETA trusted = TRUE; } } // // Establish contact with LSA. // if (trusted) { RtlInitString(&name, "ClusSvcNM"); status = LsaRegisterLogonProcess(&name, &lsaHandle, &ignore); // // Turn off TCB privilege // if (!wasEnabled) { subStatus = RtlAdjustPrivilege( SE_TCB_PRIVILEGE, FALSE, FALSE, &wasEnabled ); if (!NT_SUCCESS(subStatus)) { ClRtlLogPrint( LOG_UNUSUAL, "[NM] Failed to disable TCB privilege, " "status %1!u!.\n", subStatus ); } } } else { status = LsaConnectUntrusted(&lsaHandle); } if (!NT_SUCCESS(status)) { status = LsaNtStatusToWinError(status); ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to obtain LSA logon handle in %1!ws! mode, " "status %2!u!.\n", (trusted) ? L"trusted" : L"untrusted", status ); goto error_exit; } // // Lookup the authentication package. // RtlInitString( &name, MSV1_0_PACKAGE_NAME ); status = LsaLookupAuthenticationPackage(lsaHandle, &name, &packageId); if (!NT_SUCCESS(status)) { status = LsaNtStatusToWinError(status); ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to local authentication package with " "name %1!ws!, status %2!u!.\n", name.Buffer, status ); goto error_exit; } // // Build the derive credentials request with the provided // mixing bytes. // requestSize = sizeof(MSV1_0_DERIVECRED_REQUEST) + MixingBytesSize; request = LocalAlloc(LMEM_FIXED, requestSize); if (request == NULL) { ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to allocate LSA request of size %1!u! bytes.\n" ); status = ERROR_NOT_ENOUGH_MEMORY; goto error_exit; } request->MessageType = MsV1_0DeriveCredential; RtlCopyMemory(&(request->LogonId), &logonId, sizeof(logonId)); request->DeriveCredType = MSV1_0_DERIVECRED_TYPE_SHA1; request->DeriveCredInfoLength = MixingBytesSize; RtlCopyMemory( &(request->DeriveCredSubmitBuffer[0]), MixingBytes, MixingBytesSize ); // // Make the call through LSA to the authentication package. // status = LsaCallAuthenticationPackage( lsaHandle, packageId, request, requestSize, &response, &responseSize, &subStatus ); if (!NT_SUCCESS(status)) { status = LsaNtStatusToWinError(status); subStatus = LsaNtStatusToWinError(subStatus); ClRtlLogPrint(LOG_UNUSUAL, "[NM] DeriveCredential call to authentication " "package failed, status %1!u!, auth package " "status %2!u!.\n", status, subStatus ); goto error_exit; } // // Allocate a non-LSA buffer to store the key. // keyLength = response->DeriveCredInfoLength; key = MIDL_user_allocate(keyLength); if (key == NULL) { status = ERROR_NOT_ENOUGH_MEMORY; ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to allocate buffer for cluster " "key of size %1!u!.\n", keyLength ); goto error_exit; } // // Store the derived credentials in the key buffer. // RtlCopyMemory(key, &(response->DeriveCredReturnBuffer[0]), keyLength); // // Zero the derived credential buffer to be extra paranoid. // RtlZeroMemory( &(response->DeriveCredReturnBuffer[0]), response->DeriveCredInfoLength ); status = STATUS_SUCCESS; *Key = key; *KeyLength = keyLength; error_exit: if (lsaHandle != NULL) { LsaDeregisterLogonProcess(lsaHandle); lsaHandle = NULL; } if (request != NULL) { LocalFree(request); request = NULL; } if (response != NULL) { LsaFreeReturnBuffer(response); response = NULL; } return(status); } // NmpGenerateClusterKey DWORD NmpGetClusterKey( OUT PVOID KeyBuffer, IN OUT DWORD * KeyBufferLength ) /*++ Routine Description: Copy the shared cluster key into the buffer provided. Arguments: KeyBuffer - buffer to which key should be copied KeyBufferLength - IN: length of KeyBuffer OUT: required buffer size, if input buffer length is insufficient Return value: ERROR_INSUFFICIENT_BUFFER if KeyBuffer is too small. ERROR_FILE_NOT_FOUND if NmpClusterKey has not yet been generated. ERROR_SUCCESS on success. Notes: Acquires and releases NM lock. Since NM lock is implemented as a critical section, calling thread is permitted to already hold NM lock. --*/ { DWORD status; NmpAcquireLock(); if (NmpClusterKey == NULL) { status = ERROR_FILE_NOT_FOUND; ClRtlLogPrint(LOG_UNUSUAL, "[NM] The cluster key has not yet been derived.\n" ); } else{ if (KeyBuffer == NULL || NmpClusterKeyLength > *KeyBufferLength) { status = ERROR_INSUFFICIENT_BUFFER; } else { RtlCopyMemory(KeyBuffer, NmpClusterKey, NmpClusterKeyLength); status = ERROR_SUCCESS; } *KeyBufferLength = NmpClusterKeyLength; } NmpReleaseLock(); return(status); } // NmpGetClusterKey DWORD NmpRegenerateClusterKey( VOID ) /*++ Routine Description: Forces regeneration of cluster key. Must be called during cluster initialization to generate cluster key the first time. Notes: Acquires and releases NM lock. --*/ { DWORD status; BOOLEAN lockAcquired; PVOID oldKey = NULL; DWORD oldKeyLength = 0; PVOID key = NULL; DWORD keyLength = 0; PVOID mixingBytes; DWORD mixingBytesSize; NmpAcquireLock(); lockAcquired = TRUE; NmpLockedEnterApi(NmStateOnlinePending); // // Form the mixing bytes. // if (NmpClusterInstanceId == NULL) { status = ERROR_INVALID_PARAMETER; ClRtlLogPrint(LOG_UNUSUAL, "[NM] Need cluster instance id in order to derive " "cluster key, status %1!u!.\n", status ); goto error_exit; } mixingBytesSize = NM_WCSLEN(NmpClusterInstanceId); mixingBytes = MIDL_user_allocate(mixingBytesSize); if (mixingBytes == NULL) { status = ERROR_NOT_ENOUGH_MEMORY; ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to allocate buffer of size %1!u! " "for mixing bytes to derive cluster key.\n", mixingBytesSize ); goto error_exit; } RtlCopyMemory(mixingBytes, NmpClusterInstanceId, mixingBytesSize); // // Make a copy of the old key to detect changes. // if (NmpClusterKey != NULL) { CL_ASSERT(NmpClusterKeyLength > 0); oldKey = MIDL_user_allocate(NmpClusterKeyLength); if (oldKey == NULL) { ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to allocate buffer for cluster " "key copy.\n" ); status = ERROR_NOT_ENOUGH_MEMORY; goto error_exit; } oldKeyLength = NmpClusterKeyLength; RtlCopyMemory(oldKey, NmpClusterKey, NmpClusterKeyLength); } NmpReleaseLock(); lockAcquired = FALSE; status = NmpGenerateClusterKey( mixingBytes, mixingBytesSize, &key, &keyLength ); if (status != ERROR_SUCCESS) { ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to generate cluster key, " "status %1!u!.\n", status ); goto error_exit; } NmpAcquireLock(); lockAcquired = TRUE; // // Make sure another thread didn't beat us in obtaining a key. // We replace the cluster key with the generated key if it is // not different from the old key (or somebody set it to NULL). // if (NmpClusterKey != NULL && (oldKey == NULL || NmpClusterKeyLength != oldKeyLength || RtlCompareMemory( NmpClusterKey, oldKey, oldKeyLength ) != oldKeyLength ) ) { // // Keep the current NmpClusterKey. // } else { MIDL_user_free(NmpClusterKey); NmpClusterKey = key; NmpClusterKeyLength = keyLength; key = NULL; } error_exit: if (lockAcquired) { NmpLockedLeaveApi(); NmpReleaseLock(); } else { NmpLeaveApi(); } if (key != NULL) { MIDL_user_free(key); key = NULL; } if (mixingBytes != NULL) { MIDL_user_free(mixingBytes); mixingBytes = NULL; } return(status); } // NmpRegenerateClusterKey VOID NmpFreeClusterKey( VOID ) /*++ Routine Description: Called during NmShutdown. --*/ { if (NmpClusterKey != NULL) { MIDL_user_free(NmpClusterKey); NmpClusterKey = NULL; NmpClusterKeyLength = 0; } return; } // NmpFreeClusterKey