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.

703 lines
18 KiB

  1. /*++
  2. Copyright (c) 1996, 1997 Microsoft Corporation
  3. Module Name:
  4. winpw.c
  5. Abstract:
  6. This module contains routines for retrieving and verification of the
  7. Windows NT and Windows 95 password associated with the client calling
  8. protected storage.
  9. Author:
  10. Scott Field (sfield) 12-Dec-96
  11. --*/
  12. #include <windows.h>
  13. #include <lmcons.h>
  14. #include <sha.h>
  15. #include "lnklist.h"
  16. #include "winpw.h"
  17. #include "module.h"
  18. #include "unicode.h"
  19. #include "unicode5.h"
  20. #include "debug.h"
  21. #include "secmisc.h"
  22. #define MPR_PROCESS "MPREXE.EXE"
  23. #define MPRSERV_MODULE "MPRSERV.DLL"
  24. #define GLOBAL_USERNAME 0x0E8
  25. #define PWL_USERNAME 0x170
  26. #define GLOBAL_PASSWORD 0x188
  27. #define PWL_PASSWORD 0x210
  28. //
  29. // this one comes and goes only when needed
  30. //
  31. typedef DWORD (WINAPI *WNETVERIFYPASSWORD)(
  32. LPCSTR lpszPassword,
  33. BOOL *pfMatch
  34. );
  35. typedef DWORD (WINAPI *WNETGETUSERA)(
  36. LPCSTR lpName,
  37. LPSTR lpUserName,
  38. LPDWORD lpnLength
  39. );
  40. WNETGETUSERA _WNetGetUserA = NULL;
  41. //
  42. // global Win95 password buffer. Only need one entry because Win95
  43. // only allows one user logged on at a time.
  44. //
  45. static WIN95_PASSWORD g_Win95Password;
  46. BOOL
  47. VerifyWindowsPasswordNT(
  48. LPCWSTR Password
  49. );
  50. BOOL
  51. GetTokenLogonType(
  52. HANDLE hToken,
  53. LPDWORD lpdwLogonType
  54. );
  55. BOOL
  56. GetTokenLogonType(
  57. HANDLE hToken,
  58. LPDWORD lpdwLogonType
  59. )
  60. /*++
  61. This function retrieves the logon type associated with the
  62. access token specified by the hToken parameter. On success,
  63. the DWORD buffer provided by the dwLogonType parameter is
  64. filled with the logon type which corresponds to the currently
  65. known logon types supported by the LogonUser() Windows NT
  66. API call.
  67. The token specified by the hToken parameter must have been
  68. opened with at least TOKEN_QUERY access.
  69. This function is only relevant on Windows NT and should not
  70. be called on Windows 95, as it will always return FALSE.
  71. --*/
  72. {
  73. UCHAR InfoBuffer[1024];
  74. DWORD dwInfoBufferSize = sizeof(InfoBuffer);
  75. PTOKEN_GROUPS SlowBuffer = NULL;
  76. PTOKEN_GROUPS ptgGroups = (PTOKEN_GROUPS)InfoBuffer;
  77. PSID psidInteractive = NULL;
  78. SID_IDENTIFIER_AUTHORITY siaNtAuthority = SECURITY_NT_AUTHORITY;
  79. BOOL bSuccess;
  80. bSuccess = GetTokenInformation(
  81. hToken,
  82. TokenGroups,
  83. ptgGroups,
  84. dwInfoBufferSize,
  85. &dwInfoBufferSize
  86. );
  87. //
  88. // if fast buffer wasn't big enough, allocate enough storage
  89. // and try again.
  90. //
  91. if(!bSuccess && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
  92. SlowBuffer = (PTOKEN_GROUPS)SSAlloc(dwInfoBufferSize);
  93. if(SlowBuffer != NULL) {
  94. ptgGroups = SlowBuffer;
  95. bSuccess = GetTokenInformation(
  96. hToken,
  97. TokenGroups,
  98. ptgGroups,
  99. dwInfoBufferSize,
  100. &dwInfoBufferSize
  101. );
  102. if(!bSuccess) {
  103. SSFree(SlowBuffer);
  104. SlowBuffer = NULL;
  105. }
  106. }
  107. }
  108. if(!bSuccess)
  109. return FALSE;
  110. //
  111. // initialize a single well-known logon Sid, since
  112. // we only compare the prefix and then the Rid
  113. // note that if performance were of utmost importance, we should
  114. // use InitializeSid + GetSidSubAuthority (to _set_ the Rid).
  115. // also note that we could do a simple memcmp against just the sid
  116. // identifier authority, but this assumes that Sid versions/layouts don't
  117. // change
  118. //
  119. bSuccess = AllocateAndInitializeSid(
  120. &siaNtAuthority,
  121. 1,
  122. SECURITY_INTERACTIVE_RID,
  123. 0, 0, 0, 0, 0, 0, 0,
  124. &psidInteractive
  125. );
  126. if(bSuccess) {
  127. UINT x;
  128. bSuccess = FALSE; // assume no match
  129. //
  130. // loop through groups checking for equality against
  131. // the well-known logon Sids.
  132. //
  133. for(x = 0 ; x < ptgGroups->GroupCount ; x++)
  134. {
  135. DWORD Rid;
  136. //
  137. // first, see if subauthority count matches, since
  138. // not too many sids have only one subauthority.
  139. //
  140. if(*GetSidSubAuthorityCount(ptgGroups->Groups[x].Sid) != 1)
  141. continue;
  142. //
  143. // next, see if the Sid prefix matches, since
  144. // all the logon Sids have the same prefix
  145. // "S-1-5"
  146. //
  147. if(!EqualPrefixSid(psidInteractive, ptgGroups->Groups[x].Sid))
  148. continue;
  149. //
  150. // if it's a logon sid prefix, just compare the Rid
  151. // to the known values.
  152. //
  153. Rid = *GetSidSubAuthority(ptgGroups->Groups[x].Sid, 0);
  154. switch (Rid) {
  155. case SECURITY_INTERACTIVE_RID:
  156. *lpdwLogonType = LOGON32_LOGON_INTERACTIVE;
  157. break;
  158. case SECURITY_BATCH_RID:
  159. *lpdwLogonType = LOGON32_LOGON_BATCH;
  160. break;
  161. case SECURITY_SERVICE_RID:
  162. *lpdwLogonType = LOGON32_LOGON_SERVICE;
  163. break;
  164. case SECURITY_NETWORK_RID:
  165. *lpdwLogonType = LOGON32_LOGON_NETWORK;
  166. break;
  167. default:
  168. continue; // ignore unknown logon type and continue
  169. }
  170. bSuccess = TRUE; // indicate success and bail
  171. break;
  172. }
  173. }
  174. if(SlowBuffer)
  175. SSFree(SlowBuffer);
  176. if(psidInteractive)
  177. FreeSid(psidInteractive);
  178. return bSuccess;
  179. }
  180. BOOL
  181. SetPasswordNT(
  182. PLUID LogonID,
  183. BYTE HashedPassword[A_SHA_DIGEST_LEN]
  184. )
  185. /*++
  186. This function adds the hashed password that is referenced by the specified
  187. Logon ID.
  188. --*/
  189. {
  190. #if 0
  191. return AddNTPassword(LogonID, HashedPassword);
  192. #else
  193. return TRUE; // do nothing, just return success
  194. #endif
  195. }
  196. BOOL
  197. GetPasswordNT(
  198. BYTE HashedPassword[A_SHA_DIGEST_LEN]
  199. )
  200. /*++
  201. This function retrieves the hashed password associated with the calling
  202. thread access token. This requires that the calling thread is impersonating
  203. the user associated with the password request. The credentials associated
  204. with the authentication ID are returned. This is done because WinNT
  205. supports multiple logged on users, and we must return the correct credentials.
  206. --*/
  207. {
  208. #if 0
  209. LUID AuthenticationId;
  210. if(!GetThreadAuthenticationId(
  211. GetCurrentThread(),
  212. &AuthenticationId
  213. )) return FALSE;
  214. return FindNTPassword(&AuthenticationId, HashedPassword);
  215. #else
  216. return FALSE; // no cache to search, just return FALSE
  217. #endif
  218. }
  219. BOOL
  220. GetSpecialCasePasswordNT(
  221. BYTE HashedPassword[A_SHA_DIGEST_LEN], // derived bits when fSpecialCase == TRUE
  222. LPBOOL fSpecialCase // legal special case encountered?
  223. )
  224. /*++
  225. This routine determines if the calling thread's access token is eligible
  226. to recieve a special case hashed password.
  227. If an legal special case is encountered (Local System Account), we
  228. fill the HashPassword buffer with an consistent hash, set fSpecialCase to
  229. TRUE, and return TRUE.
  230. If an illegal special case is encountered (Network SID), fSpecialCase is
  231. set FALSE, and we return FALSE.
  232. If we encounter an access token that appears to have valid credentials,
  233. but we have no way to get at them (Interactive, Batch, Service ... ),
  234. fSpecialCase is set FALSE and we return TRUE.
  235. The calling thread MUST be imperonsating the client in question prior to
  236. making this call.
  237. --*/
  238. {
  239. HANDLE hToken = NULL;
  240. DWORD dwLogonType;
  241. A_SHA_CTX context;
  242. BOOL fSuccess = FALSE;
  243. *fSpecialCase = FALSE;
  244. if(!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken))
  245. return FALSE;
  246. //
  247. // first, get the token logon type.
  248. //
  249. fSuccess = GetTokenLogonType(hToken, &dwLogonType);
  250. //
  251. // if we got the token logon type ok, check if it's an appropriate type.
  252. // otherwise, check for the local system special case.
  253. //
  254. if(fSuccess) {
  255. //
  256. // we only indicate success for the interactive logon type.
  257. // note default is fSuccess == TRUE when going to cleanup
  258. //
  259. if(dwLogonType != LOGON32_LOGON_INTERACTIVE)
  260. fSuccess = FALSE;
  261. goto cleanup;
  262. } else {
  263. SID_IDENTIFIER_AUTHORITY sia = SECURITY_NT_AUTHORITY;
  264. PSID pSystemSid;
  265. PSID pTokenSid;
  266. fSuccess = GetTokenUserSid(hToken, &pTokenSid);
  267. if(!fSuccess)
  268. goto cleanup;
  269. //
  270. // build local system sid and compare.
  271. //
  272. fSuccess = AllocateAndInitializeSid(
  273. &sia,
  274. 1,
  275. SECURITY_LOCAL_SYSTEM_RID,
  276. 0, 0, 0, 0, 0, 0, 0,
  277. &pSystemSid
  278. );
  279. if( fSuccess ) {
  280. //
  281. // check sid equality. If so, hash and tell the caller about it.
  282. //
  283. if( EqualSid(pSystemSid, pTokenSid) ) {
  284. //
  285. // hash the special case user Sid
  286. //
  287. A_SHAInit(&context);
  288. A_SHAUpdate(&context, (LPBYTE)pTokenSid, GetLengthSid(pTokenSid));
  289. A_SHAFinal(&context, HashedPassword);
  290. *fSpecialCase = TRUE;
  291. }
  292. FreeSid(pSystemSid);
  293. }
  294. SSFree(pTokenSid);
  295. }
  296. cleanup:
  297. if(hToken)
  298. CloseHandle(hToken);
  299. return fSuccess;
  300. }
  301. BOOL
  302. SetPassword95(
  303. BYTE HashedUsername[A_SHA_DIGEST_LEN],
  304. BYTE HashedPassword[A_SHA_DIGEST_LEN]
  305. )
  306. /*++
  307. This function adds the hashed password that is referenced by the hashed
  308. user name.
  309. Set HashedUsername and HashedPassword to NULL when calling to zero-out
  310. the single password entry.
  311. --*/
  312. {
  313. if(HashedUsername == NULL || HashedPassword == NULL) {
  314. g_Win95Password.bValid = FALSE;
  315. RtlSecureZeroMemory(g_Win95Password.HashedPassword, A_SHA_DIGEST_LEN);
  316. RtlSecureZeroMemory(g_Win95Password.HashedUsername, A_SHA_DIGEST_LEN);
  317. return TRUE;
  318. }
  319. memcpy(g_Win95Password.HashedUsername, HashedUsername, A_SHA_DIGEST_LEN);
  320. memcpy(g_Win95Password.HashedPassword, HashedPassword, A_SHA_DIGEST_LEN);
  321. g_Win95Password.bValid = TRUE;
  322. return TRUE;
  323. }
  324. BOOL
  325. GetPassword95(
  326. BYTE HashedPassword[A_SHA_DIGEST_LEN]
  327. )
  328. /*++
  329. This function retrieves the hashed password associated with the calling
  330. thread. In Win95, only one user is logged on, so this operation is
  331. a simple copy from global memory, once the hash of the current user
  332. matches that which was stored with the hashed credential.
  333. --*/
  334. {
  335. A_SHA_CTX context;
  336. BYTE HashUsername[A_SHA_DIGEST_LEN];
  337. CHAR Username[UNLEN+1];
  338. DWORD cchUsername = UNLEN;
  339. //
  340. // don't release credential unless hash of username matches
  341. // sfield: use WNetGetUser() instead of GetUserName() as WNetGetUser()
  342. // will correspond to the password associated with what the network
  343. // provider gave us.
  344. //
  345. if(_WNetGetUserA(NULL, Username, &cchUsername) != NO_ERROR) {
  346. //
  347. // for Win95, if nobody is logged on, empty user name + password
  348. //
  349. if(GetLastError() != ERROR_NOT_LOGGED_ON)
  350. return FALSE;
  351. Username[0] = '\0'; // not really necessary
  352. cchUsername = 1;
  353. } else {
  354. // arg, WNetGetUserA() doesn't fill in cchUsername
  355. cchUsername = lstrlenA(Username) + 1; // include terminal NULL
  356. if(g_Win95Password.bValid == FALSE)
  357. return FALSE;
  358. }
  359. cchUsername--; // do not include terminal NULL
  360. A_SHAInit(&context);
  361. A_SHAUpdate(&context, Username, cchUsername);
  362. A_SHAFinal(&context, HashUsername);
  363. //
  364. // non empty username, may not be empty password
  365. //
  366. if(cchUsername) {
  367. if(memcmp(HashUsername, g_Win95Password.HashedUsername, A_SHA_DIGEST_LEN) != 0) {
  368. //
  369. // rare case on Win95: if we didn't automatically flush the entry
  370. // during a logoff (this can occur if network provider not hooked),
  371. // flush it now because we know the entry cannot possibly be valid.
  372. //
  373. g_Win95Password.bValid = FALSE;
  374. return FALSE;
  375. }
  376. memcpy(HashedPassword, g_Win95Password.HashedPassword, A_SHA_DIGEST_LEN);
  377. return TRUE;
  378. }
  379. //
  380. // empty user name == empty password
  381. //
  382. memcpy(HashedPassword, HashUsername, A_SHA_DIGEST_LEN);
  383. return TRUE;
  384. }
  385. BOOL
  386. VerifyWindowsPassword(
  387. LPCWSTR Password
  388. )
  389. /*++
  390. This function verifies that the specified password matches that of the
  391. current user.
  392. On Windows 95, the current user equates to the user is currently logged
  393. onto the machine.
  394. On Windows NT, the current user equates to the user which is being
  395. impersonated during the call. On Windows NT, the caller MUST be
  396. impersonating the user associated with the validation.
  397. On Windows NT, a side effect of the validation is notification of
  398. a new logon to the credential manager. This is ignored because the
  399. authentication ID present in the new logon does not match the
  400. authentication ID present in the impersonated access token.
  401. --*/
  402. {
  403. return VerifyWindowsPasswordNT(Password);
  404. }
  405. BOOL
  406. VerifyWindowsPasswordNT(
  407. LPCWSTR Password
  408. )
  409. {
  410. HANDLE hPriorToken = NULL;
  411. HANDLE hToken;
  412. HANDLE hLogonToken = NULL;
  413. PTOKEN_USER pTokenInfo = NULL;
  414. DWORD cbTokenInfoSize;
  415. WCHAR User[UNLEN+1];
  416. WCHAR Domain[DNLEN+1];
  417. DWORD cchUser = UNLEN;
  418. DWORD cchDomain = DNLEN;
  419. SID_NAME_USE peUse;
  420. BOOL bSuccess = FALSE;
  421. //
  422. // find out domain and user name associated with current user
  423. //
  424. if(!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken))
  425. return FALSE;
  426. cbTokenInfoSize = 512;
  427. pTokenInfo = (PTOKEN_USER)SSAlloc(cbTokenInfoSize);
  428. if(pTokenInfo == NULL)
  429. goto cleanup;
  430. if(!GetTokenInformation(
  431. hToken,
  432. TokenUser,
  433. pTokenInfo,
  434. cbTokenInfoSize,
  435. &cbTokenInfoSize
  436. )) {
  437. //
  438. // realloc and try again
  439. //
  440. if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
  441. SSFree(pTokenInfo);
  442. pTokenInfo = (PTOKEN_USER)SSAlloc(cbTokenInfoSize);
  443. if(pTokenInfo == NULL)
  444. goto cleanup;
  445. if(!GetTokenInformation(
  446. hToken,
  447. TokenUser,
  448. pTokenInfo,
  449. cbTokenInfoSize,
  450. &cbTokenInfoSize
  451. )) {
  452. goto cleanup;
  453. }
  454. } else {
  455. goto cleanup;
  456. }
  457. }
  458. if(!LookupAccountSidW(
  459. NULL, // default lookup logic
  460. pTokenInfo->User.Sid,
  461. User,
  462. &cchUser,
  463. Domain,
  464. &cchDomain,
  465. &peUse
  466. ))
  467. goto cleanup;
  468. //
  469. // WinNT:
  470. // first try network logon type, if that fails, grovel the token
  471. // and try the same logon type which is associated with the impersonation
  472. // token.
  473. //
  474. //
  475. // arg! LogonUser() fails in some cases if we are impersonating!
  476. // so save off impersonation token, revert, and put it back later.
  477. //
  478. if(!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &hPriorToken)) {
  479. hPriorToken = NULL;
  480. } else {
  481. RevertToSelf();
  482. }
  483. //
  484. // network logon type is fastest, and in default NT install, everyone
  485. // has the SeNetworkLogonRight, so it's very likely to pass the logon right
  486. // test
  487. //
  488. if(!LogonUserW(
  489. User,
  490. Domain,
  491. (LPWSTR)Password,
  492. LOGON32_LOGON_NETWORK,
  493. LOGON32_PROVIDER_DEFAULT,
  494. &hLogonToken
  495. )) {
  496. DWORD dwLastError = GetLastError();
  497. DWORD dwLogonType;
  498. //
  499. // retry with different logon type if necessary.
  500. // note: ERROR_LOGON_TYPE_NOT_GRANTED currently only occurs
  501. // if the password matches but user didn't have specified logon
  502. // type. So, currently, we could treat this as a successful validation
  503. // without retrying, but this is subject to change in future, so retry
  504. // anyway.
  505. //
  506. if( dwLastError == ERROR_LOGON_TYPE_NOT_GRANTED &&
  507. GetTokenLogonType(hPriorToken, &dwLogonType)
  508. ) {
  509. bSuccess = LogonUserW(
  510. User,
  511. Domain,
  512. (LPWSTR)Password,
  513. dwLogonType,
  514. LOGON32_PROVIDER_DEFAULT,
  515. &hLogonToken
  516. );
  517. }
  518. if(!bSuccess)
  519. hLogonToken = NULL; // LogonUser() has tendency to leave garbage in hToken
  520. goto cleanup;
  521. }
  522. bSuccess = TRUE;
  523. cleanup:
  524. if(hPriorToken != NULL) {
  525. SetThreadToken(NULL, hPriorToken);
  526. CloseHandle(hPriorToken);
  527. }
  528. CloseHandle(hToken);
  529. if(hLogonToken)
  530. CloseHandle(hLogonToken);
  531. if(pTokenInfo)
  532. SSFree(pTokenInfo);
  533. return bSuccess;
  534. }