windows-nt/Source/XPSP1/NT/com/ole32/dcomss/olescm/winsta.cxx
2020-09-26 16:20:57 +08:00

530 lines
18 KiB
C++

//+-------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1997.
//
// File: winsta.cxx
//
// Contents: winstation caching code
//
//--------------------------------------------------------------------------
#include "act.hxx"
#ifndef _CHICAGO_
//
// Private Types.
//
//-------------------------------------------------------------------------
// Definitions for Runas Cache
// NOTE: This exists to overcome limitation on NT Winstations of 15
// Reusing same token will map to same winstation. We also cache
// winstation once we get it.
//-------------------------------------------------------------------------
const DWORD RUN_AS_TIMEOUT = 360000;
#define RUNAS_CACHE_SIZE 200
// The pUser and pGroups fields are assumed to point to one contiguous memory
// block that can be freed by a single call to Free(pUser).
WCHAR wszRunAsWinstaDesktop[RUNAS_CACHE_SIZE][100];
typedef struct SRunAsCache
{
HANDLE hToken;
WCHAR *pwszWinstaDesktop;
WCHAR *pwszWDStore;
LONG dwRefCount;
DWORD lHash;
TOKEN_USER *pUser;
TOKEN_GROUPS *pGroups;
TOKEN_GROUPS *pRestrictions;
DWORD lBirth;
struct SRunAsCache *pNext;
SRunAsCache()
{
hToken = NULL;
pwszWinstaDesktop = NULL;
pwszWDStore = NULL;
dwRefCount = 0;
lHash = 0;
pUser = NULL;
pGroups = NULL;
pRestrictions = NULL;
lBirth = 0;
pNext = NULL;
}
} SRunAsCache;
#define RUNAS_CACHECTRL_CREATENOTFOUND 1
#define RUNAS_CACHECTRL_REFERENCE 2
#define RUNAS_CACHECTRL_GETTOKEN 4
//Macro to invalidate an entry -- must be idempotent
#define INVALIDATE_RUNAS_ENTRY(pEntry) pEntry->lHash = 0
// Lock for LogonUser cache.
CRITICAL_SECTION gTokenCS;
// The run as cache is an array of entries divided into 2 circular lists.
// The gRunAsHead list contains cache entries in use with the most
// frequently used entries first. The gRunAsFree list contains free
// entries.
extern SRunAsCache gRunAsFree;
static SRunAsCache gRunAsCache[RUNAS_CACHE_SIZE];
#if 0
// Make the table smaller in debug to test the cache full case.
SRunAsCache gRunAsCache[] =
{
{ NULL, NULL, &wszRunAsWinstaDesktop[0][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[1] },
{ NULL, NULL, &wszRunAsWinstaDesktop[1][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[2] },
{ NULL, NULL, &wszRunAsWinstaDesktop[2][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[3] },
{ NULL, NULL, &wszRunAsWinstaDesktop[3][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[4] },
{ NULL, NULL, &wszRunAsWinstaDesktop[4][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[5] },
#if DBG == 1
{ NULL, NULL, &wszRunAsWinstaDesktop[5][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsFree }
#else
{ NULL, NULL, &wszRunAsWinstaDesktop[5][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[6] },
{ NULL, NULL, &wszRunAsWinstaDesktop[6][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[7] },
{ NULL, NULL, &wszRunAsWinstaDesktop[7][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[8] },
{ NULL, NULL, &wszRunAsWinstaDesktop[8][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[9] },
{ NULL, NULL, &wszRunAsWinstaDesktop[9][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[10] },
{ NULL, NULL, &wszRunAsWinstaDesktop[10][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[11] },
{ NULL, NULL, &wszRunAsWinstaDesktop[11][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[12] },
{ NULL, NULL, &wszRunAsWinstaDesktop[12][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[13] },
{ NULL, NULL, &wszRunAsWinstaDesktop[13][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[14] },
{ NULL, NULL, &wszRunAsWinstaDesktop[14][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[15] },
{ NULL, NULL, &wszRunAsWinstaDesktop[15][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsFree }
#endif
};
SRunAsCache gRunAsHead = { NULL, NULL, NULL, 0, 0, NULL, NULL, NULL, 0, &gRunAsHead };
SRunAsCache gRunAsFree = { NULL, NULL, NULL, 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[0] };
#endif
SRunAsCache gRunAsHead;
SRunAsCache gRunAsFree;
//+-------------------------------------------------------------------------
//
// Function: InitRunAsCache
//
// Synopsis: One time initialization of runas cache
//
//+-------------------------------------------------------------------------
void InitRunAsCache()
{
for (int i=0; i < (RUNAS_CACHE_SIZE-1); i++)
{
gRunAsCache[i].pNext = &gRunAsCache[i+1];
gRunAsCache[i].pwszWDStore = &wszRunAsWinstaDesktop[i][0];
}
gRunAsCache[i].pNext = &gRunAsFree;
gRunAsCache[i].pwszWDStore = &wszRunAsWinstaDesktop[i][0];
gRunAsHead.pNext = &gRunAsHead;
gRunAsFree.pNext = &gRunAsCache[0];
}
//+-------------------------------------------------------------------------
//
// Function: HashSid
//
// Synopsis: Compute a DWORD hash for a SID.
//
//--------------------------------------------------------------------------
DWORD HashSid( PSID pVoid )
{
SID *pSID = (SID *) pVoid;
DWORD lHash = 0;
DWORD i;
// Hash the identifier authority.
for (i = 0; i < 6; i++)
lHash ^= pSID->IdentifierAuthority.Value[i];
// Hash the sub authority.
for (i = 0; i < pSID->SubAuthorityCount; i++)
lHash ^= pSID->SubAuthority[i];
return lHash;
}
//+-------------------------------------------------------------------------
//
// Function: RunAsCache
//
// Synopsis: Return a token from the cache if present. Otherwise
// return the original token.
//
// Description: This function caches LogonUser tokens because each
// token has its own windowstation. Since there are
// a limited number of windowstations, by caching tokens
// we can reduce the number of windowstations used and thus
// allow more servers to be created. The cache moves the
// most frequently used tokens to the head of the list and
// discards tokens from the end of the list when full.
// When the cache is full, alternating requests for different
// tokens will prevent the last token from having a chance
// to advance in the list.
// [vinaykr - 9/1/98]
// Token caching alone is not enough because Tokens time out.
// So we cache winstations and reference count them to
// ensure proper allocation to a window station.
//
// Notes: Tokens in the cache must be timed out so that changes to
// the user name, user groups, user privileges, and user
// password take effect. The timeout must be balanced
// between the need to cache as many tokens as possible and
// the fact that cached tokens are useless when the password
// changes.
// [vinaykr - 9/1/98]
// Time out removed in favour of reference counting.
// Entry cleaned up when reference count goes to 0.
//--------------------------------------------------------------------------
HRESULT RunAsCache(IN DWORD dwCacheCtrl,
IN HANDLE &hToken,
OUT SRunAsCache** ppRunAsCache)
{
HRESULT hr;
BOOL fSuccess;
DWORD cbUser = 0;
DWORD cbGroups = 0;
DWORD cbRestrictions = 0;
TOKEN_USER *pUser = NULL;
TOKEN_GROUPS *pGroups;
TOKEN_GROUPS *pRestrictions;
DWORD lHash;
DWORD i;
HANDLE hCopy;
DWORD lNow;
SRunAsCache *pCurr = NULL;
SRunAsCache *pPrev;
SRunAsCache sSwap;
*ppRunAsCache = NULL;
// Find out how large the user SID is.
GetTokenInformation( hToken, TokenUser, NULL, 0, &cbUser );
if (cbUser == 0) { hr = E_UNEXPECTED; goto Cleanup; }
// Find out how large the group SIDs are.
GetTokenInformation( hToken, TokenGroups, NULL, 0, &cbGroups );
// Find out how large the restricted SIDs are.
GetTokenInformation( hToken, TokenRestrictedSids, NULL, 0, &cbRestrictions );
// Allocate memory to hold the SIDs.
cbUser = (cbUser + 7) & ~7;
cbGroups = (cbGroups + 7) & ~7;
pUser = (TOKEN_USER *) PrivMemAlloc( cbUser + cbGroups + cbRestrictions );
pGroups = (TOKEN_GROUPS *) (((BYTE *) pUser) + cbUser);
pRestrictions = (TOKEN_GROUPS *) (((BYTE *) pGroups) + cbGroups);
if (pUser == NULL) { hr = E_OUTOFMEMORY; goto Cleanup; }
// Get the user SID.
fSuccess = GetTokenInformation( hToken, TokenUser, pUser, cbUser, &cbUser );
if (!fSuccess) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Cleanup; }
// Get the group SIDs.
fSuccess = GetTokenInformation( hToken, TokenGroups, pGroups, cbGroups, &cbGroups );
if (!fSuccess) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Cleanup; }
// Get the restricted SIDs.
fSuccess = GetTokenInformation( hToken, TokenRestrictedSids, pRestrictions,
cbRestrictions, &cbRestrictions );
if (!fSuccess) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Cleanup; }
// Get the SID hash but skip the logon group that is unique for every
// call to logon user.
lHash = HashSid( pUser->User.Sid );
for (i = 0; i < pGroups->GroupCount; i++)
if ((pGroups->Groups[i].Attributes & SE_GROUP_LOGON_ID) == 0)
lHash ^= HashSid( pGroups->Groups[i].Sid );
for (i = 0; i < pRestrictions->GroupCount; i++)
lHash ^= HashSid( pRestrictions->Groups[i].Sid );
// Take lock.
EnterCriticalSection( &gTokenCS );
// Look for an existing token.
lNow = GetTickCount();
pPrev = &gRunAsHead;
pCurr = pPrev->pNext;
while (pPrev->pNext != &gRunAsHead)
{
// If the current entry is too old, delete it.
// (lNow - pCurr->lBirth >= RUN_AS_TIMEOUT))
// [vinaykr 9/1] Changed to use refcount
// If refcount is 0 delete entry
if (!pCurr->dwRefCount)
{
CloseHandle( pCurr->hToken );
PrivMemFree( pCurr->pUser );
pPrev->pNext = pCurr->pNext;
pCurr->pNext = gRunAsFree.pNext;
gRunAsFree.pNext = pCurr;
}
else
{
// If the current entry matches, break.
if (pCurr->lHash == lHash &&
pCurr->pGroups->GroupCount == pGroups->GroupCount)
{
// Check the user SID.
if (EqualSid(pCurr->pUser->User.Sid, pUser->User.Sid))
{
// Check the group SIDs.
for (i = 0; i < pGroups->GroupCount; i++)
if ((pGroups->Groups[i].Attributes & SE_GROUP_LOGON_ID) == 0)
if (!EqualSid( pCurr->pGroups->Groups[i].Sid,
pGroups->Groups[i].Sid ))
break;
// If those matched, check the restricted SIDs
if (i >= pGroups->GroupCount)
{
for (i = 0; i < pRestrictions->GroupCount; i++)
if (!EqualSid( pCurr->pRestrictions->Groups[i].Sid,
pRestrictions->Groups[i].Sid ))
break;
if (i >= pRestrictions->GroupCount)
break;
}
}
}
pPrev = pPrev->pNext;
}
pCurr = pPrev->pNext;
}
fSuccess = (pCurr != &gRunAsHead);
// Found a token
if (fSuccess)
{
// Duplicate this token if token requested
if (dwCacheCtrl & RUNAS_CACHECTRL_GETTOKEN)
{
fSuccess = DuplicateTokenEx( pCurr->hToken, MAXIMUM_ALLOWED,
NULL, SecurityDelegation, TokenPrimary,
&hCopy );
}
if (fSuccess)
{
// Discard the passed in token and return a copy of the cached
// token.
CloseHandle( hToken );
hToken = hCopy;
}
}
// If not found, find an empty slot only if we are trying to
// set into this. Note this can also be taken if unable to
// duplicate found token above.
if ((!fSuccess) &&
(dwCacheCtrl & RUNAS_CACHECTRL_CREATENOTFOUND))
{
// Duplicate this token.
fSuccess = DuplicateTokenEx( hToken, MAXIMUM_ALLOWED, NULL,
SecurityDelegation, TokenPrimary, &hCopy );
if (fSuccess)
{
// Get an entry from the free list.
if (gRunAsFree.pNext != &gRunAsFree)
{
pCurr = gRunAsFree.pNext;
gRunAsFree.pNext = pCurr->pNext;
pCurr->pNext = &gRunAsHead;
pPrev->pNext = pCurr;
}
// If no empty slot, release the last used entry.
else
{
pCurr = pPrev;
CloseHandle( pCurr->hToken );
PrivMemFree( pCurr->pUser );
}
// Save the duplicate.
pCurr->hToken = hCopy;
pCurr->lHash = lHash;
pCurr->pUser = pUser;
pCurr->pGroups = pGroups;
pCurr->pRestrictions = pRestrictions;
pCurr->lBirth = lNow;
pCurr->dwRefCount = 0;
pCurr->pwszWinstaDesktop = NULL;
pUser = NULL;
}
else
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
// If an entry was computed and a reference
// to it was requested, refcount it.
if (fSuccess)
{
if (dwCacheCtrl & RUNAS_CACHECTRL_REFERENCE)
pCurr->dwRefCount++;
}
// Release lock.
LeaveCriticalSection( &gTokenCS );
// Free any resources allocated by the function.
Cleanup:
if (pUser != NULL)
PrivMemFree(pUser);
if (SUCCEEDED(hr))
{
ASSERT(pCurr);
*ppRunAsCache = pCurr;
}
return hr;
}
//+-------------------------------------------------------------------------
//+
//+ Function: RunAsGetTokenElem
//+
//+ Synopsis: Gets token and/or Winstationdesktop string given a token
//+ Assumption is that if an entry is found in the cache a handle
//+ to the entry is returned with a reference being taken on the
//+ entry. This handle can then be used for other operations and
//+ needs to be explicitly released to release the entry.
//+
//+-------------------------------------------------------------------------
HRESULT RunAsGetTokenElem(IN OUT HANDLE *pToken,
OUT void **ppvElemHandle)
{
HRESULT hr;
SRunAsCache* pElem = NULL;
*ppvElemHandle = NULL;
hr = RunAsCache(RUNAS_CACHECTRL_REFERENCE |
RUNAS_CACHECTRL_GETTOKEN |
RUNAS_CACHECTRL_CREATENOTFOUND,
*pToken,
&pElem);
if (SUCCEEDED(hr))
{
*ppvElemHandle = (void*)pElem;
}
return hr;
}
//+-------------------------------------------------------------------------
//+
//+ Function: RunAsSetWinstaDesktop
//+
//+ Synopsis: Given a handle to an entry, sets the desktop string
//+ Assumption is that entry is referenced and therefore
//+ a valid one.
//+
//+-------------------------------------------------------------------------
void RunAsSetWinstaDesktop(void *pvElemHandle, WCHAR *pwszWinstaDesktop)
{
if (!pvElemHandle)
return;
SRunAsCache* pElem = (SRunAsCache*) pvElemHandle;
Win4Assert( (!pElem->pwszWinstaDesktop) ||
(lstrcmpW(pElem->pwszWinstaDesktop, pwszWinstaDesktop)
==0) );
if (!pElem->pwszWinstaDesktop)
{
lstrcpyW(pElem->pwszWDStore, pwszWinstaDesktop);
pElem->pwszWinstaDesktop = pElem->pwszWDStore;
}
}
//+-------------------------------------------------------------------------
//+
//+ Function: RunAsRelease
//+
//+ Synopsis: Given a handle to an entry, releases reference on it
//+ Assumption is that entry is referenced and therefore
//+ a valid one.
//+
//+-------------------------------------------------------------------------
void RunAsRelease(void *pvElemHandle)
{
if (!pvElemHandle)
return;
SRunAsCache* pElem = (SRunAsCache*) pvElemHandle;
// When refcount goes to 0 allow lazy clean up based on token
// time out
// Take lock.
EnterCriticalSection( &gTokenCS );
if ((--pElem->dwRefCount) == 0)
{
INVALIDATE_RUNAS_ENTRY(pElem);
}
LeaveCriticalSection( &gTokenCS );
// Release lock.
}
//+-------------------------------------------------------------------------
//+
//+ Function: RunAsInvalidateAndRelease
//+
//+ Synopsis: Given a handle to an entry, invalidates it and releases
//+ reference on it in response to some error.
//+ Assumption is that entry is referenced and therefore
//+ a valid one.
//+
//+-------------------------------------------------------------------------
void RunAsInvalidateAndRelease(void *pvElemHandle)
{
if (!pvElemHandle)
return;
SRunAsCache* pElem = (SRunAsCache*) pvElemHandle;
// Take lock.
EnterCriticalSection( &gTokenCS );
INVALIDATE_RUNAS_ENTRY(pElem);
// Release lock.
LeaveCriticalSection( &gTokenCS );
RunAsRelease(pvElemHandle);
}
WCHAR *RunAsGetWinsta(void *pvElemHandle)
{
if (!pvElemHandle)
return NULL;
SRunAsCache* pElem = (SRunAsCache*) pvElemHandle;
return pElem->pwszWinstaDesktop;
}
#endif // _CHICAGO_