1904 lines
55 KiB
C++
1904 lines
55 KiB
C++
//+-------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
//
|
|
// Copyright (C) Microsoft Corporation, 1997 - 1999
|
|
//
|
|
// File: sidcache.cpp
|
|
//
|
|
// This file contains the implementation of a SID/Name cache.
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
#include "aclpriv.h"
|
|
|
|
#if(_WIN32_WINNT >= 0x0500)
|
|
#include <dsgetdc.h> // DsGetDcName
|
|
#include <iads.h>
|
|
#endif
|
|
|
|
#define SECURITY_WIN32
|
|
#include <security.h> // TranslateName
|
|
#include <lm.h> // NetApiBufferFree
|
|
#include <shlwapi.h> // StrChr, StrRChr
|
|
|
|
// 10 minutes
|
|
#define SID_CACHE_AGE_LIMIT (10*60*1000)
|
|
|
|
TCHAR const c_szNTProvider[] = TEXT("WinNT://");
|
|
#define NTPROV_LEN (ARRAYSIZE(c_szNTProvider)-1)
|
|
|
|
#define ACLUI_ALIGNED_SID_LENGTH(p) ((PtrAlignSize(RtlLengthSid((p)))))
|
|
|
|
PSIDCACHE g_pSidCache = NULL;
|
|
|
|
PSIDCACHE GetSidCache()
|
|
{
|
|
if (NULL == g_pSidCache)
|
|
{
|
|
// The cache starts with an extra ref here that will be released
|
|
// during our DLL_PROCESS_DETACH
|
|
g_pSidCache = new CSidCache;
|
|
|
|
if (g_pSidCache)
|
|
{
|
|
g_pSidCache->AddRef();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_pSidCache->AddRef();
|
|
}
|
|
|
|
return g_pSidCache;
|
|
}
|
|
|
|
void FreeSidCache()
|
|
{
|
|
if (g_pSidCache)
|
|
{
|
|
g_pSidCache->Release();
|
|
g_pSidCache = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// CSidCache implementation
|
|
//
|
|
|
|
CSidCache::CSidCache()
|
|
: m_pszCachedServer(NULL), m_pszCachedDomain(NULL),
|
|
m_hInitThread(NULL), m_pszLastDc(NULL), m_pszLastDomain(NULL),
|
|
m_cRef(1)
|
|
{
|
|
HINSTANCE hInstThisDll;
|
|
DWORD dwThreadID;
|
|
|
|
ZeroMemory(m_dpaSidHashTable, SIZEOF(m_dpaSidHashTable));
|
|
ExceptionPropagatingInitializeCriticalSection(&m_csHashTableLock);
|
|
ExceptionPropagatingInitializeCriticalSection(&m_csDomainNameLock);
|
|
ExceptionPropagatingInitializeCriticalSection(&m_csDcNameLock);
|
|
|
|
// Give the thread we are about to create a ref to the dll,
|
|
// so that the dll will remain for the lifetime of the thread
|
|
hInstThisDll = LoadLibrary(c_szDllName);
|
|
if (hInstThisDll != NULL)
|
|
{
|
|
// also do an AddRef() for the worker thread to release later
|
|
AddRef();
|
|
|
|
// Start a thread to cache the well-known and built-in SIDs
|
|
m_hInitThread = CreateThread(NULL, 0, InitThread, this, 0, &dwThreadID);
|
|
|
|
if (!m_hInitThread)
|
|
{
|
|
// Failed to create the thread, do cleanup
|
|
FreeLibrary(hInstThisDll);
|
|
Release();
|
|
}
|
|
}
|
|
}
|
|
|
|
ULONG
|
|
CSidCache::AddRef()
|
|
{
|
|
return InterlockedIncrement(&m_cRef);
|
|
}
|
|
|
|
ULONG
|
|
CSidCache::Release()
|
|
{
|
|
if (InterlockedDecrement(&m_cRef) == 0)
|
|
{
|
|
delete this;
|
|
return 0;
|
|
}
|
|
return m_cRef;
|
|
}
|
|
|
|
CSidCache::~CSidCache()
|
|
{
|
|
int i;
|
|
|
|
TraceEnter(TRACE_SIDCACHE, "CSidCache::~CSidCache");
|
|
|
|
Lock();
|
|
for (i = 0; i < BUCKET_COUNT; i++)
|
|
{
|
|
DestroyDPA(m_dpaSidHashTable[i]);
|
|
m_dpaSidHashTable[i] = NULL;
|
|
}
|
|
Unlock();
|
|
|
|
LockDomain();
|
|
LocalFreeString(&m_pszCachedServer);
|
|
LocalFreeString(&m_pszCachedDomain);
|
|
UnlockDomain();
|
|
|
|
LockDc();
|
|
LocalFreeString(&m_pszLastDc);
|
|
LocalFreeString(&m_pszLastDomain);
|
|
UnlockDc();
|
|
|
|
DeleteCriticalSection(&m_csHashTableLock);
|
|
DeleteCriticalSection(&m_csDomainNameLock);
|
|
DeleteCriticalSection(&m_csDcNameLock);
|
|
|
|
|
|
if (m_hInitThread != NULL)
|
|
{
|
|
CloseHandle(m_hInitThread);
|
|
}
|
|
|
|
TraceLeaveVoid();
|
|
}
|
|
|
|
|
|
BOOL
|
|
CSidCache::LookupSids(HDPA hSids,
|
|
LPCTSTR pszServer,
|
|
LPSECURITYINFO2 psi2,
|
|
PUSER_LIST *ppUserList)
|
|
{
|
|
BOOL fResult = FALSE;
|
|
|
|
TraceEnter(TRACE_SIDCACHE, "CSidCache::LookupSids");
|
|
TraceAssert(hSids != NULL);
|
|
|
|
if (NULL == hSids)
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
TraceLeaveValue(FALSE);
|
|
}
|
|
|
|
if (NULL != ppUserList)
|
|
*ppUserList = NULL;
|
|
|
|
if (0 != DPA_GetPtrCount(hSids))
|
|
{
|
|
HDPA hEntryList = DPA_Create(4);
|
|
|
|
if (NULL == hEntryList)
|
|
TraceLeaveValue(FALSE);
|
|
|
|
InternalLookupSids(hSids, pszServer, psi2, hEntryList);
|
|
|
|
if (0 != DPA_GetPtrCount(hEntryList) && NULL != ppUserList)
|
|
fResult = BuildUserList(hEntryList, pszServer, ppUserList);
|
|
|
|
DPA_Destroy(hEntryList);
|
|
}
|
|
|
|
TraceLeaveValue(fResult);
|
|
}
|
|
|
|
|
|
BOOL
|
|
CSidCache::LookupSidsAsync(HDPA hSids,
|
|
LPCTSTR pszServer,
|
|
LPSECURITYINFO2 psi2,
|
|
HWND hWndNotify,
|
|
UINT uMsgNotify)
|
|
{
|
|
BOOL fResult = FALSE;
|
|
|
|
TraceEnter(TRACE_SIDCACHE, "CSidCache::LookupSids");
|
|
TraceAssert(hSids != NULL);
|
|
|
|
if (NULL == hSids)
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
TraceLeaveValue(FALSE);
|
|
}
|
|
|
|
if (0 != DPA_GetPtrCount(hSids))
|
|
{
|
|
fResult = InternalLookupSids(hSids,
|
|
pszServer,
|
|
psi2,
|
|
NULL,
|
|
hWndNotify,
|
|
uMsgNotify);
|
|
}
|
|
|
|
TraceLeaveValue(fResult);
|
|
}
|
|
|
|
|
|
#if(_WIN32_WINNT >= 0x0500)
|
|
BOOL
|
|
CSidCache::LookupNames(PDS_SELECTION_LIST pDsSelList,
|
|
LPCTSTR pszServer,
|
|
PUSER_LIST *ppUserList,
|
|
BOOL bStandalone)
|
|
{
|
|
BOOL fResult = FALSE;
|
|
HDPA hEntryList;
|
|
|
|
TraceEnter(TRACE_SIDCACHE, "CSidCache::LookupNames");
|
|
TraceAssert(pDsSelList != NULL);
|
|
TraceAssert(ppUserList != NULL);
|
|
|
|
if (NULL == pDsSelList)
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
TraceLeaveValue(FALSE);
|
|
}
|
|
|
|
if (NULL != ppUserList)
|
|
*ppUserList = NULL;
|
|
|
|
hEntryList = DPA_Create(4);
|
|
|
|
if (NULL == hEntryList)
|
|
TraceLeaveValue(FALSE);
|
|
|
|
InternalLookupNames(pDsSelList, pszServer, hEntryList, bStandalone);
|
|
|
|
if (0 != DPA_GetPtrCount(hEntryList))
|
|
{
|
|
fResult = TRUE; // so far, so good
|
|
|
|
if (NULL != ppUserList)
|
|
fResult = BuildUserList(hEntryList, pszServer, ppUserList);
|
|
}
|
|
|
|
DPA_Destroy(hEntryList);
|
|
|
|
TraceLeaveValue(fResult);
|
|
}
|
|
#endif // #if(_WIN32_WINNT >= 0x0500)
|
|
|
|
|
|
void
|
|
CSidCache::GetDomainName(LPCTSTR pszServer, LPTSTR pszDomain, ULONG cchDomain)
|
|
{
|
|
TraceEnter(TRACE_SIDCACHE, "CSidCache::GetDomainName");
|
|
TraceAssert(NULL != pszDomain);
|
|
TraceAssert(0 != cchDomain);
|
|
|
|
pszDomain[0] = TEXT('\0');
|
|
|
|
LockDomain();
|
|
|
|
if (m_pszCachedDomain == NULL ||
|
|
(pszServer == NULL && m_pszCachedServer != NULL) ||
|
|
(pszServer != NULL && (m_pszCachedServer == NULL ||
|
|
CompareString(LOCALE_USER_DEFAULT,
|
|
0,
|
|
pszServer,
|
|
-1,
|
|
m_pszCachedServer,
|
|
-1) != CSTR_EQUAL)))
|
|
{
|
|
//
|
|
// It's a different server than last time, so ask LSA
|
|
// for the domain name.
|
|
//
|
|
LocalFreeString(&m_pszCachedDomain);
|
|
LocalFreeString(&m_pszCachedServer);
|
|
|
|
if (pszServer != NULL)
|
|
LocalAllocString(&m_pszCachedServer, pszServer);
|
|
|
|
LSA_HANDLE hLSA = GetLSAConnection(pszServer, POLICY_VIEW_LOCAL_INFORMATION);
|
|
if (hLSA != NULL)
|
|
{
|
|
PPOLICY_ACCOUNT_DOMAIN_INFO pDomainInfo = NULL;
|
|
|
|
LsaQueryInformationPolicy(hLSA,
|
|
PolicyAccountDomainInformation,
|
|
(PVOID*)&pDomainInfo);
|
|
if (pDomainInfo != NULL)
|
|
{
|
|
CopyUnicodeString(&m_pszCachedDomain, &pDomainInfo->DomainName);
|
|
LsaFreeMemory(pDomainInfo);
|
|
|
|
Trace((TEXT("Domain for %s is %s"), pszServer, m_pszCachedDomain));
|
|
}
|
|
LsaClose(hLSA);
|
|
}
|
|
else if (NULL != pszServer) // use the server name
|
|
{
|
|
// Skip leading backslashes
|
|
while (TEXT('\\') == *pszServer)
|
|
pszServer++;
|
|
|
|
LocalAllocString(&m_pszCachedDomain, pszServer);
|
|
|
|
if (m_pszCachedDomain)
|
|
{
|
|
// If there is a period, truncate the name at that point so
|
|
// that something like "nttest.microsoft.com" becomes "nttest"
|
|
LPTSTR pszDot = StrChr(m_pszCachedDomain, TEXT('.'));
|
|
if (pszDot)
|
|
*pszDot = TEXT('\0');
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_pszCachedDomain)
|
|
lstrcpyn(pszDomain, m_pszCachedDomain, cchDomain);
|
|
|
|
UnlockDomain();
|
|
|
|
TraceLeaveVoid();
|
|
}
|
|
|
|
|
|
DWORD
|
|
_GetDcName(LPCTSTR pszServer, LPCTSTR pszDomain, LPTSTR *ppszDC)
|
|
{
|
|
DWORD dwErr;
|
|
|
|
if (!ppszDC)
|
|
return ERROR_INVALID_PARAMETER;
|
|
|
|
*ppszDC = NULL;
|
|
|
|
#if(_WIN32_WINNT >= 0x0500)
|
|
PDOMAIN_CONTROLLER_INFO pDCInfo = NULL;
|
|
TraceMsg("Calling DsGetDcName");
|
|
dwErr = DsGetDcName(pszServer,
|
|
pszDomain,
|
|
NULL,
|
|
NULL,
|
|
DS_IS_FLAT_NAME,
|
|
&pDCInfo);
|
|
if (ERROR_SUCCESS == dwErr)
|
|
{
|
|
TraceAssert(NULL != pDCInfo);
|
|
LocalAllocString(ppszDC, pDCInfo->DomainControllerName);
|
|
NetApiBufferFree(pDCInfo);
|
|
}
|
|
#else
|
|
LPTSTR pszDcName = NULL;
|
|
// NetGetAnyDCName only works for trusted domains, but is faster
|
|
// and returns either PDC or BDC. NetGetDCName returns only PDC.
|
|
TraceMsg("Calling NetGetAnyDCName / NetGetDCName");
|
|
dwErr = NetGetAnyDCName(pszServer, pszDomain, (LPBYTE*)&pszDcName);
|
|
if (ERROR_NO_SUCH_DOMAIN == dwErr)
|
|
dwErr = NetGetDCName(pszServer, pszDomain, (LPBYTE*)&pszDcName);
|
|
if (pszDcName)
|
|
{
|
|
LocalAllocString(ppszDC, pszDcName);
|
|
NetApiBufferFree(pszDcName);
|
|
}
|
|
#endif
|
|
|
|
if (ERROR_SUCCESS == dwErr && !*ppszDC)
|
|
dwErr = ERROR_OUTOFMEMORY;
|
|
|
|
return dwErr;
|
|
}
|
|
|
|
void
|
|
CSidCache::GetDcName(LPCTSTR pszDomain, LPTSTR pszDC, ULONG cchDC)
|
|
{
|
|
TraceEnter(TRACE_SIDCACHE, "CSidCache::GetDcName");
|
|
TraceAssert(NULL != pszDC);
|
|
TraceAssert(0 != cchDC);
|
|
|
|
pszDC[0] = TEXT('\0');
|
|
|
|
LockDc();
|
|
|
|
if (m_pszLastDc == NULL ||
|
|
(pszDomain == NULL && m_pszLastDomain != NULL) ||
|
|
(pszDomain != NULL && (m_pszLastDomain == NULL ||
|
|
CompareString(LOCALE_USER_DEFAULT,
|
|
0,
|
|
pszDomain,
|
|
-1,
|
|
m_pszLastDomain,
|
|
-1) != CSTR_EQUAL)))
|
|
{
|
|
//
|
|
// It's a different domain than last time, so look for a DC
|
|
//
|
|
LocalFreeString(&m_pszLastDc);
|
|
LocalFreeString(&m_pszLastDomain);
|
|
|
|
if (pszDomain != NULL)
|
|
LocalAllocString(&m_pszLastDomain, pszDomain);
|
|
|
|
_GetDcName(NULL, pszDomain, &m_pszLastDc);
|
|
|
|
Trace((TEXT("DC for %s is %s"), pszDomain, m_pszLastDc));
|
|
}
|
|
|
|
if (m_pszLastDc)
|
|
lstrcpyn(pszDC, m_pszLastDc, cchDC);
|
|
|
|
UnlockDc();
|
|
|
|
TraceLeaveVoid();
|
|
}
|
|
|
|
|
|
BSTR
|
|
CSidCache::GetNT4DisplayName(LPCTSTR pszAccount,
|
|
LPCTSTR pszName,
|
|
LPCTSTR pszServer,
|
|
BOOL bStandalone)
|
|
{
|
|
BSTR strResult = NULL;
|
|
TCHAR szComputer[UNCLEN];
|
|
LPTSTR pszT = NULL;
|
|
PUSER_INFO_2 pui = NULL;
|
|
|
|
if (!pszAccount || !*pszAccount || !pszName || !*pszName)
|
|
return NULL;
|
|
|
|
TraceEnter(TRACE_SIDCACHE, "CSidCache::GetNT4DisplayName");
|
|
|
|
if (!bStandalone
|
|
&& (pszT = StrChr(pszAccount, TEXT('\\'))))
|
|
{
|
|
// Copy the domain name
|
|
TCHAR szDomain[DNLEN];
|
|
lstrcpyn(szDomain,
|
|
pszAccount,
|
|
min((size_t)(pszT - pszAccount + 1), ARRAYSIZE(szDomain)));
|
|
|
|
// See if we can use pszServer for NetUserGetInfo
|
|
TCHAR szAccountDomain[DNLEN];
|
|
szAccountDomain[0] = TEXT('\0');
|
|
GetDomainName(pszServer, szAccountDomain, ARRAYSIZE(szAccountDomain));
|
|
|
|
if (lstrcmpi(szDomain, szAccountDomain))
|
|
{
|
|
// Different domain, find a DC
|
|
szComputer[0] = TEXT('\0');
|
|
GetDcName(szDomain, szComputer, ARRAYSIZE(szComputer));
|
|
if (TEXT('\0') != szComputer[0])
|
|
pszServer = szComputer;
|
|
}
|
|
}
|
|
|
|
TraceMsg("Calling NetUserGetInfo");
|
|
if (NERR_Success == NetUserGetInfo(pszServer, pszName, 2, (LPBYTE *)&pui)
|
|
&& NULL != pui->usri2_full_name
|
|
&& *pui->usri2_full_name)
|
|
{
|
|
strResult = SysAllocString(pui->usri2_full_name);
|
|
}
|
|
|
|
NetApiBufferFree(pui);
|
|
|
|
Trace((TEXT("Returning Full Name '%s' for '%s'"), strResult, pszAccount));
|
|
TraceLeaveValue(strResult);
|
|
}
|
|
|
|
|
|
int
|
|
CSidCache::HashSid(PSID pSid)
|
|
{
|
|
DWORD dwHash = 0;
|
|
|
|
if (NULL != pSid)
|
|
{
|
|
PBYTE pbSid = (PBYTE)pSid;
|
|
PBYTE pbEndSid = pbSid + GetLengthSid(pSid);
|
|
|
|
while (pbSid < pbEndSid)
|
|
dwHash += *pbSid++;
|
|
}
|
|
|
|
return dwHash % BUCKET_COUNT;
|
|
}
|
|
|
|
|
|
int CALLBACK
|
|
CSidCache::CompareSid(LPVOID p1, LPVOID p2, LPARAM lParam)
|
|
{
|
|
int nResult = 0;
|
|
PSID_CACHE_ENTRY pEntry1 = (PSID_CACHE_ENTRY)p1;
|
|
PSID_CACHE_ENTRY pEntry2 = (PSID_CACHE_ENTRY)p2;
|
|
PSID pSid1 = NULL;
|
|
PSID pSid2 = NULL;
|
|
|
|
if (pEntry1)
|
|
pSid1 = pEntry1->pSid;
|
|
else if (lParam)
|
|
pSid1 = (PSID)lParam;
|
|
|
|
if (pEntry2)
|
|
pSid2 = pEntry2->pSid;
|
|
|
|
if (pSid1 == NULL)
|
|
nResult = -1;
|
|
else if (pSid2 == NULL)
|
|
nResult = 1;
|
|
else
|
|
{
|
|
DWORD dwLength = GetLengthSid(pSid1);
|
|
|
|
// Compare SID lengths
|
|
nResult = dwLength - GetLengthSid(pSid2);
|
|
|
|
if (nResult == 0)
|
|
{
|
|
// Lengths are equal, compare the bits
|
|
PBYTE pbSid1 = (PBYTE)pSid1;
|
|
PBYTE pbSid2 = (PBYTE)pSid2;
|
|
|
|
// Could compare Identifier Authorities and SubAuthorities instead
|
|
while (nResult == 0 && dwLength != 0)
|
|
{
|
|
dwLength--;
|
|
nResult = *pbSid1++ - *pbSid2++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nResult;
|
|
}
|
|
|
|
|
|
PSID_CACHE_ENTRY
|
|
CSidCache::FindSid(PSID pSid)
|
|
{
|
|
PSID_CACHE_ENTRY pEntry = NULL;
|
|
int iBucket;
|
|
|
|
TraceEnter(TRACE_SIDCACHE, "CSidCache::FindSid");
|
|
TraceAssert(pSid != NULL);
|
|
TraceAssert(IsValidSid(pSid));
|
|
|
|
iBucket = HashSid(pSid);
|
|
|
|
Lock();
|
|
|
|
if (m_dpaSidHashTable[iBucket] != NULL)
|
|
{
|
|
int iEntry = DPA_Search(m_dpaSidHashTable[iBucket],
|
|
NULL,
|
|
0,
|
|
CompareSid,
|
|
(LPARAM)pSid,
|
|
DPAS_SORTED);
|
|
if (iEntry != -1)
|
|
{
|
|
pEntry = (PSID_CACHE_ENTRY)DPA_FastGetPtr(m_dpaSidHashTable[iBucket],
|
|
iEntry);
|
|
TraceAssert(pEntry != NULL);
|
|
TraceAssert(EqualSid(pSid, pEntry->pSid));
|
|
|
|
if (0 != pEntry->dwLastAccessTime)
|
|
{
|
|
DWORD dwCurrentTime = GetTickCount();
|
|
|
|
if ((dwCurrentTime - pEntry->dwLastAccessTime) > SID_CACHE_AGE_LIMIT)
|
|
{
|
|
// The entry has aged out, remove it.
|
|
Trace((TEXT("Removing stale entry: %s"), pEntry->pszName));
|
|
DPA_DeletePtr(m_dpaSidHashTable[iBucket], iEntry);
|
|
LocalFree(pEntry);
|
|
pEntry = NULL;
|
|
}
|
|
else
|
|
pEntry->dwLastAccessTime = dwCurrentTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
Unlock();
|
|
|
|
TraceLeaveValue(pEntry);
|
|
}
|
|
|
|
|
|
PSID_CACHE_ENTRY
|
|
CSidCache::MakeEntry(PSID pSid,
|
|
SID_NAME_USE SidType,
|
|
LPCTSTR pszName,
|
|
LPCTSTR pszLogonName)
|
|
{
|
|
PSID_CACHE_ENTRY pEntry = NULL;
|
|
ULONG cbSid;
|
|
ULONG cbName = 0;
|
|
ULONG cbLogonName = 0;
|
|
|
|
TraceEnter(TRACE_SIDCACHE, "CSidCache::MakeEntry");
|
|
TraceAssert(pSid != NULL);
|
|
|
|
cbSid = GetLengthSid(pSid);
|
|
if (NULL != pszName && *pszName)
|
|
cbName = StringByteSize(pszName);
|
|
if (NULL != pszLogonName && *pszLogonName)
|
|
cbLogonName = StringByteSize(pszLogonName);
|
|
|
|
pEntry = (PSID_CACHE_ENTRY)LocalAlloc(LPTR,
|
|
SIZEOF(SID_CACHE_ENTRY)
|
|
+ cbSid
|
|
+ cbName
|
|
+ cbLogonName);
|
|
if (pEntry != NULL)
|
|
{
|
|
PBYTE pData = (PBYTE)(pEntry+1);
|
|
|
|
pEntry->SidType = SidType;
|
|
|
|
pEntry->pSid = (PSID)pData;
|
|
CopyMemory(pData, pSid, cbSid);
|
|
pData += cbSid;
|
|
|
|
if (0 != cbName)
|
|
{
|
|
pEntry->pszName = (LPCTSTR)pData;
|
|
CopyMemory(pData, pszName, cbName);
|
|
pData += cbName;
|
|
}
|
|
|
|
if (0 != cbLogonName)
|
|
{
|
|
pEntry->pszLogonName = (LPCTSTR)pData;
|
|
CopyMemory(pData, pszLogonName, cbLogonName);
|
|
//pData += cbLogonName;
|
|
}
|
|
|
|
// Well-known entries never age out
|
|
if (SidTypeWellKnownGroup == SidType || IsAliasSid(pSid))
|
|
pEntry->dwLastAccessTime = 0;
|
|
else
|
|
pEntry->dwLastAccessTime = GetTickCount();
|
|
}
|
|
|
|
TraceLeaveValue(pEntry);
|
|
}
|
|
|
|
|
|
BOOL
|
|
CSidCache::AddEntry(PSID_CACHE_ENTRY pEntry)
|
|
{
|
|
BOOL fResult = FALSE;
|
|
int iSidBucket;
|
|
|
|
TraceEnter(TRACE_SIDCACHE, "CSidCache::AddEntry");
|
|
TraceAssert(pEntry != NULL);
|
|
|
|
if (NULL == pEntry)
|
|
TraceLeaveValue(FALSE);
|
|
|
|
iSidBucket = HashSid(pEntry->pSid);
|
|
|
|
Lock();
|
|
|
|
if (m_dpaSidHashTable[iSidBucket] == NULL)
|
|
m_dpaSidHashTable[iSidBucket] = DPA_Create(4);
|
|
|
|
if (NULL != m_dpaSidHashTable[iSidBucket])
|
|
{
|
|
DPA_AppendPtr(m_dpaSidHashTable[iSidBucket], pEntry);
|
|
DPA_Sort(m_dpaSidHashTable[iSidBucket], CompareSid, 0);
|
|
fResult = TRUE;
|
|
}
|
|
|
|
Unlock();
|
|
|
|
TraceLeaveValue(fResult);
|
|
}
|
|
|
|
|
|
BOOL
|
|
CSidCache::BuildUserList(HDPA hEntryList,
|
|
LPCTSTR pszServer,
|
|
PUSER_LIST *ppUserList)
|
|
{
|
|
ULONG cEntries;
|
|
TCHAR szAliasDomain[MAX_PATH];
|
|
PSID_CACHE_ENTRY pEntry;
|
|
ULONG cb = 0;
|
|
ULONG cSidsLen = 0;
|
|
ULONG cbAliasDomain = 0;
|
|
ULONG i;
|
|
|
|
TraceEnter(TRACE_SIDCACHE, "CSidCache::BuildUserList");
|
|
TraceAssert(hEntryList != NULL);
|
|
TraceAssert(ppUserList != NULL);
|
|
|
|
cEntries = DPA_GetPtrCount(hEntryList);
|
|
TraceAssert(0 != cEntries);
|
|
|
|
//
|
|
// This name replaces "BUILTIN" for Alias SIDs
|
|
//
|
|
GetDomainName(pszServer, szAliasDomain, ARRAYSIZE(szAliasDomain));
|
|
cbAliasDomain = StringByteSize(szAliasDomain);
|
|
|
|
//
|
|
// Add the sizes
|
|
//
|
|
cb = SIZEOF(USER_LIST) + ((cEntries - 1) * SIZEOF(USER_INFO));
|
|
for (i = 0; i < cEntries; i++)
|
|
{
|
|
pEntry = (PSID_CACHE_ENTRY)DPA_FastGetPtr(hEntryList, i);
|
|
TraceAssert(NULL != pEntry);
|
|
|
|
cSidsLen += ACLUI_ALIGNED_SID_LENGTH(pEntry->pSid);
|
|
|
|
if (SidTypeAlias == pEntry->SidType)
|
|
cb += cbAliasDomain;
|
|
else if (pEntry->pszLogonName)
|
|
cb += StringByteSize(pEntry->pszLogonName);
|
|
|
|
if (pEntry->pszName)
|
|
cb += StringByteSize(pEntry->pszName);
|
|
}
|
|
|
|
cb += cSidsLen;
|
|
//
|
|
// Allocate and build the return buffer
|
|
//
|
|
*ppUserList = (PUSER_LIST)LocalAlloc(LPTR, cb);
|
|
|
|
if (NULL == *ppUserList)
|
|
TraceLeaveValue(FALSE);
|
|
|
|
(*ppUserList)->cUsers = cEntries;
|
|
|
|
PBYTE pData = NULL;
|
|
PBYTE pCharData = NULL;
|
|
|
|
//
|
|
//NTRAID#NTBUG9-364410-2001/20/23-hiteshr
|
|
//Sids were non aligned if cEntries > 1
|
|
//
|
|
pData = (PBYTE)&(*ppUserList)->rgUsers[cEntries];
|
|
pCharData = pData + cSidsLen;
|
|
|
|
for (i = 0; i < cEntries; i++)
|
|
{
|
|
pEntry = (PSID_CACHE_ENTRY)DPA_FastGetPtr(hEntryList, i);
|
|
TraceAssert(NULL != pEntry);
|
|
|
|
(*ppUserList)->rgUsers[i].SidType = pEntry->SidType;
|
|
|
|
TraceAssert(NULL != pEntry->pSid);
|
|
(*ppUserList)->rgUsers[i].pSid = (PSID)pData;
|
|
cb = GetLengthSid(pEntry->pSid);
|
|
CopyMemory(pData, pEntry->pSid, cb);
|
|
pData += cb;
|
|
|
|
if (SidTypeAlias == pEntry->SidType)
|
|
{
|
|
(*ppUserList)->rgUsers[i].pszLogonName = (LPCTSTR)pCharData;
|
|
|
|
// Copy the "BUILTIN" domain name
|
|
if (cbAliasDomain)
|
|
{
|
|
CopyMemory(pCharData, szAliasDomain, cbAliasDomain);
|
|
pCharData += cbAliasDomain - SIZEOF(TCHAR);
|
|
|
|
if (NULL != pEntry->pszName)
|
|
*(LPTSTR)pCharData = TEXT('\\');
|
|
else
|
|
*(LPTSTR)pCharData = TEXT('\0');
|
|
|
|
pCharData += SIZEOF(TCHAR);
|
|
}
|
|
// The rest of the name is copied below
|
|
}
|
|
else if (NULL != pEntry->pszLogonName)
|
|
{
|
|
(*ppUserList)->rgUsers[i].pszLogonName = (LPCTSTR)pCharData;
|
|
cb = StringByteSize(pEntry->pszLogonName);
|
|
CopyMemory(pCharData, pEntry->pszLogonName, cb);
|
|
pCharData += cb;
|
|
}
|
|
|
|
if (NULL != pEntry->pszName)
|
|
{
|
|
(*ppUserList)->rgUsers[i].pszName = (LPCTSTR)pCharData;
|
|
cb = StringByteSize(pEntry->pszName);
|
|
CopyMemory(pCharData, pEntry->pszName, cb);
|
|
pCharData += cb;
|
|
}
|
|
}
|
|
|
|
TraceLeaveValue(TRUE);
|
|
}
|
|
|
|
|
|
//
|
|
// Wrapper around sspi's TranslateName that automatically handles
|
|
// the buffer sizing
|
|
//
|
|
HRESULT
|
|
TranslateNameInternal(LPCTSTR pszAccountName,
|
|
EXTENDED_NAME_FORMAT AccountNameFormat,
|
|
EXTENDED_NAME_FORMAT DesiredNameFormat,
|
|
BSTR *pstrTranslatedName)
|
|
{
|
|
#if(_WIN32_WINNT >= 0x0500)
|
|
#if DBG
|
|
//
|
|
// These match up with the EXTENDED_NAME_FORMAT enumeration.
|
|
// They're for debugger output only.
|
|
//
|
|
static const LPCTSTR rgpszFmt[] = {
|
|
TEXT("NameUnknown"),
|
|
TEXT("FullyQualifiedDN"),
|
|
TEXT("NameSamCompatible"),
|
|
TEXT("NameDisplay"),
|
|
TEXT("NameDomainSimple"),
|
|
TEXT("NameEnterpriseSimple"),
|
|
TEXT("NameUniqueId"),
|
|
TEXT("NameCanonical"),
|
|
TEXT("NameUserPrincipal"),
|
|
TEXT("NameCanonicalEx"),
|
|
TEXT("NameServicePrincipal") };
|
|
#endif // DBG
|
|
|
|
TraceEnter(TRACE_SIDCACHE, "TranslateNameInternal");
|
|
Trace((TEXT("Calling TranslateName for \"%s\""), pszAccountName));
|
|
Trace((TEXT("Translating %s -> %s"),
|
|
rgpszFmt[AccountNameFormat], rgpszFmt[DesiredNameFormat]));
|
|
|
|
if (!pszAccountName || !*pszAccountName || !pstrTranslatedName)
|
|
TraceLeaveResult(E_INVALIDARG);
|
|
|
|
HRESULT hr = NOERROR;
|
|
//
|
|
// cchTrans is static so that if a particular installation's
|
|
// account names are really long, we'll not be resizing the
|
|
// buffer for each account.
|
|
//
|
|
static ULONG cchTrans = MAX_PATH;
|
|
ULONG cch = cchTrans;
|
|
|
|
*pstrTranslatedName = SysAllocStringLen(NULL, cch);
|
|
if (NULL == *pstrTranslatedName)
|
|
ExitGracefully(hr, E_OUTOFMEMORY, "Unable to allocate name buffer");
|
|
|
|
**pstrTranslatedName = L'\0';
|
|
|
|
//
|
|
// TranslateName is delay-loaded from secur32.dll using the linker's
|
|
// delay-load mechanism. Therefore, wrap with an exception handler.
|
|
//
|
|
__try
|
|
{
|
|
while(!::TranslateName(pszAccountName,
|
|
AccountNameFormat,
|
|
DesiredNameFormat,
|
|
*pstrTranslatedName,
|
|
&cch))
|
|
{
|
|
if (ERROR_INSUFFICIENT_BUFFER == GetLastError())
|
|
{
|
|
Trace((TEXT("Resizing buffer to %d chars"), cch));
|
|
if (!SysReAllocStringLen(pstrTranslatedName, NULL, cch))
|
|
ExitGracefully(hr, E_OUTOFMEMORY, "Unable to reallocate name buffer");
|
|
|
|
**pstrTranslatedName = L'\0';
|
|
}
|
|
else
|
|
{
|
|
hr = E_FAIL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
cchTrans = max(cch, cchTrans);
|
|
}
|
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
exit_gracefully:
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
SysFreeString(*pstrTranslatedName);
|
|
*pstrTranslatedName = NULL;
|
|
}
|
|
|
|
TraceLeaveResult(hr);
|
|
#else
|
|
return E_NOTIMPL;
|
|
#endif // _WIN32_WINNT >= 0x0500
|
|
}
|
|
|
|
|
|
void
|
|
CSidCache::GetUserFriendlyName(LPCTSTR pszSamLogonName,
|
|
LPCTSTR pszSamAccountName,
|
|
LPCTSTR pszServer,
|
|
BOOL bUseSamCompatibleInfo,
|
|
BOOL bIsStandalone,
|
|
BSTR *pstrLogonName,
|
|
BSTR *pstrDisplayName)
|
|
{
|
|
BSTR strFQDN = NULL;
|
|
|
|
TraceEnter(TRACE_SIDCACHE, "CSidCache::GetUserFriendlyName");
|
|
TraceAssert(NULL != pszSamLogonName);
|
|
|
|
//
|
|
// Start by getting the FQDN. Cracking is most efficient when the
|
|
// FQDN is the starting point.
|
|
//
|
|
// TranslateName takes a while to complete, so bUseSamCompatibleInfo
|
|
// should be TRUE whenever possible, e.g. for local accounts on a non-DC
|
|
// or anything where we know a FQDN doesn't exist.
|
|
//
|
|
if (!bUseSamCompatibleInfo &&
|
|
FAILED(TranslateNameInternal(pszSamLogonName,
|
|
NameSamCompatible,
|
|
NameFullyQualifiedDN,
|
|
&strFQDN)))
|
|
{
|
|
//
|
|
// No FQDN available for this account. Must be an NT4
|
|
// account. Return SAM-compatible info to the caller.
|
|
//
|
|
bUseSamCompatibleInfo = TRUE;
|
|
}
|
|
|
|
if (NULL != pstrLogonName)
|
|
{
|
|
*pstrLogonName = NULL;
|
|
|
|
if (!bUseSamCompatibleInfo)
|
|
{
|
|
TranslateNameInternal(strFQDN,
|
|
NameFullyQualifiedDN,
|
|
NameUserPrincipal,
|
|
pstrLogonName);
|
|
}
|
|
}
|
|
|
|
if (NULL != pstrDisplayName)
|
|
{
|
|
*pstrDisplayName = NULL;
|
|
|
|
if (bUseSamCompatibleInfo ||
|
|
FAILED(TranslateNameInternal(strFQDN,
|
|
NameFullyQualifiedDN,
|
|
NameDisplay,
|
|
pstrDisplayName)))
|
|
{
|
|
*pstrDisplayName = GetNT4DisplayName(pszSamLogonName,
|
|
pszSamAccountName,
|
|
pszServer,
|
|
bIsStandalone);
|
|
}
|
|
}
|
|
|
|
SysFreeString(strFQDN);
|
|
|
|
TraceLeaveVoid();
|
|
}
|
|
|
|
|
|
BOOL
|
|
CSidCache::InternalLookupSids(HDPA hSids,
|
|
LPCTSTR pszServer,
|
|
LPSECURITYINFO2 psi2,
|
|
HDPA hEntryList,
|
|
HWND hWndNotify,
|
|
UINT uMsgNotify)
|
|
{
|
|
ULONG cSids;
|
|
HDPA hUnknownSids;
|
|
PSID_CACHE_ENTRY pEntry;
|
|
ULONG i;
|
|
|
|
TraceEnter(TRACE_SIDCACHE, "CSidCache::InternalLookupSids");
|
|
TraceAssert(hSids != NULL);
|
|
|
|
if (hSids == NULL)
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
TraceLeaveValue(FALSE);
|
|
}
|
|
|
|
cSids = DPA_GetPtrCount(hSids);
|
|
TraceAssert(0 != cSids);
|
|
|
|
hUnknownSids = DPA_Create(4);
|
|
|
|
if (NULL == hUnknownSids)
|
|
TraceLeaveValue(FALSE);
|
|
|
|
//
|
|
// See if any exist in the cache already
|
|
//
|
|
for (i = 0; i < cSids; i++)
|
|
{
|
|
pEntry = FindSid((PSID)DPA_FastGetPtr(hSids, i));
|
|
|
|
if (pEntry)
|
|
{
|
|
if (hWndNotify)
|
|
PostMessage(hWndNotify, uMsgNotify, 0, (LPARAM)pEntry->pSid);
|
|
else if (hEntryList)
|
|
DPA_AppendPtr(hEntryList, pEntry);
|
|
}
|
|
else
|
|
DPA_AppendPtr(hUnknownSids, DPA_FastGetPtr(hSids, i));
|
|
}
|
|
|
|
//
|
|
// Call LSA to lookup any that we don't have cached
|
|
//
|
|
if (0 != DPA_GetPtrCount(hUnknownSids))
|
|
{
|
|
if (!psi2 ||
|
|
FAILED(LookupSidsFromObject(hUnknownSids, psi2, hEntryList)))
|
|
{
|
|
LookupSidsHelper(hUnknownSids,
|
|
pszServer,
|
|
hEntryList,
|
|
hWndNotify,
|
|
uMsgNotify);
|
|
}
|
|
}
|
|
|
|
DPA_Destroy(hUnknownSids);
|
|
|
|
TraceLeaveValue(TRUE);
|
|
}
|
|
|
|
#if(_WIN32_WINNT >= 0x0500)
|
|
#include <adsnms.h> // USER_CLASS_NAME, etc.
|
|
#else
|
|
#define COMPUTER_CLASS_NAME TEXT("Computer")
|
|
#define USER_CLASS_NAME TEXT("User")
|
|
#define GROUP_CLASS_NAME TEXT("Group")
|
|
#define GLOBALGROUP_CLASS_NAME TEXT("GlobalGroup")
|
|
#define LOCALGROUP_CLASS_NAME TEXT("LocalGroup")
|
|
#endif
|
|
|
|
TCHAR const c_szForeignSecurityPrincipal[] = TEXT("foreignSecurityPrincipal");
|
|
|
|
static const struct
|
|
{
|
|
LPCTSTR pszClass;
|
|
SID_NAME_USE sidType;
|
|
} c_aSidClasses[] =
|
|
{
|
|
USER_CLASS_NAME, SidTypeUser,
|
|
GROUP_CLASS_NAME, SidTypeGroup,
|
|
GLOBALGROUP_CLASS_NAME, SidTypeGroup,
|
|
LOCALGROUP_CLASS_NAME, SidTypeGroup,
|
|
COMPUTER_CLASS_NAME, SidTypeComputer,
|
|
c_szForeignSecurityPrincipal, SidTypeGroup,
|
|
};
|
|
|
|
SID_NAME_USE
|
|
GetSidType(PSID pSid, LPCTSTR pszClass)
|
|
{
|
|
SID_NAME_USE sidType = SidTypeUnknown;
|
|
|
|
TraceEnter(TRACE_SIDCACHE, "GetSidType");
|
|
|
|
if (pSid)
|
|
{
|
|
TraceAssert(IsValidSid(pSid));
|
|
|
|
if (EqualSystemSid(pSid, UI_SID_World) || IsCreatorSid(pSid))
|
|
TraceLeaveValue(SidTypeWellKnownGroup);
|
|
|
|
if (IsAliasSid(pSid))
|
|
TraceLeaveValue(SidTypeAlias);
|
|
|
|
if (*GetSidSubAuthorityCount(pSid) == 1 && IsNTAuthority(pSid))
|
|
{
|
|
DWORD sa = *GetSidSubAuthority(pSid, 0);
|
|
if (sa && sa <= SECURITY_RESTRICTED_CODE_RID && sa != SECURITY_LOGON_IDS_RID)
|
|
TraceLeaveValue(SidTypeWellKnownGroup);
|
|
if (SECURITY_LOCAL_SYSTEM_RID == sa)
|
|
TraceLeaveValue(SidTypeWellKnownGroup);
|
|
}
|
|
}
|
|
|
|
if (pszClass)
|
|
{
|
|
// Didn't recognize the SID, try the class name
|
|
for (int i = 0; i < ARRAYSIZE(c_aSidClasses); i++)
|
|
{
|
|
if (!lstrcmpi(pszClass, c_aSidClasses[i].pszClass))
|
|
TraceLeaveValue(c_aSidClasses[i].sidType);
|
|
}
|
|
Trace((TEXT("Unexpected class type: %s"), pszClass));
|
|
}
|
|
|
|
// Don't know what type it is, so take a guess. This is just
|
|
// for picking an icon, so it doesn't matter too much.
|
|
TraceLeaveValue(SidTypeUser); // SidTypeGroup would be just as valid
|
|
}
|
|
|
|
HRESULT
|
|
CSidCache::LookupSidsFromObject(HDPA hSids,
|
|
LPSECURITYINFO2 psi2,
|
|
HDPA hEntryList)
|
|
{
|
|
HRESULT hr;
|
|
ULONG cSids;
|
|
LPDATAOBJECT pdoNames = NULL;
|
|
STGMEDIUM medium = {0};
|
|
FORMATETC fe = { (CLIPFORMAT)g_cfSidInfoList, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
|
PSID_INFO_LIST pSidList = NULL;
|
|
UINT i;
|
|
|
|
TraceEnter(TRACE_SIDCACHE, "CSidCache::LookupSidsFromObject");
|
|
TraceAssert(hSids != NULL);
|
|
TraceAssert(psi2 != NULL);
|
|
|
|
cSids = DPA_GetPtrCount(hSids);
|
|
TraceAssert(cSids != 0);
|
|
|
|
hr = psi2->LookupSids(cSids, DPA_GetPtrPtr(hSids), &pdoNames);
|
|
FailGracefully(hr, "ISecurityInformation2::LookupSids failed");
|
|
|
|
hr = pdoNames->GetData(&fe, &medium);
|
|
FailGracefully(hr, "Unable to get CFSTR_ACLUI_SID_INFO_LIST from DataObject");
|
|
|
|
pSidList = (PSID_INFO_LIST)GlobalLock(medium.hGlobal);
|
|
if (!pSidList)
|
|
ExitGracefully(hr, E_FAIL, "Unable to lock stgmedium.hGlobal");
|
|
|
|
TraceAssert(pSidList->cItems > 0);
|
|
|
|
for (i = 0; i < pSidList->cItems; i++)
|
|
{
|
|
PSID_CACHE_ENTRY pEntry = MakeEntry(pSidList->aSidInfo[i].pSid,
|
|
GetSidType(pSidList->aSidInfo[i].pSid,
|
|
pSidList->aSidInfo[i].pwzClass),
|
|
pSidList->aSidInfo[i].pwzCommonName,
|
|
pSidList->aSidInfo[i].pwzUPN);
|
|
if (pEntry)
|
|
{
|
|
if (AddEntry(pEntry))
|
|
{
|
|
if (hEntryList)
|
|
DPA_AppendPtr(hEntryList, pEntry);
|
|
}
|
|
else
|
|
LocalFree(pEntry);
|
|
}
|
|
}
|
|
|
|
exit_gracefully:
|
|
|
|
if (pSidList)
|
|
GlobalUnlock(medium.hGlobal);
|
|
ReleaseStgMedium(&medium);
|
|
DoRelease(pdoNames);
|
|
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
BOOL
|
|
CSidCache::LookupSidsHelper(HDPA hSids,
|
|
LPCTSTR pszServer,
|
|
HDPA hEntryList,
|
|
HWND hWndNotify,
|
|
UINT uMsgNotify,
|
|
BOOL bSecondTry)
|
|
{
|
|
BOOL fResult = FALSE;
|
|
ULONG cSids;
|
|
LSA_HANDLE hlsa = NULL;
|
|
PLSA_REFERENCED_DOMAIN_LIST pRefDomains = NULL;
|
|
PLSA_TRANSLATED_NAME pTranslatedNames = NULL;
|
|
DWORD dwStatus;
|
|
BOOL bIsDC = FALSE;
|
|
BOOL bIsStandalone = FALSE;
|
|
HDPA hUnknownSids = NULL;
|
|
|
|
TraceEnter(TRACE_SIDCACHE, "CSidCache::LookupSidsHelper");
|
|
TraceAssert(hSids != NULL);
|
|
|
|
cSids = DPA_GetPtrCount(hSids);
|
|
if (!cSids)
|
|
TraceLeaveValue(FALSE);
|
|
|
|
//
|
|
// Call LSA to lookup SIDs for the names
|
|
//
|
|
hlsa = GetLSAConnection(pszServer, POLICY_LOOKUP_NAMES);
|
|
if (NULL == hlsa && NULL != pszServer && !bSecondTry)
|
|
{
|
|
// Try the local machine
|
|
pszServer = NULL;
|
|
hlsa = GetLSAConnection(NULL, POLICY_LOOKUP_NAMES);
|
|
}
|
|
if (NULL == hlsa)
|
|
TraceLeaveValue(FALSE);
|
|
|
|
dwStatus = LsaLookupSids(hlsa,
|
|
cSids,
|
|
DPA_GetPtrPtr(hSids),
|
|
&pRefDomains,
|
|
&pTranslatedNames);
|
|
|
|
bIsStandalone = IsStandalone(pszServer, &bIsDC);
|
|
|
|
if (STATUS_SUCCESS == dwStatus || STATUS_SOME_NOT_MAPPED == dwStatus)
|
|
{
|
|
TraceAssert(pTranslatedNames);
|
|
TraceAssert(pRefDomains);
|
|
|
|
//
|
|
// Build cache entries with NT4 style names
|
|
//
|
|
for (ULONG i = 0; i < cSids; i++)
|
|
{
|
|
BOOL bTryUPN = TRUE;
|
|
BSTR strLogonName = NULL;
|
|
BSTR strDisplayName = NULL;
|
|
LPTSTR pszDeletedAccount = NULL;
|
|
LPTSTR pszSID = NULL;
|
|
|
|
PLSA_TRANSLATED_NAME pLsaName = &pTranslatedNames[i];
|
|
PLSA_TRUST_INFORMATION pLsaDomain = NULL;
|
|
PSID pSid = DPA_FastGetPtr(hSids, i);
|
|
|
|
TCHAR szAccountName[MAX_PATH];
|
|
TCHAR szDomainName[MAX_PATH];
|
|
|
|
BOOL bNoCache = FALSE;
|
|
|
|
szAccountName[0] = TEXT('\0');
|
|
szDomainName[0] = TEXT('\0');
|
|
|
|
// Get the referenced domain, if any
|
|
if (pLsaName->DomainIndex >= 0 && pRefDomains)
|
|
{
|
|
TraceAssert((ULONG)pLsaName->DomainIndex < pRefDomains->Entries);
|
|
pLsaDomain = &pRefDomains->Domains[pLsaName->DomainIndex];
|
|
}
|
|
|
|
// Make NULL-terminated copies of the domain and account name strings
|
|
CopyUnicodeString(szAccountName, ARRAYSIZE(szAccountName), &pLsaName->Name);
|
|
if (pLsaDomain)
|
|
CopyUnicodeString(szDomainName, ARRAYSIZE(szDomainName), &pLsaDomain->Name);
|
|
|
|
// Some optimization to avoid TranslateName when possible
|
|
if (!bIsDC)
|
|
{
|
|
if (bIsStandalone)
|
|
{
|
|
// Non-DC, standalone, therefore no UPN
|
|
bTryUPN = FALSE;
|
|
}
|
|
else if (SidTypeUser == pLsaName->Use)
|
|
{
|
|
TCHAR szTargetDomain[DNLEN];
|
|
szTargetDomain[0] = TEXT('\0');
|
|
GetDomainName(pszServer, szTargetDomain, ARRAYSIZE(szTargetDomain));
|
|
if (CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT,
|
|
NORM_IGNORECASE,
|
|
szTargetDomain,
|
|
-1,
|
|
szDomainName,
|
|
-1))
|
|
{
|
|
// Local account on non-DC, therefore no UPN
|
|
bTryUPN = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Build NT4 "domain\user" style name
|
|
//
|
|
if (szDomainName[0] != TEXT('\0'))
|
|
{
|
|
lstrcat(szDomainName, TEXT("\\"));
|
|
lstrcat(szDomainName, szAccountName);
|
|
}
|
|
|
|
// What we've got so far is our baseline.
|
|
// Adjust these based on SID type.
|
|
LPTSTR pszName = szAccountName;
|
|
LPTSTR pszLogonName = szDomainName;
|
|
|
|
switch (pLsaName->Use)
|
|
{
|
|
case SidTypeUser: // 1
|
|
// Get "User Principal Name" etc.
|
|
GetUserFriendlyName(pszLogonName,
|
|
pszName,
|
|
pszServer,
|
|
!bTryUPN,
|
|
bIsStandalone,
|
|
&strLogonName,
|
|
&strDisplayName);
|
|
if (strLogonName)
|
|
pszLogonName = strLogonName;
|
|
if (strDisplayName)
|
|
pszName = strDisplayName;
|
|
break;
|
|
|
|
case SidTypeGroup: // 2
|
|
case SidTypeDomain: // 3
|
|
// nothing
|
|
break;
|
|
|
|
case SidTypeAlias: // 4
|
|
if (!IsAliasSid(pSid))
|
|
{
|
|
// Sometimes get SidTypeAlias for non-BUILTIN sids,
|
|
// e.g. Domain Local Groups. Treat these as groups
|
|
// so we don't replace the Domain name.
|
|
// Raid #383755
|
|
pLsaName->Use = SidTypeGroup;
|
|
break;
|
|
}
|
|
// else Fall Through
|
|
case SidTypeWellKnownGroup: // 5
|
|
// No logon name for these
|
|
pszLogonName = NULL;
|
|
break;
|
|
|
|
case SidTypeDeletedAccount: // 6
|
|
// Display "Account Deleted(Sid)"
|
|
ConvertSidToStringSid(pSid, &pszSID);
|
|
if(FormatStringID(&pszDeletedAccount,
|
|
::hModule,
|
|
IDS_SID_DELETED_1,
|
|
pszSID))
|
|
{
|
|
if (pszSID)
|
|
LocalFreeString(&pszSID);
|
|
if (pszDeletedAccount)
|
|
pszName = pszDeletedAccount;
|
|
pszLogonName = NULL;
|
|
}
|
|
else
|
|
{
|
|
bNoCache = TRUE;
|
|
}
|
|
break;
|
|
|
|
case SidTypeInvalid: // 7
|
|
bNoCache = TRUE;
|
|
break;
|
|
|
|
case SidTypeUnknown: // 8
|
|
// Some SIDs can only be looked up on a DC, so
|
|
// if pszServer is not a DC, remember them and
|
|
// look them up on a DC after this loop is done.
|
|
if (!bSecondTry && !bIsStandalone && !bIsDC)
|
|
{
|
|
if (!hUnknownSids)
|
|
hUnknownSids = DPA_Create(4);
|
|
if (hUnknownSids)
|
|
DPA_AppendPtr(hUnknownSids, pSid);
|
|
|
|
bNoCache = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// Display "Account Unknown(Sid)"
|
|
ConvertSidToStringSid(pSid, &pszSID);
|
|
if(FormatStringID(&pszDeletedAccount,
|
|
::hModule,
|
|
IDS_SID_UNKNOWN_1,
|
|
pszSID))
|
|
{
|
|
if (pszSID)
|
|
LocalFreeString(&pszSID);
|
|
if (pszDeletedAccount)
|
|
pszName = pszDeletedAccount;
|
|
pszLogonName = NULL;
|
|
}
|
|
else
|
|
{
|
|
bNoCache = TRUE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
#if(_WIN32_WINNT >= 0x0500)
|
|
case SidTypeComputer: // 9
|
|
if (*pszName)
|
|
{
|
|
// Strip the trailing '$'
|
|
int nLen = lstrlen(pszName);
|
|
if (nLen && pszName[nLen-1] == TEXT('$'))
|
|
{
|
|
pszName[nLen-1] = TEXT('\0');
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
if (!bNoCache)
|
|
{
|
|
//
|
|
// Make a cache entry and save it
|
|
//
|
|
PSID_CACHE_ENTRY pEntry = MakeEntry(pSid,
|
|
pLsaName->Use,
|
|
pszName,
|
|
pszLogonName);
|
|
if (pEntry)
|
|
{
|
|
if (AddEntry(pEntry))
|
|
{
|
|
fResult = TRUE; // we added something to the cache
|
|
|
|
if (hWndNotify)
|
|
PostMessage(hWndNotify, uMsgNotify, 0, (LPARAM)pEntry->pSid);
|
|
else if (hEntryList)
|
|
DPA_AppendPtr(hEntryList, pEntry);
|
|
}
|
|
else
|
|
LocalFree(pEntry);
|
|
}
|
|
}
|
|
|
|
if (strLogonName)
|
|
SysFreeString(strLogonName);
|
|
if (strDisplayName)
|
|
SysFreeString(strDisplayName);
|
|
LocalFreeString(&pszDeletedAccount);
|
|
}
|
|
}
|
|
else if (STATUS_NONE_MAPPED == dwStatus && !bSecondTry && !bIsStandalone && !bIsDC)
|
|
{
|
|
hUnknownSids = DPA_Clone(hSids, NULL);
|
|
}
|
|
|
|
// Cleanup
|
|
if (pTranslatedNames)
|
|
LsaFreeMemory(pTranslatedNames);
|
|
if (pRefDomains)
|
|
LsaFreeMemory(pRefDomains);
|
|
LsaClose(hlsa);
|
|
|
|
|
|
if (hUnknownSids)
|
|
{
|
|
//
|
|
// Some (or all) SIDs were unknown on the target machine,
|
|
// try a DC for the target machine's primary domain.
|
|
//
|
|
// This typically happens for certain Alias SIDs, such
|
|
// as Print Operators and System Operators, for which LSA
|
|
// only returns names if the lookup is done on a DC.
|
|
//
|
|
LPTSTR pszDC = NULL;
|
|
|
|
TraceAssert(!bSecondTry);
|
|
|
|
// We don't bother trying if standalone, and don't
|
|
// do this if the target machine is already a DC.
|
|
TraceAssert(!bIsStandalone && !bIsDC);
|
|
|
|
_GetDcName(pszServer, NULL, &pszDC);
|
|
|
|
if (pszDC)
|
|
{
|
|
// Recurse
|
|
if (LookupSidsHelper(hUnknownSids,
|
|
pszDC,
|
|
hEntryList,
|
|
hWndNotify,
|
|
uMsgNotify,
|
|
TRUE))
|
|
{
|
|
fResult = TRUE;
|
|
}
|
|
LocalFree(pszDC);
|
|
}
|
|
|
|
DPA_Destroy(hUnknownSids);
|
|
}
|
|
|
|
TraceLeaveValue(fResult);
|
|
}
|
|
|
|
|
|
#if(_WIN32_WINNT >= 0x0500)
|
|
|
|
BSTR GetNT4AccountName(LPTSTR pszWinNTPath)
|
|
{
|
|
// pszWinNTPath is expected to look like
|
|
// "WinNT://domain/user"
|
|
// or
|
|
// "WinNT://domain/machine/user"
|
|
//
|
|
// The "WinNT://" part is optional.
|
|
//
|
|
// In either case, we want the last 2 elements,
|
|
// e.g. "domain/user" and "machine/user".
|
|
//
|
|
// The approach is to find the next to last '/' and add 1.
|
|
// If there are less than 2 slashes, return the original string.
|
|
|
|
BSTR strResult = NULL;
|
|
LPTSTR pszResult = pszWinNTPath;
|
|
if (pszWinNTPath)
|
|
{
|
|
LPTSTR pszSlash = StrRChr(pszWinNTPath, pszWinNTPath + lstrlen(pszWinNTPath) - 1, TEXT('/'));
|
|
if (pszSlash)
|
|
{
|
|
pszSlash = StrRChr(pszWinNTPath, pszSlash-1, TEXT('/'));
|
|
if (pszSlash)
|
|
pszResult = pszSlash + 1;
|
|
}
|
|
}
|
|
|
|
if (pszResult)
|
|
{
|
|
strResult = SysAllocString(pszResult);
|
|
if (strResult)
|
|
{
|
|
// At this point, there is at most one forward slash
|
|
// in the string. Convert it to a backslash.
|
|
LPTSTR pszSlash = StrChr(strResult, TEXT('/'));
|
|
if (pszSlash)
|
|
*pszSlash = TEXT('\\');
|
|
}
|
|
}
|
|
|
|
return strResult;
|
|
}
|
|
|
|
BOOL
|
|
_LookupName(LPCTSTR pszServer,
|
|
LPCTSTR pszAccount,
|
|
PSID *ppSid,
|
|
SID_NAME_USE *pSidType)
|
|
{
|
|
BOOL fResult = FALSE;
|
|
BYTE buffer[sizeof(SID) + SID_MAX_SUB_AUTHORITIES*sizeof(ULONG)];
|
|
PSID pSid = (PSID)buffer;
|
|
DWORD cbSid = sizeof(buffer);
|
|
TCHAR szDomain[MAX_PATH];
|
|
DWORD cchDomain = ARRAYSIZE(szDomain);
|
|
SID_NAME_USE sidType;
|
|
|
|
fResult = LookupAccountName(pszServer,
|
|
pszAccount,
|
|
pSid,
|
|
&cbSid,
|
|
szDomain,
|
|
&cchDomain,
|
|
&sidType);
|
|
if (fResult)
|
|
{
|
|
*ppSid = LocalAllocSid(pSid);
|
|
if (*ppSid)
|
|
{
|
|
if (pSidType)
|
|
*pSidType = sidType;
|
|
}
|
|
else
|
|
fResult = FALSE;
|
|
}
|
|
|
|
return fResult;
|
|
}
|
|
|
|
BOOL
|
|
CSidCache::InternalLookupNames(PDS_SELECTION_LIST pDsSelList,
|
|
LPCTSTR pszServer,
|
|
HDPA hEntryList,
|
|
BOOL bStandalone)
|
|
{
|
|
BOOL fResult = FALSE;
|
|
ULONG cNames;
|
|
HDPA hSids = NULL;
|
|
PSID_CACHE_ENTRY pEntry;
|
|
ULONG i;
|
|
ULONG cNoSID = 0;
|
|
HRESULT hrCom = E_FAIL;
|
|
IADsPathname *pPath = NULL;
|
|
|
|
TraceEnter(TRACE_SIDCACHE, "CSidCache::InternalLookupNames");
|
|
TraceAssert(pDsSelList != NULL);
|
|
TraceAssert(hEntryList != NULL);
|
|
|
|
if (pDsSelList == NULL || hEntryList == NULL)
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
TraceLeaveValue(FALSE);
|
|
}
|
|
|
|
cNames = pDsSelList->cItems;
|
|
TraceAssert(cNames != 0);
|
|
|
|
if (0 == cNames)
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
TraceLeaveValue(FALSE);
|
|
}
|
|
|
|
hSids = DPA_Create(4);
|
|
|
|
for (i = 0; i < cNames; i++)
|
|
{
|
|
PSID pSid = NULL;
|
|
PSID pSidFree = NULL;
|
|
LPVARIANT pvarSid = pDsSelList->aDsSelection[i].pvarFetchedAttributes;
|
|
SID_NAME_USE sidType = SidTypeUnknown;
|
|
BSTR strNT4Name = NULL;
|
|
|
|
if (NULL == pvarSid || (VT_ARRAY | VT_UI1) != V_VT(pvarSid)
|
|
|| FAILED(SafeArrayAccessData(V_ARRAY(pvarSid), &pSid)))
|
|
{
|
|
// If there's no SID, then we can't use it in an ACL
|
|
Trace((TEXT("No SID returned for %s"), pDsSelList->aDsSelection[i].pwzADsPath));
|
|
|
|
// If it's the NT provider, try to lookup the SID by name
|
|
if (CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT,
|
|
0,
|
|
c_szNTProvider,
|
|
NTPROV_LEN,
|
|
pDsSelList->aDsSelection[i].pwzADsPath,
|
|
NTPROV_LEN))
|
|
{
|
|
strNT4Name = GetNT4AccountName(pDsSelList->aDsSelection[i].pwzADsPath + NTPROV_LEN);
|
|
if (strNT4Name)
|
|
{
|
|
Trace((TEXT("Using LSA to lookup SID for %s"), strNT4Name));
|
|
if (_LookupName(pszServer, strNT4Name, &pSidFree, &sidType))
|
|
{
|
|
pSid = pSidFree;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NULL == pSid)
|
|
{
|
|
cNoSID++;
|
|
continue;
|
|
}
|
|
}
|
|
TraceAssert(NULL != pSid);
|
|
|
|
// Is it already in the cache?
|
|
pEntry = FindSid(pSid);
|
|
if (pEntry)
|
|
{
|
|
DPA_AppendPtr(hEntryList, pEntry);
|
|
}
|
|
else
|
|
{
|
|
// Not cached, try to make an entry using the info returned
|
|
// by the object picker.
|
|
if (SidTypeUnknown == sidType)
|
|
sidType = GetSidType(pSid, pDsSelList->aDsSelection[i].pwzClass);
|
|
|
|
if (!lstrcmpi(c_szForeignSecurityPrincipal, pDsSelList->aDsSelection[i].pwzClass))
|
|
{
|
|
// Object picker returns non-localized names for these (the
|
|
// DS Configuration Container is not localized). Look up the
|
|
// localized name from LSA. 175278
|
|
|
|
// This happens automatically below (pEntry is NULL).
|
|
}
|
|
else if (SidTypeAlias == sidType || SidTypeWellKnownGroup == sidType)
|
|
{
|
|
// Only need the name
|
|
pEntry = MakeEntry(pSid,
|
|
sidType,
|
|
pDsSelList->aDsSelection[i].pwzName,
|
|
NULL);
|
|
}
|
|
else if (pDsSelList->aDsSelection[i].pwzUPN && *pDsSelList->aDsSelection[i].pwzUPN)
|
|
{
|
|
// We have both name and UPN
|
|
pEntry = MakeEntry(pSid,
|
|
sidType,
|
|
pDsSelList->aDsSelection[i].pwzName,
|
|
pDsSelList->aDsSelection[i].pwzUPN);
|
|
}
|
|
else if (CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT,
|
|
0,
|
|
c_szNTProvider,
|
|
NTPROV_LEN,
|
|
pDsSelList->aDsSelection[i].pwzADsPath,
|
|
NTPROV_LEN))
|
|
{
|
|
// It's downlevel ("WinNT://blah")
|
|
if (NULL == strNT4Name)
|
|
strNT4Name = GetNT4AccountName(pDsSelList->aDsSelection[i].pwzADsPath + NTPROV_LEN);
|
|
if (strNT4Name)
|
|
{
|
|
// We have the NT4 name, now look for a Friendly Name
|
|
BSTR strDisplay = GetNT4DisplayName(strNT4Name,
|
|
pDsSelList->aDsSelection[i].pwzName,
|
|
pszServer,
|
|
bStandalone);
|
|
pEntry = MakeEntry(pSid,
|
|
sidType,
|
|
strDisplay ? strDisplay : pDsSelList->aDsSelection[i].pwzName,
|
|
strNT4Name);
|
|
SysFreeString(strDisplay);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// It's not a downlevel, so it must be
|
|
// 1. WellKnown/Universal (no ADsPath)
|
|
// or
|
|
// 2. Uplevel ("GC:" or "LDAP:") but
|
|
// has no UPN
|
|
//
|
|
// If it has an ADs path, try to get an
|
|
// NT4 name such as "NTDEV\Domain Users".
|
|
//
|
|
// Note that wellknown things such "Authenticated User"
|
|
// can fall under either 1 or 2 above, depending on what
|
|
// scope it was selected from. That's why we try to pick
|
|
// them off higher up.
|
|
TraceAssert(NULL == strNT4Name);
|
|
if (pDsSelList->aDsSelection[i].pwzADsPath &&
|
|
*pDsSelList->aDsSelection[i].pwzADsPath)
|
|
{
|
|
// DsCrackNames doesn't accept full ADs paths, so use
|
|
// IADsPathname to retrieve the DN (no provider/server).
|
|
if (FAILED(hrCom))
|
|
hrCom = CoInitialize(NULL);
|
|
if (!pPath)
|
|
{
|
|
CoCreateInstance(CLSID_Pathname,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_IADsPathname,
|
|
(LPVOID*)&pPath);
|
|
}
|
|
if (pPath)
|
|
{
|
|
BSTR strT;
|
|
if (SUCCEEDED(pPath->Set(pDsSelList->aDsSelection[i].pwzADsPath,
|
|
ADS_SETTYPE_FULL)))
|
|
{
|
|
if (SUCCEEDED(pPath->Retrieve(ADS_FORMAT_X500_DN,
|
|
&strT)))
|
|
{
|
|
// Try to get an NT4 account name
|
|
TranslateNameInternal(strT,
|
|
NameFullyQualifiedDN,
|
|
NameSamCompatible,
|
|
&strNT4Name);
|
|
SysFreeString(strT);
|
|
}
|
|
if (!strNT4Name)
|
|
{
|
|
// Retrieve or CrackName failed. Try to build
|
|
// an NT4-style name from the server name.
|
|
if (SUCCEEDED(pPath->Retrieve(ADS_FORMAT_SERVER,
|
|
&strT)))
|
|
{
|
|
TCHAR szNT4Name[MAX_PATH];
|
|
GetDomainName(strT, szNT4Name, ARRAYSIZE(szNT4Name));
|
|
PathAppend(szNT4Name, pDsSelList->aDsSelection[i].pwzName);
|
|
strNT4Name = SysAllocString(szNT4Name);
|
|
SysFreeString(strT);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pEntry = MakeEntry(pSid,
|
|
sidType,
|
|
pDsSelList->aDsSelection[i].pwzName,
|
|
strNT4Name);
|
|
}
|
|
|
|
//
|
|
// Do we have a cache entry yet?
|
|
//
|
|
if (pEntry)
|
|
{
|
|
if (AddEntry(pEntry))
|
|
{
|
|
DPA_AppendPtr(hEntryList, pEntry);
|
|
}
|
|
else
|
|
{
|
|
LocalFree(pEntry);
|
|
pEntry = NULL;
|
|
}
|
|
}
|
|
|
|
if (!pEntry && hSids)
|
|
{
|
|
// Look up the SID the hard way
|
|
Trace((TEXT("Using LSA to lookup %s"), pDsSelList->aDsSelection[i].pwzADsPath));
|
|
PSID pSidCopy = LocalAllocSid(pSid);
|
|
if (pSidCopy)
|
|
{
|
|
DPA_AppendPtr(hSids, pSidCopy);
|
|
}
|
|
}
|
|
}
|
|
|
|
SysFreeString(strNT4Name);
|
|
|
|
if (pSidFree)
|
|
LocalFree(pSidFree);
|
|
else
|
|
SafeArrayUnaccessData(V_ARRAY(pvarSid));
|
|
}
|
|
|
|
TraceAssert(0 == cNoSID);
|
|
|
|
//
|
|
// Call LSA to lookup names for the SIDs that aren't cached yet
|
|
//
|
|
if (hSids && 0 != DPA_GetPtrCount(hSids))
|
|
LookupSidsHelper(hSids, pszServer, hEntryList);
|
|
|
|
if (NULL != hSids)
|
|
DestroyDPA(hSids);
|
|
|
|
DoRelease(pPath);
|
|
|
|
if (SUCCEEDED(hrCom))
|
|
CoUninitialize();
|
|
|
|
TraceLeaveValue(TRUE);
|
|
}
|
|
|
|
#endif // #if(_WIN32_WINNT >= 0x0500)
|
|
|
|
|
|
DWORD WINAPI
|
|
CSidCache::InitThread(LPVOID pvThreadData)
|
|
{
|
|
PSIDCACHE pThis = (PSIDCACHE)pvThreadData;
|
|
|
|
// Our caller already gave us a ref on the dll to prevent the race window where
|
|
// we are created but we the dll is freed before we can call LoadLibrary()
|
|
// HINSTANCE hInstThisDll = LoadLibrary(c_szDllName);
|
|
|
|
TraceEnter(TRACE_SIDCACHE, "CSidCache::InitThread");
|
|
|
|
if (pThis)
|
|
{
|
|
// Lookup some well-known SIDs to pre-load the cache
|
|
HDPA hSids;
|
|
hSids = DPA_Create(COUNT_SYSTEM_SID_TYPES);
|
|
if (hSids)
|
|
{
|
|
for (int i = 0; i < COUNT_SYSTEM_SID_TYPES; i++)
|
|
{
|
|
DPA_AppendPtr(hSids, QuerySystemSid((UI_SystemSid)i));
|
|
}
|
|
|
|
pThis->LookupSidsHelper(hSids, NULL, NULL, NULL, 0);
|
|
|
|
DPA_Destroy(hSids);
|
|
}
|
|
|
|
pThis->Release();
|
|
}
|
|
|
|
TraceLeave();
|
|
FreeLibraryAndExitThread(GetModuleHandle(c_szDllName), 0);
|
|
return 0;
|
|
}
|