|
|
//+-----------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 1992 - 1997
//
// File: sidcache.cxx
//
// Contents: routines to cache username->sid translations to speed up
// logon performance
//
//
// History: 27-May-1998 MikeSw Created
//
//------------------------------------------------------------------------
#include <kerb.hxx>
#include <kerbp.h>
#define KERB_LOGON_SID_CACHE_KEY L"SidCache"
#define KERB_LOGON_SID_CACHE_ENTRIES L"CacheEntries"
#define KERB_LOGON_SID_CACHE_ENTRY_NAME L"Entry%d"
#define KERB_MACHINE_SID_CACHE_NAME L"MachineSid"
#define KERB_LOGON_SID_CACHE_MAX_ENTRIES 1000
#define KERB_LOGON_SID_CACHE_DEFAULT_ENTRIES 10
#define KERB_LOGON_SID_CACHE_ENTRY_NAME_SIZE (sizeof(L"Entry") * 4*sizeof(WCHAR))
#define KERB_LOGON_SID_CACHE_VERSION 0
KERBEROS_LIST KerbSidCache; HKEY KerbSidCacheKey; ULONG KerbSidCacheMaxEntries = KERB_LOGON_SID_CACHE_DEFAULT_ENTRIES; ULONG KerbSidCacheEntries;
#define VALIDATE_POINTER(Ptr,Size,Base,Bound,NewBase,Type) \
if (((PUCHAR)(Ptr) < (PUCHAR)(Base)) || (((PUCHAR)(Ptr) + (Size)) > (PUCHAR)(Bound))) \ { \ Status = STATUS_INVALID_PARAMETER; \ goto Cleanup; \ } \ else \ { \ (Ptr) = (Type) ((PUCHAR) (Ptr) - (ULONG_PTR) Base + (ULONG_PTR) NewBase); \ } \
//+-------------------------------------------------------------------------
//
// Function: KerbVerifyUnpackAndLinkSidCacheEntry
//
// Synopsis: Unmarshalls the entry & verifies that the pointers all
// match up within the buffer. If this is the case, it
// inserts the entry into the list & zeroes out the pointer
// so the caller won't free it.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbVerifyUnpackAndLinkSidCacheEntry( IN PKERB_SID_CACHE_ENTRY * SidCacheEntry, IN ULONG CacheEntrySize ) { NTSTATUS Status = STATUS_SUCCESS; PKERB_SID_CACHE_ENTRY CacheEntry = *SidCacheEntry; PUCHAR Bound;
//
// First check the base structure size
//
if (CacheEntrySize < sizeof(KERB_SID_CACHE_ENTRY)) { Status = STATUS_INVALID_PARAMETER; goto Cleanup; }
//
// Now check the version
//
if (CacheEntry->Version != KERB_LOGON_SID_CACHE_VERSION) { Status = STATUS_INVALID_PARAMETER; goto Cleanup; }
if (CacheEntry->Size != CacheEntrySize) { Status = STATUS_INVALID_PARAMETER; goto Cleanup; } //
// Now check all the pointers
//
Bound = (PUCHAR) CacheEntry->Base + CacheEntrySize;
VALIDATE_POINTER( CacheEntry->Sid, RtlLengthRequiredSid(5), // space for 5 sub authorities
CacheEntry->Base, Bound, CacheEntry, PSID );
VALIDATE_POINTER( CacheEntry->LogonUserName.Buffer, CacheEntry->LogonUserName.MaximumLength, CacheEntry->Base, Bound, CacheEntry, PWSTR );
VALIDATE_POINTER( CacheEntry->LogonDomainName.Buffer, CacheEntry->LogonDomainName.MaximumLength, CacheEntry->Base, Bound, CacheEntry, PWSTR );
VALIDATE_POINTER( CacheEntry->LogonRealm.Buffer, CacheEntry->LogonRealm.MaximumLength, CacheEntry->Base, Bound, CacheEntry, PWSTR );
KerbInitializeListEntry( &CacheEntry->Next );
//
// This inserts at the head, not the tail
//
KerbInsertListEntryTail( &CacheEntry->Next, &KerbSidCache );
*SidCacheEntry = NULL;
Cleanup:
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbInitializeLogonSidCache
//
// Synopsis: Initializes the list of cached logon sids
//
// Effects: Reads data from the registry
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbInitializeLogonSidCache( VOID ) { NTSTATUS Status = STATUS_SUCCESS; DWORD RegErr = ERROR_SUCCESS; DWORD Disposition = 0; PKERB_SID_CACHE_ENTRY NextEntry = NULL; ULONG NextEntrySize = 0; ULONG ValueType; ULONG Index; HKEY KerbParamKey = NULL; WCHAR EntryName[KERB_LOGON_SID_CACHE_ENTRY_NAME_SIZE];
//
// Initialize the list
//
Status = KerbInitializeList(&KerbSidCache);
if (!NT_SUCCESS(Status)) { goto Cleanup; }
//
// Create the sid cache key
//
RegErr = RegCreateKeyEx( HKEY_LOCAL_MACHINE, KERB_PARAMETER_PATH, 0, NULL, 0, KEY_ALL_ACCESS, 0, &KerbParamKey, &Disposition ); if (RegErr != ERROR_SUCCESS) { DebugLog((DEB_ERROR,"Failed to create %ws key: %d\n",KERB_PARAMETER_PATH, RegErr)); Status = STATUS_UNSUCCESSFUL; goto Cleanup; }
RegErr = RegCreateKeyEx( KerbParamKey, KERB_LOGON_SID_CACHE_KEY, 0, // reserved
NULL, // no class
0, // no options
KEY_ALL_ACCESS, NULL, // no security attributes
&KerbSidCacheKey, &Disposition ); if (RegErr != ERROR_SUCCESS) { DebugLog((DEB_ERROR,"Failed to create key %ws: %d\n",KERB_LOGON_SID_CACHE_KEY, RegErr)); Status = STATUS_UNSUCCESSFUL; goto Cleanup; }
//
// Read out the size of the cache
//
NextEntrySize = sizeof(ULONG);
RegErr = RegQueryValueEx( KerbSidCacheKey, KERB_LOGON_SID_CACHE_ENTRIES, NULL, // reserved,
&ValueType, (PUCHAR) &KerbSidCacheMaxEntries, &NextEntrySize ); if (RegErr == ERROR_SUCCESS) { //
// Make sure the value is within the range & is of the correc type
//
if ( (ValueType != REG_DWORD) || (KerbSidCacheMaxEntries > KERB_LOGON_SID_CACHE_MAX_ENTRIES) || (KerbSidCacheMaxEntries == 0) )
{ KerbSidCacheMaxEntries = KERB_LOGON_SID_CACHE_DEFAULT_ENTRIES; } }
//
// Now read in all the entries. Loop through up to the max number of
// entries, reading the entry, and inserting at the tail of the list.
//
for (Index = 0; Index < KerbSidCacheMaxEntries ; Index++ ) { swprintf(EntryName,KERB_LOGON_SID_CACHE_ENTRY_NAME, Index);
//
// Query the size of the entry
//
NextEntrySize = NULL; RegErr = RegQueryValueEx( KerbSidCacheKey, EntryName, NULL, &ValueType, (PUCHAR) NextEntry, &NextEntrySize ); if ((RegErr == ERROR_SUCCESS) && (ValueType == REG_BINARY)) { //
// Allocate space for the entry and re-query to get the real
// value.
//
NextEntry = (PKERB_SID_CACHE_ENTRY) KerbAllocate(NextEntrySize); if (NextEntry == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
RegErr = RegQueryValueEx( KerbSidCacheKey, EntryName, NULL, &ValueType, (PUCHAR) NextEntry, &NextEntrySize ); if (RegErr != ERROR_SUCCESS) { DebugLog((DEB_ERROR,"Failed to query for sid cache value: %d\n", RegErr )); Status = STATUS_UNSUCCESSFUL; goto Cleanup; }
//
// Call a helper routine to unpack,verify, and insert
Status = KerbVerifyUnpackAndLinkSidCacheEntry( &NextEntry, NextEntrySize ); //
// If the entry was invalid, remove it now
//
if (!NT_SUCCESS(Status)) { (VOID) RegDeleteValue( KerbSidCacheKey, EntryName ); Status = STATUS_SUCCESS; }
if (NextEntry != NULL) { KerbFree(NextEntry); NextEntry = NULL; } NextEntrySize = 0;
} }
Cleanup: if (KerbParamKey != NULL) { RegCloseKey(KerbParamKey); } if (NextEntry != NULL) { KerbFree(NextEntry); } if (!NT_SUCCESS(Status)) { if (KerbSidCacheKey != NULL) { RegCloseKey(KerbSidCacheKey); KerbSidCacheKey = NULL; } } return(Status); }
//+-------------------------------------------------------------------------
//
// Function: KerbScavengeSidCache
//
// Synopsis: removes any stale entries from the sid cache to make it fit
// within bounds
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID KerbScavengeSidCache( VOID ) { PKERB_SID_CACHE_ENTRY CacheEntry; while (KerbSidCacheEntries > KerbSidCacheMaxEntries) { //
// Pickup the last entry and remove it
//
CacheEntry = CONTAINING_RECORD(KerbSidCache.List.Blink, KERB_SID_CACHE_ENTRY, Next.Next);
KerbReferenceListEntry( &KerbSidCache, &CacheEntry->Next, TRUE ); KerbDereferenceSidCacheEntry( CacheEntry ); KerbSidCacheEntries--;
}
}
//+-------------------------------------------------------------------------
//
// Function: KerbWriteSidCache
//
// Synopsis: Writes the sid cache back to the registry
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbWriteSidCache( VOID ) { ULONG Index = 0; WCHAR EntryName[KERB_LOGON_SID_CACHE_ENTRY_NAME_SIZE]; PLIST_ENTRY ListEntry; PKERB_SID_CACHE_ENTRY CacheEntry;
for (ListEntry = KerbSidCache.List.Flink ; ListEntry != &KerbSidCache.List ; ListEntry = ListEntry->Flink ) { CacheEntry = CONTAINING_RECORD(ListEntry, KERB_SID_CACHE_ENTRY, Next.Next); swprintf(EntryName,KERB_LOGON_SID_CACHE_ENTRY_NAME, Index++);
(VOID) RegSetValueEx( KerbSidCacheKey, EntryName, 0, // reserved
REG_BINARY, (PUCHAR) CacheEntry, CacheEntry->Size ); }
return(STATUS_SUCCESS);
}
//+-------------------------------------------------------------------------
//
// Function: KerbPromoteSidCacheEntry
//
// Synopsis: Moves a cache entry to the front of the list
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID KerbPromoteSidCacheEntry( IN PKERB_SID_CACHE_ENTRY CacheEntry ) { KerbLockList(&KerbSidCache); RemoveEntryList(&CacheEntry->Next.Next); InsertHeadList(&KerbSidCache.List, &CacheEntry->Next.Next); KerbUnlockList(&KerbSidCache); }
//+-------------------------------------------------------------------------
//
// Function: KerbLocateLogonSidCacheEntry
//
// Synopsis: Locates a logon sid cache entry by user name and domain name
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns: returns a referenced cache entry, if available
//
// Notes:
//
//
//--------------------------------------------------------------------------
PKERB_SID_CACHE_ENTRY KerbLocateLogonSidCacheEntry( IN PUNICODE_STRING LogonUserName, IN PUNICODE_STRING LogonDomainName ) { PKERB_SID_CACHE_ENTRY CacheEntry = NULL; PLIST_ENTRY ListEntry;
if (!KerbGlobalUseSidCache) { return(NULL); } KerbLockList(&KerbSidCache);
for (ListEntry = KerbSidCache.List.Flink ; ListEntry != &KerbSidCache.List ; ListEntry = ListEntry->Flink ) { CacheEntry = CONTAINING_RECORD(ListEntry, KERB_SID_CACHE_ENTRY, Next.Next);
if (RtlEqualUnicodeString( &CacheEntry->LogonUserName, LogonUserName, TRUE ) && RtlEqualUnicodeString( &CacheEntry->LogonDomainName, LogonDomainName, TRUE ) ) { //
// We found it
//
KerbReferenceListEntry( &KerbSidCache, &CacheEntry->Next, FALSE // don't remove
);
break; } CacheEntry = NULL; }
KerbUnlockList(&KerbSidCache); return(CacheEntry); }
//+-------------------------------------------------------------------------
//
// Function: KerbDereferenceSidCacheEntry
//
// Synopsis: Dereferences the entry, possibly freeing it
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID KerbDereferenceSidCacheEntry( IN PKERB_SID_CACHE_ENTRY CacheEntry ) { KerbLockList(&KerbSidCache); if (KerbDereferenceListEntry( &CacheEntry->Next, &KerbSidCache )) { KerbFree(CacheEntry); } KerbUnlockList(&KerbSidCache); }
//+-------------------------------------------------------------------------
//
// Function: KerbCacheLogonSid
//
// Synopsis: Caches a username/domainname & Sid combination for logon
//
// Effects: caches the user name/domainname/sid in the registry
//
// Arguments: LogonUserName - user name supplied to LogonUser
// LogonDomainName - domain name supplied to LogonUser
// LogonRealm - Realm actually containing the account
// UserSid - Sid of user who just logged on
//
// Requires:
//
// Returns: none - this is just a cache
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID KerbCacheLogonSid( IN PUNICODE_STRING LogonUserName, IN PUNICODE_STRING LogonDomainName, IN PUNICODE_STRING LogonRealm, IN PSID UserSid ) { NTSTATUS Status = STATUS_SUCCESS; PKERB_SID_CACHE_ENTRY CacheEntry; ULONG CacheEntrySize = 0; PUCHAR Where;
if (!KerbGlobalUseSidCache) { return; }
CacheEntry = KerbLocateLogonSidCacheEntry( LogonUserName, LogonDomainName );
//
// If we found the entry & it is the same as this one, move it up in
// the list
//
if (CacheEntry != NULL) { if (RtlEqualUnicodeString( &CacheEntry->LogonRealm, LogonRealm, TRUE ) && RtlEqualSid( CacheEntry->Sid, UserSid ) )
{ KerbPromoteSidCacheEntry( CacheEntry ); goto Cleanup; } else { //
// Remove it from the list
//
KerbLockList(&KerbSidCache); KerbReferenceListEntry( &KerbSidCache, &CacheEntry->Next, TRUE // remove
);
KerbDereferenceSidCacheEntry( CacheEntry ); KerbDereferenceSidCacheEntry( CacheEntry ); KerbUnlockList(&KerbSidCache); CacheEntry = NULL; } }
//
// Now build a new entry
//
CacheEntrySize = sizeof(KERB_SID_CACHE_ENTRY) + RtlLengthSid(UserSid) + LogonUserName->Length + sizeof(WCHAR) + LogonDomainName->Length + sizeof(WCHAR) + LogonRealm->Length + sizeof(WCHAR);
CacheEntry = (PKERB_SID_CACHE_ENTRY) KerbAllocate(CacheEntrySize); if (CacheEntry == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
//
// Fill in all the fixed fields.
//
KerbInitializeListEntry( &CacheEntry->Next ); CacheEntry->Base = (ULONG_PTR) CacheEntry; CacheEntry->Version = KERB_LOGON_SID_CACHE_VERSION; CacheEntry->Size = CacheEntrySize; Where = (PUCHAR) (CacheEntry + 1);
//
// Copy in the sid
//
CacheEntry->Sid = (PSID) Where; Where += RtlLengthSid( UserSid );
RtlCopyMemory( CacheEntry->Sid, UserSid, Where - (PUCHAR) CacheEntry->Sid );
//
// Put the various strings
//
KerbPutString( LogonUserName, &CacheEntry->LogonUserName, 0, // no offset
&Where );
KerbPutString( LogonDomainName, &CacheEntry->LogonDomainName, 0, // no offset
&Where );
KerbPutString( LogonRealm, &CacheEntry->LogonRealm, 0, // no offset
&Where );
//
// Insert it into the list
//
KerbLockList( &KerbSidCache ); KerbInsertListEntry( &CacheEntry->Next, &KerbSidCache ); //
// Dereference it because we aren't returning the cache entry
//
KerbDereferenceListEntry( &CacheEntry->Next, &KerbSidCache );
KerbSidCacheEntries++;
//
// Remove any extra entries
//
KerbScavengeSidCache();
KerbUnlockList ( &KerbSidCache );
Cleanup: if (NT_SUCCESS(Status)) { KerbWriteSidCache(); }
}
//+-------------------------------------------------------------------------
//
// Function: KerbWriteMachineSid
//
// Synopsis: Writes the machine account sid to the registry
//
// Effects:
//
// Arguments: MachineSid - If present, is stored. If not present,
// is deleted from registry
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID KerbWriteMachineSid( IN OPTIONAL PSID MachineSid ) { if (ARGUMENT_PRESENT(MachineSid)) { (VOID) RegSetValueEx( KerbSidCacheKey, KERB_MACHINE_SID_CACHE_NAME, 0, // reserved
REG_BINARY, (PUCHAR) MachineSid, RtlLengthSid(MachineSid) ); } else { (VOID) RegDeleteValue( KerbSidCacheKey, KERB_MACHINE_SID_CACHE_NAME ); } }
//+-------------------------------------------------------------------------
//
// Function: Reads the machine sid from the registry
//
// Synopsis:
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbGetMachineSid( OUT PSID * MachineSid ) { BYTE Buffer[sizeof(SID) + SID_MAX_SUB_AUTHORITIES * sizeof(ULONG)]; ULONG BufferSize = sizeof(Buffer); PSID Sid = (PSID) Buffer; DWORD RegErr; NTSTATUS Status = STATUS_SUCCESS; ULONG ValueType;
*MachineSid = NULL; RegErr = RegQueryValueEx( KerbSidCacheKey, KERB_MACHINE_SID_CACHE_NAME, NULL, &ValueType, Buffer, &BufferSize ); if (RegErr != ERROR_SUCCESS) { DebugLog((DEB_ERROR,"Failed to query for machine sid value: %d\n", RegErr )); Status = STATUS_OBJECT_NAME_NOT_FOUND; goto Cleanup; }
//
// If it isn't valid, delete it now
//
if (!RtlValidSid(Sid)) { (VOID) RegDeleteValue( KerbSidCacheKey, KERB_MACHINE_SID_CACHE_NAME ); }
*MachineSid = (PSID) KerbAllocate(RtlLengthSid(Buffer));
if (*MachineSid == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } RtlCopyMemory( *MachineSid, Sid, RtlLengthSid(Sid) );
Cleanup: return(Status);
}
|