//+----------------------------------------------------------------------- // // 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 #include #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); }