windows-nt/Source/XPSP1/NT/ds/security/protocols/kerberos/client2/sidcache.cxx
2020-09-26 16:20:57 +08:00

955 lines
21 KiB
C++

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