//+------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1997. // // File: winsta.cxx // // Contents: winstation caching code // //-------------------------------------------------------------------------- #include "act.hxx" // // 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]; 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 = S_OK; 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 = NULL; 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; ASSERT( (!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); }