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.
467 lines
11 KiB
467 lines
11 KiB
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (c) Microsoft Corp. All rights reserved.
|
|
//
|
|
// FILE
|
|
//
|
|
// localfile.cpp
|
|
//
|
|
// SYNOPSIS
|
|
//
|
|
// Defines the class LocalFile.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <ias.h>
|
|
#include <iasutf8.h>
|
|
#include <sdoias.h>
|
|
|
|
#include <algorithm>
|
|
|
|
#include <localfile.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;
|
|
|
|
|
|
//////////
|
|
// 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;
|
|
}
|
|
|
|
|
|
LocalFile::LocalFile() throw ()
|
|
: computerNameLen(0)
|
|
{
|
|
}
|
|
|
|
|
|
STDMETHODIMP LocalFile::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 Accountant::Initialize();
|
|
}
|
|
|
|
|
|
STDMETHODIMP LocalFile::Shutdown()
|
|
{
|
|
log.Close();
|
|
return Accountant::Shutdown();
|
|
}
|
|
|
|
|
|
STDMETHODIMP LocalFile::PutProperty(LONG Id, VARIANT *pValue)
|
|
{
|
|
if (pValue == NULL) { return E_INVALIDARG; }
|
|
|
|
switch (Id)
|
|
{
|
|
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:
|
|
return log.SetPeriod(
|
|
static_cast<NEW_LOG_FILE_FREQUENCY>(
|
|
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)
|
|
{ return E_INVALIDARG; }
|
|
return 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;
|
|
}
|
|
|
|
case PROPERTY_ACCOUNTING_LOG_DELETE_IF_FULL:
|
|
{
|
|
if (V_VT(pValue) != VT_BOOL) { return DISP_E_TYPEMISMATCH; }
|
|
log.SetDeleteIfFull((V_BOOL(pValue)) != 0);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
return Accountant::PutProperty(Id, pValue);
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
void LocalFile::Process(IASTL::IASRequest& request)
|
|
{
|
|
// Do some custom preprocessing.
|
|
switch (request.get_Request())
|
|
{
|
|
case IAS_REQUEST_ACCOUNTING:
|
|
{
|
|
if (request.get_Response() == IAS_RESPONSE_INVALID)
|
|
{
|
|
request.SetResponse(IAS_RESPONSE_ACCOUNTING, S_OK);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IAS_REQUEST_ACCESS_REQUEST:
|
|
{
|
|
InsertClassAttribute(request);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Create a FormattedBuffer of the correct type.
|
|
FormattedBuffer buffer((format == formatODBCRecord) ? '\"' : '\0');
|
|
|
|
RecordEvent(&buffer, request);
|
|
}
|
|
|
|
|
|
void LocalFile::InsertRecord(
|
|
void* context,
|
|
IASTL::IASRequest& request,
|
|
const SYSTEMTIME& localTime,
|
|
PATTRIBUTEPOSITION first,
|
|
PATTRIBUTEPOSITION last
|
|
)
|
|
{
|
|
FormattedBuffer& buffer = *static_cast<FormattedBuffer*>(context);
|
|
|
|
// Invoke the currently configured formatter.
|
|
(this->*format)(request, buffer, localTime, first, last);
|
|
|
|
// We're done.
|
|
buffer.endRecord();
|
|
}
|
|
|
|
|
|
void LocalFile::Flush(
|
|
void* context,
|
|
IASTL::IASRequest& request,
|
|
const SYSTEMTIME& localTime
|
|
)
|
|
{
|
|
FormattedBuffer& buffer = *static_cast<FormattedBuffer*>(context);
|
|
|
|
if (!buffer.empty())
|
|
{
|
|
if (!log.Write(
|
|
request.get_Protocol(),
|
|
localTime,
|
|
buffer.getBuffer(),
|
|
buffer.getLength()
|
|
))
|
|
{
|
|
IASTL::issue_error(HRESULT_FROM_WIN32(ERROR_WRITE_FAULT));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void LocalFile::formatODBCRecord(
|
|
IASTL::IASRequest& request,
|
|
FormattedBuffer& buffer,
|
|
const SYSTEMTIME& localTime,
|
|
PATTRIBUTEPOSITION firstPos,
|
|
PATTRIBUTEPOSITION lastPos
|
|
) const
|
|
{
|
|
//////////
|
|
// 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, IASTL::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 LocalFile::formatW3CRecord(
|
|
IASTL::IASRequest& request,
|
|
FormattedBuffer& buffer,
|
|
const SYSTEMTIME& localTime,
|
|
PATTRIBUTEPOSITION firstPos,
|
|
PATTRIBUTEPOSITION lastPos
|
|
) const
|
|
{
|
|
//////////
|
|
// Column 1: NAS-IP-Addresses
|
|
//////////
|
|
|
|
PIASATTRIBUTE attr = IASPeekAttribute(
|
|
request,
|
|
RADIUS_ATTRIBUTE_NAS_IP_ADDRESS,
|
|
IASTYPE_INET_ADDR
|
|
);
|
|
if (attr == 0)
|
|
{
|
|
attr = IASPeekAttribute(
|
|
request,
|
|
IAS_ATTRIBUTE_CLIENT_IP_ADDRESS,
|
|
IASTYPE_INET_ADDR
|
|
);
|
|
}
|
|
|
|
if (attr != 0)
|
|
{
|
|
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));
|
|
}
|
|
}
|
|
}
|