windows-nt/Source/XPSP1/NT/net/ias/providers/ntuser/basecamp/basecamp.cpp

766 lines
19 KiB
C++
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2000, Microsoft Corp. All rights reserved.
//
// FILE
//
// basecamp.cpp
//
// SYNOPSIS
//
// Defines the class BaseCampHostBase.
//
///////////////////////////////////////////////////////////////////////////////
#include <ias.h>
#include <extension.h>
#include <basecamp.h>
#include <memory>
//////////
// The offset between the UNIX and NT epochs.
//////////
const DWORDLONG UNIX_EPOCH = 116444736000000000ui64;
/////////
// Convert a single attribute from IAS to BaseCamp format.
// Returns (dst + 1) if successful, (dst) otherwise.
/////////
PRADIUS_ATTRIBUTE
WINAPI
ConvertToBaseCampAttribute(
IN PIASATTRIBUTE src,
IN PRADIUS_ATTRIBUTE dst
) throw ()
{
/////////
// Convert the attribute ID.
/////////
if (src->dwId < 256)
{
// If it's a radius standard attribute, then use 'as is'.
dst->dwAttrType = src->dwId;
}
else
{
// Map internal attributes.
switch (src->dwId)
{
case IAS_ATTRIBUTE_CLIENT_IP_ADDRESS:
dst->dwAttrType = ratSrcIPAddress;
break;
case IAS_ATTRIBUTE_CLIENT_UDP_PORT:
dst->dwAttrType = ratSrcPort;
break;
case IAS_ATTRIBUTE_NT4_ACCOUNT_NAME:
dst->dwAttrType = ratStrippedUserName;
break;
case IAS_ATTRIBUTE_FULLY_QUALIFIED_USER_NAME:
dst->dwAttrType = ratFQUserName;
break;
case IAS_ATTRIBUTE_NP_NAME:
dst->dwAttrType = ratPolicyName;
break;
default:
// No mapping exists.
return dst;
}
}
/////////
// Convert the attribute value.
/////////
switch (src->Value.itType)
{
case IASTYPE_BOOLEAN:
case IASTYPE_INTEGER:
case IASTYPE_ENUM:
{
dst->fDataType = rdtInteger;
dst->cbDataLength = sizeof(DWORD);
dst->dwValue = src->Value.Integer;
++dst;
break;
}
case IASTYPE_INET_ADDR:
{
dst->fDataType = rdtAddress;
dst->cbDataLength = sizeof(DWORD);
dst->dwValue = src->Value.InetAddr;
++dst;
break;
}
case IASTYPE_STRING:
{
IASAttributeAnsiAlloc(src);
if (src->Value.String.pszAnsi)
{
dst->fDataType = rdtString;
dst->cbDataLength = strlen(src->Value.String.pszAnsi) + 1;
dst->lpValue = src->Value.String.pszAnsi;
++dst;
}
break;
}
case IASTYPE_OCTET_STRING:
case IASTYPE_PROV_SPECIFIC:
{
dst->fDataType = rdtString;
dst->cbDataLength = src->Value.OctetString.dwLength;
dst->lpValue = (PCSTR)src->Value.OctetString.lpValue;
++dst;
break;
}
case IASTYPE_UTC_TIME:
{
DWORDLONG val;
// Move in the high DWORD.
val = src->Value.UTCTime.dwHighDateTime;
val <<= 32;
// Move in the low DWORD.
val |= src->Value.UTCTime.dwLowDateTime;
// Convert to the UNIX epoch.
val -= UNIX_EPOCH;
// Convert to seconds.
val /= 10000000;
dst->fDataType = rdtTime;
dst->cbDataLength = sizeof(DWORD);
dst->dwValue = (DWORD)val;
++dst;
break;
}
}
return dst;
}
/////////
// Converts a request object to an array of BaseCamp attributes.
/////////
VOID
WINAPI
ConvertRequestToBaseCampAttributes(
IN IASRequest& request,
IN BOOL inbound,
OUT PRADIUS_ATTRIBUTE* ppAttrs
)
{
/////////
// Determine the packetCode.
/////////
DWORD packetCode = 0;
if (!inbound)
{
// For outbound requests we look at the response.
switch (request.get_Response())
{
case IAS_RESPONSE_ACCESS_ACCEPT:
packetCode = 2;
break;
case IAS_RESPONSE_ACCESS_REJECT:
packetCode = 3;
break;
case IAS_RESPONSE_ACCOUNTING:
packetCode = 5;
break;
case IAS_RESPONSE_ACCESS_CHALLENGE:
packetCode = 11;
break;
}
}
if (!packetCode)
{
// If we made it here, either this is an inbound request or we
// haven't made a decision yet on an outbound request.
switch (request.get_Request())
{
case IAS_REQUEST_ACCESS_REQUEST:
packetCode = 1;
break;
case IAS_REQUEST_ACCOUNTING:
packetCode = 4;
break;
}
}
///////
// Determine which attributes to convert based on the packetCode.
///////
DWORD always, never;
switch (packetCode)
{
case 1: // Access-Request
always = IAS_RECVD_FROM_CLIENT | IAS_RECVD_FROM_PROTOCOL;
never = IAS_INCLUDE_IN_RESPONSE;
break;
case 2: // Access-Accept
always = IAS_INCLUDE_IN_ACCEPT;
never = IAS_RECVD_FROM_CLIENT |
IAS_INCLUDE_IN_REJECT | IAS_INCLUDE_IN_CHALLENGE;
break;
case 3: // Access-Reject
always = IAS_INCLUDE_IN_REJECT;
never = IAS_RECVD_FROM_CLIENT |
IAS_INCLUDE_IN_ACCEPT | IAS_INCLUDE_IN_CHALLENGE;
break;
case 4: // Accounting-Request
always = IAS_RECVD_FROM_CLIENT | IAS_RECVD_FROM_PROTOCOL;
never = IAS_INCLUDE_IN_RESPONSE;
break;
case 5: // Accounting-Response
always = IAS_INCLUDE_IN_RESPONSE;
never = IAS_RECVD_FROM_CLIENT;
break;
case 11: // Access-Challenge.
always = IAS_INCLUDE_IN_CHALLENGE;
never = IAS_RECVD_FROM_CLIENT |
IAS_INCLUDE_IN_ACCEPT | IAS_INCLUDE_IN_REJECT;
break;
default:
always = 0;
never = 0;
}
// Get the packet header (won't be present for RAS).
PIASATTRIBUTE header = IASPeekAttribute(
request,
IAS_ATTRIBUTE_CLIENT_PACKET_HEADER,
IASTYPE_OCTET_STRING
);
// Allocate memory for the converted attributes. We need six extra elements
// for derived attributes and the terminator.
DWORD numAttrs = request.GetAttributeCount();
*ppAttrs = (PRADIUS_ATTRIBUTE)
CoTaskMemAlloc((numAttrs + 6UL) * sizeof(RADIUS_ATTRIBUTE));
if (*ppAttrs == NULL) { _com_issue_error(E_OUTOFMEMORY); }
// Cursor into the attribute array.
PRADIUS_ATTRIBUTE dst = *ppAttrs;
// Packet code.
dst->dwAttrType = ratCode;
dst->fDataType = rdtInteger;
dst->cbDataLength = sizeof(DWORD);
dst->dwValue = packetCode;
++dst;
// Authentication provider.
dst->dwAttrType = ratProvider;
dst->fDataType = rdtInteger;
dst->cbDataLength = sizeof(DWORD);
PIASATTRIBUTE providerType = IASPeekAttribute(
request,
IAS_ATTRIBUTE_PROVIDER_TYPE,
IASTYPE_ENUM
);
if (providerType == 0)
{
// most of the time, the absence of the provider type is used
// for rapNone
dst->dwValue = rapNone;
}
else
{
switch(providerType->Value.Integer)
{
case IAS_PROVIDER_NONE:
{
dst->dwValue = rapNone;
break;
}
case IAS_PROVIDER_WINDOWS:
{
dst->dwValue = rapWindowsNT;
break;
}
case IAS_PROVIDER_RADIUS_PROXY:
{
dst->dwValue = rapProxy;
break;
}
default:
{
// should never be here
dst->dwValue = rapUnknown;
}
}
}
++dst;
// Identifier
if (header)
{
dst->dwAttrType = ratIdentifier;
dst->fDataType = rdtInteger;
dst->cbDataLength = sizeof(DWORD);
dst->dwValue = *(PBYTE)(header->Value.OctetString.lpValue + 1);
++dst;
}
// Check for a Chap-Challenge.
PIASATTRIBUTE challenge = IASPeekAttribute(
request,
RADIUS_ATTRIBUTE_CHAP_CHALLENGE,
IASTYPE_OCTET_STRING
);
if (challenge)
{
// Use the Chap-Challenge if present ...
dst->dwAttrType = ratAuthenticator;
dst->fDataType = rdtString;
dst->cbDataLength = challenge->Value.OctetString.dwLength;
dst->lpValue = (const char*)challenge->Value.OctetString.lpValue;
++dst;
}
else if (header)
{
// ... otherwise use the Request Authenticator.
dst->dwAttrType = ratAuthenticator;
dst->fDataType = rdtString;
dst->cbDataLength = 16;
dst->lpValue = (const char*)header->Value.OctetString.lpValue + 4;
++dst;
}
// Get all the regular attributes from the request.
USES_IAS_STACK_VECTOR();
IASAttributeVectorOnStack(attrs, NULL, numAttrs);
attrs.load(request);
// Iterate through and convert each attribute.
for (IASAttributeVector::iterator i = attrs.begin(); i != attrs.end(); ++i)
{
if ( (i->pAttribute->dwFlags & always) ||
!(i->pAttribute->dwFlags & never ))
{
dst = ConvertToBaseCampAttribute(i->pAttribute, dst);
}
}
// All done, so add the terminator.
dst->dwAttrType = ratMinimum;
dst->fDataType = rdtUnknown;
dst->cbDataLength = 0;
dst->lpValue = NULL;
}
/////////
// Determines if an extension should only be loaded under NT4.
/////////
BOOL
WINAPI
IsNT4Only(
PCWSTR path
) throw ()
{
// Strip everything before the last backslash.
const WCHAR* basename = wcsrchr(path, L'\\');
if (basename == NULL)
{
basename = path;
}
else
{
++basename;
}
// Is this the authsam extension?
return _wcsicmp(basename, L"AUTHSAM.DLL") == 0;
}
/////////
// Free an array of BaseCamp attributes.
/////////
VOID
WINAPI
FreeBaseCampAttributes(
IN PRADIUS_ATTRIBUTE ppAttrs
) throw ()
{
CoTaskMemFree(ppAttrs);
}
///////
// Store an array of BaseCamp attributes in a request.
///////
VOID
WINAPI
StoreBaseCampAttributes(
IN IASRequest& request,
IN const RADIUS_ATTRIBUTE* pAttrs
)
{
// Set the flags based on the response.
DWORD flags;
if (request.get_Response() == IAS_RESPONSE_ACCESS_REJECT)
{
flags = IAS_INCLUDE_IN_REJECT;
}
else
{
flags = IAS_INCLUDE_IN_ACCEPT;
}
// Iterate through the attributes, convert, and store.
const RADIUS_ATTRIBUTE* i;
for (i = pAttrs; i->dwAttrType != ratMinimum; ++i)
{
IASAttribute attr(true);
attr->dwFlags = flags;
attr->dwId = i->dwAttrType;
switch (i->fDataType)
{
case rdtAddress:
{
attr->Value.itType = IASTYPE_INET_ADDR;
attr->Value.InetAddr = i->dwValue;
break;
}
case rdtInteger:
case rdtTime:
{
attr->Value.itType = IASTYPE_INTEGER;
attr->Value.InetAddr = i->dwValue;
break;
}
default:
{
attr.setOctetString(i->cbDataLength, (PBYTE)i->lpValue);
}
}
attr.store(request);
}
}
///////////////////////////////////////////////////////////////////////////////
//
// CLASS
//
// OutAttributes
//
// DESCRIPTION
//
// Manages the RADIUS_ATTRIBUTE's returned by an extension.
//
///////////////////////////////////////////////////////////////////////////////
class OutAttributes
{
public:
OutAttributes(BaseCampExtension& ext) throw ()
: owner(ext),attrs(NULL)
{ }
~OutAttributes() throw ()
{ owner.freeAttrs(attrs); }
operator bool() const throw ()
{ return attrs != NULL; }
PRADIUS_ATTRIBUTE* operator&() throw ()
{ return &attrs; }
operator const RADIUS_ATTRIBUTE*() const throw ()
{ return attrs; }
private:
BaseCampExtension& owner;
PRADIUS_ATTRIBUTE attrs;
// Not implemented.
OutAttributes(const OutAttributes&);
OutAttributes& operator=(const OutAttributes&);
};
BaseCampHostBase::BaseCampHostBase(
PCSTR friendlyName,
PCWSTR registryKey,
PCWSTR registryValue,
BOOL inboundPacket,
DWORD actions
) throw ()
: name(friendlyName),
extensionsKey(registryKey),
extensionsValue(registryValue),
inbound(inboundPacket),
allowedActions(actions),
numExtensions(0),
extensions(NULL)
{
}
STDMETHODIMP BaseCampHostBase::Initialize()
{
// Allocate an attribute for the Authentication-Type.
DWORD error = IASAttributeAlloc(1, &authType);
if (error != NO_ERROR) { return HRESULT_FROM_WIN32(error); }
// Initialize the attribute fields.
authType->dwId = IAS_ATTRIBUTE_AUTHENTICATION_TYPE;
authType->Value.itType = IASTYPE_ENUM;
authType->Value.Enumerator = IAS_AUTH_CUSTOM;
DWORD status = NO_ERROR;
HKEY hKey = NULL;
do
{
// Open the registry key.
status = RegOpenKeyW(
HKEY_LOCAL_MACHINE,
extensionsKey,
&hKey
);
if (status != NO_ERROR) { break; }
// Allocate a buffer to hold the value.
DWORD type, length;
status = RegQueryValueExW(
hKey,
extensionsValue,
NULL,
&type,
NULL,
&length
);
if (status != NO_ERROR) { break; }
PBYTE data = (PBYTE)_alloca(length);
// Read the registry value.
status = RegQueryValueExW(
hKey,
extensionsValue,
NULL,
&type,
data,
&length
);
if (status != NO_ERROR) { break; }
// Make sure it's the right type.
if (type != REG_MULTI_SZ)
{
status = ERROR_INVALID_DATA;
break;
}
// Count the number of strings.
PCWSTR path;
for (path = (PCWSTR)data; *path; path += wcslen(path) + 1)
{
if (!IsNT4Only(path)) { ++numExtensions; }
}
// If there are no extensions, then we're done.
if (numExtensions == 0) { break; }
// Allocate memory to hold the extensions.
extensions = new (std::nothrow) BaseCampExtension[numExtensions];
if (extensions == NULL)
{
status = ERROR_NOT_ENOUGH_MEMORY;
break;
}
// Load the DLL's.
BaseCampExtension* ext = extensions;
for (path = (PCWSTR)data; *path; path += wcslen(path) + 1)
{
if (!IsNT4Only(path))
{
IASTracePrintf("Loading %s extension %S", name, path);
status = ext->load(path);
if (status != NO_ERROR)
{
break;
}
++ext;
}
}
} while (false);
// If we failed, shutdown.
if (status != NO_ERROR) { BaseCampHostBase::Shutdown(); }
// Close the registry.
if (hKey) { RegCloseKey(hKey); }
// If no extensions are registered, then it's not really an error.
if (status == ERROR_FILE_NOT_FOUND) { status = NO_ERROR; }
return HRESULT_FROM_WIN32(status);
}
STDMETHODIMP BaseCampHostBase::Shutdown()
{
numExtensions = 0;
delete[] extensions;
extensions = NULL;
authType.release();
return S_OK;
}
IASREQUESTSTATUS BaseCampHostBase::onSyncRequest(IRequest* pRequest) throw ()
{
// Short-circuit if there aren't any extensions,
if (numExtensions == 0) { return IAS_REQUEST_STATUS_CONTINUE; }
IASTracePrintf("%s processing request.", name);
// Array of converted attributes. These are at function scope so we can
// clean-up after an exception.
PRADIUS_ATTRIBUTE pAttrs = NULL;
// Default status is to continue.
IASREQUESTSTATUS retval = IAS_REQUEST_STATUS_CONTINUE;
try
{
IASRequest request(pRequest);
// Convert any VSAs to RADIUS wire format.
filter.radiusFromIAS(request);
// Convert request to an array of BaseCamp attributes.
ConvertRequestToBaseCampAttributes(
request,
inbound,
&pAttrs
);
// Extensions can only return an action for Access-Requests.
RADIUS_ACTION fAction = raContinue, *pfAction;
if (allowedActions &&
request.get_Request() == IAS_REQUEST_ACCESS_REQUEST)
{
pfAction = &fAction;
}
else
{
pfAction = NULL;
}
BOOL handled = FALSE;
// Invoke each extension.
for (DWORD i = 0; i < numExtensions && !handled; ++i)
{
IASTracePrintf("Invoking extension %S.", extensions[i].getName());
OutAttributes outAttrs(extensions[i]);
DWORD error = extensions[i].process(
pAttrs,
&outAttrs,
pfAction
);
// Abort on error ...
if (error != NO_ERROR)
{
IASTraceFailure("RadiusExtensionProcess", error);
_com_issue_error(IAS_INTERNAL_ERROR);
}
// Process the action.
if (fAction == raAccept && (allowedActions & ACTION_ACCEPT))
{
IASTraceString("Extension action: Accept");
authType.store(request);
request.SetResponse(IAS_RESPONSE_ACCESS_ACCEPT);
retval = IAS_REQUEST_STATUS_CONTINUE;
handled = TRUE;
}
else if (fAction == raReject && (allowedActions & ACTION_REJECT))
{
IASTraceString("Extension action: Reject");
// In the reject case, we have to remove any existing
// authentication type, because we may be an authorization
// extension.
DWORD attrId = IAS_ATTRIBUTE_AUTHENTICATION_TYPE;
request.RemoveAttributesByType(1, &attrId);
authType.store(request);
request.SetResponse(IAS_RESPONSE_ACCESS_REJECT, IAS_AUTH_FAILURE);
retval = IAS_REQUEST_STATUS_HANDLED;
handled = TRUE;
}
else
{
IASTraceString("Extension action: Continue");
}
// Store the outAttrs (if any).
if (outAttrs)
{
StoreBaseCampAttributes(request, outAttrs);
}
}
// Convert any VSAs back to internal format.
filter.radiusToIAS(request);
}
catch (const _com_error& ce)
{
IASTraceExcept();
pRequest->SetResponse(IAS_RESPONSE_DISCARD_PACKET, ce.Error());
retval = IAS_REQUEST_STATUS_ABORT;
}
// Clean up the attributes.
FreeBaseCampAttributes(pAttrs);
return retval;
}