//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation, 1997 - 1999 // // File: sidcache.cpp // // This file contains the implementation of a SID/Name cache. // //-------------------------------------------------------------------------- #include "aclpriv.h" #include // DsGetDcName #include #define SECURITY_WIN32 #include // TranslateName #include // NetApiBufferFree #include // 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() { ASSERT( 0 != m_cRef ); ULONG cRef = InterlockedDecrement(&m_cRef); if ( 0 == cRef ) { delete this; } return 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); } 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); } 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; 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); } 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 + 1]; lstrcpyn(szDomain, pszAccount, min((size_t)(pszT - pszAccount + 1), ARRAYSIZE(szDomain))); // See if we can use pszServer for NetUserGetInfo TCHAR szAccountDomain[DNLEN +1]; 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 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); } 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); } #include // USER_CLASS_NAME, etc. 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 bWellKnown) { 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 + 1]; 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')) { StringCchCat(szDomainName,ARRAYSIZE(szDomainName),TEXT("\\")); StringCchCat(szDomainName, ARRAYSIZE(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 if(bWellKnown) 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; case SidTypeComputer: // 9 if (*pszName) { // Strip the trailing '$' int nLen = lstrlen(pszName); if (nLen && pszName[nLen-1] == TEXT('$')) { pszName[nLen-1] = TEXT('\0'); } } break; } 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); } 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(AutoBstr(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); } 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; //-1 becuase we donot want to create cache for Admininstrators sid hSids = DPA_Create(COUNT_SYSTEM_SID_TYPES -1); if (hSids) { for (int i = 0; i < (COUNT_SYSTEM_SID_TYPES -1); i++) { DPA_AppendPtr(hSids, QuerySystemSid((UI_SystemSid)i)); } pThis->LookupSidsHelper(hSids, NULL, NULL, NULL, 0,FALSE,TRUE); DPA_Destroy(hSids); } pThis->Release(); } TraceLeave(); FreeLibraryAndExitThread(GetModuleHandle(c_szDllName), 0); return 0; }