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.

670 lines
15 KiB

  1. ///////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright (c) 1998, Microsoft Corp. All rights reserved.
  4. //
  5. // FILE
  6. //
  7. // subauth.c
  8. //
  9. // SYNOPSIS
  10. //
  11. // Declares the subauthentication and password change notification routines
  12. // for MD5-CHAP.
  13. //
  14. // MODIFICATION HISTORY
  15. //
  16. // 09/01/1998 Original version.
  17. // 11/02/1998 Handle change notifications on a separate thread.
  18. // 11/03/1998 NewPassword may be NULL.
  19. // 11/12/1998 Use private heap.
  20. // Use CreateThread.
  21. // 03/08/1999 Only store passwords for user accounts.
  22. // 03/29/1999 Initialize out parameters to NULL when calling
  23. // SamrQueryInformationUser.
  24. //
  25. ///////////////////////////////////////////////////////////////////////////////
  26. #include <nt.h>
  27. #include <ntrtl.h>
  28. #include <nturtl.h>
  29. #include <ntlsa.h>
  30. #include <windows.h>
  31. #include <samrpc.h>
  32. #include <samisrv.h>
  33. #include <lsarpc.h>
  34. #include <lsaisrv.h>
  35. #include <lmcons.h>
  36. #include <logonmsv.h>
  37. #include <rasfmsub.h>
  38. #include <cleartxt.h>
  39. #include <md5.h>
  40. #include <md5port.h>
  41. #include <rassfmhp.h>
  42. // Non-zero if the API is locked.
  43. static LONG theLock;
  44. //////////
  45. // Macros to lock/unlock the API during intialization.
  46. //////////
  47. #define API_LOCK() \
  48. while (InterlockedExchange(&theLock, 1)) Sleep(5)
  49. #define API_UNLOCK() \
  50. InterlockedExchange(&theLock, 0)
  51. // Cached handle to the local account domain.
  52. SAMPR_HANDLE theAccountDomain;
  53. // TRUE if we have a handle to the local account domain.
  54. static BOOL theConnectFlag;
  55. //////////
  56. // Macro that ensures we have a connection and bails on failure.
  57. //////////
  58. #define CHECK_CONNECT() \
  59. if (!theConnectFlag) { \
  60. status = ConnectToDomain(); \
  61. if (!NT_SUCCESS(status)) { return status; } \
  62. }
  63. //////////
  64. // Initializes the cached handle to the local account domain.
  65. //////////
  66. NTSTATUS
  67. NTAPI
  68. ConnectToDomain( VOID )
  69. {
  70. NTSTATUS status;
  71. PLSAPR_POLICY_INFORMATION policyInfo;
  72. SAMPR_HANDLE hServer;
  73. API_LOCK();
  74. // If we've already been initialized, there's nothing to do.
  75. if (theConnectFlag)
  76. {
  77. status = STATUS_SUCCESS;
  78. goto exit;
  79. }
  80. //////////
  81. // Open a handle to the local account domain.
  82. //////////
  83. policyInfo = NULL;
  84. status = LsaIQueryInformationPolicyTrusted(
  85. PolicyAccountDomainInformation,
  86. &policyInfo
  87. );
  88. if (!NT_SUCCESS(status)) { goto exit; }
  89. status = SamIConnect(
  90. NULL,
  91. &hServer,
  92. 0,
  93. TRUE
  94. );
  95. if (!NT_SUCCESS(status)) { goto free_info; }
  96. status = SamrOpenDomain(
  97. hServer,
  98. 0,
  99. (PRPC_SID)policyInfo->PolicyAccountDomainInfo.DomainSid,
  100. &theAccountDomain
  101. );
  102. // Did we succeed ?
  103. if (NT_SUCCESS(status)) { theConnectFlag = TRUE; }
  104. SamrCloseHandle(&hServer);
  105. free_info:
  106. LsaIFree_LSAPR_POLICY_INFORMATION(
  107. PolicyAccountDomainInformation,
  108. policyInfo
  109. );
  110. exit:
  111. API_UNLOCK();
  112. return status;
  113. }
  114. //////////
  115. // Returns a SAM handle for the local account domain.
  116. // This handle must not be closed.
  117. //////////
  118. NTSTATUS
  119. NTAPI
  120. GetDomainHandle(
  121. OUT SAMPR_HANDLE *DomainHandle
  122. )
  123. {
  124. NTSTATUS status;
  125. CHECK_CONNECT();
  126. *DomainHandle = theAccountDomain;
  127. return STATUS_SUCCESS;
  128. }
  129. //////////
  130. // Returns a SAM handle for the given user.
  131. // The caller is responsible for closing the returned handle.
  132. //////////
  133. NTSTATUS
  134. NTAPI
  135. GetUserHandle(
  136. IN PUNICODE_STRING UserName,
  137. OUT SAMPR_HANDLE *UserHandle
  138. )
  139. {
  140. NTSTATUS status;
  141. SAMPR_ULONG_ARRAY RidArray;
  142. SAMPR_ULONG_ARRAY UseArray;
  143. CHECK_CONNECT();
  144. RidArray.Element = NULL;
  145. UseArray.Element = NULL;
  146. status = SamrLookupNamesInDomain(
  147. theAccountDomain,
  148. 1,
  149. (PRPC_UNICODE_STRING)UserName,
  150. &RidArray,
  151. &UseArray
  152. );
  153. if (!NT_SUCCESS(status)) { goto exit; }
  154. if (UseArray.Element[0] != SidTypeUser)
  155. {
  156. status = STATUS_NONE_MAPPED;
  157. goto free_arrays;
  158. }
  159. status = SamrOpenUser(
  160. theAccountDomain,
  161. 0,
  162. RidArray.Element[0],
  163. UserHandle
  164. );
  165. free_arrays:
  166. SamIFree_SAMPR_ULONG_ARRAY( &UseArray );
  167. SamIFree_SAMPR_ULONG_ARRAY( &RidArray );
  168. exit:
  169. return status;
  170. }
  171. /////////
  172. // Process an MD5-CHAP authentication.
  173. /////////
  174. NTSTATUS
  175. NTAPI
  176. ProcessMD5ChapAuthentication(
  177. IN SAM_HANDLE UserHandle,
  178. IN PUSER_ALL_INFORMATION UserAll,
  179. IN UCHAR ChallengeId,
  180. IN DWORD ChallengeLength,
  181. IN PUCHAR Challenge,
  182. IN PUCHAR Response
  183. )
  184. {
  185. NTSTATUS status;
  186. UNICODE_STRING uniPwd;
  187. ANSI_STRING ansiPwd;
  188. MD5_CTX context;
  189. /////////
  190. // Retrieve the cleartext password.
  191. /////////
  192. status = RetrieveCleartextPassword(
  193. UserHandle,
  194. UserAll,
  195. &uniPwd
  196. );
  197. if (status != STATUS_SUCCESS) { return status; }
  198. //////////
  199. // Convert the password to ANSI.
  200. //////////
  201. status = RtlUnicodeStringToAnsiString(
  202. &ansiPwd,
  203. &uniPwd,
  204. TRUE
  205. );
  206. // We're through with the Unicode password.
  207. RtlFreeUnicodeString(&uniPwd);
  208. if (!NT_SUCCESS(status)) { return STATUS_WRONG_PASSWORD; }
  209. //////////
  210. // Compute the correct response.
  211. //////////
  212. MD5Init(&context);
  213. MD5Update(&context, &ChallengeId, 1);
  214. MD5Update(&context, (PBYTE)ansiPwd.Buffer, ansiPwd.Length);
  215. MD5Update(&context, Challenge, ChallengeLength);
  216. MD5Final(&context);
  217. // We're through with the ANSI password.
  218. RtlFreeAnsiString(&ansiPwd);
  219. //////////
  220. // Does the actual response match the correct response ?
  221. //////////
  222. if (memcmp(context.digest, Response, 16) == 0)
  223. {
  224. return STATUS_SUCCESS;
  225. }
  226. return STATUS_WRONG_PASSWORD;
  227. }
  228. NTSTATUS
  229. NTAPI
  230. MD5ChapSubAuthentication(
  231. IN SAM_HANDLE UserHandle,
  232. IN PUSER_ALL_INFORMATION UserAll,
  233. IN PRAS_SUBAUTH_INFO RasInfo
  234. )
  235. {
  236. MD5CHAP_SUBAUTH_INFO* info = (MD5CHAP_SUBAUTH_INFO*)(RasInfo->Data);
  237. return ProcessMD5ChapAuthentication(
  238. UserHandle,
  239. UserAll,
  240. info->uchChallengeId,
  241. sizeof(info->uchChallenge),
  242. info->uchChallenge,
  243. info->uchResponse
  244. );
  245. }
  246. NTSTATUS
  247. NTAPI
  248. MD5ChapExSubAuthentication(
  249. IN SAM_HANDLE UserHandle,
  250. IN PUSER_ALL_INFORMATION UserAll,
  251. IN PRAS_SUBAUTH_INFO RasInfo
  252. )
  253. {
  254. MD5CHAP_EX_SUBAUTH_INFO* info = (MD5CHAP_EX_SUBAUTH_INFO*)(RasInfo->Data);
  255. DWORD challengeLength = RasInfo->DataSize -
  256. sizeof(MD5CHAP_EX_SUBAUTH_INFO) + 1;
  257. return ProcessMD5ChapAuthentication(
  258. UserHandle,
  259. UserAll,
  260. info->uchChallengeId,
  261. challengeLength,
  262. info->uchChallenge,
  263. info->uchResponse
  264. );
  265. }
  266. //////////
  267. // Entry point for the subauthentication DLL.
  268. //////////
  269. NTSTATUS
  270. NTAPI
  271. Msv1_0SubAuthenticationRoutine(
  272. IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
  273. IN PVOID LogonInformation,
  274. IN ULONG Flags,
  275. IN PUSER_ALL_INFORMATION UserAll,
  276. OUT PULONG WhichFields,
  277. OUT PULONG UserFlags,
  278. OUT PBOOLEAN Authoritative,
  279. OUT PLARGE_INTEGER LogoffTime,
  280. OUT PLARGE_INTEGER KickoffTime
  281. )
  282. {
  283. NTSTATUS status;
  284. LARGE_INTEGER logonTime;
  285. PNETLOGON_NETWORK_INFO logonInfo;
  286. PRAS_SUBAUTH_INFO rasInfo;
  287. MD5CHAP_SUBAUTH_INFO *chapInfo;
  288. PWSTR password;
  289. UNICODE_STRING uniPassword;
  290. SAMPR_HANDLE hUser;
  291. /////////
  292. // Initialize the out parameters.
  293. /////////
  294. *WhichFields = 0;
  295. *UserFlags = 0;
  296. *Authoritative = TRUE;
  297. /////////
  298. // Check some basic restrictions.
  299. /////////
  300. if (LogonLevel != NetlogonNetworkInformation)
  301. {
  302. return STATUS_INVALID_INFO_CLASS;
  303. }
  304. if (UserAll->UserAccountControl & USER_ACCOUNT_DISABLED)
  305. {
  306. return STATUS_ACCOUNT_DISABLED;
  307. }
  308. NtQuerySystemTime(&logonTime);
  309. if (UserAll->AccountExpires.QuadPart != 0 &&
  310. UserAll->AccountExpires.QuadPart <= logonTime.QuadPart)
  311. {
  312. return STATUS_ACCOUNT_EXPIRED;
  313. }
  314. if (UserAll->PasswordMustChange.QuadPart <= logonTime.QuadPart)
  315. {
  316. return UserAll->PasswordLastSet.QuadPart ? STATUS_PASSWORD_EXPIRED
  317. : STATUS_PASSWORD_MUST_CHANGE;
  318. }
  319. /////////
  320. // Extract the MD5CHAP_SUBAUTH_INFO struct.
  321. /////////
  322. logonInfo = (PNETLOGON_NETWORK_INFO)LogonInformation;
  323. rasInfo = (PRAS_SUBAUTH_INFO)logonInfo->NtChallengeResponse.Buffer;
  324. if (rasInfo == NULL ||
  325. logonInfo->NtChallengeResponse.Length < sizeof(RAS_SUBAUTH_INFO) ||
  326. rasInfo->ProtocolType != RAS_SUBAUTH_PROTO_MD5CHAP ||
  327. rasInfo->DataSize != sizeof(MD5CHAP_SUBAUTH_INFO))
  328. {
  329. return STATUS_INVALID_PARAMETER;
  330. }
  331. chapInfo = (MD5CHAP_SUBAUTH_INFO*)rasInfo->Data;
  332. /////////
  333. // Open a handle to the user object.
  334. /////////
  335. status = GetUserHandle(
  336. &(logonInfo->Identity.UserName),
  337. &hUser
  338. );
  339. if (status != NO_ERROR) { return status; }
  340. /////////
  341. // Verify the MD5-CHAP password.
  342. /////////
  343. status = ProcessMD5ChapAuthentication(
  344. hUser,
  345. UserAll,
  346. chapInfo->uchChallengeId,
  347. sizeof(chapInfo->uchChallenge),
  348. chapInfo->uchChallenge,
  349. chapInfo->uchResponse
  350. );
  351. if (status != NO_ERROR) { goto close_user; }
  352. /////////
  353. // Check account restrictions.
  354. /////////
  355. status = SamIAccountRestrictions(
  356. hUser,
  357. NULL,
  358. NULL,
  359. &UserAll->LogonHours,
  360. LogoffTime,
  361. KickoffTime
  362. );
  363. close_user:
  364. SamrCloseHandle(&hUser);
  365. return status;
  366. }
  367. /////////
  368. // Info needed to process a change notification.
  369. /////////
  370. typedef struct _PWD_CHANGE_INFO {
  371. ULONG RelativeId;
  372. WCHAR NewPassword[1];
  373. } PWD_CHANGE_INFO, *PPWD_CHANGE_INFO;
  374. /////////
  375. // Start routine for notification worker thread.
  376. /////////
  377. DWORD
  378. WINAPI
  379. PasswordChangeNotifyWorker(
  380. IN PPWD_CHANGE_INFO ChangeInfo
  381. )
  382. {
  383. NTSTATUS status;
  384. SAMPR_HANDLE hUser;
  385. PUSER_CONTROL_INFORMATION uci;
  386. ULONG accountControl;
  387. USER_PARAMETERS_INFORMATION *oldInfo, newInfo;
  388. BOOL cleartextAllowed;
  389. PWSTR oldUserParms, newUserParms;
  390. //////////
  391. // Ensure we're connected to the SAM domain.
  392. //////////
  393. if (!theConnectFlag)
  394. {
  395. status = ConnectToDomain();
  396. if (!NT_SUCCESS(status)) { goto exit; }
  397. }
  398. //////////
  399. // Retrieve the UserParameters
  400. //////////
  401. status = SamrOpenUser(
  402. theAccountDomain,
  403. 0,
  404. ChangeInfo->RelativeId,
  405. &hUser
  406. );
  407. if (!NT_SUCCESS(status)) { goto exit; }
  408. uci = NULL;
  409. status = SamrQueryInformationUser(
  410. hUser,
  411. UserControlInformation,
  412. (PSAMPR_USER_INFO_BUFFER*)&uci
  413. );
  414. if (!NT_SUCCESS(status)) { goto close_user; }
  415. // Save the info ...
  416. accountControl = uci->UserAccountControl;
  417. // ... and free the buffer.
  418. SamIFree_SAMPR_USER_INFO_BUFFER(
  419. (PSAMPR_USER_INFO_BUFFER)uci,
  420. UserControlInformation
  421. );
  422. // We're only interested in normal accounts.
  423. if (!(accountControl & USER_NORMAL_ACCOUNT)) { goto close_user; }
  424. oldInfo = NULL;
  425. status = SamrQueryInformationUser(
  426. hUser,
  427. UserParametersInformation,
  428. (PSAMPR_USER_INFO_BUFFER*)&oldInfo
  429. );
  430. if (!NT_SUCCESS(status)) { goto close_user; }
  431. //////////
  432. // Make a null-terminated copy.
  433. //////////
  434. oldUserParms = (PWSTR)
  435. RtlAllocateHeap(
  436. RasSfmHeap(),
  437. 0,
  438. oldInfo->Parameters.Length + sizeof(WCHAR)
  439. );
  440. if (oldUserParms == NULL)
  441. {
  442. status = STATUS_NO_MEMORY;
  443. goto free_user_info;
  444. }
  445. memcpy(
  446. oldUserParms,
  447. oldInfo->Parameters.Buffer,
  448. oldInfo->Parameters.Length
  449. );
  450. oldUserParms[oldInfo->Parameters.Length / sizeof(WCHAR)] = L'\0';
  451. //////////
  452. // Should we store the cleartext password in UserParameters?
  453. //////////
  454. status = IsCleartextEnabled(
  455. hUser,
  456. &cleartextAllowed
  457. );
  458. if (!NT_SUCCESS(status)) { goto free_user_parms; }
  459. newUserParms = NULL;
  460. if (cleartextAllowed)
  461. {
  462. // We either set the new password ...
  463. status = IASParmsSetUserPassword(
  464. oldUserParms,
  465. ChangeInfo->NewPassword,
  466. &newUserParms
  467. );
  468. }
  469. else
  470. {
  471. // ... or we erase the old one.
  472. status = IASParmsClearUserPassword(
  473. oldUserParms,
  474. &newUserParms
  475. );
  476. }
  477. // Write the UserParameters back to SAM if necessary.
  478. if (NT_SUCCESS(status) && newUserParms != NULL)
  479. {
  480. newInfo.Parameters.Length = (USHORT)(sizeof(WCHAR) * (lstrlenW(newUserParms) + 1));
  481. newInfo.Parameters.MaximumLength = newInfo.Parameters.Length;
  482. newInfo.Parameters.Buffer = newUserParms;
  483. status = SamrSetInformationUser(
  484. hUser,
  485. UserParametersInformation,
  486. (PSAMPR_USER_INFO_BUFFER)&newInfo
  487. );
  488. IASParmsFreeUserParms(newUserParms);
  489. }
  490. free_user_parms:
  491. RtlFreeHeap(
  492. RasSfmHeap(),
  493. 0,
  494. oldUserParms
  495. );
  496. free_user_info:
  497. SamIFree_SAMPR_USER_INFO_BUFFER(
  498. (PSAMPR_USER_INFO_BUFFER)oldInfo,
  499. UserParametersInformation
  500. );
  501. close_user:
  502. SamrCloseHandle(&hUser);
  503. exit:
  504. RtlFreeHeap(
  505. RasSfmHeap(),
  506. 0,
  507. ChangeInfo
  508. );
  509. return status;
  510. }
  511. //////////
  512. // Password change DLL entry point.
  513. //////////
  514. NTSTATUS
  515. NTAPI
  516. PasswordChangeNotify(
  517. IN PUNICODE_STRING UserName,
  518. IN ULONG RelativeId,
  519. IN PUNICODE_STRING NewPassword
  520. )
  521. {
  522. ULONG length;
  523. PPWD_CHANGE_INFO info;
  524. HANDLE hWorker;
  525. DWORD threadId;
  526. // Calculate the length of the new password.
  527. length = NewPassword ? NewPassword->Length : 0;
  528. // Allocate the PWD_CHANGE_INFO struct.
  529. info = (PPWD_CHANGE_INFO)
  530. RtlAllocateHeap(
  531. RasSfmHeap(),
  532. 0,
  533. sizeof(PWD_CHANGE_INFO) + length
  534. );
  535. if (info == NULL) { return STATUS_NO_MEMORY; }
  536. // Save the RelativeId.
  537. info->RelativeId = RelativeId;
  538. // Save the NewPassword.
  539. if (length) { memcpy(info->NewPassword, NewPassword->Buffer, length); }
  540. // Make sure it's null-terminated.
  541. info->NewPassword[length / sizeof(WCHAR)] = L'\0';
  542. // Create a worker thread.
  543. hWorker = CreateThread(
  544. NULL,
  545. 0,
  546. PasswordChangeNotifyWorker,
  547. info,
  548. 0,
  549. &threadId
  550. );
  551. if (hWorker)
  552. {
  553. CloseHandle(hWorker);
  554. return STATUS_SUCCESS;
  555. }
  556. RtlFreeHeap(
  557. RasSfmHeap(),
  558. 0,
  559. info
  560. );
  561. return STATUS_UNSUCCESSFUL;
  562. }