windows-nt/Source/XPSP1/NT/net/ias/providers/accounting/account.cpp
2020-09-26 16:20:57 +08:00

640 lines
17 KiB
C++

///////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 1998, Microsoft Corp. All rights reserved.
//
// FILE
//
// account.cpp
//
// SYNOPSIS
//
// Defines the class Accountant.
//
// MODIFICATION HISTORY
//
// 08/05/1998 Original version.
// 09/16/1998 Add Reason-Code pseudo-attribute.
// 12/02/1998 Fix backward compatibility of IAS 1.0 format.
// 01/19/1999 Log Access-Challenge's.
// 01/25/1999 Date and time are separate fields.
// 03/23/1999 Add support for text qualifiers.
// 03/26/1999 Use the new LogFile::setEnabled method.
// 05/18/1999 Store computerName as UTF-8.
// 07/09/1999 Discard the request if logging fails.
// 08/05/1999 Always log Class attribute.
// 01/25/2000 Remove PROPERTY_ACCOUNTING_LOG_ENABLE.
// 02/29/2000 Don't touch Class attribute when proxying.
// 03/13/2000 Log reason code for Accounting-Requests.
//
///////////////////////////////////////////////////////////////////////////////
#include <ias.h>
#include <iasutf8.h>
#include <sdoias.h>
#include <algorithm>
#include <account.h>
#include <classattr.h>
#include <formbuf.h>
#define STACK_ALLOC(type, num) (type*)_alloca(sizeof(type) * (num))
//////////
// Misc. enumerator constants.
//////////
const LONG INET_LOG_FORMAT_INTERNET_STD = 0;
const LONG INET_LOG_FORMAT_NCSA = 3;
const LONG INET_LOG_FORMAT_ODBC_RADIUS = 0xFFFF;
const DWORD ACCOUNTING_INTERIM = 3;
//////////
// Returns TRUE if the request contains an interim accounting record.
//////////
extern "C"
BOOL
WINAPI
IsInterimRecord(
IAttributesRaw* pRaw
)
{
PIASATTRIBUTE attr = IASPeekAttribute(
pRaw,
RADIUS_ATTRIBUTE_ACCT_STATUS_TYPE,
IASTYPE_ENUM
);
return attr && attr->Value.Enumerator == ACCOUNTING_INTERIM;
}
//////////
// Inserts a class attribute into the request.
//////////
extern "C"
HRESULT
WINAPI
InsertClassAttribute(
IAttributesRaw* pRaw
)
{
//////////
// Check if this was proxied.
//////////
PIASATTRIBUTE attr = IASPeekAttribute(
pRaw,
IAS_ATTRIBUTE_PROVIDER_TYPE,
IASTYPE_ENUM
);
if (attr && attr->Value.Enumerator == IAS_PROVIDER_RADIUS_PROXY)
{
return S_OK;
}
//////////
// Check if Generate Class Attribute is disabled
//////////
PIASATTRIBUTE generateClassAttr = IASPeekAttribute(
pRaw,
IAS_ATTRIBUTE_GENERATE_CLASS_ATTRIBUTE,
IASTYPE_BOOLEAN
);
if (generateClassAttr && generateClassAttr->Value.Boolean == FALSE)
{
return S_OK;
}
//////////
// Create a new class attribute
// Do not remove any existing class attribute.
//////////
ATTRIBUTEPOSITION pos;
pos.pAttribute = IASClass::createAttribute(NULL);
if (pos.pAttribute == NULL) { return E_OUTOFMEMORY; }
//////////
// Insert into the request.
//////////
HRESULT hr = pRaw->AddAttributes(1, &pos);
IASAttributeRelease(pos.pAttribute);
return hr;
}
Accountant::Accountant() throw ()
: logAuth(FALSE),
logAcct(FALSE),
logInterim(FALSE),
computerNameLen(0)
{ }
STDMETHODIMP Accountant::Initialize()
{
// Get the Unicode computer name.
WCHAR uniName[MAX_COMPUTERNAME_LENGTH + 1];
DWORD len = sizeof(uniName) / sizeof(WCHAR);
if (!GetComputerNameW(uniName, &len))
{
// If it failed, we'll just use an empty string.
len = 0;
}
// Convert the Unicode to UTF-8.
computerNameLen = IASUnicodeToUtf8(uniName, len, computerName);
IASClass::initialize();
return schema.initialize();
}
STDMETHODIMP Accountant::Shutdown()
{
log.close();
schema.shutdown();
return S_OK;
}
STDMETHODIMP Accountant::PutProperty(LONG Id, VARIANT *pValue)
{
if (pValue == NULL) { return E_INVALIDARG; }
switch (Id)
{
case PROPERTY_ACCOUNTING_LOG_ACCOUNTING:
{
if (V_VT(pValue) != VT_BOOL) { return DISP_E_TYPEMISMATCH; }
logAcct = V_BOOL(pValue);
log.setEnabled(logAcct | logInterim | logAuth);
break;
}
case PROPERTY_ACCOUNTING_LOG_ACCOUNTING_INTERIM:
{
if (V_VT(pValue) != VT_BOOL) { return DISP_E_TYPEMISMATCH; }
logInterim = V_BOOL(pValue);
log.setEnabled(logAcct | logInterim | logAuth);
break;
}
case PROPERTY_ACCOUNTING_LOG_AUTHENTICATION:
{
if (V_VT(pValue) != VT_BOOL) { return DISP_E_TYPEMISMATCH; }
logAuth = V_BOOL(pValue);
log.setEnabled(logAcct | logInterim | logAuth);
break;
}
case PROPERTY_ACCOUNTING_LOG_OPEN_NEW_FREQUENCY:
{
if (V_VT(pValue) != VT_I4) { return DISP_E_TYPEMISMATCH; }
switch (V_I4(pValue))
{
case IAS_LOGGING_UNLIMITED_SIZE:
case IAS_LOGGING_WHEN_FILE_SIZE_REACHES:
case IAS_LOGGING_DAILY:
case IAS_LOGGING_WEEKLY:
case IAS_LOGGING_MONTHLY:
log.setPeriod(V_I4(pValue));
break;
default:
return E_INVALIDARG;
}
break;
}
case PROPERTY_ACCOUNTING_LOG_OPEN_NEW_SIZE:
{
if (V_VT(pValue) != VT_I4) { return DISP_E_TYPEMISMATCH; }
if (V_I4(pValue) <= 0) { return E_INVALIDARG; }
log.setMaxSize(V_I4(pValue) * 0x100000ui64);
break;
}
case PROPERTY_ACCOUNTING_LOG_FILE_DIRECTORY:
{
if (V_VT(pValue) != VT_BSTR) { return DISP_E_TYPEMISMATCH; }
if (V_BSTR(pValue) == NULL || wcslen(V_BSTR(pValue)) > MAX_PATH - 12)
{ return E_INVALIDARG; }
log.setDirectory(V_BSTR(pValue));
break;
}
case PROPERTY_ACCOUNTING_LOG_IAS1_FORMAT:
{
if (V_VT(pValue) != VT_I4) { return DISP_E_TYPEMISMATCH; }
switch (V_I4(pValue))
{
case INET_LOG_FORMAT_ODBC_RADIUS:
format = formatODBCRecord;
break;
case INET_LOG_FORMAT_INTERNET_STD:
format = formatW3CRecord;
break;
default:
return E_INVALIDARG;
}
break;
}
default:
{
// We just ignore properties that we don't understand.
return S_OK;
}
}
return S_OK;
}
IASREQUESTSTATUS Accountant::onSyncRequest(IRequest* pRequest) throw ()
{
// Flag indicating final outcome of logging.
BOOL success = TRUE;
try
{
// Convert to an IASRequest object.
IASRequest request(pRequest);
// Get the local SYSTEMTIME.
SYSTEMTIME st;
GetLocalTime(&st);
// Create a FormattedBuffer of the correct type.
FormattedBuffer buffer(format == formatODBCRecord ? '\"' : '\0');
//////////
// This ungodly mess performs the actual processing logic of the
// accounting handler. I love simple, clean algorithms.
//////////
switch (request.get_Request())
{
case IAS_REQUEST_ACCOUNTING:
{
if (IsInterimRecord(request) ? logInterim : logAcct)
{
appendRecord(request, PKT_ACCOUNTING_REQUEST, buffer, st);
}
if (request.get_Response() == IAS_RESPONSE_INVALID)
{
request.SetResponse(IAS_RESPONSE_ACCOUNTING, S_OK);
}
break;
}
case IAS_REQUEST_ACCESS_REQUEST:
InsertClassAttribute(request);
switch (request.get_Response())
{
case IAS_RESPONSE_ACCESS_ACCEPT:
if (logAuth)
{
appendRecord(request, PKT_ACCESS_REQUEST, buffer, st);
appendRecord(request, PKT_ACCESS_ACCEPT, buffer, st);
}
break;
case IAS_RESPONSE_ACCESS_REJECT:
if (logAuth)
{
appendRecord(request, PKT_ACCESS_REQUEST, buffer, st);
appendRecord(request, PKT_ACCESS_REJECT, buffer, st);
}
break;
case IAS_RESPONSE_ACCESS_CHALLENGE:
if (logAuth)
{
appendRecord(request, PKT_ACCESS_REQUEST, buffer, st);
appendRecord(request, PKT_ACCESS_CHALLENGE, buffer, st);
}
break;
default:
if (logAuth)
{
appendRecord(request, PKT_ACCESS_REQUEST, buffer, st);
}
}
break;
}
// If the buffer isn't empty, then write the contents to the logfile.
if (!buffer.empty())
{
success = log.write(&st, buffer.getBuffer(), buffer.getLength());
}
}
catch (...)
{
// Something went wrong.
success = FALSE;
}
// If we didn't succeed then discard the packet.
if (!success)
{
pRequest->SetResponse(IAS_RESPONSE_DISCARD_PACKET, IAS_NO_RECORD);
return IAS_REQUEST_STATUS_ABORT;
}
return IAS_REQUEST_STATUS_CONTINUE;
}
void Accountant::appendRecord(
IASRequest& request,
PacketType packetType,
FormattedBuffer& buffer,
const SYSTEMTIME& localTime
) const throw (_com_error)
{
//////////
// Retrieve all the attributes from the request. Save room for three extra
// attributes: Packet-Type, Reason-Code, and a null-terminator.
//////////
PATTRIBUTEPOSITION firstPos, curPos, lastPos;
DWORD nattr = request.GetAttributeCount();
firstPos = STACK_ALLOC(ATTRIBUTEPOSITION, nattr + 3);
nattr = request.GetAttributes(nattr, firstPos, 0, NULL);
lastPos = firstPos + nattr;
//////////
// Compute the attribute filter and reason code.
//////////
DWORD always, never, reason = 0;
switch (packetType)
{
case PKT_ACCESS_REQUEST:
always = IAS_RECVD_FROM_CLIENT | IAS_RECVD_FROM_PROTOCOL;
never = IAS_INCLUDE_IN_RESPONSE;
break;
case PKT_ACCESS_ACCEPT:
always = IAS_INCLUDE_IN_ACCEPT;
never = IAS_RECVD_FROM_CLIENT |
IAS_INCLUDE_IN_REJECT | IAS_INCLUDE_IN_CHALLENGE;
break;
case PKT_ACCESS_REJECT:
always = IAS_INCLUDE_IN_REJECT;
never = IAS_RECVD_FROM_CLIENT |
IAS_INCLUDE_IN_ACCEPT | IAS_INCLUDE_IN_CHALLENGE;
reason = request.get_Reason();
break;
case PKT_ACCESS_CHALLENGE:
always = IAS_INCLUDE_IN_CHALLENGE;
never = IAS_RECVD_FROM_CLIENT |
IAS_INCLUDE_IN_ACCEPT | IAS_INCLUDE_IN_REJECT;
break;
case PKT_ACCOUNTING_REQUEST:
always = IAS_RECVD_FROM_CLIENT | IAS_RECVD_FROM_PROTOCOL;
never = IAS_INCLUDE_IN_RESPONSE;
reason = request.get_Reason();
break;
}
//////////
// Filter the attributes based on flags.
//////////
for (curPos = firstPos; curPos != lastPos; )
{
// We can release here since the request still holds a reference.
IASAttributeRelease(curPos->pAttribute);
if (!(curPos->pAttribute->dwFlags & always) &&
(curPos->pAttribute->dwFlags & never ) &&
(curPos->pAttribute->dwId != RADIUS_ATTRIBUTE_CLASS))
{
--lastPos;
std::swap(lastPos->pAttribute, curPos->pAttribute);
}
else
{
++curPos;
}
}
//////////
// Add the Packet-Type pseudo-attribute.
//////////
IASATTRIBUTE packetTypeAttr;
packetTypeAttr.dwId = IAS_ATTRIBUTE_PACKET_TYPE;
packetTypeAttr.dwFlags = (DWORD)-1;
packetTypeAttr.Value.itType = IASTYPE_ENUM;
packetTypeAttr.Value.Enumerator = packetType;
lastPos->pAttribute = &packetTypeAttr;
++lastPos;
//////////
// Add the Reason-Code pseudo-attribute.
//////////
IASATTRIBUTE reasonCodeAttr;
reasonCodeAttr.dwId = IAS_ATTRIBUTE_REASON_CODE;
reasonCodeAttr.dwFlags = (DWORD)-1;
reasonCodeAttr.Value.itType = IASTYPE_INTEGER;
reasonCodeAttr.Value.Integer = reason;
lastPos->pAttribute = &reasonCodeAttr;
++lastPos;
//////////
// Invoke the currently configured formatter.
//////////
(this->*format)(request, buffer, localTime, firstPos, lastPos);
//////////
// We're done.
//////////
buffer.endRecord();
}
void Accountant::formatODBCRecord(
IASRequest& request,
FormattedBuffer& buffer,
const SYSTEMTIME& localTime,
PATTRIBUTEPOSITION firstPos,
PATTRIBUTEPOSITION lastPos
) const throw (_com_error)
{
//////////
// Column 1: Computer name.
//////////
buffer.append('\"');
buffer.append((PBYTE)computerName, computerNameLen);
buffer.append('\"');
//////////
// Column 2: Service name.
//////////
buffer.beginColumn();
switch (request.get_Protocol())
{
case IAS_PROTOCOL_RADIUS:
buffer.append((const BYTE*)"\"IAS\"", 5);
break;
case IAS_PROTOCOL_RAS:
buffer.append((const BYTE*)"\"RAS\"", 5);
break;
}
//////////
// Column 3: Record time.
//////////
buffer.beginColumn();
buffer.appendDate(localTime);
buffer.beginColumn();
buffer.appendTime(localTime);
//////////
// Allocate a blank record.
//////////
PATTRIBUTEPOSITION *firstField, *curField, *lastField;
size_t nfield = schema.getNumFields() + 1;
firstField = STACK_ALLOC(PATTRIBUTEPOSITION, nfield);
memset(firstField, 0, sizeof(PATTRIBUTEPOSITION) * nfield);
lastField = firstField + nfield;
//////////
// Sort the attributes to coalesce multi-valued attributes.
//////////
std::sort(firstPos, lastPos, IASOrderByID());
//////////
// Add a null terminator. This will make it easier to handle multi-valued
// attributes.
//////////
lastPos->pAttribute = NULL;
//////////
// Fill in the fields.
//////////
PATTRIBUTEPOSITION curPos;
DWORD lastSeen = (DWORD)-1;
for (curPos = firstPos; curPos != lastPos; ++curPos)
{
// Only process if this is a new attribute type.
if (curPos->pAttribute->dwId != lastSeen)
{
lastSeen = curPos->pAttribute->dwId;
firstField[schema.getOrdinal(lastSeen)] = curPos;
}
}
//////////
// Pack the record into the buffer. We skip field 0, since that's where
// we map all the attributes we don't want to log.
//////////
for (curField = firstField + 1; curField != lastField; ++curField)
{
buffer.beginColumn();
if (*curField) { buffer.append(*curField); }
}
}
void Accountant::formatW3CRecord(
IASRequest& request,
FormattedBuffer& buffer,
const SYSTEMTIME& localTime,
PATTRIBUTEPOSITION firstPos,
PATTRIBUTEPOSITION lastPos
) const throw (_com_error)
{
//////////
// Column 1: NAS-IP-Addresses
//////////
PIASATTRIBUTE attr = IASPeekAttribute(request,
IAS_ATTRIBUTE_CLIENT_IP_ADDRESS,
IASTYPE_INET_ADDR);
if (attr) { buffer.append(attr->Value); }
//////////
// Column 2: User-Name
//////////
buffer.beginColumn();
attr = IASPeekAttribute(request,
RADIUS_ATTRIBUTE_USER_NAME,
IASTYPE_OCTET_STRING);
if (attr) { buffer.append(attr->Value); }
//////////
// Column 3: Record time.
//////////
buffer.beginColumn();
buffer.appendDate(localTime);
buffer.beginColumn();
buffer.appendTime(localTime);
//////////
// Column 4: Service name.
//////////
buffer.beginColumn();
switch (request.get_Protocol())
{
case IAS_PROTOCOL_RADIUS:
buffer.append("IAS");
break;
case IAS_PROTOCOL_RAS:
buffer.append("RAS");
break;
}
//////////
// Column 5: Computer name.
//////////
buffer.beginColumn();
buffer.append((PBYTE)computerName, computerNameLen);
//////////
// Pack the attributes into the buffer.
//////////
PATTRIBUTEPOSITION curPos;
for (curPos = firstPos; curPos != lastPos; ++curPos)
{
if (!schema.excludeFromLog(curPos->pAttribute->dwId))
{
buffer.beginColumn();
buffer.append(curPos->pAttribute->dwId);
buffer.beginColumn();
buffer.append(*(curPos->pAttribute));
}
}
}