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.

797 lines
22 KiB

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