Leaked source code of windows server 2003
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.

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