//+----------------------------------------------------------------------- // // Microsoft Windows // // Copyright (c) Microsoft Corporation 2000 // // File: creds.cxx // // Contents: Credential functions: // // // History: KDamour 15Mar00 Stolen from NTLM // //------------------------------------------------------------------------ #include "global.h" // // Crit Sect to protect various globals in this module. // RTL_CRITICAL_SECTION l_CredentialCritSect; LIST_ENTRY l_CredentialList; // Simple variable to make sure that the package was initialize BOOL g_bCredentialsInitialized = FALSE; //+-------------------------------------------------------------------- // // Function: CredHandlerInit // // Synopsis: Initializes the credential manager package // // Arguments: none // // Returns: NTSTATUS // // Notes: Called by SpInitialize // //--------------------------------------------------------------------- NTSTATUS CredHandlerInit(VOID) { NTSTATUS Status = STATUS_SUCCESS; // // Initialize the Credential list to be empty. // Status = RtlInitializeCriticalSection(&l_CredentialCritSect); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "CredHandlerInit: Failed to initialize critsec 0x%x\n", Status)); goto CleanUp; } InitializeListHead( &l_CredentialList ); // Simple variable test to make sure all initialized; g_bCredentialsInitialized = TRUE; CleanUp: return Status; } NTSTATUS CredHandlerInsertCred( IN PDIGEST_CREDENTIAL pDigestCred ) { RtlEnterCriticalSection( &l_CredentialCritSect ); pDigestCred->Unlinked = FALSE; InsertHeadList( &l_CredentialList, &pDigestCred->Next ); RtlLeaveCriticalSection( &l_CredentialCritSect ); return STATUS_SUCCESS; } // Initialize the Credential Structure NTSTATUS CredentialInit( IN PDIGEST_CREDENTIAL pDigestCred) { NTSTATUS Status = STATUS_SUCCESS; ASSERT(pDigestCred); if (!pDigestCred) { return STATUS_INVALID_PARAMETER; } ZeroMemory(pDigestCred, sizeof(DIGEST_CREDENTIAL)); pDigestCred->Unlinked = TRUE; pDigestCred->CredentialHandle = (ULONG_PTR)pDigestCred; pDigestCred->lReferences = 0; return(Status); } // Free up memory utilized by Credential the Credential Structure NTSTATUS CredentialFree( IN PDIGEST_CREDENTIAL pDigestCred) { NTSTATUS Status = STATUS_SUCCESS; ASSERT(pDigestCred); ASSERT(0 == pDigestCred->lReferences); UnicodeStringFree(&(pDigestCred->ustrAccountName)); UnicodeStringFree(&(pDigestCred->ustrDomainName)); // Erase any password information if (((pDigestCred->ustrPassword).MaximumLength) && ((pDigestCred->ustrPassword).Buffer)) { SecureZeroMemory((pDigestCred->ustrPassword).Buffer, (pDigestCred->ustrPassword).MaximumLength); } UnicodeStringFree(&(pDigestCred->ustrPassword)); UnicodeStringFree(&(pDigestCred->ustrDomain)); UnicodeStringFree(&(pDigestCred->ustrUpn)); DigestFreeMemory(pDigestCred); return(Status); } /*++ Routine Description: This routine checks to see if the Credential Handle is from a currently active client, and references the Credential if it is valid. The caller can request that the Credential be dereferenced only. For a client's Credential to be valid, the Credential value must be on our list of active Credentials. Arguments: CredentialHandle - Points to the CredentialHandle of the Credential to be referenced. DereferenceCredential - This boolean value indicates whether the caller wants the logon process's Credential to be referenced (FALSE) or decremented (TRUE) Return Value: NULL - the Credential was not found. Otherwise - returns a pointer to the referenced credential. --*/ NTSTATUS CredHandlerHandleToPtr( IN ULONG_PTR CredentialHandle, IN BOOLEAN DereferenceCredential, OUT PDIGEST_CREDENTIAL * UserCredential ) { PLIST_ENTRY ListEntry = NULL; PDIGEST_CREDENTIAL Credential = NULL; NTSTATUS Status = STATUS_SUCCESS; SECPKG_CLIENT_INFO ClientInfo; SECPKG_CALL_INFO CallInfo; // LONG lDereferenceCount = 1; ULONG ulDereferenceCount = 1; LONG lReferences = 0; DebugLog((DEB_TRACE_FUNC, "CredHandlerHandleToPtr: Entering Credential 0x%x\n", CredentialHandle)); // set default output ASSERT(UserCredential); *UserCredential = NULL ; ZeroMemory( &CallInfo, sizeof(CallInfo) ); ZeroMemory( &ClientInfo, sizeof(ClientInfo) ); if(g_LsaFunctions->GetCallInfo(&CallInfo)) { ulDereferenceCount = CallInfo.CallCount; DebugLog((DEB_TRACE, "CredHandlerHandleToPtr: CallCount 0x%x\n", CallInfo.CallCount)); DebugLog((DEB_TRACE, "CredHandlerHandleToPtr: Attributes 0x%x\n", CallInfo.Attributes)); DebugLog((DEB_TRACE, "CredHandlerHandleToPtr: PID %d Thread %d\n", CallInfo.ProcessId, CallInfo.ThreadId)); } else { ZeroMemory( &CallInfo, sizeof(CallInfo) ); } Status = g_LsaFunctions->GetClientInfo(&ClientInfo); if(!NT_SUCCESS(Status)) { // // this call can fail during a cleanup call. so ignore that for now, // and check for cleanup disposition. // if ((CallInfo.Attributes & SECPKG_CALL_CLEANUP) != 0) { Status = STATUS_SUCCESS; RtlZeroMemory( &ClientInfo, sizeof(SECPKG_CLIENT_INFO) ); ClientInfo.HasTcbPrivilege = TRUE; ClientInfo.ProcessID = CallInfo.ProcessId; } if( !NT_SUCCESS( Status ) ) { DebugLog(( DEB_ERROR, "CredHandlerHandleToPtr: GetClientInfo returned 0x%lx\n", Status)); return( Status ); } } if( CallInfo.Attributes & SECPKG_CALL_CLEANUP ) { DebugLog(( DEB_TRACE, "CredHandlerHandleToPtr: Cleanup Called pid: 0x%lx handle: %p refcount: %lu\n", ClientInfo.ProcessID, CredentialHandle, ulDereferenceCount)); } // // Acquire exclusive access to the Credential list // RtlEnterCriticalSection( &l_CredentialCritSect ); // // Now walk the list of Credentials looking for a match. // for ( ListEntry = l_CredentialList.Flink; ListEntry != &l_CredentialList; ListEntry = ListEntry->Flink ) { Credential = CONTAINING_RECORD( ListEntry, DIGEST_CREDENTIAL, Next ); // // Found a match ... reference this Credential // (if the Credential is being removed, we would increment // and then decrement the reference, so don't bother doing // either - since they cancel each other out). // if (( Credential == (PDIGEST_CREDENTIAL) CredentialHandle)) { // Make sure we have the privilege of accessing // this handle if (!ClientInfo.HasTcbPrivilege && (Credential->ClientProcessID != ClientInfo.ProcessID) ) { DebugLog((DEB_ERROR, "CredHandlerHandleToPtr: ProcessIDs are different. Access forbidden.\n")); break; } if (!DereferenceCredential) { lReferences = InterlockedIncrement(&Credential->lReferences); DebugLog((DEB_TRACE, "CredHandlerHandleToPtr: Incremented ReferenceCount %ld\n", lReferences)); } else { DebugLog((DEB_TRACE, "CredHandlerHandleToPtr: Dereferencing credential\n" )); ASSERT((ulDereferenceCount > 0)); // Note: Subtract one off of the deref count, this avoids an extra interlock operation // After exit, SpFreeCredentialsHandle will call CredHandlerRelease ulDereferenceCount--; if( ulDereferenceCount == 1 ) { DebugLog((DEB_TRACE, "CredHandlerHandleToPtr: Dereferencing by one count\n" )); lReferences = InterlockedDecrement( &Credential->lReferences ); ASSERT( (lReferences > 0) ); } else if( ulDereferenceCount > 1 ) { // // there is no equivalent to InterlockedSubtract. // so, turn it into an Add with some signed magic. // LONG lDecrementToIncrement = 0 - ulDereferenceCount; DebugLog((DEB_TRACE, "CredHandlerHandleToPtr: Dereferencing by %lu count\n", ulDereferenceCount )); lReferences = InterlockedExchangeAdd( &Credential->lReferences, lDecrementToIncrement ); lReferences += lDecrementToIncrement; ASSERT( (lReferences > 0) ); } } // Found the Credential *UserCredential = Credential ; goto CleanUp; } } // // No match found // DebugLog((DEB_WARN, "CredHandlerHandleToCredential: Tried to reference unknown Credential 0x%lx\n", CredentialHandle )); Status = STATUS_INVALID_HANDLE; CleanUp: RtlLeaveCriticalSection( &l_CredentialCritSect ); DebugLog((DEB_TRACE_FUNC, "CredHandlerHandleToPtr: Leaving Credential 0x%x\n", CredentialHandle)); return(Status); } // Locate a Credential based on a LogonId, ProcessID // For either the Logon list or the Credential list NTSTATUS CredHandlerLocatePtr( IN PLUID pLogonId, IN ULONG CredentialUseFlags, OUT PDIGEST_CREDENTIAL * UserCredential ) { PLIST_ENTRY ListEntry = NULL; PDIGEST_CREDENTIAL Credential = NULL; NTSTATUS Status = STATUS_SUCCESS; // SECPKG_CLIENT_INFO ClientInfo; SECPKG_CALL_INFO CallInfo; LONG lReferences = 0; DebugLog((DEB_TRACE_FUNC, "CredHandlerLocatePtr: Entering\n")); *UserCredential = NULL ; // If we do not have a LogonId if (!pLogonId) { return(STATUS_INVALID_HANDLE); } // // Match both flags // // CredentialUseFlags |= CredentialFlags; if (!g_LsaFunctions->GetCallInfo(&CallInfo)) { DebugLog((DEB_ERROR,"CredHandlerLocatePtr: Failed to get call info\n")); return(STATUS_INVALID_HANDLE); // Really this is another error } // // Acquire exclusive access to the Credential list // RtlEnterCriticalSection( &l_CredentialCritSect ); // // Now walk the list of Credentials looking for a match. // for ( ListEntry = l_CredentialList.Flink; ListEntry != &l_CredentialList; ListEntry = ListEntry->Flink ) { Credential = CONTAINING_RECORD( ListEntry, DIGEST_CREDENTIAL, Next ); // // Found a match ... reference this Credential // (if the Credential is being removed, we would increment // and then decrement the reference, so don't bother doing // either - since they cancel each other out). // // If this is a session credential then check for appropriate flags (like inbound or outbound) if ((Credential->CredentialUseFlags & DIGEST_CRED_MATCH_FLAGS) != CredentialUseFlags) { continue; } if (RtlEqualLuid(&(Credential->LogonId), pLogonId) && (Credential->ClientProcessID == CallInfo.ProcessId)) { lReferences = InterlockedIncrement(&Credential->lReferences); DebugLog((DEB_TRACE, "CredHandlerLocatePtr: ReferenceCount %ld\n", lReferences)); // Found the Credential *UserCredential = Credential ; goto CleanUp; } } // // No match found // DebugLog((DEB_WARN, "CredHandlerLocatePtr: Tried to reference unknown LogonId (%x:%lx)\n", pLogonId->HighPart, pLogonId->LowPart )); Status = STATUS_INVALID_HANDLE; CleanUp: RtlLeaveCriticalSection( &l_CredentialCritSect ); DebugLog((DEB_TRACE_FUNC, "CredHandlerLocatePtr: Leaving Status 0x%x\n", Status)); return(Status); } //+-------------------------------------------------------------------- // // Function: CredHandlerRelease // // Synopsis: Releases the Credential by decreasing reference counter // if Credential reference count drops to zero, Credential is deleted // // Arguments: pCredential - pointer to credential to de-reference // // Returns: NTSTATUS // // Notes: Called by ASC. Since multiple threads can have a credential // checked out, simply decrease the reference counter on release. // //--------------------------------------------------------------------- NTSTATUS CredHandlerRelease( PDIGEST_CREDENTIAL pCredential) { NTSTATUS Status = STATUS_SUCCESS; LONG lReferences = 0; DebugLog((DEB_TRACE_FUNC, "CredHandlerRelease: Entering for Credential 0x%0x\n", pCredential)); lReferences = InterlockedDecrement(&pCredential->lReferences); DebugLog((DEB_TRACE, "CredHandlerRelease: ReferenceCount %ld\n", lReferences)); ASSERT( lReferences >= 0 ); // // If the count has dropped to zero, then free all alloced stuff // Care must be taken since Cred is still in linked list - need to grab critsec // and then test again if zero since another thread might have taken a ref to cred // if (lReferences == 0) { RtlEnterCriticalSection( &l_CredentialCritSect ); // Check to make sure no one took a reference since InterlockDecrement if (pCredential->lReferences) { DebugLog((DEB_TRACE, "CredHandlerRelease: Another thread took a reference. No action taken\n")); } else { // Safe to remove from list and delete // Check if added into linked list if (!pCredential->Unlinked) { RemoveEntryList( &pCredential->Next ); DebugLog((DEB_TRACE, "CredHandlerRelease: Unlinked Credential 0x%x\n", pCredential)); } DebugLog((DEB_TRACE, "CredHandlerRelease: Deleting Credential 0x%x\n", pCredential)); Status = CredentialFree(pCredential); } RtlLeaveCriticalSection( &l_CredentialCritSect ); } DebugLog((DEB_TRACE_FUNC, "CredHandlerRelease: Leaving Status 0x%x\n", Status)); return(Status); } // Helper functions for processing fields within the credentials //+-------------------------------------------------------------------- // // Function: CredHandlerPasswdSet // // Synopsis: Set the unicode string password in the credential // // Arguments: pCredential - pointer to credential to use // pustrPasswd - pointer to new password // // Returns: NTSTATUS // // Notes: might want to use record locking in the future instead // of an update flag on credentials // //--------------------------------------------------------------------- NTSTATUS CredHandlerPasswdSet( IN OUT PDIGEST_CREDENTIAL pCredential, IN PUNICODE_STRING pustrPasswd) { NTSTATUS Status = STATUS_SUCCESS; // Protect writing the info into the credential RtlEnterCriticalSection( &l_CredentialCritSect ); if (pCredential->ustrPassword.Buffer) { SecureZeroMemory((pCredential->ustrPassword).Buffer, (pCredential->ustrPassword).MaximumLength); UnicodeStringFree(&(pCredential->ustrPassword)); } Status = UnicodeStringDuplicatePassword(&(pCredential->ustrPassword),pustrPasswd); RtlLeaveCriticalSection( &l_CredentialCritSect ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "CredHandlerPasswdSet: Error in setting Credential password, status 0x%0x\n", Status )); } return(Status); } //+-------------------------------------------------------------------- // // Function: CredHandlerPasswdGet // // Synopsis: Get the unicode string password in the credential // Locking is only necessary for the logon creds and not the session creds // but it is just as well to keep it unifom // // Arguments: pCredential - pointer to credential to use // pustrPasswd - pointer to destination copy of password // // Returns: NTSTATUS // // Notes: // //--------------------------------------------------------------------- NTSTATUS CredHandlerPasswdGet( IN PDIGEST_CREDENTIAL pCredential, OUT PUNICODE_STRING pustrPasswd) { NTSTATUS Status = STATUS_SUCCESS; // Protect reading/writing the credential password RtlEnterCriticalSection( &l_CredentialCritSect ); if (pustrPasswd->Buffer) { SecureZeroMemory(pustrPasswd->Buffer, pustrPasswd->MaximumLength); UnicodeStringFree(pustrPasswd); } Status = UnicodeStringDuplicatePassword(pustrPasswd, &(pCredential->ustrPassword)); RtlLeaveCriticalSection( &l_CredentialCritSect ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "CredHandlerPasswdSet: Error in setting Credential password, status 0x%0x\n", Status )); } return(Status); }