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

747 lines
20 KiB
C++

///////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2000, Microsoft Corp. All rights reserved.
//
// FILE
//
// proxy.cpp
//
// SYNOPSIS
//
// Defines the class RadiusProxy.
//
// MODIFICATION HISTORY
//
// 01/26/2000 Original version.
//
///////////////////////////////////////////////////////////////////////////////
#include <proxypch.h>
#include <datastore2.h>
#include <proxy.h>
#include <radpack.h>
#include <iasevent.h>
#include <iasinfo.h>
#include <iasutil.h>
#include <dsobj.h>
///////////////////////////////////////////////////////////////////////////////
//
// CLASS
//
// Resolver
//
// DESCRIPTION
//
// Utility class for resolving hostnames and iterating through the results.
//
///////////////////////////////////////////////////////////////////////////////
class Resolver
{
public:
Resolver() throw ()
: first(NULL), last(NULL)
{ }
~Resolver() throw ()
{ if (first != &addr) delete[] first; }
// Returns true if the result set contains the specified address.
bool contains(ULONG address) const throw ()
{
for (const ULONG* i = first; i != last; ++i)
{
if (*i == address) { return true; }
}
return false;
}
// Resolves the given name. The return value is the error code.
ULONG resolve(const PCWSTR name = NULL) throw ()
{
// Clear out the existing result set.
if (first != &addr)
{
delete[] first;
first = last = NULL;
}
if (name)
{
// First try for a quick score on dotted decimal.
addr = ias_inet_wtoh(name);
if (addr != INADDR_NONE)
{
addr = htonl(addr);
first = &addr;
last = first + 1;
return NO_ERROR;
}
}
// That didn't work, so look up the name.
PHOSTENT he = IASGetHostByName(name);
if (!he) { return GetLastError(); }
// Count the number of addresses returned.
ULONG naddr = 0;
while (he->h_addr_list[naddr]) { ++naddr; }
// Allocate an array to hold them.
first = last = new (std::nothrow) ULONG[naddr];
if (first)
{
for (ULONG i = 0; i < naddr; ++i)
{
*last++ = *(PULONG)he->h_addr_list[i];
}
}
LocalFree(he);
return first ? NO_ERROR : WSA_NOT_ENOUGH_MEMORY;
}
const ULONG* begin() const throw ()
{ return first; }
const ULONG* end() const throw ()
{ return last; }
private:
ULONG addr, *first, *last;
// Not implemented.
Resolver(const Resolver&);
Resolver& operator=(const Resolver&);
};
///////////////////////////////////////////////////////////////////////////////
//
// CLASS
//
// IASRemoteServer
//
// DESCRIPTION
//
// Extends RemoteServer to add IAS specific server information.
//
///////////////////////////////////////////////////////////////////////////////
class IASRemoteServer : public RemoteServer
{
public:
IASRemoteServer(
const RemoteServerConfig& config,
RadiusRemoteServerEntry* entry
)
: RemoteServer(config),
counters(entry)
{
// Create the Remote-Server-Address attribute.
IASAttribute name(true);
name->dwId = IAS_ATTRIBUTE_REMOTE_SERVER_ADDRESS;
name->Value.itType = IASTYPE_INET_ADDR;
name->Value.InetAddr = ntohl(config.ipAddress);
attrs.push_back(name);
// Update our PerfMon entry.
if (counters)
{
counters->dwCounters[radiusAuthClientServerPortNumber] =
ntohs(config.authPort);
counters->dwCounters[radiusAccClientServerPortNumber] =
ntohs(config.acctPort);
}
}
// Attributes to be added to each request.
IASAttributeVectorWithBuffer<1> attrs;
// PerfMon counters.
RadiusRemoteServerEntry* counters;
};
RadiusProxy::RadiusProxy()
: engine(this)
{
}
RadiusProxy::~RadiusProxy() throw ()
{
}
STDMETHODIMP RadiusProxy::PutProperty(LONG Id, VARIANT* pValue)
{
if (pValue == NULL) { return E_INVALIDARG; }
HRESULT hr;
switch (Id)
{
case PROPERTY_RADIUSPROXY_SERVERGROUPS:
{
if (V_VT(pValue) != VT_DISPATCH) { return DISP_E_TYPEMISMATCH; }
try
{
configure(V_UNKNOWN(pValue));
hr = S_OK;
}
catch (const _com_error& ce)
{
hr = ce.Error();
}
break;
}
default:
{
hr = DISP_E_MEMBERNOTFOUND;
}
}
return hr;
}
void RadiusProxy::onEvent(
const RadiusEvent& event
) throw ()
{
// Convert the event context to an IASRemoteServer.
IASRemoteServer* server = static_cast<IASRemoteServer*>(event.context);
// Update the counters.
counters.updateCounters(
event.portType,
event.eventType,
(server ? server->counters : NULL),
event.data
);
// We always use the address as an insertion string.
WCHAR addr[16], misc[16];
ias_inet_htow(ntohl(event.ipAddress), addr);
// Set up the default parameters for event reporting.
DWORD eventID = 0;
DWORD numStrings = 1;
DWORD dataSize = 0;
PCWSTR strings[2] = { addr, misc };
const void* rawData = NULL;
// Map the RADIUS event to an IAS event ID.
switch (event.eventType)
{
case eventInvalidAddress:
eventID = PROXY_E_INVALID_ADDRESS;
_itow(ntohs(event.ipPort), misc, 10);
numStrings = 2;
break;
case eventMalformedPacket:
eventID = PROXY_E_MALFORMED_RESPONSE;
dataSize = event.packetLength;
rawData = event.packet;
break;
case eventBadAuthenticator:
eventID = PROXY_E_BAD_AUTHENTICATOR;
break;
case eventBadSignature:
eventID = PROXY_E_BAD_SIGNATURE;
break;
case eventMissingSignature:
eventID = PROXY_E_MISSING_SIGNATURE;
break;
case eventUnknownType:
eventID = PROXY_E_UNKNOWN_TYPE;
_itow(event.packet[0], misc, 10);
numStrings = 2;
break;
case eventUnexpectedResponse:
eventID = PROXY_E_UNEXPECTED_RESPONSE;
dataSize = event.packetLength;
rawData = event.packet;
break;
case eventSendError:
eventID = PROXY_E_SEND_ERROR;
_itow(event.data, misc, 10);
numStrings = 2;
break;
case eventReceiveError:
eventID = PROXY_E_RECV_ERROR;
_itow(event.data, misc, 10);
numStrings = 2;
break;
case eventServerAvailable:
eventID = PROXY_S_SERVER_AVAILABLE;
break;
case eventServerUnavailable:
eventID = PROXY_E_SERVER_UNAVAILABLE;
_itow(server->maxLost, misc, 10);
numStrings = 2;
break;
}
if (eventID)
{
IASReportEvent(
eventID,
numStrings,
dataSize,
strings,
(void*)rawData
);
}
}
void RadiusProxy::onComplete(
RadiusProxyEngine::Result result,
PVOID context,
RemoteServer* server,
BYTE code,
const RadiusAttribute* begin,
const RadiusAttribute* end
) throw ()
{
IRequest* comreq = (IRequest*)context;
IASRESPONSE response = IAS_RESPONSE_DISCARD_PACKET;
// Map the result to a reason code.
IASREASON reason;
switch (result)
{
case RadiusProxyEngine::resultSuccess:
reason = IAS_SUCCESS;
break;
case RadiusProxyEngine::resultNotEnoughMemory:
reason = IAS_INTERNAL_ERROR;
break;
case RadiusProxyEngine::resultUnknownServerGroup:
reason = IAS_PROXY_UNKNOWN_GROUP;
break;
case RadiusProxyEngine::resultUnknownServer:
reason = IAS_PROXY_UNKNOWN_SERVER;
break;
case RadiusProxyEngine::resultInvalidRequest:
reason = IAS_PROXY_PACKET_TOO_LONG;
break;
case RadiusProxyEngine::resultSendError:
reason = IAS_PROXY_SEND_ERROR;
break;
case RadiusProxyEngine::resultRequestTimeout:
reason = IAS_PROXY_TIMEOUT;
break;
default:
reason = IAS_INTERNAL_ERROR;
}
try
{
IASRequest request(comreq);
// Always store the server attributes if available.
if (server)
{
static_cast<IASRemoteServer*>(server)->attrs.store(request);
}
if (reason == IAS_SUCCESS)
{
// Set the response code and determine the flags used for returned
// attributes.
DWORD flags = 0;
switch (code)
{
case RADIUS_ACCESS_ACCEPT:
{
response = IAS_RESPONSE_ACCESS_ACCEPT;
flags = IAS_INCLUDE_IN_ACCEPT;
break;
}
case RADIUS_ACCESS_REJECT:
{
response = IAS_RESPONSE_ACCESS_REJECT;
reason = IAS_PROXY_REJECT;
flags = IAS_INCLUDE_IN_REJECT;
break;
}
case RADIUS_ACCESS_CHALLENGE:
{
response = IAS_RESPONSE_ACCESS_CHALLENGE;
flags = IAS_INCLUDE_IN_CHALLENGE;
break;
}
case RADIUS_ACCOUNTING_RESPONSE:
{
response = IAS_RESPONSE_ACCOUNTING;
flags = IAS_INCLUDE_IN_ACCEPT;
break;
}
default:
{
// The RadiusProxyEngine should never do this.
_com_issue_error(E_FAIL);
}
}
// Convert the received attributes to IAS format.
AttributeVector incoming;
for (const RadiusAttribute* src = begin; src != end; ++src)
{
// Temporary hack to workaround bug in the protocol.
if (src->type != RADIUS_SIGNATURE)
{
translator.fromRadius(*src, flags, incoming);
}
}
if (!incoming.empty())
{
// Get the existing attributes.
AttributeVector existing;
existing.load(request);
// Erase any attributes that are already in the request.
AttributeIterator i, j;
for (i = existing.begin(); i != existing.end(); ++i)
{
// Both the flags ...
if (i->pAttribute->dwFlags & flags)
{
for (j = incoming.begin(); j != incoming.end(); )
{
// ... and the ID have to match.
if (j->pAttribute->dwId == i->pAttribute->dwId)
{
j = incoming.erase(j);
}
else
{
++j;
}
}
}
}
// Store the remaining attributes.
incoming.store(request);
}
}
}
catch (const _com_error& ce)
{
response = IAS_RESPONSE_DISCARD_PACKET;
if (ce.Error() == E_INVALIDARG)
{
// We must have had an error translating from RADIUS to IAS format.
reason = IAS_PROXY_MALFORMED_RESPONSE;
}
else
{
// Probably memory allocation.
reason = IAS_INTERNAL_ERROR;
}
}
// Give it back to the pipeline.
comreq->SetResponse(response, reason);
comreq->ReturnToSource(IAS_REQUEST_STATUS_HANDLED);
// This balances the AddRef we did before calling forwardRequest.
comreq->Release();
}
void RadiusProxy::onAsyncRequest(IRequest* pRequest) throw ()
{
try
{
IASRequest request(pRequest);
// Set the packet code based on the request type.
BYTE packetCode;
switch (request.get_Request())
{
case IAS_REQUEST_ACCESS_REQUEST:
{
packetCode = RADIUS_ACCESS_REQUEST;
break;
}
case IAS_REQUEST_ACCOUNTING:
{
packetCode = RADIUS_ACCOUNTING_REQUEST;
break;
}
default:
{
// The pipeline should never give us a request of the wrong type.
_com_issue_error(E_FAIL);
}
}
// Get the attributes from the request.
AttributeVector all, outgoing;
all.load(request);
for (AttributeIterator i = all.begin(); i != all.end(); ++i)
{
// Send all the attributes received from the client except Proxy-State.
if (i->pAttribute->dwFlags & IAS_RECVD_FROM_CLIENT &&
i->pAttribute->dwId != RADIUS_ATTRIBUTE_PROXY_STATE)
{
translator.toRadius(*(i->pAttribute), outgoing);
}
}
// If the request authenticator contains the CHAP challenge:
// it must be used so get the request authenticator (always to
// simplify the code)
PBYTE requestAuthenticator = 0;
IASAttribute radiusHeader;
if (radiusHeader.load(
request,
IAS_ATTRIBUTE_CLIENT_PACKET_HEADER,
IASTYPE_OCTET_STRING
))
{
requestAuthenticator = radiusHeader->Value.OctetString.lpValue + 4;
}
// Allocate an array of RadiusAttributes.
size_t nbyte = outgoing.size() * sizeof(RadiusAttribute);
RadiusAttribute* begin = (RadiusAttribute*)_alloca(nbyte);
RadiusAttribute* end = begin;
// Load the individual attributes.
for (AttributeIterator j = outgoing.begin(); j != outgoing.end(); ++j)
{
end->type = (BYTE)(j->pAttribute->dwId);
end->length = (BYTE)(j->pAttribute->Value.OctetString.dwLength);
end->value = j->pAttribute->Value.OctetString.lpValue;
++end;
}
// Get the RADIUS Server group. This may be NULL since NAS-State bypasses
// proxy policy.
PIASATTRIBUTE group = IASPeekAttribute(
request,
IAS_ATTRIBUTE_PROVIDER_NAME,
IASTYPE_STRING
);
// AddRef the request because we're giving it to the engine.
pRequest->AddRef();
// Add the request authenticator to the parameters of forwardRequest
// That can be NULL
engine.forwardRequest(
(PVOID)pRequest,
(group ? group->Value.String.pszWide : L""),
packetCode,
requestAuthenticator,
begin,
end
);
}
catch (...)
{
// We weren't able to forward it to the engine.
pRequest->SetResponse(IAS_RESPONSE_DISCARD_PACKET, IAS_INTERNAL_ERROR);
pRequest->ReturnToSource(IAS_REQUEST_STATUS_HANDLED);
}
}
void RadiusProxy::configure(IUnknown* root)
{
// Get our IP addresses. We don't care if this fails.
Resolver localAddress, serverAddress;
localAddress.resolve();
// Open the RADIUS Server Groups container. If it's not there, we'll just
// assume there's nothing to configure.
DataStoreObject inGroups(
root,
L"RADIUS Server Groups\0"
);
if (inGroups.empty()) { return; }
// Reserve space for each group.
ServerGroups outGroups(inGroups.numChildren());
// Iterate through the groups.
DataStoreObject inGroup;
while (inGroups.nextChild(inGroup))
{
// Get the group name.
CComBSTR groupName;
inGroup.getValue(L"Name", &groupName);
// Reserve space for each server. This is really a guess since a server
// may resolve to multiple IP addresses.
RemoteServers outServers(inGroup.numChildren());
// Iterate through the servers.
DataStoreObject inServer;
while (inGroup.nextChild(inServer))
{
USES_CONVERSION;
// Populate the RemoteServerConfig. It has a lot of fields.
RemoteServerConfig config;
CComBSTR name;
inServer.getValue(L"Name", &name);
CLSIDFromString(name, &config.guid);
ULONG port;
inServer.getValue(L"Server Authentication Port", &port, 1812);
config.authPort = htons((USHORT)port);
inServer.getValue(L"Server Accounting Port", &port, 1813);
config.acctPort = htons((USHORT)port);
CComBSTR bstrAuth;
inServer.getValue(L"Authentication Secret", &bstrAuth);
config.authSecret = W2A(bstrAuth);
CComBSTR bstrAcct;
inServer.getValue(L"Accounting Secret", &bstrAcct, bstrAuth);
config.acctSecret = W2A(bstrAcct);
inServer.getValue(L"Priority", &config.priority, 1);
inServer.getValue(L"Weight", &config.weight, 50);
// Ignore any zero weight servers.
if (config.weight == 0) { continue; }
// We don't use this feature for now.
config.sendSignature = false;
inServer.getValue(
L"Forward Accounting On/Off",
&config.sendAcctOnOff,
true
);
inServer.getValue(L"Request Timeout", &config.timeout, 3);
// Don't allow zero for timeout
if (config.timeout == 0) { config.timeout = 1; }
inServer.getValue(L"Max Lost Requests", &config.maxLost, 5);
// Don't allow zero for maxLost.
if (config.maxLost == 0) { config.maxLost = 1; }
inServer.getValue(
L"Blackout Interval",
&config.blackout,
10 * config.timeout
);
if (config.blackout < config.timeout)
{
// Blackout interval must be >= request timeout.
config.blackout = config.timeout;
}
// These need to be in msec.
config.timeout *= 1000;
config.blackout *= 1000;
// Now we have to resolve the server name to an IP address.
CComBSTR address;
inServer.getValue(L"Address", &address);
ULONG error = serverAddress.resolve(address);
if (error)
{
WCHAR errorCode[16];
_itow(GetLastError(), errorCode, 10);
PCWSTR strings[3] = { address, groupName, errorCode };
IASReportEvent(
PROXY_E_HOST_NOT_FOUND,
3,
0,
strings,
NULL
);
}
// Create a server entry for each address.
for (const ULONG* addr = serverAddress.begin();
addr != serverAddress.end();
++addr)
{
// Don't allow them to proxy locally.
if (localAddress.contains(*addr))
{
WCHAR ipAddress[16];
ias_inet_htow(ntohl(*addr), ipAddress);
PCWSTR strings[3] = { address, groupName, ipAddress };
IASReportEvent(
PROXY_E_LOCAL_SERVER,
3,
0,
strings,
NULL
);
continue;
}
// Look up the PerfMon counters.
RadiusRemoteServerEntry* entry = counters.getRemoteServerEntry(
*addr
);
// Create the new server
config.ipAddress = *addr;
RemoteServerPtr outServer(new IASRemoteServer(
config,
entry
));
outServers.push_back(outServer);
}
}
// Ignore any empty groups.
if (outServers.empty()) { continue; }
// Create the new group.
ServerGroupPtr outGroup(new ServerGroup(
groupName,
outServers.begin(),
outServers.end()
));
outGroups.push_back(outGroup);
}
// Wow, we're finally done.
engine.setServerGroups(
outGroups.begin(),
outGroups.end()
);
}