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.

718 lines
19 KiB

  1. ///////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright (c) 1998, Microsoft Corp. All rights reserved.
  4. //
  5. // FILE
  6. //
  7. // EAPSession.cpp
  8. //
  9. // SYNOPSIS
  10. //
  11. // This file defines the class EAPSession.
  12. //
  13. // MODIFICATION HISTORY
  14. //
  15. // 01/15/1998 Original version.
  16. // 06/02/1998 Pass NT4-Account-Name for the identity.
  17. // 06/12/1998 Changed put_Response to SetResponse.
  18. // 07/29/1998 Preserve all attributes added by pipeline.
  19. // 08/27/1998 Use new EAPFSM class.
  20. // 10/09/1998 Preserve order when saving profile attributes.
  21. // 10/13/1998 Add maxPacketLength property.
  22. // 10/24/1998 Account lockout support.
  23. // 11/13/1998 Pass event log handle to RasEapBegin.
  24. // 11/24/1998 Don't accept a Framed-MTU of less than 64 bytes.
  25. // 02/03/1999 Change NP-Authentication-Type to 5.
  26. // 02/13/1999 MPPE keys are returned as type 26.
  27. // 05/04/1999 New error codes.
  28. // 05/11/1999 Fix RADIUS encryption.
  29. // 05/20/1999 Identity is now a Unicode string.
  30. // 02/17/2000 Key encryption is now handled by the protocol.
  31. //
  32. ///////////////////////////////////////////////////////////////////////////////
  33. #include <ias.h>
  34. #include <iaslsa.h>
  35. #include <lockout.h>
  36. #include <samutil.h>
  37. #include <sdoias.h>
  38. #include <eapdnary.h>
  39. #include <eapsession.h>
  40. #include <eapstate.h>
  41. #include <eaptype.h>
  42. // Default value for the Framed-MTU attribute.
  43. const DWORD FRAMED_MTU_DEFAULT = 1500;
  44. // Minimum allowed value for the Framed-MTU attribute.
  45. const DWORD FRAMED_MTU_MIN = 64;
  46. // The maximum length of the frame header. i.e. max(2,4).
  47. // 2 for the PPP header and 4 for the 802.1X header.
  48. // The length of the frame header plus the length
  49. // of the EAP packet must be less than the Framed-MTU.
  50. const DWORD FRAME_HEADER_LENGTH = 4;
  51. // Absolute maximum length of an EAP packet. We bound this to limit worst-case
  52. // memory consumption.
  53. const DWORD MAX_MAX_PACKET_LENGTH = 2048;
  54. //////////
  55. // Inject a PPP_EAP_PACKET into a request.
  56. //////////
  57. VOID
  58. WINAPI
  59. InjectPacket(
  60. IASRequest& request,
  61. const PPP_EAP_PACKET& packet
  62. )
  63. {
  64. // Get the raw buffer to be packed.
  65. const BYTE* buf = (const BYTE*)&packet;
  66. DWORD nbyte = IASExtractWORD(packet.Length);
  67. IASTracePrintf("Inserting outbound EAP-Message of length %lu.", nbyte);
  68. // Determine the maximum chunk size.
  69. DWORD chunkSize;
  70. switch (request.get_Protocol())
  71. {
  72. case IAS_PROTOCOL_RADIUS:
  73. chunkSize = 253;
  74. break;
  75. default:
  76. chunkSize = nbyte;
  77. }
  78. // Split the buffer into chunks.
  79. while (nbyte)
  80. {
  81. // Compute how many bytes of the EAP-Message to store in this attribute.
  82. DWORD length = min(nbyte, chunkSize);
  83. // Initialize the attribute fields.
  84. IASAttribute attr(true);
  85. attr.setOctetString(length, buf);
  86. attr->dwId = RADIUS_ATTRIBUTE_EAP_MESSAGE;
  87. attr->dwFlags = IAS_INCLUDE_IN_RESPONSE;
  88. // Inject the attribute into the request.
  89. attr.store(request);
  90. // Update our state.
  91. nbyte -= length;
  92. buf += length;
  93. }
  94. }
  95. //////////
  96. // Extracts the Vendor-Type field from a Microsoft VSA. Returns zero if the
  97. // attribute is not a valid Microsoft VSA.
  98. //////////
  99. BYTE
  100. WINAPI
  101. ExtractMicrosoftVendorType(
  102. const IASATTRIBUTE& attr
  103. ) throw ()
  104. {
  105. if (attr.dwId == RADIUS_ATTRIBUTE_VENDOR_SPECIFIC &&
  106. attr.Value.itType == IASTYPE_OCTET_STRING &&
  107. attr.Value.OctetString.dwLength > 6 &&
  108. !memcmp(attr.Value.OctetString.lpValue, "\x00\x00\x01\x37", 4))
  109. {
  110. return *(attr.Value.OctetString.lpValue + 4);
  111. }
  112. return (BYTE)0;
  113. }
  114. //////////
  115. // Inject an array of RAS attributes into a request.
  116. //////////
  117. VOID
  118. WINAPI
  119. InjectRASAttributes(
  120. IASRequest& request,
  121. const RAS_AUTH_ATTRIBUTE* rasAttrs,
  122. DWORD flags
  123. )
  124. {
  125. if (rasAttrs == NULL) { return; }
  126. //////////
  127. // Translate them to IAS format.
  128. //////////
  129. IASTraceString("Translating attributes returned by EAP DLL.");
  130. IASAttributeVectorWithBuffer<8> iasAttrs;
  131. EAPTranslator::translate(iasAttrs, rasAttrs);
  132. //////////
  133. // Iterate through the converted attributes to set the flags and remove any
  134. // matching attributes from the request.
  135. //////////
  136. IASAttributeVector::iterator i;
  137. for (i = iasAttrs.begin(); i != iasAttrs.end(); ++i)
  138. {
  139. IASTracePrintf("Inserting attribute %lu", i->pAttribute->dwId);
  140. i->pAttribute->dwFlags = flags;
  141. request.RemoveAttributesByType(1, &(i->pAttribute->dwId));
  142. }
  143. //////////
  144. // Add them to the request.
  145. //////////
  146. iasAttrs.store(request);
  147. }
  148. //////////
  149. // Performs NT-SAM PAP and MD5-CHAP authentication based on RAS attributes.
  150. //////////
  151. DWORD
  152. WINAPI
  153. AuthenticateUser(
  154. IAS_STRING& account,
  155. RAS_AUTH_ATTRIBUTE* pInAttributes
  156. ) throw ()
  157. {
  158. //////////
  159. // Check the input parameters.
  160. //////////
  161. if (!pInAttributes) { return NO_ERROR; }
  162. //////////
  163. // Get the NT-SAM userName and domain.
  164. //////////
  165. PCWSTR userName, domain;
  166. EXTRACT_SAM_IDENTITY(account, domain, userName);
  167. //////////
  168. // Find the credentials populated by the EAP DLL.
  169. //////////
  170. PRAS_AUTH_ATTRIBUTE rasUserPassword = NULL,
  171. rasMD5CHAPPassword = NULL,
  172. rasMD5CHAPChallenge = NULL;
  173. for ( ; pInAttributes->raaType != raatMinimum; ++pInAttributes)
  174. {
  175. switch (pInAttributes->raaType)
  176. {
  177. case raatUserPassword:
  178. rasUserPassword = pInAttributes;
  179. break;
  180. case raatMD5CHAPPassword:
  181. rasMD5CHAPPassword = pInAttributes;
  182. break;
  183. case raatMD5CHAPChallenge:
  184. rasMD5CHAPChallenge = pInAttributes;
  185. break;
  186. }
  187. }
  188. DWORD status = NO_ERROR;
  189. // Is this MD5-CHAP?
  190. if (rasMD5CHAPPassword && rasMD5CHAPChallenge)
  191. {
  192. _ASSERT(rasMD5CHAPPassword->dwLength == 17);
  193. // The ID is the first byte of the password ...
  194. BYTE challengeID = *(PBYTE)(rasMD5CHAPPassword->Value);
  195. // ... and the password is the rest.
  196. PBYTE chapPassword = (PBYTE)(rasMD5CHAPPassword->Value) + 1;
  197. IASTracePrintf("Performing CHAP authentication for user %S\\%S.",
  198. domain, userName);
  199. HANDLE token;
  200. status = IASLogonCHAP(
  201. userName,
  202. domain,
  203. challengeID,
  204. (PBYTE)(rasMD5CHAPChallenge->Value),
  205. rasMD5CHAPChallenge->dwLength,
  206. chapPassword,
  207. &token
  208. );
  209. CloseHandle(token);
  210. }
  211. // Is this PAP?
  212. else if (rasUserPassword)
  213. {
  214. // Convert to a null-terminated string.
  215. IAS_OCTET_STRING octstr = { rasUserPassword->dwLength,
  216. (PBYTE)rasUserPassword->Value };
  217. PCSTR userPwd = IAS_OCT2ANSI(octstr);
  218. IASTracePrintf("Performing PAP authentication for user %S\\%S.",
  219. domain, userName);
  220. HANDLE token;
  221. status = IASLogonPAP(
  222. userName,
  223. domain,
  224. userPwd,
  225. &token
  226. );
  227. CloseHandle(token);
  228. }
  229. return status;
  230. }
  231. //////////
  232. // Updates the AccountLockout database.
  233. //////////
  234. VOID
  235. WINAPI
  236. UpdateAcccountLockoutDB(
  237. IAS_STRING& account,
  238. DWORD authResult
  239. ) throw ()
  240. {
  241. // Get the NT-SAM userName and domain.
  242. PCWSTR userName, domain;
  243. EXTRACT_SAM_IDENTITY(account, domain, userName);
  244. // Lookup the user in the lockout DB.
  245. HANDLE hAccount;
  246. AccountLockoutOpenAndQuery(userName, domain, &hAccount);
  247. // Report the result.
  248. if (authResult == NO_ERROR)
  249. {
  250. AccountLockoutUpdatePass(hAccount);
  251. }
  252. else
  253. {
  254. AccountLockoutUpdateFail(hAccount);
  255. }
  256. // Close the handle.
  257. AccountLockoutClose(hAccount);
  258. }
  259. //////////
  260. // Define the static members.
  261. //////////
  262. LONG EAPSession::theNextID = 0;
  263. LONG EAPSession::theRefCount = 0;
  264. IASAttribute EAPSession::theNormalTimeout;
  265. IASAttribute EAPSession::theInteractiveTimeout;
  266. HANDLE EAPSession::theIASEventLog;
  267. HANDLE EAPSession::theRASEventLog;
  268. EAPSession::EAPSession(const IASAttribute& accountName, const EAPType& eapType)
  269. : id((DWORD)InterlockedIncrement(&theNextID)),
  270. type(eapType),
  271. account(accountName),
  272. state(EAPState::createAttribute(id), false),
  273. fsm((BYTE)eapType.dwEapTypeId),
  274. maxPacketLength(FRAMED_MTU_DEFAULT - FRAME_HEADER_LENGTH),
  275. workBuffer(NULL),
  276. sendPacket(NULL)
  277. { }
  278. EAPSession::~EAPSession() throw ()
  279. {
  280. delete[] sendPacket;
  281. if (workBuffer) { type.RasEapEnd(workBuffer); }
  282. }
  283. IASREQUESTSTATUS EAPSession::begin(
  284. IASRequest& request,
  285. PPPP_EAP_PACKET recvPacket
  286. )
  287. {
  288. //////////
  289. // Get all the attributes from the request.
  290. //////////
  291. USES_IAS_STACK_VECTOR();
  292. IASAttributeVectorOnStack(all, request, 0);
  293. all.load(request, 0, NULL);
  294. //////////
  295. // Scan for the Framed-MTU attribute and compute the profile size.
  296. //////////
  297. DWORD profileSize = 0;
  298. IASAttributeVector::iterator i;
  299. for (i = all.begin(); i != all.end(); ++i)
  300. {
  301. if (i->pAttribute->dwId == RADIUS_ATTRIBUTE_FRAMED_MTU)
  302. {
  303. DWORD framedMTU = i->pAttribute->Value.Integer;
  304. // Only process valid values.
  305. if (framedMTU >= FRAMED_MTU_MIN)
  306. {
  307. // Leave room for the frame header.
  308. maxPacketLength = framedMTU - FRAME_HEADER_LENGTH;
  309. // Make sure we're within bounds.
  310. if (maxPacketLength > MAX_MAX_PACKET_LENGTH)
  311. {
  312. maxPacketLength = MAX_MAX_PACKET_LENGTH;
  313. }
  314. }
  315. IASTracePrintf("Setting max. packet length to %lu.", maxPacketLength);
  316. }
  317. if (!(i->pAttribute->dwFlags & IAS_RECVD_FROM_PROTOCOL))
  318. {
  319. ++profileSize;
  320. }
  321. }
  322. //////////
  323. // Save and remove the profile attributes.
  324. //////////
  325. profile.reserve(profileSize);
  326. for (i = all.begin(); i != all.end(); ++i)
  327. {
  328. if (!(i->pAttribute->dwFlags & IAS_RECVD_FROM_PROTOCOL))
  329. {
  330. profile.push_back(*i);
  331. }
  332. }
  333. profile.remove(request);
  334. //////////
  335. // Convert the attributes received from the client to RAS format.
  336. //////////
  337. PRAS_AUTH_ATTRIBUTE ras = IAS_STACK_NEW(RAS_AUTH_ATTRIBUTE, all.size() + 1);
  338. EAPTranslator::translate(ras, all, IAS_RECVD_FROM_CLIENT);
  339. //////////
  340. // Initialize the EAPInput struct.
  341. //////////
  342. EAPInput eapInput;
  343. eapInput.fAuthenticator = TRUE;
  344. eapInput.pUserAttributes = ras;
  345. eapInput.bInitialId = recvPacket->Id + (BYTE)1;
  346. eapInput.pwszIdentity = account->Value.String.pszWide;
  347. switch (request.get_Protocol())
  348. {
  349. case IAS_PROTOCOL_RADIUS:
  350. eapInput.hReserved = theIASEventLog;
  351. break;
  352. case IAS_PROTOCOL_RAS:
  353. eapInput.hReserved = theRASEventLog;
  354. break;
  355. }
  356. //////////
  357. // Begin the session with the EAP DLL.
  358. //////////
  359. DWORD error = type.RasEapBegin(&workBuffer, &eapInput);
  360. if (error != NO_ERROR)
  361. {
  362. IASTraceFailure("RasEapBegin", error);
  363. request.SetResponse(IAS_RESPONSE_DISCARD_PACKET, IAS_INTERNAL_ERROR);
  364. return IAS_REQUEST_STATUS_ABORT;
  365. }
  366. //////////
  367. // We have successfully established the session, so process the message.
  368. //////////
  369. return process(request, recvPacket);
  370. }
  371. IASREQUESTSTATUS EAPSession::process(
  372. IASRequest& request,
  373. PPPP_EAP_PACKET recvPacket
  374. )
  375. {
  376. // Trigger an event on the FSM.
  377. switch (fsm.onReceiveEvent(*recvPacket))
  378. {
  379. case EAPFSM::MAKE_MESSAGE:
  380. break;
  381. case EAPFSM::REPLAY_LAST:
  382. IASTraceString("EAP-Message appears to be a retransmission. "
  383. "Replaying last action.");
  384. return doAction(request);
  385. case EAPFSM::FAIL_NEGOTIATE:
  386. IASTraceString("EAP negotiation failed. Rejecting user.");
  387. request.SetResponse(IAS_RESPONSE_ACCESS_REJECT,
  388. IAS_UNSUPPORTED_AUTH_TYPE);
  389. return IAS_REQUEST_STATUS_HANDLED;
  390. case EAPFSM::DISCARD:
  391. IASTraceString("EAP-Message is unexpected. Discarding packet.");
  392. request.SetResponse(IAS_RESPONSE_DISCARD_PACKET,
  393. IAS_UNEXPECTED_REQUEST);
  394. return IAS_REQUEST_STATUS_ABORT;
  395. }
  396. // Allocate a temporary packet to hold the response.
  397. PPPP_EAP_PACKET tmpPacket = (PPPP_EAP_PACKET)_alloca(maxPacketLength);
  398. // Clear the previous output from the DLL.
  399. eapOutput.clear();
  400. DWORD error;
  401. error = type.RasEapMakeMessage(
  402. workBuffer,
  403. recvPacket,
  404. tmpPacket,
  405. maxPacketLength,
  406. &eapOutput,
  407. NULL
  408. );
  409. if (error != NO_ERROR)
  410. {
  411. IASTraceFailure("RasEapMakeMessage", error);
  412. request.SetResponse(IAS_RESPONSE_DISCARD_PACKET, IAS_INTERNAL_ERROR);
  413. return IAS_REQUEST_STATUS_ABORT;
  414. }
  415. while (eapOutput.Action == EAPACTION_Authenticate)
  416. {
  417. IASTraceString("EAP DLL invoked default authenticator.");
  418. // Authenticate the user.
  419. DWORD authResult = AuthenticateUser(
  420. account->Value.String,
  421. eapOutput.pUserAttributes
  422. );
  423. //////////
  424. // Convert the profile to RAS format.
  425. //////////
  426. DWORD filter;
  427. if (authResult == NO_ERROR)
  428. {
  429. IASTraceString("Default authentication succeeded.");
  430. filter = IAS_INCLUDE_IN_ACCEPT;
  431. }
  432. else
  433. {
  434. IASTraceFailure("Default authentication", authResult);
  435. filter = IAS_INCLUDE_IN_REJECT;
  436. }
  437. PRAS_AUTH_ATTRIBUTE ras = IAS_STACK_NEW(RAS_AUTH_ATTRIBUTE,
  438. profile.size() + 1);
  439. EAPTranslator::translate(ras, profile, filter);
  440. //////////
  441. // Give the result to the EAP DLL.
  442. //////////
  443. EAPInput authInput;
  444. authInput.dwAuthResultCode = authResult;
  445. authInput.fAuthenticationComplete = TRUE;
  446. authInput.pUserAttributes = ras;
  447. eapOutput.clear();
  448. error = type.RasEapMakeMessage(
  449. workBuffer,
  450. NULL,
  451. tmpPacket,
  452. maxPacketLength,
  453. &eapOutput,
  454. &authInput
  455. );
  456. if (error != NO_ERROR)
  457. {
  458. IASTraceFailure("RasEapMakeMessage", error);
  459. request.SetResponse(IAS_RESPONSE_DISCARD_PACKET, IAS_INTERNAL_ERROR);
  460. return IAS_REQUEST_STATUS_ABORT;
  461. }
  462. }
  463. //////////
  464. // Trigger an event on the FSM.
  465. //////////
  466. fsm.onDllEvent(eapOutput.Action, *tmpPacket);
  467. // Clear the old send packet ...
  468. delete[] sendPacket;
  469. sendPacket = NULL;
  470. // ... and save the new one if available.
  471. switch (eapOutput.Action)
  472. {
  473. case EAPACTION_SendAndDone:
  474. case EAPACTION_Send:
  475. case EAPACTION_SendWithTimeout:
  476. case EAPACTION_SendWithTimeoutInteractive:
  477. {
  478. size_t length = IASExtractWORD(tmpPacket->Length);
  479. sendPacket = (PPPP_EAP_PACKET)new BYTE[length];
  480. memcpy(sendPacket, tmpPacket, length);
  481. }
  482. }
  483. //////////
  484. // Perform the requested action.
  485. //////////
  486. return doAction(request);
  487. }
  488. IASREQUESTSTATUS EAPSession::doAction(IASRequest& request)
  489. {
  490. IASTraceString("Processing output from EAP DLL.");
  491. switch (eapOutput.Action)
  492. {
  493. case EAPACTION_SendAndDone:
  494. {
  495. InjectPacket(request, *sendPacket);
  496. }
  497. case EAPACTION_Done:
  498. {
  499. // Update the account lockout database.
  500. UpdateAcccountLockoutDB(
  501. account->Value.String,
  502. eapOutput.dwAuthResultCode
  503. );
  504. // Add the profile first, so that the EAP DLL can override it.
  505. profile.store(request);
  506. DWORD flags = eapOutput.dwAuthResultCode ? IAS_INCLUDE_IN_REJECT
  507. : IAS_INCLUDE_IN_ACCEPT;
  508. InjectRASAttributes(request, eapOutput.pUserAttributes, flags);
  509. if (eapOutput.dwAuthResultCode == NO_ERROR)
  510. {
  511. IASTraceString("EAP authentication succeeded.");
  512. type.getFriendlyName().store(request);
  513. request.SetResponse(IAS_RESPONSE_ACCESS_ACCEPT, S_OK);
  514. }
  515. else
  516. {
  517. IASTraceFailure("EAP authentication", eapOutput.dwAuthResultCode);
  518. HRESULT hr = IASMapWin32Error(eapOutput.dwAuthResultCode,
  519. IAS_AUTH_FAILURE);
  520. request.SetResponse(IAS_RESPONSE_ACCESS_REJECT, hr);
  521. }
  522. return IAS_REQUEST_STATUS_HANDLED;
  523. }
  524. case EAPACTION_SendWithTimeoutInteractive:
  525. case EAPACTION_SendWithTimeout:
  526. {
  527. if (eapOutput.Action == EAPACTION_SendWithTimeoutInteractive)
  528. {
  529. theInteractiveTimeout.store(request);
  530. }
  531. else
  532. {
  533. theNormalTimeout.store(request);
  534. }
  535. }
  536. case EAPACTION_Send:
  537. {
  538. InjectRASAttributes(request,
  539. eapOutput.pUserAttributes,
  540. IAS_INCLUDE_IN_CHALLENGE);
  541. InjectPacket(request, *sendPacket);
  542. state.store(request);
  543. IASTraceString("Issuing Access-Challenge.");
  544. request.SetResponse(IAS_RESPONSE_ACCESS_CHALLENGE, S_OK);
  545. break;
  546. }
  547. case EAPACTION_NoAction:
  548. default:
  549. {
  550. IASTraceString("EAP DLL returned No Action. Discarding packet.");
  551. request.SetResponse(IAS_RESPONSE_DISCARD_PACKET, IAS_INTERNAL_ERROR);
  552. }
  553. }
  554. return IAS_REQUEST_STATUS_ABORT;
  555. }
  556. HRESULT EAPSession::initialize() throw ()
  557. {
  558. std::_Lockit _Lk;
  559. if (theRefCount == 0)
  560. {
  561. PIASATTRIBUTE attrs[2];
  562. DWORD dw = IASAttributeAlloc(2, attrs);
  563. if (dw != NO_ERROR) { return HRESULT_FROM_WIN32(dw); }
  564. theNormalTimeout.attach(attrs[0], false);
  565. theNormalTimeout->dwId = RADIUS_ATTRIBUTE_SESSION_TIMEOUT;
  566. theNormalTimeout->Value.itType = IASTYPE_INTEGER;
  567. theNormalTimeout->Value.Integer = 6;
  568. theNormalTimeout.setFlag(IAS_INCLUDE_IN_CHALLENGE);
  569. theInteractiveTimeout.attach(attrs[1], false);
  570. theInteractiveTimeout->dwId = RADIUS_ATTRIBUTE_SESSION_TIMEOUT;
  571. theInteractiveTimeout->Value.itType = IASTYPE_INTEGER;
  572. theInteractiveTimeout->Value.Integer = 30;
  573. theInteractiveTimeout.setFlag(IAS_INCLUDE_IN_CHALLENGE);
  574. theIASEventLog = RegisterEventSourceW(NULL, L"IAS");
  575. theRASEventLog = RegisterEventSourceW(NULL, L"RemoteAccess");
  576. }
  577. ++theRefCount;
  578. return S_OK;
  579. }
  580. void EAPSession::finalize() throw ()
  581. {
  582. std::_Lockit _Lk;
  583. if (--theRefCount == 0)
  584. {
  585. DeregisterEventSource(theRASEventLog);
  586. DeregisterEventSource(theIASEventLog);
  587. theInteractiveTimeout.release();
  588. theNormalTimeout.release();
  589. }
  590. }