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.

746 lines
20 KiB

  1. ///////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright (c) 2000, Microsoft Corp. All rights reserved.
  4. //
  5. // FILE
  6. //
  7. // proxy.cpp
  8. //
  9. // SYNOPSIS
  10. //
  11. // Defines the class RadiusProxy.
  12. //
  13. // MODIFICATION HISTORY
  14. //
  15. // 01/26/2000 Original version.
  16. //
  17. ///////////////////////////////////////////////////////////////////////////////
  18. #include <proxypch.h>
  19. #include <datastore2.h>
  20. #include <proxy.h>
  21. #include <radpack.h>
  22. #include <iasevent.h>
  23. #include <iasinfo.h>
  24. #include <iasutil.h>
  25. #include <dsobj.h>
  26. ///////////////////////////////////////////////////////////////////////////////
  27. //
  28. // CLASS
  29. //
  30. // Resolver
  31. //
  32. // DESCRIPTION
  33. //
  34. // Utility class for resolving hostnames and iterating through the results.
  35. //
  36. ///////////////////////////////////////////////////////////////////////////////
  37. class Resolver
  38. {
  39. public:
  40. Resolver() throw ()
  41. : first(NULL), last(NULL)
  42. { }
  43. ~Resolver() throw ()
  44. { if (first != &addr) delete[] first; }
  45. // Returns true if the result set contains the specified address.
  46. bool contains(ULONG address) const throw ()
  47. {
  48. for (const ULONG* i = first; i != last; ++i)
  49. {
  50. if (*i == address) { return true; }
  51. }
  52. return false;
  53. }
  54. // Resolves the given name. The return value is the error code.
  55. ULONG resolve(const PCWSTR name = NULL) throw ()
  56. {
  57. // Clear out the existing result set.
  58. if (first != &addr)
  59. {
  60. delete[] first;
  61. first = last = NULL;
  62. }
  63. if (name)
  64. {
  65. // First try for a quick score on dotted decimal.
  66. addr = ias_inet_wtoh(name);
  67. if (addr != INADDR_NONE)
  68. {
  69. addr = htonl(addr);
  70. first = &addr;
  71. last = first + 1;
  72. return NO_ERROR;
  73. }
  74. }
  75. // That didn't work, so look up the name.
  76. PHOSTENT he = IASGetHostByName(name);
  77. if (!he) { return GetLastError(); }
  78. // Count the number of addresses returned.
  79. ULONG naddr = 0;
  80. while (he->h_addr_list[naddr]) { ++naddr; }
  81. // Allocate an array to hold them.
  82. first = last = new (std::nothrow) ULONG[naddr];
  83. if (first)
  84. {
  85. for (ULONG i = 0; i < naddr; ++i)
  86. {
  87. *last++ = *(PULONG)he->h_addr_list[i];
  88. }
  89. }
  90. LocalFree(he);
  91. return first ? NO_ERROR : WSA_NOT_ENOUGH_MEMORY;
  92. }
  93. const ULONG* begin() const throw ()
  94. { return first; }
  95. const ULONG* end() const throw ()
  96. { return last; }
  97. private:
  98. ULONG addr, *first, *last;
  99. // Not implemented.
  100. Resolver(const Resolver&);
  101. Resolver& operator=(const Resolver&);
  102. };
  103. ///////////////////////////////////////////////////////////////////////////////
  104. //
  105. // CLASS
  106. //
  107. // IASRemoteServer
  108. //
  109. // DESCRIPTION
  110. //
  111. // Extends RemoteServer to add IAS specific server information.
  112. //
  113. ///////////////////////////////////////////////////////////////////////////////
  114. class IASRemoteServer : public RemoteServer
  115. {
  116. public:
  117. IASRemoteServer(
  118. const RemoteServerConfig& config,
  119. RadiusRemoteServerEntry* entry
  120. )
  121. : RemoteServer(config),
  122. counters(entry)
  123. {
  124. // Create the Remote-Server-Address attribute.
  125. IASAttribute name(true);
  126. name->dwId = IAS_ATTRIBUTE_REMOTE_SERVER_ADDRESS;
  127. name->Value.itType = IASTYPE_INET_ADDR;
  128. name->Value.InetAddr = ntohl(config.ipAddress);
  129. attrs.push_back(name);
  130. // Update our PerfMon entry.
  131. if (counters)
  132. {
  133. counters->dwCounters[radiusAuthClientServerPortNumber] =
  134. ntohs(config.authPort);
  135. counters->dwCounters[radiusAccClientServerPortNumber] =
  136. ntohs(config.acctPort);
  137. }
  138. }
  139. // Attributes to be added to each request.
  140. IASAttributeVectorWithBuffer<1> attrs;
  141. // PerfMon counters.
  142. RadiusRemoteServerEntry* counters;
  143. };
  144. RadiusProxy::RadiusProxy()
  145. : engine(this)
  146. {
  147. }
  148. RadiusProxy::~RadiusProxy() throw ()
  149. {
  150. }
  151. STDMETHODIMP RadiusProxy::PutProperty(LONG Id, VARIANT* pValue)
  152. {
  153. if (pValue == NULL) { return E_INVALIDARG; }
  154. HRESULT hr;
  155. switch (Id)
  156. {
  157. case PROPERTY_RADIUSPROXY_SERVERGROUPS:
  158. {
  159. if (V_VT(pValue) != VT_DISPATCH) { return DISP_E_TYPEMISMATCH; }
  160. try
  161. {
  162. configure(V_UNKNOWN(pValue));
  163. hr = S_OK;
  164. }
  165. catch (const _com_error& ce)
  166. {
  167. hr = ce.Error();
  168. }
  169. break;
  170. }
  171. default:
  172. {
  173. hr = DISP_E_MEMBERNOTFOUND;
  174. }
  175. }
  176. return hr;
  177. }
  178. void RadiusProxy::onEvent(
  179. const RadiusEvent& event
  180. ) throw ()
  181. {
  182. // Convert the event context to an IASRemoteServer.
  183. IASRemoteServer* server = static_cast<IASRemoteServer*>(event.context);
  184. // Update the counters.
  185. counters.updateCounters(
  186. event.portType,
  187. event.eventType,
  188. (server ? server->counters : NULL),
  189. event.data
  190. );
  191. // We always use the address as an insertion string.
  192. WCHAR addr[16], misc[16];
  193. ias_inet_htow(ntohl(event.ipAddress), addr);
  194. // Set up the default parameters for event reporting.
  195. DWORD eventID = 0;
  196. DWORD numStrings = 1;
  197. DWORD dataSize = 0;
  198. PCWSTR strings[2] = { addr, misc };
  199. const void* rawData = NULL;
  200. // Map the RADIUS event to an IAS event ID.
  201. switch (event.eventType)
  202. {
  203. case eventInvalidAddress:
  204. eventID = PROXY_E_INVALID_ADDRESS;
  205. _itow(ntohs(event.ipPort), misc, 10);
  206. numStrings = 2;
  207. break;
  208. case eventMalformedPacket:
  209. eventID = PROXY_E_MALFORMED_RESPONSE;
  210. dataSize = event.packetLength;
  211. rawData = event.packet;
  212. break;
  213. case eventBadAuthenticator:
  214. eventID = PROXY_E_BAD_AUTHENTICATOR;
  215. break;
  216. case eventBadSignature:
  217. eventID = PROXY_E_BAD_SIGNATURE;
  218. break;
  219. case eventMissingSignature:
  220. eventID = PROXY_E_MISSING_SIGNATURE;
  221. break;
  222. case eventUnknownType:
  223. eventID = PROXY_E_UNKNOWN_TYPE;
  224. _itow(event.packet[0], misc, 10);
  225. numStrings = 2;
  226. break;
  227. case eventUnexpectedResponse:
  228. eventID = PROXY_E_UNEXPECTED_RESPONSE;
  229. dataSize = event.packetLength;
  230. rawData = event.packet;
  231. break;
  232. case eventSendError:
  233. eventID = PROXY_E_SEND_ERROR;
  234. _itow(event.data, misc, 10);
  235. numStrings = 2;
  236. break;
  237. case eventReceiveError:
  238. eventID = PROXY_E_RECV_ERROR;
  239. _itow(event.data, misc, 10);
  240. numStrings = 2;
  241. break;
  242. case eventServerAvailable:
  243. eventID = PROXY_S_SERVER_AVAILABLE;
  244. break;
  245. case eventServerUnavailable:
  246. eventID = PROXY_E_SERVER_UNAVAILABLE;
  247. _itow(server->maxLost, misc, 10);
  248. numStrings = 2;
  249. break;
  250. }
  251. if (eventID)
  252. {
  253. IASReportEvent(
  254. eventID,
  255. numStrings,
  256. dataSize,
  257. strings,
  258. (void*)rawData
  259. );
  260. }
  261. }
  262. void RadiusProxy::onComplete(
  263. RadiusProxyEngine::Result result,
  264. PVOID context,
  265. RemoteServer* server,
  266. BYTE code,
  267. const RadiusAttribute* begin,
  268. const RadiusAttribute* end
  269. ) throw ()
  270. {
  271. IRequest* comreq = (IRequest*)context;
  272. IASRESPONSE response = IAS_RESPONSE_DISCARD_PACKET;
  273. // Map the result to a reason code.
  274. IASREASON reason;
  275. switch (result)
  276. {
  277. case RadiusProxyEngine::resultSuccess:
  278. reason = IAS_SUCCESS;
  279. break;
  280. case RadiusProxyEngine::resultNotEnoughMemory:
  281. reason = IAS_INTERNAL_ERROR;
  282. break;
  283. case RadiusProxyEngine::resultUnknownServerGroup:
  284. reason = IAS_PROXY_UNKNOWN_GROUP;
  285. break;
  286. case RadiusProxyEngine::resultUnknownServer:
  287. reason = IAS_PROXY_UNKNOWN_SERVER;
  288. break;
  289. case RadiusProxyEngine::resultInvalidRequest:
  290. reason = IAS_PROXY_PACKET_TOO_LONG;
  291. break;
  292. case RadiusProxyEngine::resultSendError:
  293. reason = IAS_PROXY_SEND_ERROR;
  294. break;
  295. case RadiusProxyEngine::resultRequestTimeout:
  296. reason = IAS_PROXY_TIMEOUT;
  297. break;
  298. default:
  299. reason = IAS_INTERNAL_ERROR;
  300. }
  301. try
  302. {
  303. IASRequest request(comreq);
  304. // Always store the server attributes if available.
  305. if (server)
  306. {
  307. static_cast<IASRemoteServer*>(server)->attrs.store(request);
  308. }
  309. if (reason == IAS_SUCCESS)
  310. {
  311. // Set the response code and determine the flags used for returned
  312. // attributes.
  313. DWORD flags = 0;
  314. switch (code)
  315. {
  316. case RADIUS_ACCESS_ACCEPT:
  317. {
  318. response = IAS_RESPONSE_ACCESS_ACCEPT;
  319. flags = IAS_INCLUDE_IN_ACCEPT;
  320. break;
  321. }
  322. case RADIUS_ACCESS_REJECT:
  323. {
  324. response = IAS_RESPONSE_ACCESS_REJECT;
  325. reason = IAS_PROXY_REJECT;
  326. flags = IAS_INCLUDE_IN_REJECT;
  327. break;
  328. }
  329. case RADIUS_ACCESS_CHALLENGE:
  330. {
  331. response = IAS_RESPONSE_ACCESS_CHALLENGE;
  332. flags = IAS_INCLUDE_IN_CHALLENGE;
  333. break;
  334. }
  335. case RADIUS_ACCOUNTING_RESPONSE:
  336. {
  337. response = IAS_RESPONSE_ACCOUNTING;
  338. flags = IAS_INCLUDE_IN_ACCEPT;
  339. break;
  340. }
  341. default:
  342. {
  343. // The RadiusProxyEngine should never do this.
  344. _com_issue_error(E_FAIL);
  345. }
  346. }
  347. // Convert the received attributes to IAS format.
  348. AttributeVector incoming;
  349. for (const RadiusAttribute* src = begin; src != end; ++src)
  350. {
  351. // Temporary hack to workaround bug in the protocol.
  352. if (src->type != RADIUS_SIGNATURE)
  353. {
  354. translator.fromRadius(*src, flags, incoming);
  355. }
  356. }
  357. if (!incoming.empty())
  358. {
  359. // Get the existing attributes.
  360. AttributeVector existing;
  361. existing.load(request);
  362. // Erase any attributes that are already in the request.
  363. AttributeIterator i, j;
  364. for (i = existing.begin(); i != existing.end(); ++i)
  365. {
  366. // Both the flags ...
  367. if (i->pAttribute->dwFlags & flags)
  368. {
  369. for (j = incoming.begin(); j != incoming.end(); )
  370. {
  371. // ... and the ID have to match.
  372. if (j->pAttribute->dwId == i->pAttribute->dwId)
  373. {
  374. j = incoming.erase(j);
  375. }
  376. else
  377. {
  378. ++j;
  379. }
  380. }
  381. }
  382. }
  383. // Store the remaining attributes.
  384. incoming.store(request);
  385. }
  386. }
  387. }
  388. catch (const _com_error& ce)
  389. {
  390. response = IAS_RESPONSE_DISCARD_PACKET;
  391. if (ce.Error() == E_INVALIDARG)
  392. {
  393. // We must have had an error translating from RADIUS to IAS format.
  394. reason = IAS_PROXY_MALFORMED_RESPONSE;
  395. }
  396. else
  397. {
  398. // Probably memory allocation.
  399. reason = IAS_INTERNAL_ERROR;
  400. }
  401. }
  402. // Give it back to the pipeline.
  403. comreq->SetResponse(response, reason);
  404. comreq->ReturnToSource(IAS_REQUEST_STATUS_HANDLED);
  405. // This balances the AddRef we did before calling forwardRequest.
  406. comreq->Release();
  407. }
  408. void RadiusProxy::onAsyncRequest(IRequest* pRequest) throw ()
  409. {
  410. try
  411. {
  412. IASRequest request(pRequest);
  413. // Set the packet code based on the request type.
  414. BYTE packetCode;
  415. switch (request.get_Request())
  416. {
  417. case IAS_REQUEST_ACCESS_REQUEST:
  418. {
  419. packetCode = RADIUS_ACCESS_REQUEST;
  420. break;
  421. }
  422. case IAS_REQUEST_ACCOUNTING:
  423. {
  424. packetCode = RADIUS_ACCOUNTING_REQUEST;
  425. break;
  426. }
  427. default:
  428. {
  429. // The pipeline should never give us a request of the wrong type.
  430. _com_issue_error(E_FAIL);
  431. }
  432. }
  433. // Get the attributes from the request.
  434. AttributeVector all, outgoing;
  435. all.load(request);
  436. for (AttributeIterator i = all.begin(); i != all.end(); ++i)
  437. {
  438. // Send all the attributes received from the client except Proxy-State.
  439. if (i->pAttribute->dwFlags & IAS_RECVD_FROM_CLIENT &&
  440. i->pAttribute->dwId != RADIUS_ATTRIBUTE_PROXY_STATE)
  441. {
  442. translator.toRadius(*(i->pAttribute), outgoing);
  443. }
  444. }
  445. // If the request authenticator contains the CHAP challenge:
  446. // it must be used so get the request authenticator (always to
  447. // simplify the code)
  448. PBYTE requestAuthenticator = 0;
  449. IASAttribute radiusHeader;
  450. if (radiusHeader.load(
  451. request,
  452. IAS_ATTRIBUTE_CLIENT_PACKET_HEADER,
  453. IASTYPE_OCTET_STRING
  454. ))
  455. {
  456. requestAuthenticator = radiusHeader->Value.OctetString.lpValue + 4;
  457. }
  458. // Allocate an array of RadiusAttributes.
  459. size_t nbyte = outgoing.size() * sizeof(RadiusAttribute);
  460. RadiusAttribute* begin = (RadiusAttribute*)_alloca(nbyte);
  461. RadiusAttribute* end = begin;
  462. // Load the individual attributes.
  463. for (AttributeIterator j = outgoing.begin(); j != outgoing.end(); ++j)
  464. {
  465. end->type = (BYTE)(j->pAttribute->dwId);
  466. end->length = (BYTE)(j->pAttribute->Value.OctetString.dwLength);
  467. end->value = j->pAttribute->Value.OctetString.lpValue;
  468. ++end;
  469. }
  470. // Get the RADIUS Server group. This may be NULL since NAS-State bypasses
  471. // proxy policy.
  472. PIASATTRIBUTE group = IASPeekAttribute(
  473. request,
  474. IAS_ATTRIBUTE_PROVIDER_NAME,
  475. IASTYPE_STRING
  476. );
  477. // AddRef the request because we're giving it to the engine.
  478. pRequest->AddRef();
  479. // Add the request authenticator to the parameters of forwardRequest
  480. // That can be NULL
  481. engine.forwardRequest(
  482. (PVOID)pRequest,
  483. (group ? group->Value.String.pszWide : L""),
  484. packetCode,
  485. requestAuthenticator,
  486. begin,
  487. end
  488. );
  489. }
  490. catch (...)
  491. {
  492. // We weren't able to forward it to the engine.
  493. pRequest->SetResponse(IAS_RESPONSE_DISCARD_PACKET, IAS_INTERNAL_ERROR);
  494. pRequest->ReturnToSource(IAS_REQUEST_STATUS_HANDLED);
  495. }
  496. }
  497. void RadiusProxy::configure(IUnknown* root)
  498. {
  499. // Get our IP addresses. We don't care if this fails.
  500. Resolver localAddress, serverAddress;
  501. localAddress.resolve();
  502. // Open the RADIUS Server Groups container. If it's not there, we'll just
  503. // assume there's nothing to configure.
  504. DataStoreObject inGroups(
  505. root,
  506. L"RADIUS Server Groups\0"
  507. );
  508. if (inGroups.empty()) { return; }
  509. // Reserve space for each group.
  510. ServerGroups outGroups(inGroups.numChildren());
  511. // Iterate through the groups.
  512. DataStoreObject inGroup;
  513. while (inGroups.nextChild(inGroup))
  514. {
  515. // Get the group name.
  516. CComBSTR groupName;
  517. inGroup.getValue(L"Name", &groupName);
  518. // Reserve space for each server. This is really a guess since a server
  519. // may resolve to multiple IP addresses.
  520. RemoteServers outServers(inGroup.numChildren());
  521. // Iterate through the servers.
  522. DataStoreObject inServer;
  523. while (inGroup.nextChild(inServer))
  524. {
  525. USES_CONVERSION;
  526. // Populate the RemoteServerConfig. It has a lot of fields.
  527. RemoteServerConfig config;
  528. CComBSTR name;
  529. inServer.getValue(L"Name", &name);
  530. CLSIDFromString(name, &config.guid);
  531. ULONG port;
  532. inServer.getValue(L"Server Authentication Port", &port, 1812);
  533. config.authPort = htons((USHORT)port);
  534. inServer.getValue(L"Server Accounting Port", &port, 1813);
  535. config.acctPort = htons((USHORT)port);
  536. CComBSTR bstrAuth;
  537. inServer.getValue(L"Authentication Secret", &bstrAuth);
  538. config.authSecret = W2A(bstrAuth);
  539. CComBSTR bstrAcct;
  540. inServer.getValue(L"Accounting Secret", &bstrAcct, bstrAuth);
  541. config.acctSecret = W2A(bstrAcct);
  542. inServer.getValue(L"Priority", &config.priority, 1);
  543. inServer.getValue(L"Weight", &config.weight, 50);
  544. // Ignore any zero weight servers.
  545. if (config.weight == 0) { continue; }
  546. // We don't use this feature for now.
  547. config.sendSignature = false;
  548. inServer.getValue(
  549. L"Forward Accounting On/Off",
  550. &config.sendAcctOnOff,
  551. true
  552. );
  553. inServer.getValue(L"Request Timeout", &config.timeout, 3);
  554. // Don't allow zero for timeout
  555. if (config.timeout == 0) { config.timeout = 1; }
  556. inServer.getValue(L"Max Lost Requests", &config.maxLost, 5);
  557. // Don't allow zero for maxLost.
  558. if (config.maxLost == 0) { config.maxLost = 1; }
  559. inServer.getValue(
  560. L"Blackout Interval",
  561. &config.blackout,
  562. 10 * config.timeout
  563. );
  564. if (config.blackout < config.timeout)
  565. {
  566. // Blackout interval must be >= request timeout.
  567. config.blackout = config.timeout;
  568. }
  569. // These need to be in msec.
  570. config.timeout *= 1000;
  571. config.blackout *= 1000;
  572. // Now we have to resolve the server name to an IP address.
  573. CComBSTR address;
  574. inServer.getValue(L"Address", &address);
  575. ULONG error = serverAddress.resolve(address);
  576. if (error)
  577. {
  578. WCHAR errorCode[16];
  579. _itow(GetLastError(), errorCode, 10);
  580. PCWSTR strings[3] = { address, groupName, errorCode };
  581. IASReportEvent(
  582. PROXY_E_HOST_NOT_FOUND,
  583. 3,
  584. 0,
  585. strings,
  586. NULL
  587. );
  588. }
  589. // Create a server entry for each address.
  590. for (const ULONG* addr = serverAddress.begin();
  591. addr != serverAddress.end();
  592. ++addr)
  593. {
  594. // Don't allow them to proxy locally.
  595. if (localAddress.contains(*addr))
  596. {
  597. WCHAR ipAddress[16];
  598. ias_inet_htow(ntohl(*addr), ipAddress);
  599. PCWSTR strings[3] = { address, groupName, ipAddress };
  600. IASReportEvent(
  601. PROXY_E_LOCAL_SERVER,
  602. 3,
  603. 0,
  604. strings,
  605. NULL
  606. );
  607. continue;
  608. }
  609. // Look up the PerfMon counters.
  610. RadiusRemoteServerEntry* entry = counters.getRemoteServerEntry(
  611. *addr
  612. );
  613. // Create the new server
  614. config.ipAddress = *addr;
  615. RemoteServerPtr outServer(new IASRemoteServer(
  616. config,
  617. entry
  618. ));
  619. outServers.push_back(outServer);
  620. }
  621. }
  622. // Ignore any empty groups.
  623. if (outServers.empty()) { continue; }
  624. // Create the new group.
  625. ServerGroupPtr outGroup(new ServerGroup(
  626. groupName,
  627. outServers.begin(),
  628. outServers.end()
  629. ));
  630. outGroups.push_back(outGroup);
  631. }
  632. // Wow, we're finally done.
  633. engine.setServerGroups(
  634. outGroups.begin(),
  635. outGroups.end()
  636. );
  637. }