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.

757 lines
19 KiB

  1. ///////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright (c) 2000, Microsoft Corp. All rights reserved.
  4. //
  5. // FILE
  6. //
  7. // ntsamauth.cpp
  8. //
  9. // SYNOPSIS
  10. //
  11. // Defines the class NTSamAuthentication.
  12. //
  13. ///////////////////////////////////////////////////////////////////////////////
  14. #include <ias.h>
  15. #include <ntsamauth.h>
  16. #include <autohdl.h>
  17. #include <blob.h>
  18. #include <iaslsa.h>
  19. #include <iastlutl.h>
  20. #include <lockout.h>
  21. #include <mbstring.h>
  22. #include <samutil.h>
  23. #include <sdoias.h>
  24. bool NTSamAuthentication::allowLM;
  25. STDMETHODIMP NTSamAuthentication::Initialize()
  26. {
  27. DWORD error = IASLsaInitialize();
  28. if (error == NO_ERROR)
  29. {
  30. AccountLockoutInitialize();
  31. }
  32. return HRESULT_FROM_WIN32(error);
  33. }
  34. STDMETHODIMP NTSamAuthentication::Shutdown()
  35. {
  36. AccountLockoutShutdown();
  37. IASLsaUninitialize();
  38. return S_OK;
  39. }
  40. STDMETHODIMP NTSamAuthentication::PutProperty(LONG Id, VARIANT *pValue)
  41. {
  42. if (Id == PROPERTY_NTSAM_ALLOW_LM_AUTHENTICATION &&
  43. pValue != NULL &&
  44. V_VT(pValue) == VT_BOOL)
  45. {
  46. allowLM = V_BOOL(pValue) ? true : false;
  47. IASTracePrintf(
  48. "Setting LM Authentication allowed to %s.",
  49. (allowLM ? "TRUE" : "FALSE")
  50. );
  51. }
  52. return S_OK;
  53. }
  54. bool NTSamAuthentication::enforceLmRestriction(
  55. IASTL::IASRequest& request
  56. )
  57. {
  58. if (!allowLM)
  59. {
  60. IASTraceString("LanManager authentication is not enabled.");
  61. IASProcessFailure(request, IAS_LM_NOT_ALLOWED);
  62. }
  63. return allowLM;
  64. }
  65. void NTSamAuthentication::doMsChapAuthentication(
  66. IASTL::IASRequest& request,
  67. PCWSTR domainName,
  68. PCWSTR username,
  69. BYTE identity,
  70. PBYTE challenge,
  71. PBYTE ntResponse,
  72. PBYTE lmResponse
  73. )
  74. {
  75. DWORD status;
  76. auto_handle<> token;
  77. IAS_MSCHAP_PROFILE profile;
  78. status = IASLogonMSCHAP(
  79. username,
  80. domainName,
  81. challenge,
  82. ntResponse,
  83. lmResponse,
  84. &profile,
  85. &token
  86. );
  87. if (status == NO_ERROR)
  88. {
  89. MSChapMPPEKeys::insert(
  90. request,
  91. profile.LanmanSessionKey,
  92. profile.UserSessionKey,
  93. challenge
  94. );
  95. MSChapDomain::insert(
  96. request,
  97. identity,
  98. profile.LogonDomainName
  99. );
  100. }
  101. storeLogonResult(request, status, token);
  102. }
  103. void NTSamAuthentication::doMsChap2Authentication(
  104. IASTL::IASRequest& request,
  105. PCWSTR domainName,
  106. PCWSTR username,
  107. BYTE identity,
  108. IAS_OCTET_STRING& challenge,
  109. PBYTE response,
  110. PBYTE peerChallenge
  111. )
  112. {
  113. //////////
  114. // Get the hash username.
  115. //////////
  116. PIASATTRIBUTE attr = IASPeekAttribute(
  117. request,
  118. IAS_ATTRIBUTE_ORIGINAL_USER_NAME,
  119. IASTYPE_OCTET_STRING
  120. );
  121. if (!attr)
  122. {
  123. attr = IASPeekAttribute(
  124. request,
  125. RADIUS_ATTRIBUTE_USER_NAME,
  126. IASTYPE_OCTET_STRING
  127. );
  128. if (!attr)
  129. {
  130. _com_issue_error(IAS_MALFORMED_REQUEST);
  131. }
  132. }
  133. PCSTR rawUserName = IAS_OCT2ANSI(attr->Value.OctetString);
  134. PCSTR hashUserName = (PCSTR)_mbschr((const BYTE*)rawUserName, '\\');
  135. hashUserName = hashUserName ? (hashUserName + 1) : rawUserName;
  136. //////////
  137. // Authenticate the user.
  138. //////////
  139. DWORD status;
  140. auto_handle<> token;
  141. IAS_MSCHAP_V2_PROFILE profile;
  142. status = IASLogonMSCHAPv2(
  143. username,
  144. domainName,
  145. hashUserName,
  146. challenge.lpValue,
  147. challenge.dwLength,
  148. response,
  149. peerChallenge,
  150. &profile,
  151. &token
  152. );
  153. //////////
  154. // Process the result.
  155. //////////
  156. if (status == NO_ERROR)
  157. {
  158. MSMPPEKey::insert(
  159. request,
  160. sizeof(profile.RecvSessionKey),
  161. profile.RecvSessionKey,
  162. FALSE
  163. );
  164. MSMPPEKey::insert(
  165. request,
  166. sizeof(profile.SendSessionKey),
  167. profile.SendSessionKey,
  168. TRUE
  169. );
  170. MSChap2Success::insert(
  171. request,
  172. identity,
  173. profile.AuthResponse
  174. );
  175. MSChapDomain::insert(
  176. request,
  177. identity,
  178. profile.LogonDomainName
  179. );
  180. }
  181. storeLogonResult(request, status, token);
  182. }
  183. IASREQUESTSTATUS NTSamAuthentication::onSyncRequest(IRequest* pRequest) throw ()
  184. {
  185. HANDLE hAccount = 0;
  186. try
  187. {
  188. IASTL::IASRequest request(pRequest);
  189. //////////
  190. // Extract the NT4-Account-Name attribute.
  191. //////////
  192. IASTL::IASAttribute identity;
  193. if (!identity.load(
  194. request,
  195. IAS_ATTRIBUTE_NT4_ACCOUNT_NAME,
  196. IASTYPE_STRING
  197. ))
  198. {
  199. return IAS_REQUEST_STATUS_CONTINUE;
  200. }
  201. //////////
  202. // Convert the User-Name to SAM format.
  203. //////////
  204. PCWSTR domain, username;
  205. EXTRACT_SAM_IDENTITY(identity->Value.String, domain, username);
  206. IASTracePrintf(
  207. "NT-SAM Authentication handler received request for %S\\%S.",
  208. domain,
  209. username
  210. );
  211. //////////
  212. // Check if the account has been locked out.
  213. //////////
  214. if (AccountLockoutOpenAndQuery(
  215. username,
  216. domain,
  217. &hAccount
  218. ))
  219. {
  220. IASTraceString("Account has been locked out locally -- rejecting.");
  221. AccountLockoutClose(hAccount);
  222. request.SetResponse(IAS_RESPONSE_ACCESS_REJECT, IAS_DIALIN_LOCKED_OUT);
  223. return IAS_REQUEST_STATUS_CONTINUE;
  224. }
  225. // Try each authentication type.
  226. if (!tryMsChap2All(request, domain, username) &&
  227. !tryMsChapAll(request, domain, username) &&
  228. !tryMd5Chap(request, domain, username) &&
  229. !tryPap(request, domain, username))
  230. {
  231. // Since the EAP request handler is invoked after policy
  232. // evaluation, we have to set the auth type here.
  233. if (IASPeekAttribute(
  234. request,
  235. RADIUS_ATTRIBUTE_EAP_MESSAGE,
  236. IASTYPE_OCTET_STRING
  237. ))
  238. {
  239. storeAuthenticationType(request, IAS_AUTH_EAP);
  240. }
  241. else
  242. {
  243. // Otherwise, the auth type is "Unauthenticated".
  244. storeAuthenticationType(request, IAS_AUTH_NONE);
  245. }
  246. }
  247. //////////
  248. // Update the lockout database based on the results.
  249. //////////
  250. if (request.get_Response() == IAS_RESPONSE_ACCESS_ACCEPT)
  251. {
  252. AccountLockoutUpdatePass(hAccount);
  253. }
  254. else if (request.get_Reason() == IAS_AUTH_FAILURE)
  255. {
  256. AccountLockoutUpdateFail(hAccount);
  257. }
  258. }
  259. catch (const _com_error& ce)
  260. {
  261. IASTraceExcept();
  262. IASProcessFailure(pRequest, ce.Error());
  263. }
  264. AccountLockoutClose(hAccount);
  265. return IAS_REQUEST_STATUS_CONTINUE;
  266. }
  267. void NTSamAuthentication::storeAuthenticationType(
  268. IASTL::IASRequest& request,
  269. DWORD authType
  270. )
  271. {
  272. IASTL::IASAttribute attr(true);
  273. attr->dwId = IAS_ATTRIBUTE_AUTHENTICATION_TYPE;
  274. attr->Value.itType = IASTYPE_ENUM;
  275. attr->Value.Enumerator = authType;
  276. attr.store(request);
  277. }
  278. void NTSamAuthentication::storeLogonResult(
  279. IASTL::IASRequest& request,
  280. DWORD status,
  281. HANDLE token
  282. )
  283. {
  284. if (status == ERROR_SUCCESS)
  285. {
  286. IASTraceString("LogonUser succeeded.");
  287. storeTokenGroups(request, token);
  288. request.SetResponse(IAS_RESPONSE_ACCESS_ACCEPT, S_OK);
  289. }
  290. else
  291. {
  292. IASTraceFailure("LogonUser", status);
  293. IASProcessFailure(request, IASMapWin32Error(status, IAS_AUTH_FAILURE));
  294. }
  295. }
  296. void NTSamAuthentication::storeTokenGroups(
  297. IASTL::IASRequest& request,
  298. HANDLE token
  299. )
  300. {
  301. DWORD returnLength;
  302. //////////
  303. // Determine the needed buffer size.
  304. //////////
  305. BOOL success = GetTokenInformation(
  306. token,
  307. TokenGroups,
  308. NULL,
  309. 0,
  310. &returnLength
  311. );
  312. DWORD status = GetLastError();
  313. // Should have failed with ERROR_INSUFFICIENT_BUFFER.
  314. if (success || status != ERROR_INSUFFICIENT_BUFFER)
  315. {
  316. IASTraceFailure("GetTokenInformation", status);
  317. _com_issue_error(HRESULT_FROM_WIN32(status));
  318. }
  319. //////////
  320. // Allocate an attribute.
  321. //////////
  322. IASTL::IASAttribute groups(true);
  323. //////////
  324. // Allocate a buffer to hold the TOKEN_GROUPS array.
  325. //////////
  326. groups->Value.OctetString.lpValue = (PBYTE)CoTaskMemAlloc(returnLength);
  327. if (!groups->Value.OctetString.lpValue)
  328. {
  329. _com_issue_error(E_OUTOFMEMORY);
  330. }
  331. //////////
  332. // Get the Token Groups info.
  333. //////////
  334. GetTokenInformation(
  335. token,
  336. TokenGroups,
  337. groups->Value.OctetString.lpValue,
  338. returnLength,
  339. &groups->Value.OctetString.dwLength
  340. );
  341. //////////
  342. // Set the id and type of the initialized attribute.
  343. //////////
  344. groups->dwId = IAS_ATTRIBUTE_TOKEN_GROUPS;
  345. groups->Value.itType = IASTYPE_OCTET_STRING;
  346. //////////
  347. // Inject the Token-Groups into the request.
  348. //////////
  349. groups.store(request);
  350. }
  351. bool NTSamAuthentication::tryMsChap(
  352. IASTL::IASRequest& request,
  353. PCWSTR domainName,
  354. PCWSTR username,
  355. PBYTE challenge
  356. )
  357. {
  358. // Is the necessary attribute present?
  359. IASAttribute attr;
  360. if (!attr.load(
  361. request,
  362. MS_ATTRIBUTE_CHAP_RESPONSE,
  363. IASTYPE_OCTET_STRING
  364. ))
  365. {
  366. return false;
  367. }
  368. MSChapResponse& response = blob_cast<MSChapResponse>(attr);
  369. IASTraceString("Processing MS-CHAP v1 authentication.");
  370. storeAuthenticationType(request, IAS_AUTH_MSCHAP);
  371. if (!response.isLmPresent() || enforceLmRestriction(request))
  372. {
  373. doMsChapAuthentication(
  374. request,
  375. domainName,
  376. username,
  377. response.get().ident,
  378. challenge,
  379. (response.isNtPresent() ? response.get().ntResponse : NULL),
  380. (response.isLmPresent() ? response.get().lmResponse : NULL)
  381. );
  382. }
  383. return true;
  384. }
  385. bool NTSamAuthentication::tryMsChapCpw1(
  386. IASTL::IASRequest& request,
  387. PCWSTR domainName,
  388. PCWSTR username,
  389. PBYTE challenge
  390. )
  391. {
  392. // Is the necessary attribute present ?
  393. IASAttribute attr;
  394. bool present = attr.load(
  395. request,
  396. MS_ATTRIBUTE_CHAP_CPW1,
  397. IASTYPE_OCTET_STRING
  398. );
  399. if (present)
  400. {
  401. IASTraceString("Deferring MS-CHAP-CPW-1.");
  402. storeAuthenticationType(request, IAS_AUTH_MSCHAP_CPW);
  403. }
  404. return present;
  405. }
  406. bool NTSamAuthentication::tryMsChapCpw2(
  407. IASTL::IASRequest& request,
  408. PCWSTR domainName,
  409. PCWSTR username,
  410. PBYTE challenge
  411. )
  412. {
  413. // Is the necessary attribute present ?
  414. IASAttribute attr;
  415. bool present = attr.load(
  416. request,
  417. MS_ATTRIBUTE_CHAP_CPW2,
  418. IASTYPE_OCTET_STRING
  419. );
  420. if (present)
  421. {
  422. IASTraceString("Deferring MS-CHAP-CPW-2.");
  423. storeAuthenticationType(request, IAS_AUTH_MSCHAP_CPW);
  424. }
  425. return present;
  426. }
  427. bool NTSamAuthentication::tryMsChap2(
  428. IASTL::IASRequest& request,
  429. PCWSTR domainName,
  430. PCWSTR username,
  431. IAS_OCTET_STRING& challenge
  432. )
  433. {
  434. // Is the necessary attribute present?
  435. IASAttribute attr;
  436. if (!attr.load(
  437. request,
  438. MS_ATTRIBUTE_CHAP2_RESPONSE,
  439. IASTYPE_OCTET_STRING
  440. ))
  441. {
  442. return false;
  443. }
  444. MSChap2Response& response = blob_cast<MSChap2Response>(attr);
  445. IASTraceString("Processing MS-CHAP v2 authentication.");
  446. storeAuthenticationType(request, IAS_AUTH_MSCHAP2);
  447. //////////
  448. // Authenticate the user.
  449. //////////
  450. doMsChap2Authentication(
  451. request,
  452. domainName,
  453. username,
  454. response.get().ident,
  455. challenge,
  456. response.get().response,
  457. response.get().peerChallenge
  458. );
  459. return true;
  460. }
  461. bool NTSamAuthentication::tryMsChap2Cpw(
  462. IASTL::IASRequest& request,
  463. PCWSTR domainName,
  464. PCWSTR username,
  465. IAS_OCTET_STRING& challenge
  466. )
  467. {
  468. // Is the necessary attribute present ?
  469. IASAttribute attr;
  470. bool present = attr.load(
  471. request,
  472. MS_ATTRIBUTE_CHAP2_CPW,
  473. IASTYPE_OCTET_STRING
  474. );
  475. if (present)
  476. {
  477. IASTraceString("Deferring MS-CHAP v2 change password.");
  478. storeAuthenticationType(request, IAS_AUTH_MSCHAP2_CPW);
  479. }
  480. return present;
  481. }
  482. bool NTSamAuthentication::tryMd5Chap(
  483. IASTL::IASRequest& request,
  484. PCWSTR domainName,
  485. PCWSTR username
  486. )
  487. {
  488. // Is the necessary attribute present?
  489. IASTL::IASAttribute chapPassword;
  490. if (!chapPassword.load(
  491. request,
  492. RADIUS_ATTRIBUTE_CHAP_PASSWORD,
  493. IASTYPE_OCTET_STRING
  494. ))
  495. {
  496. return false;
  497. }
  498. IASTraceString("Processing MD5-CHAP authentication.");
  499. storeAuthenticationType(request, IAS_AUTH_MD5CHAP);
  500. //////////
  501. // Split up the CHAP-Password attribute.
  502. //////////
  503. // The ID is the first byte of the value ...
  504. BYTE challengeID = *(chapPassword->Value.OctetString.lpValue);
  505. // ... and the password is the rest.
  506. PBYTE password = chapPassword->Value.OctetString.lpValue + 1;
  507. //////////
  508. // Use the CHAP-Challenge if available, request authenticator otherwise.
  509. //////////
  510. IASTL::IASAttribute chapChallenge, radiusHeader;
  511. if (!chapChallenge.load(
  512. request,
  513. RADIUS_ATTRIBUTE_CHAP_CHALLENGE,
  514. IASTYPE_OCTET_STRING
  515. ) &&
  516. !radiusHeader.load(
  517. request,
  518. IAS_ATTRIBUTE_CLIENT_PACKET_HEADER,
  519. IASTYPE_OCTET_STRING
  520. ))
  521. {
  522. _com_issue_error(IAS_MALFORMED_REQUEST);
  523. }
  524. PBYTE challenge;
  525. DWORD challengeLength;
  526. if (chapChallenge)
  527. {
  528. challenge = chapChallenge->Value.OctetString.lpValue;
  529. challengeLength = chapChallenge->Value.OctetString.dwLength;
  530. }
  531. else
  532. {
  533. challenge = radiusHeader->Value.OctetString.lpValue + 4;
  534. challengeLength = 16;
  535. }
  536. //////////
  537. // Try to logon the user.
  538. //////////
  539. auto_handle<> token;
  540. DWORD status = IASLogonCHAP(
  541. username,
  542. domainName,
  543. challengeID,
  544. challenge,
  545. challengeLength,
  546. password,
  547. &token
  548. );
  549. //////////
  550. // Store the results.
  551. //////////
  552. storeLogonResult(request, status, token);
  553. return true;
  554. }
  555. bool NTSamAuthentication::tryMsChapAll(
  556. IASTL::IASRequest& request,
  557. PCWSTR domainName,
  558. PCWSTR username
  559. )
  560. {
  561. // Do we have the necessary attribute?
  562. IASTL::IASAttribute msChapChallenge;
  563. if (!msChapChallenge.load(
  564. request,
  565. MS_ATTRIBUTE_CHAP_CHALLENGE,
  566. IASTYPE_OCTET_STRING
  567. ))
  568. {
  569. return false;
  570. }
  571. if (msChapChallenge->Value.OctetString.dwLength != _MSV1_0_CHALLENGE_LENGTH)
  572. {
  573. _com_issue_error(IAS_MALFORMED_REQUEST);
  574. }
  575. PBYTE challenge = msChapChallenge->Value.OctetString.lpValue;
  576. return tryMsChap(request, domainName, username, challenge) ||
  577. tryMsChapCpw2(request, domainName, username, challenge) ||
  578. tryMsChapCpw1(request, domainName, username, challenge);
  579. }
  580. bool NTSamAuthentication::tryMsChap2All(
  581. IASTL::IASRequest& request,
  582. PCWSTR domainName,
  583. PCWSTR username
  584. )
  585. {
  586. // Do we have the necessary attribute?
  587. IASTL::IASAttribute msChapChallenge;
  588. if (!msChapChallenge.load(
  589. request,
  590. MS_ATTRIBUTE_CHAP_CHALLENGE,
  591. IASTYPE_OCTET_STRING
  592. ))
  593. {
  594. return false;
  595. }
  596. IAS_OCTET_STRING& challenge = msChapChallenge->Value.OctetString;
  597. return tryMsChap2(request, domainName, username, challenge) ||
  598. tryMsChap2Cpw(request, domainName, username, challenge);
  599. }
  600. bool NTSamAuthentication::tryPap(
  601. IASTL::IASRequest& request,
  602. PCWSTR domainName,
  603. PCWSTR username
  604. )
  605. {
  606. // Do we have the necessary attribute?
  607. IASTL::IASAttribute password;
  608. if (!password.load(
  609. request,
  610. RADIUS_ATTRIBUTE_USER_PASSWORD,
  611. IASTYPE_OCTET_STRING
  612. ))
  613. {
  614. return false;
  615. }
  616. IASTraceString("Processing PAP authentication.");
  617. storeAuthenticationType(request, IAS_AUTH_PAP);
  618. //////////
  619. // Convert the password to a string.
  620. //////////
  621. PSTR userPwd = IAS_OCT2ANSI(password->Value.OctetString);
  622. //////////
  623. // Try to logon the user.
  624. //////////
  625. auto_handle<> token;
  626. DWORD status = IASLogonPAP(
  627. username,
  628. domainName,
  629. userPwd,
  630. &token
  631. );
  632. //////////
  633. // Store the results.
  634. //////////
  635. storeLogonResult(request, status, token);
  636. return true;
  637. }