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.

639 lines
17 KiB

  1. ///////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright (c) 1998, Microsoft Corp. All rights reserved.
  4. //
  5. // FILE
  6. //
  7. // account.cpp
  8. //
  9. // SYNOPSIS
  10. //
  11. // Defines the class Accountant.
  12. //
  13. // MODIFICATION HISTORY
  14. //
  15. // 08/05/1998 Original version.
  16. // 09/16/1998 Add Reason-Code pseudo-attribute.
  17. // 12/02/1998 Fix backward compatibility of IAS 1.0 format.
  18. // 01/19/1999 Log Access-Challenge's.
  19. // 01/25/1999 Date and time are separate fields.
  20. // 03/23/1999 Add support for text qualifiers.
  21. // 03/26/1999 Use the new LogFile::setEnabled method.
  22. // 05/18/1999 Store computerName as UTF-8.
  23. // 07/09/1999 Discard the request if logging fails.
  24. // 08/05/1999 Always log Class attribute.
  25. // 01/25/2000 Remove PROPERTY_ACCOUNTING_LOG_ENABLE.
  26. // 02/29/2000 Don't touch Class attribute when proxying.
  27. // 03/13/2000 Log reason code for Accounting-Requests.
  28. //
  29. ///////////////////////////////////////////////////////////////////////////////
  30. #include <ias.h>
  31. #include <iasutf8.h>
  32. #include <sdoias.h>
  33. #include <algorithm>
  34. #include <account.h>
  35. #include <classattr.h>
  36. #include <formbuf.h>
  37. #define STACK_ALLOC(type, num) (type*)_alloca(sizeof(type) * (num))
  38. //////////
  39. // Misc. enumerator constants.
  40. //////////
  41. const LONG INET_LOG_FORMAT_INTERNET_STD = 0;
  42. const LONG INET_LOG_FORMAT_NCSA = 3;
  43. const LONG INET_LOG_FORMAT_ODBC_RADIUS = 0xFFFF;
  44. const DWORD ACCOUNTING_INTERIM = 3;
  45. //////////
  46. // Returns TRUE if the request contains an interim accounting record.
  47. //////////
  48. extern "C"
  49. BOOL
  50. WINAPI
  51. IsInterimRecord(
  52. IAttributesRaw* pRaw
  53. )
  54. {
  55. PIASATTRIBUTE attr = IASPeekAttribute(
  56. pRaw,
  57. RADIUS_ATTRIBUTE_ACCT_STATUS_TYPE,
  58. IASTYPE_ENUM
  59. );
  60. return attr && attr->Value.Enumerator == ACCOUNTING_INTERIM;
  61. }
  62. //////////
  63. // Inserts a class attribute into the request.
  64. //////////
  65. extern "C"
  66. HRESULT
  67. WINAPI
  68. InsertClassAttribute(
  69. IAttributesRaw* pRaw
  70. )
  71. {
  72. //////////
  73. // Check if this was proxied.
  74. //////////
  75. PIASATTRIBUTE attr = IASPeekAttribute(
  76. pRaw,
  77. IAS_ATTRIBUTE_PROVIDER_TYPE,
  78. IASTYPE_ENUM
  79. );
  80. if (attr && attr->Value.Enumerator == IAS_PROVIDER_RADIUS_PROXY)
  81. {
  82. return S_OK;
  83. }
  84. //////////
  85. // Check if Generate Class Attribute is disabled
  86. //////////
  87. PIASATTRIBUTE generateClassAttr = IASPeekAttribute(
  88. pRaw,
  89. IAS_ATTRIBUTE_GENERATE_CLASS_ATTRIBUTE,
  90. IASTYPE_BOOLEAN
  91. );
  92. if (generateClassAttr && generateClassAttr->Value.Boolean == FALSE)
  93. {
  94. return S_OK;
  95. }
  96. //////////
  97. // Create a new class attribute
  98. // Do not remove any existing class attribute.
  99. //////////
  100. ATTRIBUTEPOSITION pos;
  101. pos.pAttribute = IASClass::createAttribute(NULL);
  102. if (pos.pAttribute == NULL) { return E_OUTOFMEMORY; }
  103. //////////
  104. // Insert into the request.
  105. //////////
  106. HRESULT hr = pRaw->AddAttributes(1, &pos);
  107. IASAttributeRelease(pos.pAttribute);
  108. return hr;
  109. }
  110. Accountant::Accountant() throw ()
  111. : logAuth(FALSE),
  112. logAcct(FALSE),
  113. logInterim(FALSE),
  114. computerNameLen(0)
  115. { }
  116. STDMETHODIMP Accountant::Initialize()
  117. {
  118. // Get the Unicode computer name.
  119. WCHAR uniName[MAX_COMPUTERNAME_LENGTH + 1];
  120. DWORD len = sizeof(uniName) / sizeof(WCHAR);
  121. if (!GetComputerNameW(uniName, &len))
  122. {
  123. // If it failed, we'll just use an empty string.
  124. len = 0;
  125. }
  126. // Convert the Unicode to UTF-8.
  127. computerNameLen = IASUnicodeToUtf8(uniName, len, computerName);
  128. IASClass::initialize();
  129. return schema.initialize();
  130. }
  131. STDMETHODIMP Accountant::Shutdown()
  132. {
  133. log.close();
  134. schema.shutdown();
  135. return S_OK;
  136. }
  137. STDMETHODIMP Accountant::PutProperty(LONG Id, VARIANT *pValue)
  138. {
  139. if (pValue == NULL) { return E_INVALIDARG; }
  140. switch (Id)
  141. {
  142. case PROPERTY_ACCOUNTING_LOG_ACCOUNTING:
  143. {
  144. if (V_VT(pValue) != VT_BOOL) { return DISP_E_TYPEMISMATCH; }
  145. logAcct = V_BOOL(pValue);
  146. log.setEnabled(logAcct | logInterim | logAuth);
  147. break;
  148. }
  149. case PROPERTY_ACCOUNTING_LOG_ACCOUNTING_INTERIM:
  150. {
  151. if (V_VT(pValue) != VT_BOOL) { return DISP_E_TYPEMISMATCH; }
  152. logInterim = V_BOOL(pValue);
  153. log.setEnabled(logAcct | logInterim | logAuth);
  154. break;
  155. }
  156. case PROPERTY_ACCOUNTING_LOG_AUTHENTICATION:
  157. {
  158. if (V_VT(pValue) != VT_BOOL) { return DISP_E_TYPEMISMATCH; }
  159. logAuth = V_BOOL(pValue);
  160. log.setEnabled(logAcct | logInterim | logAuth);
  161. break;
  162. }
  163. case PROPERTY_ACCOUNTING_LOG_OPEN_NEW_FREQUENCY:
  164. {
  165. if (V_VT(pValue) != VT_I4) { return DISP_E_TYPEMISMATCH; }
  166. switch (V_I4(pValue))
  167. {
  168. case IAS_LOGGING_UNLIMITED_SIZE:
  169. case IAS_LOGGING_WHEN_FILE_SIZE_REACHES:
  170. case IAS_LOGGING_DAILY:
  171. case IAS_LOGGING_WEEKLY:
  172. case IAS_LOGGING_MONTHLY:
  173. log.setPeriod(V_I4(pValue));
  174. break;
  175. default:
  176. return E_INVALIDARG;
  177. }
  178. break;
  179. }
  180. case PROPERTY_ACCOUNTING_LOG_OPEN_NEW_SIZE:
  181. {
  182. if (V_VT(pValue) != VT_I4) { return DISP_E_TYPEMISMATCH; }
  183. if (V_I4(pValue) <= 0) { return E_INVALIDARG; }
  184. log.setMaxSize(V_I4(pValue) * 0x100000ui64);
  185. break;
  186. }
  187. case PROPERTY_ACCOUNTING_LOG_FILE_DIRECTORY:
  188. {
  189. if (V_VT(pValue) != VT_BSTR) { return DISP_E_TYPEMISMATCH; }
  190. if (V_BSTR(pValue) == NULL || wcslen(V_BSTR(pValue)) > MAX_PATH - 12)
  191. { return E_INVALIDARG; }
  192. log.setDirectory(V_BSTR(pValue));
  193. break;
  194. }
  195. case PROPERTY_ACCOUNTING_LOG_IAS1_FORMAT:
  196. {
  197. if (V_VT(pValue) != VT_I4) { return DISP_E_TYPEMISMATCH; }
  198. switch (V_I4(pValue))
  199. {
  200. case INET_LOG_FORMAT_ODBC_RADIUS:
  201. format = formatODBCRecord;
  202. break;
  203. case INET_LOG_FORMAT_INTERNET_STD:
  204. format = formatW3CRecord;
  205. break;
  206. default:
  207. return E_INVALIDARG;
  208. }
  209. break;
  210. }
  211. default:
  212. {
  213. // We just ignore properties that we don't understand.
  214. return S_OK;
  215. }
  216. }
  217. return S_OK;
  218. }
  219. IASREQUESTSTATUS Accountant::onSyncRequest(IRequest* pRequest) throw ()
  220. {
  221. // Flag indicating final outcome of logging.
  222. BOOL success = TRUE;
  223. try
  224. {
  225. // Convert to an IASRequest object.
  226. IASRequest request(pRequest);
  227. // Get the local SYSTEMTIME.
  228. SYSTEMTIME st;
  229. GetLocalTime(&st);
  230. // Create a FormattedBuffer of the correct type.
  231. FormattedBuffer buffer(format == formatODBCRecord ? '\"' : '\0');
  232. //////////
  233. // This ungodly mess performs the actual processing logic of the
  234. // accounting handler. I love simple, clean algorithms.
  235. //////////
  236. switch (request.get_Request())
  237. {
  238. case IAS_REQUEST_ACCOUNTING:
  239. {
  240. if (IsInterimRecord(request) ? logInterim : logAcct)
  241. {
  242. appendRecord(request, PKT_ACCOUNTING_REQUEST, buffer, st);
  243. }
  244. if (request.get_Response() == IAS_RESPONSE_INVALID)
  245. {
  246. request.SetResponse(IAS_RESPONSE_ACCOUNTING, S_OK);
  247. }
  248. break;
  249. }
  250. case IAS_REQUEST_ACCESS_REQUEST:
  251. InsertClassAttribute(request);
  252. switch (request.get_Response())
  253. {
  254. case IAS_RESPONSE_ACCESS_ACCEPT:
  255. if (logAuth)
  256. {
  257. appendRecord(request, PKT_ACCESS_REQUEST, buffer, st);
  258. appendRecord(request, PKT_ACCESS_ACCEPT, buffer, st);
  259. }
  260. break;
  261. case IAS_RESPONSE_ACCESS_REJECT:
  262. if (logAuth)
  263. {
  264. appendRecord(request, PKT_ACCESS_REQUEST, buffer, st);
  265. appendRecord(request, PKT_ACCESS_REJECT, buffer, st);
  266. }
  267. break;
  268. case IAS_RESPONSE_ACCESS_CHALLENGE:
  269. if (logAuth)
  270. {
  271. appendRecord(request, PKT_ACCESS_REQUEST, buffer, st);
  272. appendRecord(request, PKT_ACCESS_CHALLENGE, buffer, st);
  273. }
  274. break;
  275. default:
  276. if (logAuth)
  277. {
  278. appendRecord(request, PKT_ACCESS_REQUEST, buffer, st);
  279. }
  280. }
  281. break;
  282. }
  283. // If the buffer isn't empty, then write the contents to the logfile.
  284. if (!buffer.empty())
  285. {
  286. success = log.write(&st, buffer.getBuffer(), buffer.getLength());
  287. }
  288. }
  289. catch (...)
  290. {
  291. // Something went wrong.
  292. success = FALSE;
  293. }
  294. // If we didn't succeed then discard the packet.
  295. if (!success)
  296. {
  297. pRequest->SetResponse(IAS_RESPONSE_DISCARD_PACKET, IAS_NO_RECORD);
  298. return IAS_REQUEST_STATUS_ABORT;
  299. }
  300. return IAS_REQUEST_STATUS_CONTINUE;
  301. }
  302. void Accountant::appendRecord(
  303. IASRequest& request,
  304. PacketType packetType,
  305. FormattedBuffer& buffer,
  306. const SYSTEMTIME& localTime
  307. ) const throw (_com_error)
  308. {
  309. //////////
  310. // Retrieve all the attributes from the request. Save room for three extra
  311. // attributes: Packet-Type, Reason-Code, and a null-terminator.
  312. //////////
  313. PATTRIBUTEPOSITION firstPos, curPos, lastPos;
  314. DWORD nattr = request.GetAttributeCount();
  315. firstPos = STACK_ALLOC(ATTRIBUTEPOSITION, nattr + 3);
  316. nattr = request.GetAttributes(nattr, firstPos, 0, NULL);
  317. lastPos = firstPos + nattr;
  318. //////////
  319. // Compute the attribute filter and reason code.
  320. //////////
  321. DWORD always, never, reason = 0;
  322. switch (packetType)
  323. {
  324. case PKT_ACCESS_REQUEST:
  325. always = IAS_RECVD_FROM_CLIENT | IAS_RECVD_FROM_PROTOCOL;
  326. never = IAS_INCLUDE_IN_RESPONSE;
  327. break;
  328. case PKT_ACCESS_ACCEPT:
  329. always = IAS_INCLUDE_IN_ACCEPT;
  330. never = IAS_RECVD_FROM_CLIENT |
  331. IAS_INCLUDE_IN_REJECT | IAS_INCLUDE_IN_CHALLENGE;
  332. break;
  333. case PKT_ACCESS_REJECT:
  334. always = IAS_INCLUDE_IN_REJECT;
  335. never = IAS_RECVD_FROM_CLIENT |
  336. IAS_INCLUDE_IN_ACCEPT | IAS_INCLUDE_IN_CHALLENGE;
  337. reason = request.get_Reason();
  338. break;
  339. case PKT_ACCESS_CHALLENGE:
  340. always = IAS_INCLUDE_IN_CHALLENGE;
  341. never = IAS_RECVD_FROM_CLIENT |
  342. IAS_INCLUDE_IN_ACCEPT | IAS_INCLUDE_IN_REJECT;
  343. break;
  344. case PKT_ACCOUNTING_REQUEST:
  345. always = IAS_RECVD_FROM_CLIENT | IAS_RECVD_FROM_PROTOCOL;
  346. never = IAS_INCLUDE_IN_RESPONSE;
  347. reason = request.get_Reason();
  348. break;
  349. }
  350. //////////
  351. // Filter the attributes based on flags.
  352. //////////
  353. for (curPos = firstPos; curPos != lastPos; )
  354. {
  355. // We can release here since the request still holds a reference.
  356. IASAttributeRelease(curPos->pAttribute);
  357. if (!(curPos->pAttribute->dwFlags & always) &&
  358. (curPos->pAttribute->dwFlags & never ) &&
  359. (curPos->pAttribute->dwId != RADIUS_ATTRIBUTE_CLASS))
  360. {
  361. --lastPos;
  362. std::swap(lastPos->pAttribute, curPos->pAttribute);
  363. }
  364. else
  365. {
  366. ++curPos;
  367. }
  368. }
  369. //////////
  370. // Add the Packet-Type pseudo-attribute.
  371. //////////
  372. IASATTRIBUTE packetTypeAttr;
  373. packetTypeAttr.dwId = IAS_ATTRIBUTE_PACKET_TYPE;
  374. packetTypeAttr.dwFlags = (DWORD)-1;
  375. packetTypeAttr.Value.itType = IASTYPE_ENUM;
  376. packetTypeAttr.Value.Enumerator = packetType;
  377. lastPos->pAttribute = &packetTypeAttr;
  378. ++lastPos;
  379. //////////
  380. // Add the Reason-Code pseudo-attribute.
  381. //////////
  382. IASATTRIBUTE reasonCodeAttr;
  383. reasonCodeAttr.dwId = IAS_ATTRIBUTE_REASON_CODE;
  384. reasonCodeAttr.dwFlags = (DWORD)-1;
  385. reasonCodeAttr.Value.itType = IASTYPE_INTEGER;
  386. reasonCodeAttr.Value.Integer = reason;
  387. lastPos->pAttribute = &reasonCodeAttr;
  388. ++lastPos;
  389. //////////
  390. // Invoke the currently configured formatter.
  391. //////////
  392. (this->*format)(request, buffer, localTime, firstPos, lastPos);
  393. //////////
  394. // We're done.
  395. //////////
  396. buffer.endRecord();
  397. }
  398. void Accountant::formatODBCRecord(
  399. IASRequest& request,
  400. FormattedBuffer& buffer,
  401. const SYSTEMTIME& localTime,
  402. PATTRIBUTEPOSITION firstPos,
  403. PATTRIBUTEPOSITION lastPos
  404. ) const throw (_com_error)
  405. {
  406. //////////
  407. // Column 1: Computer name.
  408. //////////
  409. buffer.append('\"');
  410. buffer.append((PBYTE)computerName, computerNameLen);
  411. buffer.append('\"');
  412. //////////
  413. // Column 2: Service name.
  414. //////////
  415. buffer.beginColumn();
  416. switch (request.get_Protocol())
  417. {
  418. case IAS_PROTOCOL_RADIUS:
  419. buffer.append((const BYTE*)"\"IAS\"", 5);
  420. break;
  421. case IAS_PROTOCOL_RAS:
  422. buffer.append((const BYTE*)"\"RAS\"", 5);
  423. break;
  424. }
  425. //////////
  426. // Column 3: Record time.
  427. //////////
  428. buffer.beginColumn();
  429. buffer.appendDate(localTime);
  430. buffer.beginColumn();
  431. buffer.appendTime(localTime);
  432. //////////
  433. // Allocate a blank record.
  434. //////////
  435. PATTRIBUTEPOSITION *firstField, *curField, *lastField;
  436. size_t nfield = schema.getNumFields() + 1;
  437. firstField = STACK_ALLOC(PATTRIBUTEPOSITION, nfield);
  438. memset(firstField, 0, sizeof(PATTRIBUTEPOSITION) * nfield);
  439. lastField = firstField + nfield;
  440. //////////
  441. // Sort the attributes to coalesce multi-valued attributes.
  442. //////////
  443. std::sort(firstPos, lastPos, IASOrderByID());
  444. //////////
  445. // Add a null terminator. This will make it easier to handle multi-valued
  446. // attributes.
  447. //////////
  448. lastPos->pAttribute = NULL;
  449. //////////
  450. // Fill in the fields.
  451. //////////
  452. PATTRIBUTEPOSITION curPos;
  453. DWORD lastSeen = (DWORD)-1;
  454. for (curPos = firstPos; curPos != lastPos; ++curPos)
  455. {
  456. // Only process if this is a new attribute type.
  457. if (curPos->pAttribute->dwId != lastSeen)
  458. {
  459. lastSeen = curPos->pAttribute->dwId;
  460. firstField[schema.getOrdinal(lastSeen)] = curPos;
  461. }
  462. }
  463. //////////
  464. // Pack the record into the buffer. We skip field 0, since that's where
  465. // we map all the attributes we don't want to log.
  466. //////////
  467. for (curField = firstField + 1; curField != lastField; ++curField)
  468. {
  469. buffer.beginColumn();
  470. if (*curField) { buffer.append(*curField); }
  471. }
  472. }
  473. void Accountant::formatW3CRecord(
  474. IASRequest& request,
  475. FormattedBuffer& buffer,
  476. const SYSTEMTIME& localTime,
  477. PATTRIBUTEPOSITION firstPos,
  478. PATTRIBUTEPOSITION lastPos
  479. ) const throw (_com_error)
  480. {
  481. //////////
  482. // Column 1: NAS-IP-Addresses
  483. //////////
  484. PIASATTRIBUTE attr = IASPeekAttribute(request,
  485. IAS_ATTRIBUTE_CLIENT_IP_ADDRESS,
  486. IASTYPE_INET_ADDR);
  487. if (attr) { buffer.append(attr->Value); }
  488. //////////
  489. // Column 2: User-Name
  490. //////////
  491. buffer.beginColumn();
  492. attr = IASPeekAttribute(request,
  493. RADIUS_ATTRIBUTE_USER_NAME,
  494. IASTYPE_OCTET_STRING);
  495. if (attr) { buffer.append(attr->Value); }
  496. //////////
  497. // Column 3: Record time.
  498. //////////
  499. buffer.beginColumn();
  500. buffer.appendDate(localTime);
  501. buffer.beginColumn();
  502. buffer.appendTime(localTime);
  503. //////////
  504. // Column 4: Service name.
  505. //////////
  506. buffer.beginColumn();
  507. switch (request.get_Protocol())
  508. {
  509. case IAS_PROTOCOL_RADIUS:
  510. buffer.append("IAS");
  511. break;
  512. case IAS_PROTOCOL_RAS:
  513. buffer.append("RAS");
  514. break;
  515. }
  516. //////////
  517. // Column 5: Computer name.
  518. //////////
  519. buffer.beginColumn();
  520. buffer.append((PBYTE)computerName, computerNameLen);
  521. //////////
  522. // Pack the attributes into the buffer.
  523. //////////
  524. PATTRIBUTEPOSITION curPos;
  525. for (curPos = firstPos; curPos != lastPos; ++curPos)
  526. {
  527. if (!schema.excludeFromLog(curPos->pAttribute->dwId))
  528. {
  529. buffer.beginColumn();
  530. buffer.append(curPos->pAttribute->dwId);
  531. buffer.beginColumn();
  532. buffer.append(*(curPos->pAttribute));
  533. }
  534. }
  535. }