reactos/dll/win32/aclui/sidcache.c
2021-07-23 22:03:48 +02:00

972 lines
26 KiB
C

/*
* ReactOS Access Control List Editor
* Copyright (C) 2004-2005 ReactOS Team
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* PROJECT: ReactOS Access Control List Editor
* FILE: lib/aclui/sidcache.c
* PURPOSE: Access Control List Editor
* PROGRAMMER: Thomas Weidenmueller <w3seek@reactos.com>
*
* UPDATE HISTORY:
* 12/10/2005 Created
*/
#include "precomp.h"
#include <ntsecapi.h>
#define NDEBUG
#include <debug.h>
#define HandleToScm(Handle) (PSIDCACHEMGR)(Handle)
#define ScmToHandle(Scm) (HANDLE)(Scm)
typedef struct _SIDCACHEMGR
{
volatile LONG RefCount;
LSA_HANDLE LsaHandle;
CRITICAL_SECTION Lock;
LIST_ENTRY QueueListHead;
struct _SIDQUEUEENTRY *QueueLookingUp;
LIST_ENTRY CacheListHead;
HANDLE Heap;
HANDLE LookupEvent;
HANDLE LookupThread;
WCHAR SystemName[1];
} SIDCACHEMGR, *PSIDCACHEMGR;
typedef struct _SIDCACHECALLBACKINFO
{
PSIDREQCOMPLETIONPROC CompletionProc;
PVOID Context;
} SIDCACHECALLBACKINFO, *PSIDCACHECALLBACKINFO;
typedef struct _SIDQUEUEENTRY
{
LIST_ENTRY ListEntry;
ULONG CallbackCount;
PSIDCACHECALLBACKINFO Callbacks;
/* the SID is appended to this structure */
} SIDQUEUEENTRY, *PSIDQUEUEENTRY;
typedef struct _SIDCACHEENTRY
{
LIST_ENTRY ListEntry;
SID_NAME_USE SidNameUse;
PWSTR AccountName;
PWSTR DomainName;
/* the SID and strings are appended to this structure */
} SIDCACHEENTRY, *PSIDCACHEENTRY;
static VOID
FreeQueueEntry(IN PSIDCACHEMGR scm,
IN PSIDQUEUEENTRY QueueEntry)
{
if (QueueEntry->ListEntry.Flink != NULL)
{
RemoveEntryList(&QueueEntry->ListEntry);
}
HeapFree(scm->Heap,
0,
QueueEntry->Callbacks);
HeapFree(scm->Heap,
0,
QueueEntry);
}
static VOID
FreeCacheEntry(IN PSIDCACHEMGR scm,
IN PSIDCACHEENTRY CacheEntry)
{
RemoveEntryList(&CacheEntry->ListEntry);
HeapFree(scm->Heap,
0,
CacheEntry);
}
static VOID
CleanupSidCacheMgr(IN PSIDCACHEMGR scm)
{
LsaClose(scm->LsaHandle);
CloseHandle(scm->LookupEvent);
CloseHandle(scm->LookupThread);
/* delete the queue */
while (!IsListEmpty(&scm->QueueListHead))
{
PSIDQUEUEENTRY QueueEntry;
QueueEntry = CONTAINING_RECORD(scm->QueueListHead.Flink,
SIDQUEUEENTRY,
ListEntry);
FreeQueueEntry(scm,
QueueEntry);
}
/* delete the cache */
while (!IsListEmpty(&scm->CacheListHead))
{
PSIDCACHEENTRY CacheEntry;
CacheEntry = CONTAINING_RECORD(scm->CacheListHead.Flink,
SIDCACHEENTRY,
ListEntry);
FreeCacheEntry(scm,
CacheEntry);
}
DeleteCriticalSection(&scm->Lock);
}
static PSIDCACHEMGR
ReferenceSidCacheMgr(IN HANDLE SidCacheMgr)
{
PSIDCACHEMGR scm = HandleToScm(SidCacheMgr);
if (InterlockedIncrement(&scm->RefCount) != 1)
{
return scm;
}
return NULL;
}
static VOID
DereferenceSidCacheMgr(IN PSIDCACHEMGR scm)
{
if (InterlockedDecrement(&scm->RefCount) == 0)
{
/* Signal the lookup thread so it can terminate */
SetEvent(scm->LookupEvent);
}
}
static BOOL
OpenLSAPolicyHandle(IN LPWSTR SystemName,
IN ACCESS_MASK DesiredAccess,
OUT PLSA_HANDLE PolicyHandle)
{
LSA_OBJECT_ATTRIBUTES LsaObjectAttributes = {0};
LSA_UNICODE_STRING LsaSystemName, *psn;
NTSTATUS Status;
SIZE_T NameLength;
if (SystemName != NULL && SystemName[0] != L'\0')
{
NameLength = wcslen(SystemName);
if (NameLength > UNICODE_STRING_MAX_CHARS)
{
return FALSE;
}
LsaSystemName.Buffer = SystemName;
LsaSystemName.Length = NameLength * sizeof(WCHAR);
LsaSystemName.MaximumLength = LsaSystemName.Length + sizeof(WCHAR);
psn = &LsaSystemName;
}
else
{
psn = NULL;
}
Status = LsaOpenPolicy(psn,
&LsaObjectAttributes,
DesiredAccess,
PolicyHandle);
if (!NT_SUCCESS(Status))
{
SetLastError(LsaNtStatusToWinError(Status));
return FALSE;
}
return TRUE;
}
static BOOL
LookupSidInformation(IN PSIDCACHEMGR scm,
IN PSID pSid,
OUT PSIDREQRESULT *ReqResult)
{
PLSA_REFERENCED_DOMAIN_LIST ReferencedDomain;
PLSA_TRANSLATED_NAME Names;
PLSA_TRUST_INFORMATION Domain;
PLSA_UNICODE_STRING DomainName;
SID_NAME_USE SidNameUse = SidTypeUnknown;
PPOLICY_ACCOUNT_DOMAIN_INFO PolicyAccountDomainInfo = NULL;
NTSTATUS Status;
DWORD AccountNameSize, DomainNameSize = 0;
PSIDREQRESULT ReqRet = NULL;
BOOL Ret = FALSE;
Status = LsaLookupSids(scm->LsaHandle,
1,
&pSid,
&ReferencedDomain,
&Names);
if (NT_SUCCESS(Status))
{
SidNameUse = Names->Use;
if (ReferencedDomain != NULL &&
Names->DomainIndex >= 0)
{
Domain = &ReferencedDomain->Domains[Names->DomainIndex];
DomainName = &Domain->Name;
}
else
{
Domain = NULL;
DomainName = NULL;
}
switch (SidNameUse)
{
case SidTypeAlias:
{
if (Domain != NULL)
{
/* query the domain name for BUILTIN accounts */
Status = LsaQueryInformationPolicy(scm->LsaHandle,
PolicyAccountDomainInformation,
(PVOID*)&PolicyAccountDomainInfo);
if (NT_SUCCESS(Status))
{
DomainName = &PolicyAccountDomainInfo->DomainName;
/* make the user believe this is a group */
SidNameUse = (PolicyAccountDomainInfo != NULL ? SidTypeGroup : SidTypeUser);
}
}
break;
}
default:
{
DPRINT("Unhandled SID type: 0x%x\n", Names->Use);
break;
}
}
AccountNameSize = Names->Name.Length;
if (DomainName != NULL)
{
DomainNameSize = DomainName->Length;
}
ReqRet = HeapAlloc(scm->Heap,
0,
sizeof(SIDREQRESULT) +
(((AccountNameSize + DomainNameSize) + 2) * sizeof(WCHAR)));
if (ReqRet != NULL)
{
ReqRet->RefCount = 1;
ReqRet->AccountName = (LPWSTR)(ReqRet + 1);
ReqRet->DomainName = ReqRet->AccountName + (AccountNameSize / sizeof(WCHAR)) + 1;
CopyMemory(ReqRet->AccountName,
Names->Name.Buffer,
Names->Name.Length);
if (DomainName != NULL)
{
CopyMemory(ReqRet->DomainName,
DomainName->Buffer,
DomainName->Length);
}
ReqRet->AccountName[AccountNameSize / sizeof(WCHAR)] = L'\0';
ReqRet->DomainName[DomainNameSize / sizeof(WCHAR)] = L'\0';
ReqRet->SidNameUse = SidNameUse;
}
if (PolicyAccountDomainInfo != NULL)
{
LsaFreeMemory(PolicyAccountDomainInfo);
}
LsaFreeMemory(ReferencedDomain);
LsaFreeMemory(Names);
Ret = TRUE;
}
else if (Status == STATUS_NONE_MAPPED)
{
Ret = TRUE;
}
if (Ret)
{
*ReqResult = ReqRet;
}
return Ret;
}
static BOOL
FindSidInCache(IN PSIDCACHEMGR scm,
IN PSID pSid,
OUT PSIDREQRESULT *ReqResult)
{
PSIDCACHEENTRY CacheEntry;
PLIST_ENTRY CurrentEntry;
PSIDREQRESULT ReqRes;
BOOL Ret = FALSE;
/* NOTE: assumes the lists are locked! */
for (CurrentEntry = scm->CacheListHead.Flink;
CurrentEntry != &scm->CacheListHead;
CurrentEntry = CurrentEntry->Flink)
{
CacheEntry = CONTAINING_RECORD(CurrentEntry,
SIDCACHEENTRY,
ListEntry);
if (EqualSid(pSid,
(PSID)(CacheEntry + 1)))
{
SIZE_T ReqResultSize;
ULONG AccountNameLen, DomainNameLen;
Ret = TRUE;
AccountNameLen = wcslen(CacheEntry->AccountName);
DomainNameLen = wcslen(CacheEntry->DomainName);
ReqResultSize = sizeof(SIDREQRESULT) +
(((AccountNameLen + 1) +
(DomainNameLen + 1)) * sizeof(WCHAR));
ReqRes = HeapAlloc(scm->Heap,
0,
ReqResultSize);
if (ReqRes != NULL)
{
PWSTR Buffer = (PWSTR)(ReqRes + 1);
ReqRes->RefCount = 1;
ReqRes->AccountName = Buffer;
wcscpy(ReqRes->AccountName,
CacheEntry->AccountName);
Buffer += AccountNameLen + 1;
ReqRes->DomainName = Buffer;
wcscpy(ReqRes->DomainName,
CacheEntry->DomainName);
}
/* return the result, even if we weren't unable to
allocate enough memory! */
*ReqResult = ReqRes;
break;
}
}
return Ret;
}
static VOID
CacheLookupResults(IN PSIDCACHEMGR scm,
IN PSID pSid,
IN PSIDREQRESULT ReqResult)
{
PSIDCACHEENTRY CacheEntry;
DWORD SidLen;
SIZE_T AccountNameLen = 0;
SIZE_T DomainNameLen = 0;
SIZE_T CacheEntrySize = sizeof(SIDCACHEENTRY);
/* NOTE: assumes the lists are locked! */
SidLen = GetLengthSid(pSid);
CacheEntrySize += SidLen;
AccountNameLen = wcslen(ReqResult->AccountName);
CacheEntrySize += (AccountNameLen + 1) * sizeof(WCHAR);
DomainNameLen = wcslen(ReqResult->DomainName);
CacheEntrySize += (wcslen(ReqResult->DomainName) + 1) * sizeof(WCHAR);
CacheEntry = HeapAlloc(scm->Heap,
0,
CacheEntrySize);
if (CacheEntry != NULL)
{
PWSTR lpBuf = (PWSTR)((ULONG_PTR)(CacheEntry + 1) + SidLen);
CacheEntry->SidNameUse = ReqResult->SidNameUse;
/* append the SID */
CopySid(SidLen,
(PSID)(CacheEntry + 1),
pSid);
/* append the strings */
CacheEntry->AccountName = lpBuf;
wcscpy(lpBuf,
ReqResult->AccountName);
lpBuf += AccountNameLen + 1;
CacheEntry->DomainName = lpBuf;
wcscpy(lpBuf,
ReqResult->DomainName);
lpBuf += DomainNameLen + 1;
/* add the entry to the cache list */
InsertTailList(&scm->CacheListHead,
&CacheEntry->ListEntry);
}
}
static DWORD WINAPI
LookupThreadProc(IN LPVOID lpParameter)
{
HMODULE hModule;
PSIDCACHEMGR scm = (PSIDCACHEMGR)lpParameter;
/* Reference the dll to avoid problems in case of accidental
FreeLibrary calls... */
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
(LPCWSTR)hDllInstance,
&hModule))
{
hModule = NULL;
}
while (scm->RefCount != 0)
{
PSIDQUEUEENTRY QueueEntry = NULL;
EnterCriticalSection(&scm->Lock);
/* get the first item of the queue */
if (scm->QueueListHead.Flink != &scm->QueueListHead)
{
QueueEntry = CONTAINING_RECORD(scm->QueueListHead.Flink,
SIDQUEUEENTRY,
ListEntry);
RemoveEntryList(&QueueEntry->ListEntry);
QueueEntry->ListEntry.Flink = NULL;
}
else
{
LeaveCriticalSection(&scm->Lock);
/* wait for the next asynchronous lookup queued */
WaitForSingleObject(scm->LookupEvent,
INFINITE);
continue;
}
scm->QueueLookingUp = QueueEntry;
LeaveCriticalSection(&scm->Lock);
if (QueueEntry != NULL)
{
PSIDREQRESULT ReqResult, FoundReqResult;
PSID pSid = (PSID)(QueueEntry + 1);
/* lookup the SID information */
if (!LookupSidInformation(scm,
pSid,
&ReqResult))
{
ReqResult = NULL;
}
EnterCriticalSection(&scm->Lock);
/* see if the SID was added to the cache in the meanwhile */
if (!FindSidInCache(scm,
pSid,
&FoundReqResult))
{
if (ReqResult != NULL)
{
/* cache the results */
CacheLookupResults(scm,
pSid,
ReqResult);
}
}
else
{
if (ReqResult != NULL)
{
/* free the information of our lookup and use the cached
information*/
DereferenceSidReqResult(scm,
ReqResult);
}
ReqResult = FoundReqResult;
}
/* notify the callers unless the lookup was cancelled */
if (scm->QueueLookingUp != NULL)
{
ULONG i = 0;
while (scm->QueueLookingUp != NULL &&
i < QueueEntry->CallbackCount)
{
PVOID Context;
PSIDREQCOMPLETIONPROC CompletionProc;
Context = QueueEntry->Callbacks[i].Context;
CompletionProc = QueueEntry->Callbacks[i].CompletionProc;
LeaveCriticalSection(&scm->Lock);
/* call the completion proc without holding the lock! */
CompletionProc(ScmToHandle(scm),
pSid,
ReqResult,
Context);
EnterCriticalSection(&scm->Lock);
i++;
}
scm->QueueLookingUp = NULL;
}
LeaveCriticalSection(&scm->Lock);
/* free the queue item */
FreeQueueEntry(scm,
QueueEntry);
}
}
CleanupSidCacheMgr(scm);
HeapFree(scm->Heap,
0,
scm);
if (hModule != NULL)
{
/* dereference the library and exit */
FreeLibraryAndExitThread(hModule,
0);
}
return 0;
}
HANDLE
CreateSidCacheMgr(IN HANDLE Heap,
IN LPCWSTR SystemName)
{
PSIDCACHEMGR scm;
if (SystemName == NULL)
SystemName = L"";
scm = HeapAlloc(Heap,
0,
FIELD_OFFSET(SIDCACHEMGR,
SystemName[wcslen(SystemName) + 1]));
if (scm != NULL)
{
/* zero the static part of the structure */
ZeroMemory(scm,
FIELD_OFFSET(SIDCACHEMGR,
SystemName));
scm->RefCount = 1;
scm->Heap = Heap;
wcscpy(scm->SystemName,
SystemName);
InitializeCriticalSection(&scm->Lock);
InitializeListHead(&scm->QueueListHead);
InitializeListHead(&scm->CacheListHead);
scm->LookupEvent = CreateEvent(NULL,
FALSE,
FALSE,
NULL);
if (scm->LookupEvent == NULL)
{
goto Cleanup;
}
if (!OpenLSAPolicyHandle(scm->SystemName,
POLICY_LOOKUP_NAMES | POLICY_VIEW_LOCAL_INFORMATION,
&scm->LsaHandle))
{
goto Cleanup;
}
scm->LookupThread = CreateThread(NULL,
0,
LookupThreadProc,
scm,
0,
NULL);
if (scm->LookupThread == NULL)
{
Cleanup:
if (scm->LookupEvent != NULL)
{
CloseHandle(scm->LookupEvent);
}
if (scm->LsaHandle != NULL)
{
LsaClose(scm->LsaHandle);
}
HeapFree(Heap,
0,
scm);
scm = NULL;
}
}
else
{
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
}
return (HANDLE)scm;
}
VOID
DestroySidCacheMgr(IN HANDLE SidCacheMgr)
{
PSIDCACHEMGR scm = HandleToScm(SidCacheMgr);
if (scm != NULL)
{
/* remove the keep-alive reference */
DereferenceSidCacheMgr(scm);
}
}
static BOOL
QueueSidLookup(IN PSIDCACHEMGR scm,
IN PSID pSid,
IN PSIDREQCOMPLETIONPROC CompletionProc,
IN PVOID Context)
{
PLIST_ENTRY CurrentEntry;
PSIDQUEUEENTRY QueueEntry, FoundEntry = NULL;
BOOL Ret = FALSE;
/* NOTE: assumes the lists are locked! */
if (scm->QueueLookingUp != NULL &&
EqualSid(pSid,
(PSID)(scm->QueueLookingUp + 1)))
{
FoundEntry = scm->QueueLookingUp;
}
else
{
for (CurrentEntry = scm->QueueListHead.Flink;
CurrentEntry != &scm->QueueListHead;
CurrentEntry = CurrentEntry->Flink)
{
QueueEntry = CONTAINING_RECORD(CurrentEntry,
SIDQUEUEENTRY,
ListEntry);
if (EqualSid(pSid,
(PSID)(QueueEntry + 1)))
{
FoundEntry = QueueEntry;
break;
}
}
}
if (FoundEntry == NULL)
{
DWORD SidLength = GetLengthSid(pSid);
FoundEntry = HeapAlloc(scm->Heap,
0,
sizeof(SIDQUEUEENTRY) + SidLength);
if (FoundEntry != NULL)
{
CopySid(SidLength,
(PSID)(FoundEntry + 1),
pSid);
FoundEntry->CallbackCount = 1;
FoundEntry->Callbacks = HeapAlloc(scm->Heap,
0,
sizeof(SIDCACHECALLBACKINFO));
if (FoundEntry->Callbacks != NULL)
{
FoundEntry->Callbacks[0].CompletionProc = CompletionProc;
FoundEntry->Callbacks[0].Context = Context;
/* append it to the queue */
InsertTailList(&scm->QueueListHead,
&FoundEntry->ListEntry);
/* signal the lookup event */
SetEvent(scm->LookupEvent);
Ret = TRUE;
}
else
{
/* unable to queue it because we couldn't allocate the callbacks
array, free the memory and return */
HeapFree(scm->Heap,
0,
FoundEntry);
}
}
}
else
{
PSIDCACHECALLBACKINFO Sidccb;
/* add the callback */
Sidccb = HeapReAlloc(scm->Heap,
0,
FoundEntry->Callbacks,
(FoundEntry->CallbackCount + 1) * sizeof(SIDCACHECALLBACKINFO));
if (Sidccb != NULL)
{
FoundEntry->Callbacks = Sidccb;
FoundEntry->Callbacks[FoundEntry->CallbackCount].CompletionProc = CompletionProc;
FoundEntry->Callbacks[FoundEntry->CallbackCount++].Context = Context;
Ret = TRUE;
}
}
return Ret;
}
VOID
DequeueSidLookup(IN HANDLE SidCacheMgr,
IN PSID pSid)
{
PLIST_ENTRY CurrentEntry;
PSIDQUEUEENTRY QueueEntry;
PSIDCACHEMGR scm;
scm = ReferenceSidCacheMgr(SidCacheMgr);
if (scm != NULL)
{
EnterCriticalSection(&scm->Lock);
if (scm->QueueLookingUp != NULL &&
EqualSid(pSid,
(PSID)(scm->QueueLookingUp + 1)))
{
/* don't free the queue lookup item! this will be
done in the lookup thread */
scm->QueueLookingUp = NULL;
}
else
{
for (CurrentEntry = scm->QueueListHead.Flink;
CurrentEntry != &scm->QueueListHead;
CurrentEntry = CurrentEntry->Flink)
{
QueueEntry = CONTAINING_RECORD(CurrentEntry,
SIDQUEUEENTRY,
ListEntry);
if (EqualSid(pSid,
(PSID)(QueueEntry + 1)))
{
FreeQueueEntry(scm,
QueueEntry);
break;
}
}
}
LeaveCriticalSection(&scm->Lock);
DereferenceSidCacheMgr(scm);
}
}
VOID
ReferenceSidReqResult(IN HANDLE SidCacheMgr,
IN PSIDREQRESULT ReqResult)
{
PSIDCACHEMGR scm;
scm = ReferenceSidCacheMgr(SidCacheMgr);
if (scm != NULL)
{
InterlockedIncrement(&ReqResult->RefCount);
DereferenceSidCacheMgr(scm);
}
}
VOID
DereferenceSidReqResult(IN HANDLE SidCacheMgr,
IN PSIDREQRESULT ReqResult)
{
PSIDCACHEMGR scm;
scm = ReferenceSidCacheMgr(SidCacheMgr);
if (scm != NULL)
{
if (InterlockedDecrement(&ReqResult->RefCount) == 0)
{
HeapFree(scm->Heap,
0,
ReqResult);
}
DereferenceSidCacheMgr(scm);
}
}
BOOL
LookupSidCache(IN HANDLE SidCacheMgr,
IN PSID pSid,
IN PSIDREQCOMPLETIONPROC CompletionProc,
IN PVOID Context)
{
BOOL Found = FALSE;
PSIDREQRESULT ReqResult = NULL;
PSIDCACHEMGR scm;
scm = ReferenceSidCacheMgr(SidCacheMgr);
if (scm != NULL)
{
EnterCriticalSection(&scm->Lock);
/* search the cache */
Found = FindSidInCache(scm,
pSid,
&ReqResult);
if (!Found)
{
/* the sid is not in the cache, queue it if not already queued */
if (!QueueSidLookup(scm,
pSid,
CompletionProc,
Context))
{
PSIDREQRESULT FoundReqResult = NULL;
/* unable to queue it, look it up now */
LeaveCriticalSection(&scm->Lock);
/* lookup everything we need */
if (!LookupSidInformation(scm,
pSid,
&ReqResult))
{
ReqResult = NULL;
}
EnterCriticalSection(&scm->Lock);
/* see if the SID was added to the cache in the meanwhile */
if (!FindSidInCache(scm,
pSid,
&FoundReqResult))
{
if (ReqResult != NULL)
{
/* cache the results */
CacheLookupResults(scm,
pSid,
ReqResult);
}
}
else
{
if (ReqResult != NULL)
{
/* free the information of our lookup and use the cached
information*/
DereferenceSidReqResult(scm,
ReqResult);
}
ReqResult = FoundReqResult;
}
Found = (ReqResult != NULL);
}
}
LeaveCriticalSection(&scm->Lock);
/* call the completion callback */
if (Found)
{
CompletionProc(SidCacheMgr,
pSid,
ReqResult,
Context);
if (ReqResult != NULL)
{
HeapFree(scm->Heap,
0,
ReqResult);
}
}
DereferenceSidCacheMgr(scm);
}
return Found;
}