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.

765 lines
19 KiB

  1. ///////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright (c) 2000, Microsoft Corp. All rights reserved.
  4. //
  5. // FILE
  6. //
  7. // basecamp.cpp
  8. //
  9. // SYNOPSIS
  10. //
  11. // Defines the class BaseCampHostBase.
  12. //
  13. ///////////////////////////////////////////////////////////////////////////////
  14. #include <ias.h>
  15. #include <extension.h>
  16. #include <basecamp.h>
  17. #include <memory>
  18. //////////
  19. // The offset between the UNIX and NT epochs.
  20. //////////
  21. const DWORDLONG UNIX_EPOCH = 116444736000000000ui64;
  22. /////////
  23. // Convert a single attribute from IAS to BaseCamp format.
  24. // Returns (dst + 1) if successful, (dst) otherwise.
  25. /////////
  26. PRADIUS_ATTRIBUTE
  27. WINAPI
  28. ConvertToBaseCampAttribute(
  29. IN PIASATTRIBUTE src,
  30. IN PRADIUS_ATTRIBUTE dst
  31. ) throw ()
  32. {
  33. /////////
  34. // Convert the attribute ID.
  35. /////////
  36. if (src->dwId < 256)
  37. {
  38. // If it's a radius standard attribute, then use 'as is'.
  39. dst->dwAttrType = src->dwId;
  40. }
  41. else
  42. {
  43. // Map internal attributes.
  44. switch (src->dwId)
  45. {
  46. case IAS_ATTRIBUTE_CLIENT_IP_ADDRESS:
  47. dst->dwAttrType = ratSrcIPAddress;
  48. break;
  49. case IAS_ATTRIBUTE_CLIENT_UDP_PORT:
  50. dst->dwAttrType = ratSrcPort;
  51. break;
  52. case IAS_ATTRIBUTE_NT4_ACCOUNT_NAME:
  53. dst->dwAttrType = ratStrippedUserName;
  54. break;
  55. case IAS_ATTRIBUTE_FULLY_QUALIFIED_USER_NAME:
  56. dst->dwAttrType = ratFQUserName;
  57. break;
  58. case IAS_ATTRIBUTE_NP_NAME:
  59. dst->dwAttrType = ratPolicyName;
  60. break;
  61. default:
  62. // No mapping exists.
  63. return dst;
  64. }
  65. }
  66. /////////
  67. // Convert the attribute value.
  68. /////////
  69. switch (src->Value.itType)
  70. {
  71. case IASTYPE_BOOLEAN:
  72. case IASTYPE_INTEGER:
  73. case IASTYPE_ENUM:
  74. {
  75. dst->fDataType = rdtInteger;
  76. dst->cbDataLength = sizeof(DWORD);
  77. dst->dwValue = src->Value.Integer;
  78. ++dst;
  79. break;
  80. }
  81. case IASTYPE_INET_ADDR:
  82. {
  83. dst->fDataType = rdtAddress;
  84. dst->cbDataLength = sizeof(DWORD);
  85. dst->dwValue = src->Value.InetAddr;
  86. ++dst;
  87. break;
  88. }
  89. case IASTYPE_STRING:
  90. {
  91. IASAttributeAnsiAlloc(src);
  92. if (src->Value.String.pszAnsi)
  93. {
  94. dst->fDataType = rdtString;
  95. dst->cbDataLength = strlen(src->Value.String.pszAnsi) + 1;
  96. dst->lpValue = src->Value.String.pszAnsi;
  97. ++dst;
  98. }
  99. break;
  100. }
  101. case IASTYPE_OCTET_STRING:
  102. case IASTYPE_PROV_SPECIFIC:
  103. {
  104. dst->fDataType = rdtString;
  105. dst->cbDataLength = src->Value.OctetString.dwLength;
  106. dst->lpValue = (PCSTR)src->Value.OctetString.lpValue;
  107. ++dst;
  108. break;
  109. }
  110. case IASTYPE_UTC_TIME:
  111. {
  112. DWORDLONG val;
  113. // Move in the high DWORD.
  114. val = src->Value.UTCTime.dwHighDateTime;
  115. val <<= 32;
  116. // Move in the low DWORD.
  117. val |= src->Value.UTCTime.dwLowDateTime;
  118. // Convert to the UNIX epoch.
  119. val -= UNIX_EPOCH;
  120. // Convert to seconds.
  121. val /= 10000000;
  122. dst->fDataType = rdtTime;
  123. dst->cbDataLength = sizeof(DWORD);
  124. dst->dwValue = (DWORD)val;
  125. ++dst;
  126. break;
  127. }
  128. }
  129. return dst;
  130. }
  131. /////////
  132. // Converts a request object to an array of BaseCamp attributes.
  133. /////////
  134. VOID
  135. WINAPI
  136. ConvertRequestToBaseCampAttributes(
  137. IN IASRequest& request,
  138. IN BOOL inbound,
  139. OUT PRADIUS_ATTRIBUTE* ppAttrs
  140. )
  141. {
  142. /////////
  143. // Determine the packetCode.
  144. /////////
  145. DWORD packetCode = 0;
  146. if (!inbound)
  147. {
  148. // For outbound requests we look at the response.
  149. switch (request.get_Response())
  150. {
  151. case IAS_RESPONSE_ACCESS_ACCEPT:
  152. packetCode = 2;
  153. break;
  154. case IAS_RESPONSE_ACCESS_REJECT:
  155. packetCode = 3;
  156. break;
  157. case IAS_RESPONSE_ACCOUNTING:
  158. packetCode = 5;
  159. break;
  160. case IAS_RESPONSE_ACCESS_CHALLENGE:
  161. packetCode = 11;
  162. break;
  163. }
  164. }
  165. if (!packetCode)
  166. {
  167. // If we made it here, either this is an inbound request or we
  168. // haven't made a decision yet on an outbound request.
  169. switch (request.get_Request())
  170. {
  171. case IAS_REQUEST_ACCESS_REQUEST:
  172. packetCode = 1;
  173. break;
  174. case IAS_REQUEST_ACCOUNTING:
  175. packetCode = 4;
  176. break;
  177. }
  178. }
  179. ///////
  180. // Determine which attributes to convert based on the packetCode.
  181. ///////
  182. DWORD always, never;
  183. switch (packetCode)
  184. {
  185. case 1: // Access-Request
  186. always = IAS_RECVD_FROM_CLIENT | IAS_RECVD_FROM_PROTOCOL;
  187. never = IAS_INCLUDE_IN_RESPONSE;
  188. break;
  189. case 2: // Access-Accept
  190. always = IAS_INCLUDE_IN_ACCEPT;
  191. never = IAS_RECVD_FROM_CLIENT |
  192. IAS_INCLUDE_IN_REJECT | IAS_INCLUDE_IN_CHALLENGE;
  193. break;
  194. case 3: // Access-Reject
  195. always = IAS_INCLUDE_IN_REJECT;
  196. never = IAS_RECVD_FROM_CLIENT |
  197. IAS_INCLUDE_IN_ACCEPT | IAS_INCLUDE_IN_CHALLENGE;
  198. break;
  199. case 4: // Accounting-Request
  200. always = IAS_RECVD_FROM_CLIENT | IAS_RECVD_FROM_PROTOCOL;
  201. never = IAS_INCLUDE_IN_RESPONSE;
  202. break;
  203. case 5: // Accounting-Response
  204. always = IAS_INCLUDE_IN_RESPONSE;
  205. never = IAS_RECVD_FROM_CLIENT;
  206. break;
  207. case 11: // Access-Challenge.
  208. always = IAS_INCLUDE_IN_CHALLENGE;
  209. never = IAS_RECVD_FROM_CLIENT |
  210. IAS_INCLUDE_IN_ACCEPT | IAS_INCLUDE_IN_REJECT;
  211. break;
  212. default:
  213. always = 0;
  214. never = 0;
  215. }
  216. // Get the packet header (won't be present for RAS).
  217. PIASATTRIBUTE header = IASPeekAttribute(
  218. request,
  219. IAS_ATTRIBUTE_CLIENT_PACKET_HEADER,
  220. IASTYPE_OCTET_STRING
  221. );
  222. // Allocate memory for the converted attributes. We need six extra elements
  223. // for derived attributes and the terminator.
  224. DWORD numAttrs = request.GetAttributeCount();
  225. *ppAttrs = (PRADIUS_ATTRIBUTE)
  226. CoTaskMemAlloc((numAttrs + 6UL) * sizeof(RADIUS_ATTRIBUTE));
  227. if (*ppAttrs == NULL) { _com_issue_error(E_OUTOFMEMORY); }
  228. // Cursor into the attribute array.
  229. PRADIUS_ATTRIBUTE dst = *ppAttrs;
  230. // Packet code.
  231. dst->dwAttrType = ratCode;
  232. dst->fDataType = rdtInteger;
  233. dst->cbDataLength = sizeof(DWORD);
  234. dst->dwValue = packetCode;
  235. ++dst;
  236. // Authentication provider.
  237. dst->dwAttrType = ratProvider;
  238. dst->fDataType = rdtInteger;
  239. dst->cbDataLength = sizeof(DWORD);
  240. PIASATTRIBUTE providerType = IASPeekAttribute(
  241. request,
  242. IAS_ATTRIBUTE_PROVIDER_TYPE,
  243. IASTYPE_ENUM
  244. );
  245. if (providerType == 0)
  246. {
  247. // most of the time, the absence of the provider type is used
  248. // for rapNone
  249. dst->dwValue = rapNone;
  250. }
  251. else
  252. {
  253. switch(providerType->Value.Integer)
  254. {
  255. case IAS_PROVIDER_NONE:
  256. {
  257. dst->dwValue = rapNone;
  258. break;
  259. }
  260. case IAS_PROVIDER_WINDOWS:
  261. {
  262. dst->dwValue = rapWindowsNT;
  263. break;
  264. }
  265. case IAS_PROVIDER_RADIUS_PROXY:
  266. {
  267. dst->dwValue = rapProxy;
  268. break;
  269. }
  270. default:
  271. {
  272. // should never be here
  273. dst->dwValue = rapUnknown;
  274. }
  275. }
  276. }
  277. ++dst;
  278. // Identifier
  279. if (header)
  280. {
  281. dst->dwAttrType = ratIdentifier;
  282. dst->fDataType = rdtInteger;
  283. dst->cbDataLength = sizeof(DWORD);
  284. dst->dwValue = *(PBYTE)(header->Value.OctetString.lpValue + 1);
  285. ++dst;
  286. }
  287. // Check for a Chap-Challenge.
  288. PIASATTRIBUTE challenge = IASPeekAttribute(
  289. request,
  290. RADIUS_ATTRIBUTE_CHAP_CHALLENGE,
  291. IASTYPE_OCTET_STRING
  292. );
  293. if (challenge)
  294. {
  295. // Use the Chap-Challenge if present ...
  296. dst->dwAttrType = ratAuthenticator;
  297. dst->fDataType = rdtString;
  298. dst->cbDataLength = challenge->Value.OctetString.dwLength;
  299. dst->lpValue = (const char*)challenge->Value.OctetString.lpValue;
  300. ++dst;
  301. }
  302. else if (header)
  303. {
  304. // ... otherwise use the Request Authenticator.
  305. dst->dwAttrType = ratAuthenticator;
  306. dst->fDataType = rdtString;
  307. dst->cbDataLength = 16;
  308. dst->lpValue = (const char*)header->Value.OctetString.lpValue + 4;
  309. ++dst;
  310. }
  311. // Get all the regular attributes from the request.
  312. USES_IAS_STACK_VECTOR();
  313. IASAttributeVectorOnStack(attrs, NULL, numAttrs);
  314. attrs.load(request);
  315. // Iterate through and convert each attribute.
  316. for (IASAttributeVector::iterator i = attrs.begin(); i != attrs.end(); ++i)
  317. {
  318. if ( (i->pAttribute->dwFlags & always) ||
  319. !(i->pAttribute->dwFlags & never ))
  320. {
  321. dst = ConvertToBaseCampAttribute(i->pAttribute, dst);
  322. }
  323. }
  324. // All done, so add the terminator.
  325. dst->dwAttrType = ratMinimum;
  326. dst->fDataType = rdtUnknown;
  327. dst->cbDataLength = 0;
  328. dst->lpValue = NULL;
  329. }
  330. /////////
  331. // Determines if an extension should only be loaded under NT4.
  332. /////////
  333. BOOL
  334. WINAPI
  335. IsNT4Only(
  336. PCWSTR path
  337. ) throw ()
  338. {
  339. // Strip everything before the last backslash.
  340. const WCHAR* basename = wcsrchr(path, L'\\');
  341. if (basename == NULL)
  342. {
  343. basename = path;
  344. }
  345. else
  346. {
  347. ++basename;
  348. }
  349. // Is this the authsam extension?
  350. return _wcsicmp(basename, L"AUTHSAM.DLL") == 0;
  351. }
  352. /////////
  353. // Free an array of BaseCamp attributes.
  354. /////////
  355. VOID
  356. WINAPI
  357. FreeBaseCampAttributes(
  358. IN PRADIUS_ATTRIBUTE ppAttrs
  359. ) throw ()
  360. {
  361. CoTaskMemFree(ppAttrs);
  362. }
  363. ///////
  364. // Store an array of BaseCamp attributes in a request.
  365. ///////
  366. VOID
  367. WINAPI
  368. StoreBaseCampAttributes(
  369. IN IASRequest& request,
  370. IN const RADIUS_ATTRIBUTE* pAttrs
  371. )
  372. {
  373. // Set the flags based on the response.
  374. DWORD flags;
  375. if (request.get_Response() == IAS_RESPONSE_ACCESS_REJECT)
  376. {
  377. flags = IAS_INCLUDE_IN_REJECT;
  378. }
  379. else
  380. {
  381. flags = IAS_INCLUDE_IN_ACCEPT;
  382. }
  383. // Iterate through the attributes, convert, and store.
  384. const RADIUS_ATTRIBUTE* i;
  385. for (i = pAttrs; i->dwAttrType != ratMinimum; ++i)
  386. {
  387. IASAttribute attr(true);
  388. attr->dwFlags = flags;
  389. attr->dwId = i->dwAttrType;
  390. switch (i->fDataType)
  391. {
  392. case rdtAddress:
  393. {
  394. attr->Value.itType = IASTYPE_INET_ADDR;
  395. attr->Value.InetAddr = i->dwValue;
  396. break;
  397. }
  398. case rdtInteger:
  399. case rdtTime:
  400. {
  401. attr->Value.itType = IASTYPE_INTEGER;
  402. attr->Value.InetAddr = i->dwValue;
  403. break;
  404. }
  405. default:
  406. {
  407. attr.setOctetString(i->cbDataLength, (PBYTE)i->lpValue);
  408. }
  409. }
  410. attr.store(request);
  411. }
  412. }
  413. ///////////////////////////////////////////////////////////////////////////////
  414. //
  415. // CLASS
  416. //
  417. // OutAttributes
  418. //
  419. // DESCRIPTION
  420. //
  421. // Manages the RADIUS_ATTRIBUTE's returned by an extension.
  422. //
  423. ///////////////////////////////////////////////////////////////////////////////
  424. class OutAttributes
  425. {
  426. public:
  427. OutAttributes(BaseCampExtension& ext) throw ()
  428. : owner(ext),attrs(NULL)
  429. { }
  430. ~OutAttributes() throw ()
  431. { owner.freeAttrs(attrs); }
  432. operator bool() const throw ()
  433. { return attrs != NULL; }
  434. PRADIUS_ATTRIBUTE* operator&() throw ()
  435. { return &attrs; }
  436. operator const RADIUS_ATTRIBUTE*() const throw ()
  437. { return attrs; }
  438. private:
  439. BaseCampExtension& owner;
  440. PRADIUS_ATTRIBUTE attrs;
  441. // Not implemented.
  442. OutAttributes(const OutAttributes&);
  443. OutAttributes& operator=(const OutAttributes&);
  444. };
  445. BaseCampHostBase::BaseCampHostBase(
  446. PCSTR friendlyName,
  447. PCWSTR registryKey,
  448. PCWSTR registryValue,
  449. BOOL inboundPacket,
  450. DWORD actions
  451. ) throw ()
  452. : name(friendlyName),
  453. extensionsKey(registryKey),
  454. extensionsValue(registryValue),
  455. inbound(inboundPacket),
  456. allowedActions(actions),
  457. numExtensions(0),
  458. extensions(NULL)
  459. {
  460. }
  461. STDMETHODIMP BaseCampHostBase::Initialize()
  462. {
  463. // Allocate an attribute for the Authentication-Type.
  464. DWORD error = IASAttributeAlloc(1, &authType);
  465. if (error != NO_ERROR) { return HRESULT_FROM_WIN32(error); }
  466. // Initialize the attribute fields.
  467. authType->dwId = IAS_ATTRIBUTE_AUTHENTICATION_TYPE;
  468. authType->Value.itType = IASTYPE_ENUM;
  469. authType->Value.Enumerator = IAS_AUTH_CUSTOM;
  470. DWORD status = NO_ERROR;
  471. HKEY hKey = NULL;
  472. do
  473. {
  474. // Open the registry key.
  475. status = RegOpenKeyW(
  476. HKEY_LOCAL_MACHINE,
  477. extensionsKey,
  478. &hKey
  479. );
  480. if (status != NO_ERROR) { break; }
  481. // Allocate a buffer to hold the value.
  482. DWORD type, length;
  483. status = RegQueryValueExW(
  484. hKey,
  485. extensionsValue,
  486. NULL,
  487. &type,
  488. NULL,
  489. &length
  490. );
  491. if (status != NO_ERROR) { break; }
  492. PBYTE data = (PBYTE)_alloca(length);
  493. // Read the registry value.
  494. status = RegQueryValueExW(
  495. hKey,
  496. extensionsValue,
  497. NULL,
  498. &type,
  499. data,
  500. &length
  501. );
  502. if (status != NO_ERROR) { break; }
  503. // Make sure it's the right type.
  504. if (type != REG_MULTI_SZ)
  505. {
  506. status = ERROR_INVALID_DATA;
  507. break;
  508. }
  509. // Count the number of strings.
  510. PCWSTR path;
  511. for (path = (PCWSTR)data; *path; path += wcslen(path) + 1)
  512. {
  513. if (!IsNT4Only(path)) { ++numExtensions; }
  514. }
  515. // If there are no extensions, then we're done.
  516. if (numExtensions == 0) { break; }
  517. // Allocate memory to hold the extensions.
  518. extensions = new (std::nothrow) BaseCampExtension[numExtensions];
  519. if (extensions == NULL)
  520. {
  521. status = ERROR_NOT_ENOUGH_MEMORY;
  522. break;
  523. }
  524. // Load the DLL's.
  525. BaseCampExtension* ext = extensions;
  526. for (path = (PCWSTR)data; *path; path += wcslen(path) + 1)
  527. {
  528. if (!IsNT4Only(path))
  529. {
  530. IASTracePrintf("Loading %s extension %S", name, path);
  531. status = ext->load(path);
  532. if (status != NO_ERROR)
  533. {
  534. break;
  535. }
  536. ++ext;
  537. }
  538. }
  539. } while (false);
  540. // If we failed, shutdown.
  541. if (status != NO_ERROR) { BaseCampHostBase::Shutdown(); }
  542. // Close the registry.
  543. if (hKey) { RegCloseKey(hKey); }
  544. // If no extensions are registered, then it's not really an error.
  545. if (status == ERROR_FILE_NOT_FOUND) { status = NO_ERROR; }
  546. return HRESULT_FROM_WIN32(status);
  547. }
  548. STDMETHODIMP BaseCampHostBase::Shutdown()
  549. {
  550. numExtensions = 0;
  551. delete[] extensions;
  552. extensions = NULL;
  553. authType.release();
  554. return S_OK;
  555. }
  556. IASREQUESTSTATUS BaseCampHostBase::onSyncRequest(IRequest* pRequest) throw ()
  557. {
  558. // Short-circuit if there aren't any extensions,
  559. if (numExtensions == 0) { return IAS_REQUEST_STATUS_CONTINUE; }
  560. IASTracePrintf("%s processing request.", name);
  561. // Array of converted attributes. These are at function scope so we can
  562. // clean-up after an exception.
  563. PRADIUS_ATTRIBUTE pAttrs = NULL;
  564. // Default status is to continue.
  565. IASREQUESTSTATUS retval = IAS_REQUEST_STATUS_CONTINUE;
  566. try
  567. {
  568. IASRequest request(pRequest);
  569. // Convert any VSAs to RADIUS wire format.
  570. filter.radiusFromIAS(request);
  571. // Convert request to an array of BaseCamp attributes.
  572. ConvertRequestToBaseCampAttributes(
  573. request,
  574. inbound,
  575. &pAttrs
  576. );
  577. // Extensions can only return an action for Access-Requests.
  578. RADIUS_ACTION fAction = raContinue, *pfAction;
  579. if (allowedActions &&
  580. request.get_Request() == IAS_REQUEST_ACCESS_REQUEST)
  581. {
  582. pfAction = &fAction;
  583. }
  584. else
  585. {
  586. pfAction = NULL;
  587. }
  588. BOOL handled = FALSE;
  589. // Invoke each extension.
  590. for (DWORD i = 0; i < numExtensions && !handled; ++i)
  591. {
  592. IASTracePrintf("Invoking extension %S.", extensions[i].getName());
  593. OutAttributes outAttrs(extensions[i]);
  594. DWORD error = extensions[i].process(
  595. pAttrs,
  596. &outAttrs,
  597. pfAction
  598. );
  599. // Abort on error ...
  600. if (error != NO_ERROR)
  601. {
  602. IASTraceFailure("RadiusExtensionProcess", error);
  603. _com_issue_error(IAS_INTERNAL_ERROR);
  604. }
  605. // Process the action.
  606. if (fAction == raAccept && (allowedActions & ACTION_ACCEPT))
  607. {
  608. IASTraceString("Extension action: Accept");
  609. authType.store(request);
  610. request.SetResponse(IAS_RESPONSE_ACCESS_ACCEPT);
  611. retval = IAS_REQUEST_STATUS_CONTINUE;
  612. handled = TRUE;
  613. }
  614. else if (fAction == raReject && (allowedActions & ACTION_REJECT))
  615. {
  616. IASTraceString("Extension action: Reject");
  617. // In the reject case, we have to remove any existing
  618. // authentication type, because we may be an authorization
  619. // extension.
  620. DWORD attrId = IAS_ATTRIBUTE_AUTHENTICATION_TYPE;
  621. request.RemoveAttributesByType(1, &attrId);
  622. authType.store(request);
  623. request.SetResponse(IAS_RESPONSE_ACCESS_REJECT, IAS_AUTH_FAILURE);
  624. retval = IAS_REQUEST_STATUS_HANDLED;
  625. handled = TRUE;
  626. }
  627. else
  628. {
  629. IASTraceString("Extension action: Continue");
  630. }
  631. // Store the outAttrs (if any).
  632. if (outAttrs)
  633. {
  634. StoreBaseCampAttributes(request, outAttrs);
  635. }
  636. }
  637. // Convert any VSAs back to internal format.
  638. filter.radiusToIAS(request);
  639. }
  640. catch (const _com_error& ce)
  641. {
  642. IASTraceExcept();
  643. pRequest->SetResponse(IAS_RESPONSE_DISCARD_PACKET, ce.Error());
  644. retval = IAS_REQUEST_STATUS_ABORT;
  645. }
  646. // Clean up the attributes.
  647. FreeBaseCampAttributes(pAttrs);
  648. return retval;
  649. }