Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

529 lines
18 KiB

  1. //+-------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. // Copyright (C) Microsoft Corporation, 1997.
  5. //
  6. // File: winsta.cxx
  7. //
  8. // Contents: winstation caching code
  9. //
  10. //--------------------------------------------------------------------------
  11. #include "act.hxx"
  12. #ifndef _CHICAGO_
  13. //
  14. // Private Types.
  15. //
  16. //-------------------------------------------------------------------------
  17. // Definitions for Runas Cache
  18. // NOTE: This exists to overcome limitation on NT Winstations of 15
  19. // Reusing same token will map to same winstation. We also cache
  20. // winstation once we get it.
  21. //-------------------------------------------------------------------------
  22. const DWORD RUN_AS_TIMEOUT = 360000;
  23. #define RUNAS_CACHE_SIZE 200
  24. // The pUser and pGroups fields are assumed to point to one contiguous memory
  25. // block that can be freed by a single call to Free(pUser).
  26. WCHAR wszRunAsWinstaDesktop[RUNAS_CACHE_SIZE][100];
  27. typedef struct SRunAsCache
  28. {
  29. HANDLE hToken;
  30. WCHAR *pwszWinstaDesktop;
  31. WCHAR *pwszWDStore;
  32. LONG dwRefCount;
  33. DWORD lHash;
  34. TOKEN_USER *pUser;
  35. TOKEN_GROUPS *pGroups;
  36. TOKEN_GROUPS *pRestrictions;
  37. DWORD lBirth;
  38. struct SRunAsCache *pNext;
  39. SRunAsCache()
  40. {
  41. hToken = NULL;
  42. pwszWinstaDesktop = NULL;
  43. pwszWDStore = NULL;
  44. dwRefCount = 0;
  45. lHash = 0;
  46. pUser = NULL;
  47. pGroups = NULL;
  48. pRestrictions = NULL;
  49. lBirth = 0;
  50. pNext = NULL;
  51. }
  52. } SRunAsCache;
  53. #define RUNAS_CACHECTRL_CREATENOTFOUND 1
  54. #define RUNAS_CACHECTRL_REFERENCE 2
  55. #define RUNAS_CACHECTRL_GETTOKEN 4
  56. //Macro to invalidate an entry -- must be idempotent
  57. #define INVALIDATE_RUNAS_ENTRY(pEntry) pEntry->lHash = 0
  58. // Lock for LogonUser cache.
  59. CRITICAL_SECTION gTokenCS;
  60. // The run as cache is an array of entries divided into 2 circular lists.
  61. // The gRunAsHead list contains cache entries in use with the most
  62. // frequently used entries first. The gRunAsFree list contains free
  63. // entries.
  64. extern SRunAsCache gRunAsFree;
  65. static SRunAsCache gRunAsCache[RUNAS_CACHE_SIZE];
  66. #if 0
  67. // Make the table smaller in debug to test the cache full case.
  68. SRunAsCache gRunAsCache[] =
  69. {
  70. { NULL, NULL, &wszRunAsWinstaDesktop[0][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[1] },
  71. { NULL, NULL, &wszRunAsWinstaDesktop[1][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[2] },
  72. { NULL, NULL, &wszRunAsWinstaDesktop[2][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[3] },
  73. { NULL, NULL, &wszRunAsWinstaDesktop[3][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[4] },
  74. { NULL, NULL, &wszRunAsWinstaDesktop[4][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[5] },
  75. #if DBG == 1
  76. { NULL, NULL, &wszRunAsWinstaDesktop[5][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsFree }
  77. #else
  78. { NULL, NULL, &wszRunAsWinstaDesktop[5][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[6] },
  79. { NULL, NULL, &wszRunAsWinstaDesktop[6][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[7] },
  80. { NULL, NULL, &wszRunAsWinstaDesktop[7][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[8] },
  81. { NULL, NULL, &wszRunAsWinstaDesktop[8][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[9] },
  82. { NULL, NULL, &wszRunAsWinstaDesktop[9][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[10] },
  83. { NULL, NULL, &wszRunAsWinstaDesktop[10][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[11] },
  84. { NULL, NULL, &wszRunAsWinstaDesktop[11][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[12] },
  85. { NULL, NULL, &wszRunAsWinstaDesktop[12][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[13] },
  86. { NULL, NULL, &wszRunAsWinstaDesktop[13][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[14] },
  87. { NULL, NULL, &wszRunAsWinstaDesktop[14][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[15] },
  88. { NULL, NULL, &wszRunAsWinstaDesktop[15][0], 0, 0, NULL, NULL, NULL, 0, &gRunAsFree }
  89. #endif
  90. };
  91. SRunAsCache gRunAsHead = { NULL, NULL, NULL, 0, 0, NULL, NULL, NULL, 0, &gRunAsHead };
  92. SRunAsCache gRunAsFree = { NULL, NULL, NULL, 0, 0, NULL, NULL, NULL, 0, &gRunAsCache[0] };
  93. #endif
  94. SRunAsCache gRunAsHead;
  95. SRunAsCache gRunAsFree;
  96. //+-------------------------------------------------------------------------
  97. //
  98. // Function: InitRunAsCache
  99. //
  100. // Synopsis: One time initialization of runas cache
  101. //
  102. //+-------------------------------------------------------------------------
  103. void InitRunAsCache()
  104. {
  105. for (int i=0; i < (RUNAS_CACHE_SIZE-1); i++)
  106. {
  107. gRunAsCache[i].pNext = &gRunAsCache[i+1];
  108. gRunAsCache[i].pwszWDStore = &wszRunAsWinstaDesktop[i][0];
  109. }
  110. gRunAsCache[i].pNext = &gRunAsFree;
  111. gRunAsCache[i].pwszWDStore = &wszRunAsWinstaDesktop[i][0];
  112. gRunAsHead.pNext = &gRunAsHead;
  113. gRunAsFree.pNext = &gRunAsCache[0];
  114. }
  115. //+-------------------------------------------------------------------------
  116. //
  117. // Function: HashSid
  118. //
  119. // Synopsis: Compute a DWORD hash for a SID.
  120. //
  121. //--------------------------------------------------------------------------
  122. DWORD HashSid( PSID pVoid )
  123. {
  124. SID *pSID = (SID *) pVoid;
  125. DWORD lHash = 0;
  126. DWORD i;
  127. // Hash the identifier authority.
  128. for (i = 0; i < 6; i++)
  129. lHash ^= pSID->IdentifierAuthority.Value[i];
  130. // Hash the sub authority.
  131. for (i = 0; i < pSID->SubAuthorityCount; i++)
  132. lHash ^= pSID->SubAuthority[i];
  133. return lHash;
  134. }
  135. //+-------------------------------------------------------------------------
  136. //
  137. // Function: RunAsCache
  138. //
  139. // Synopsis: Return a token from the cache if present. Otherwise
  140. // return the original token.
  141. //
  142. // Description: This function caches LogonUser tokens because each
  143. // token has its own windowstation. Since there are
  144. // a limited number of windowstations, by caching tokens
  145. // we can reduce the number of windowstations used and thus
  146. // allow more servers to be created. The cache moves the
  147. // most frequently used tokens to the head of the list and
  148. // discards tokens from the end of the list when full.
  149. // When the cache is full, alternating requests for different
  150. // tokens will prevent the last token from having a chance
  151. // to advance in the list.
  152. // [vinaykr - 9/1/98]
  153. // Token caching alone is not enough because Tokens time out.
  154. // So we cache winstations and reference count them to
  155. // ensure proper allocation to a window station.
  156. //
  157. // Notes: Tokens in the cache must be timed out so that changes to
  158. // the user name, user groups, user privileges, and user
  159. // password take effect. The timeout must be balanced
  160. // between the need to cache as many tokens as possible and
  161. // the fact that cached tokens are useless when the password
  162. // changes.
  163. // [vinaykr - 9/1/98]
  164. // Time out removed in favour of reference counting.
  165. // Entry cleaned up when reference count goes to 0.
  166. //--------------------------------------------------------------------------
  167. HRESULT RunAsCache(IN DWORD dwCacheCtrl,
  168. IN HANDLE &hToken,
  169. OUT SRunAsCache** ppRunAsCache)
  170. {
  171. HRESULT hr;
  172. BOOL fSuccess;
  173. DWORD cbUser = 0;
  174. DWORD cbGroups = 0;
  175. DWORD cbRestrictions = 0;
  176. TOKEN_USER *pUser = NULL;
  177. TOKEN_GROUPS *pGroups;
  178. TOKEN_GROUPS *pRestrictions;
  179. DWORD lHash;
  180. DWORD i;
  181. HANDLE hCopy;
  182. DWORD lNow;
  183. SRunAsCache *pCurr = NULL;
  184. SRunAsCache *pPrev;
  185. SRunAsCache sSwap;
  186. *ppRunAsCache = NULL;
  187. // Find out how large the user SID is.
  188. GetTokenInformation( hToken, TokenUser, NULL, 0, &cbUser );
  189. if (cbUser == 0) { hr = E_UNEXPECTED; goto Cleanup; }
  190. // Find out how large the group SIDs are.
  191. GetTokenInformation( hToken, TokenGroups, NULL, 0, &cbGroups );
  192. // Find out how large the restricted SIDs are.
  193. GetTokenInformation( hToken, TokenRestrictedSids, NULL, 0, &cbRestrictions );
  194. // Allocate memory to hold the SIDs.
  195. cbUser = (cbUser + 7) & ~7;
  196. cbGroups = (cbGroups + 7) & ~7;
  197. pUser = (TOKEN_USER *) PrivMemAlloc( cbUser + cbGroups + cbRestrictions );
  198. pGroups = (TOKEN_GROUPS *) (((BYTE *) pUser) + cbUser);
  199. pRestrictions = (TOKEN_GROUPS *) (((BYTE *) pGroups) + cbGroups);
  200. if (pUser == NULL) { hr = E_OUTOFMEMORY; goto Cleanup; }
  201. // Get the user SID.
  202. fSuccess = GetTokenInformation( hToken, TokenUser, pUser, cbUser, &cbUser );
  203. if (!fSuccess) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Cleanup; }
  204. // Get the group SIDs.
  205. fSuccess = GetTokenInformation( hToken, TokenGroups, pGroups, cbGroups, &cbGroups );
  206. if (!fSuccess) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Cleanup; }
  207. // Get the restricted SIDs.
  208. fSuccess = GetTokenInformation( hToken, TokenRestrictedSids, pRestrictions,
  209. cbRestrictions, &cbRestrictions );
  210. if (!fSuccess) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Cleanup; }
  211. // Get the SID hash but skip the logon group that is unique for every
  212. // call to logon user.
  213. lHash = HashSid( pUser->User.Sid );
  214. for (i = 0; i < pGroups->GroupCount; i++)
  215. if ((pGroups->Groups[i].Attributes & SE_GROUP_LOGON_ID) == 0)
  216. lHash ^= HashSid( pGroups->Groups[i].Sid );
  217. for (i = 0; i < pRestrictions->GroupCount; i++)
  218. lHash ^= HashSid( pRestrictions->Groups[i].Sid );
  219. // Take lock.
  220. EnterCriticalSection( &gTokenCS );
  221. // Look for an existing token.
  222. lNow = GetTickCount();
  223. pPrev = &gRunAsHead;
  224. pCurr = pPrev->pNext;
  225. while (pPrev->pNext != &gRunAsHead)
  226. {
  227. // If the current entry is too old, delete it.
  228. // (lNow - pCurr->lBirth >= RUN_AS_TIMEOUT))
  229. // [vinaykr 9/1] Changed to use refcount
  230. // If refcount is 0 delete entry
  231. if (!pCurr->dwRefCount)
  232. {
  233. CloseHandle( pCurr->hToken );
  234. PrivMemFree( pCurr->pUser );
  235. pPrev->pNext = pCurr->pNext;
  236. pCurr->pNext = gRunAsFree.pNext;
  237. gRunAsFree.pNext = pCurr;
  238. }
  239. else
  240. {
  241. // If the current entry matches, break.
  242. if (pCurr->lHash == lHash &&
  243. pCurr->pGroups->GroupCount == pGroups->GroupCount)
  244. {
  245. // Check the user SID.
  246. if (EqualSid(pCurr->pUser->User.Sid, pUser->User.Sid))
  247. {
  248. // Check the group SIDs.
  249. for (i = 0; i < pGroups->GroupCount; i++)
  250. if ((pGroups->Groups[i].Attributes & SE_GROUP_LOGON_ID) == 0)
  251. if (!EqualSid( pCurr->pGroups->Groups[i].Sid,
  252. pGroups->Groups[i].Sid ))
  253. break;
  254. // If those matched, check the restricted SIDs
  255. if (i >= pGroups->GroupCount)
  256. {
  257. for (i = 0; i < pRestrictions->GroupCount; i++)
  258. if (!EqualSid( pCurr->pRestrictions->Groups[i].Sid,
  259. pRestrictions->Groups[i].Sid ))
  260. break;
  261. if (i >= pRestrictions->GroupCount)
  262. break;
  263. }
  264. }
  265. }
  266. pPrev = pPrev->pNext;
  267. }
  268. pCurr = pPrev->pNext;
  269. }
  270. fSuccess = (pCurr != &gRunAsHead);
  271. // Found a token
  272. if (fSuccess)
  273. {
  274. // Duplicate this token if token requested
  275. if (dwCacheCtrl & RUNAS_CACHECTRL_GETTOKEN)
  276. {
  277. fSuccess = DuplicateTokenEx( pCurr->hToken, MAXIMUM_ALLOWED,
  278. NULL, SecurityDelegation, TokenPrimary,
  279. &hCopy );
  280. }
  281. if (fSuccess)
  282. {
  283. // Discard the passed in token and return a copy of the cached
  284. // token.
  285. CloseHandle( hToken );
  286. hToken = hCopy;
  287. }
  288. }
  289. // If not found, find an empty slot only if we are trying to
  290. // set into this. Note this can also be taken if unable to
  291. // duplicate found token above.
  292. if ((!fSuccess) &&
  293. (dwCacheCtrl & RUNAS_CACHECTRL_CREATENOTFOUND))
  294. {
  295. // Duplicate this token.
  296. fSuccess = DuplicateTokenEx( hToken, MAXIMUM_ALLOWED, NULL,
  297. SecurityDelegation, TokenPrimary, &hCopy );
  298. if (fSuccess)
  299. {
  300. // Get an entry from the free list.
  301. if (gRunAsFree.pNext != &gRunAsFree)
  302. {
  303. pCurr = gRunAsFree.pNext;
  304. gRunAsFree.pNext = pCurr->pNext;
  305. pCurr->pNext = &gRunAsHead;
  306. pPrev->pNext = pCurr;
  307. }
  308. // If no empty slot, release the last used entry.
  309. else
  310. {
  311. pCurr = pPrev;
  312. CloseHandle( pCurr->hToken );
  313. PrivMemFree( pCurr->pUser );
  314. }
  315. // Save the duplicate.
  316. pCurr->hToken = hCopy;
  317. pCurr->lHash = lHash;
  318. pCurr->pUser = pUser;
  319. pCurr->pGroups = pGroups;
  320. pCurr->pRestrictions = pRestrictions;
  321. pCurr->lBirth = lNow;
  322. pCurr->dwRefCount = 0;
  323. pCurr->pwszWinstaDesktop = NULL;
  324. pUser = NULL;
  325. }
  326. else
  327. {
  328. hr = HRESULT_FROM_WIN32(GetLastError());
  329. }
  330. }
  331. // If an entry was computed and a reference
  332. // to it was requested, refcount it.
  333. if (fSuccess)
  334. {
  335. if (dwCacheCtrl & RUNAS_CACHECTRL_REFERENCE)
  336. pCurr->dwRefCount++;
  337. }
  338. // Release lock.
  339. LeaveCriticalSection( &gTokenCS );
  340. // Free any resources allocated by the function.
  341. Cleanup:
  342. if (pUser != NULL)
  343. PrivMemFree(pUser);
  344. if (SUCCEEDED(hr))
  345. {
  346. ASSERT(pCurr);
  347. *ppRunAsCache = pCurr;
  348. }
  349. return hr;
  350. }
  351. //+-------------------------------------------------------------------------
  352. //+
  353. //+ Function: RunAsGetTokenElem
  354. //+
  355. //+ Synopsis: Gets token and/or Winstationdesktop string given a token
  356. //+ Assumption is that if an entry is found in the cache a handle
  357. //+ to the entry is returned with a reference being taken on the
  358. //+ entry. This handle can then be used for other operations and
  359. //+ needs to be explicitly released to release the entry.
  360. //+
  361. //+-------------------------------------------------------------------------
  362. HRESULT RunAsGetTokenElem(IN OUT HANDLE *pToken,
  363. OUT void **ppvElemHandle)
  364. {
  365. HRESULT hr;
  366. SRunAsCache* pElem = NULL;
  367. *ppvElemHandle = NULL;
  368. hr = RunAsCache(RUNAS_CACHECTRL_REFERENCE |
  369. RUNAS_CACHECTRL_GETTOKEN |
  370. RUNAS_CACHECTRL_CREATENOTFOUND,
  371. *pToken,
  372. &pElem);
  373. if (SUCCEEDED(hr))
  374. {
  375. *ppvElemHandle = (void*)pElem;
  376. }
  377. return hr;
  378. }
  379. //+-------------------------------------------------------------------------
  380. //+
  381. //+ Function: RunAsSetWinstaDesktop
  382. //+
  383. //+ Synopsis: Given a handle to an entry, sets the desktop string
  384. //+ Assumption is that entry is referenced and therefore
  385. //+ a valid one.
  386. //+
  387. //+-------------------------------------------------------------------------
  388. void RunAsSetWinstaDesktop(void *pvElemHandle, WCHAR *pwszWinstaDesktop)
  389. {
  390. if (!pvElemHandle)
  391. return;
  392. SRunAsCache* pElem = (SRunAsCache*) pvElemHandle;
  393. Win4Assert( (!pElem->pwszWinstaDesktop) ||
  394. (lstrcmpW(pElem->pwszWinstaDesktop, pwszWinstaDesktop)
  395. ==0) );
  396. if (!pElem->pwszWinstaDesktop)
  397. {
  398. lstrcpyW(pElem->pwszWDStore, pwszWinstaDesktop);
  399. pElem->pwszWinstaDesktop = pElem->pwszWDStore;
  400. }
  401. }
  402. //+-------------------------------------------------------------------------
  403. //+
  404. //+ Function: RunAsRelease
  405. //+
  406. //+ Synopsis: Given a handle to an entry, releases reference on it
  407. //+ Assumption is that entry is referenced and therefore
  408. //+ a valid one.
  409. //+
  410. //+-------------------------------------------------------------------------
  411. void RunAsRelease(void *pvElemHandle)
  412. {
  413. if (!pvElemHandle)
  414. return;
  415. SRunAsCache* pElem = (SRunAsCache*) pvElemHandle;
  416. // When refcount goes to 0 allow lazy clean up based on token
  417. // time out
  418. // Take lock.
  419. EnterCriticalSection( &gTokenCS );
  420. if ((--pElem->dwRefCount) == 0)
  421. {
  422. INVALIDATE_RUNAS_ENTRY(pElem);
  423. }
  424. LeaveCriticalSection( &gTokenCS );
  425. // Release lock.
  426. }
  427. //+-------------------------------------------------------------------------
  428. //+
  429. //+ Function: RunAsInvalidateAndRelease
  430. //+
  431. //+ Synopsis: Given a handle to an entry, invalidates it and releases
  432. //+ reference on it in response to some error.
  433. //+ Assumption is that entry is referenced and therefore
  434. //+ a valid one.
  435. //+
  436. //+-------------------------------------------------------------------------
  437. void RunAsInvalidateAndRelease(void *pvElemHandle)
  438. {
  439. if (!pvElemHandle)
  440. return;
  441. SRunAsCache* pElem = (SRunAsCache*) pvElemHandle;
  442. // Take lock.
  443. EnterCriticalSection( &gTokenCS );
  444. INVALIDATE_RUNAS_ENTRY(pElem);
  445. // Release lock.
  446. LeaveCriticalSection( &gTokenCS );
  447. RunAsRelease(pvElemHandle);
  448. }
  449. WCHAR *RunAsGetWinsta(void *pvElemHandle)
  450. {
  451. if (!pvElemHandle)
  452. return NULL;
  453. SRunAsCache* pElem = (SRunAsCache*) pvElemHandle;
  454. return pElem->pwszWinstaDesktop;
  455. }
  456. #endif // _CHICAGO_