//#-------------------------------------------------------------- // // File: packetreceiver.cpp // // Synopsis: Implementation of CPacketReceiver class methods // // // History: 9/23/97 MKarki Created // // Copyright (C) 1997-98 Microsoft Corporation // All rights reserved. // //---------------------------------------------------------------- #include "radcommon.h" #include "packetreceiver.h" #include #include #include // // this is the time we allow the worker thread to sleep // const DWORD MAX_SLEEP_TIME = 1000; //1000 milli-seconds extern LONG g_lPacketCount; extern LONG g_lThreadCount; /////////////////////////////////////////////////////////////////////////////// // // Retrieve the Auto-Reject User-Name pattern from the registry. // /////////////////////////////////////////////////////////////////////////////// BSTR WINAPI IASRadiusGetPingUserName( VOID ) { LONG status; HKEY hKey; status = RegOpenKeyW( HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\IAS\\Parameters", &hKey ); if (status != NO_ERROR) { return NULL; } BSTR val = NULL; DWORD cbData, type; status = RegQueryValueExW( hKey, L"Ping User-Name", NULL, &type, NULL, &cbData ); if (status == NO_ERROR && type == REG_SZ) { PWSTR buf = (PWSTR)_alloca(cbData); status = RegQueryValueExW( hKey, L"Ping User-Name", NULL, &type, (PBYTE)buf, &cbData ); if (status == NO_ERROR && type == REG_SZ) { val = SysAllocString(buf); } } RegCloseKey(hKey); return val; } /////////////////////////////////////////////////////////////////////////////// // // Handle ping packets. // /////////////////////////////////////////////////////////////////////////////// BOOL WINAPI IASRadiusIsPing( CPacketRadius& pkt, const RegularExpression& regexp ) throw () { // Determine the ping response. PACKETTYPE outCode; switch (pkt.GetInCode()) { case ACCESS_REQUEST: outCode = ACCESS_REJECT; break; case ACCOUNTING_REQUEST: outCode = ACCOUNTING_RESPONSE; break; default: return FALSE; } // Get the User-Name. PATTRIBUTE username = pkt.GetUserName(); if (!username) { return FALSE; } // Convert to UNICODE and test against the pattern. IAS_OCTET_STRING oct = { username->byLength - 2, username->ValueStart }; if (!regexp.testString(IAS_OCT2WIDE(oct))) { return FALSE; } // Build the empty out packet. HRESULT hr = pkt.BuildOutPacket(outCode, NULL, 0); if (SUCCEEDED(hr)) { // Compute the Response-Authenticator. pkt.GenerateOutAuthenticator(); // Get the packet ... PBYTE buf = pkt.GetOutPacket(); WORD buflen = pkt.GetOutLength(); // ... and address. SOCKADDR_IN sin; sin.sin_family = AF_INET; sin.sin_port = htons(pkt.GetOutPort()); sin.sin_addr.s_addr = htonl(pkt.GetOutAddress()); // Send the ping response. sendto( pkt.GetSocket(), (const char*)buf, buflen, 0, (PSOCKADDR)&sin, sizeof(sin) ); } // This packet has been processed. InterlockedDecrement(&g_lPacketCount); return TRUE; } //+++------------------------------------------------------------- // // Function: CPacketReceiver // // Synopsis: This is the constructor of the CPacketReceiver class // // Arguments: NONE // // Returns: NONE // // // History: MKarki Created 9/26/97 // //---------------------------------------------------------------- CPacketReceiver::CPacketReceiver( VOID ) : pingPattern(NULL), m_pCDictionary (NULL), m_pCPreValidator (NULL), m_pCHashMD5 (NULL), m_pCHashHmacMD5 (NULL), m_pCClients (NULL), m_pCReportEvent (NULL) { } // end of CPacketReceiver constructor //+++------------------------------------------------------------- // // Function: ~CPacketReceiver // // Synopsis: This is the destructor of the CPacketReceiver class // // Arguments: NONE // // Returns: NONE // // History: MKarki Created 9/23/97 // //---------------------------------------------------------------- CPacketReceiver::~CPacketReceiver( VOID ) { SysFreeString(pingPattern); } // end of CPacketReceiver destructor //+++------------------------------------------------------------- // // Function: Init // // Synopsis: This is the method which initializes the // CPacketReceiver class object // // Arguments: // [in] CDictionary* // [in] CPreValidator* // [in] CHashMD5* // [in] CHashHmacMD5* // [in] CReportEvent* // // Returns: BOOL - status // // // History: MKarki Created 9/29/97 // // Called By: CContoller class method // //---------------------------------------------------------------- BOOL CPacketReceiver::Init( CDictionary *pCDictionary, CPreValidator *pCPreValidator, CHashMD5 *pCHashMD5, CHashHmacMD5 *pCHashHmacMD5, CClients *pCClients, CReportEvent *pCReportEvent ) { _ASSERT ( (NULL != pCDictionary) && (NULL != pCPreValidator) && (NULL != pCHashMD5) && (NULL != pCHashHmacMD5) && (NULL != pCClients) && (NULL != pCReportEvent) ); HRESULT hr = FinalConstruct(); if (FAILED(hr)) { return FALSE; } // Initialize the Auto-Reject pattern. if (pingPattern = IASRadiusGetPingUserName()) { regexp.setGlobal(TRUE); regexp.setIgnoreCase(TRUE); regexp.setPattern(pingPattern); } m_pCDictionary = pCDictionary; m_pCPreValidator = pCPreValidator; m_pCHashMD5 = pCHashMD5; m_pCHashHmacMD5 = pCHashHmacMD5; m_pCClients = pCClients; m_pCReportEvent = pCReportEvent; if (m_AuthEvent.initialize() || m_AcctEvent.initialize()) { return FALSE; } return (TRUE); } // end of CPacketReceiver::Init method //+++------------------------------------------------------------- // // Function: StartProcessing // // Synopsis: This is the method to start receiving inbound // data // // Arguments: // [in] fd_set - Authentication socket set // [in] fd_set - Accounting socket set // // Returns: BOOL - status // // History: MKarki Created 11/19/97 // // Called By: CContoller::InternalInit method // //---------------------------------------------------------------- BOOL CPacketReceiver::StartProcessing ( /*[in]*/ fd_set& AuthSet, /*[in]*/ fd_set& AcctSet ) { BOOL bStatus = FALSE; __try { // // enable // EnableProcessing (); m_AuthSet = AuthSet; m_AcctSet = AcctSet; // Make sure the events are clear ... m_AuthEvent.reset(); m_AcctEvent.reset(); // ... and add the to the fd_set. FD_SET (m_AuthEvent, &m_AuthSet); FD_SET (m_AcctEvent, &m_AcctSet); // // start a new thread to process authentication requests // bStatus = StartThreadIfNeeded (AUTH_PORTTYPE); if (FALSE == bStatus) { __leave; } // // start a new thread to process accounting requests // bStatus = StartThreadIfNeeded (ACCT_PORTTYPE); if (FALSE == bStatus) { __leave; } // // success // } __finally { if (FALSE == bStatus) { DisableProcessing (); } } return (bStatus); } // end of CPacketReceiver::StartProcessing method //+++------------------------------------------------------------- // // Function: StopProcessing // // Synopsis: This is the method to stop receiving inbound // data // // Arguments: none // // Returns: BOOL - status // // // History: MKarki Created 11/19/97 // // Called By: CContoller::Suspend method // //---------------------------------------------------------------- BOOL CPacketReceiver::StopProcessing ( VOID ) { DisableProcessing (); // Signal the SocketEvents to wake up the worker threads. m_AuthEvent.set(); m_AcctEvent.set(); return (TRUE); } // end of CPacketReceiver::StopProcessing method //+++------------------------------------------------------------- // // Function: ReceivePacket // // Synopsis: This is the method which receives the UDP packet // buffer and starts processing it. // // Arguments: // [in] PBYTE - in packet buffer // [in] DWORD - size of the packet // [in] DWORD - Client's IP address // [in] WORD - Client's UDP port // // Returns: HRESULT - status // // Called By: CPacketReceiver::WorkerRoutine private method // // History: MKarki Created 9/23/97 // //---------------------------------------------------------------- HRESULT CPacketReceiver::ReceivePacket( PBYTE pInBuffer, DWORD dwSize, DWORD dwIPaddress, WORD wPort, SOCKET sock, PORTTYPE portType ) { BOOL bStatus = FALSE; HRESULT hr = S_OK; CPacketRadius *pCPacketRadius = NULL; CComPtr pIIasClient; _ASSERT (pInBuffer); // // get client information for this RADIUS packet // bStatus = m_pCClients->FindObject ( dwIPaddress, &pIIasClient ); if (!bStatus) { // // free the allocated in buffer // CoTaskMemFree (pInBuffer); // // log error and generate audit event // WCHAR srcAddr[16]; ias_inet_htow(dwIPaddress, srcAddr); PCWSTR strings[] = { srcAddr }; IASReportEvent( RADIUS_E_INVALID_CLIENT, 1, 0, strings, NULL ); // // generate an Audit Log // m_pCReportEvent->Process ( RADIUS_INVALID_CLIENT, (AUTH_PORTTYPE == portType) ? ACCESS_REQUEST : ACCOUNTING_REQUEST, dwSize, dwIPaddress, 0, pInBuffer ); return RADIUS_E_ERRORS_OCCURRED; } // // create packet radius object // pCPacketRadius = new (std::nothrow) CPacketRadius ( m_pCHashMD5, m_pCHashHmacMD5, pIIasClient, m_pCReportEvent, pInBuffer, dwSize, dwIPaddress, wPort, sock, portType ); if (NULL == pCPacketRadius) { // // free the allocated in buffer // CoTaskMemFree (pInBuffer); IASTracePrintf ( "Unable to create Packet-Radius object during packet processing" ); hr = E_OUTOFMEMORY; goto Cleanup; } // // now do the preliminary verification of the packet received // hr = pCPacketRadius->PrelimVerification ( m_pCDictionary, dwSize ); if (FAILED (hr)) { goto Cleanup; } // If the Ping User-Name pattern has been set, then we must test // this packet. if (pingPattern && IASRadiusIsPing(*pCPacketRadius, regexp)) { // It was a ping packet, so we're done. delete pCPacketRadius; return S_OK; } // // now pass on this packet to the PreValidator // hr = m_pCPreValidator->StartInValidation (pCPacketRadius); if (FAILED (hr)) { goto Cleanup; } Cleanup: // // cleanup on error // if (FAILED (hr)) { if (hr != RADIUS_E_ERRORS_OCCURRED) { IASReportEvent( RADIUS_E_INTERNAL_ERROR, 0, sizeof(hr), NULL, &hr ); } // // also inform that the packet is being discarded // in_addr sin; sin.s_addr = htonl (dwIPaddress); IASTracePrintf ( "Silently discarding packet received from:%s", inet_ntoa (sin) ); // // inform that packet is being discarded // m_pCReportEvent->Process ( RADIUS_DROPPED_PACKET, (AUTH_PORTTYPE == portType)?ACCESS_REQUEST:ACCOUNTING_REQUEST, dwSize, dwIPaddress, NULL, static_cast (pInBuffer) ); // // free the memory // if (pCPacketRadius) { delete pCPacketRadius; } } return (hr); } // end of CPacketReceiver::ReceivePacket method bool CPacketReceiver::WorkerRoutine(DWORD dwInfo) throw () { // Return value from the function. Indicates whether or not the caller // should call WorkerRoutine again because we were unable to schedule a // replacement thread. bool shouldCallAgain = false; BOOL bSuccess = FALSE; DWORD dwPeerAddress = 0; WORD wPeerPort = 0; CPacketRadius *pCPacketRadius = NULL; PBYTE pBuffer = NULL; PBYTE pReAllocatedBuffer = NULL; DWORD dwSize = MAX_PACKET_SIZE; fd_set socketSet; SOCKET sock = INVALID_SOCKET; __try { if (AUTH_PORTTYPE == (PORTTYPE)dwInfo) { socketSet = m_AuthSet; } else { socketSet = m_AcctSet; } StartAgain: // // check if the processing is still going on // if (FALSE == IsProcessingEnabled ()) { IASTracePrintf ( "Worker Thread exiting as packet processing is not enabled" ); __leave; } // // allocate a new inbound packet buffer // pBuffer = reinterpret_cast (m_InBufferPool.allocate ()); if (NULL == pBuffer) { IASTracePrintf ( "unable to allocate memory from buffer pool for in-bound packet" ); // // Sleep for a second, and try again // Fix for Bug #159140 - MKarki - 4/29/98 // Sleep (MAX_SLEEP_TIME); // // we will have to check whether processing is still // enabled // goto StartAgain; } // // wait now on select // INT iRetVal = select (0, &socketSet, NULL, NULL, NULL); if (SOCKET_ERROR == iRetVal) { int iWsaError = ::WSAGetLastError(); IASTracePrintf ( "Worker Thread failed on select call with error:%d", iWsaError ); if (WSAENOBUFS == iWsaError) { IASTraceString("WARNING: out of memory condition on select in CPacketReceiver::WorkerRoutine"); // out of memory condition. Keep using this thread. shouldCallAgain = true; // to give a chance to the system to recover from a transient // condition Sleep(5); } __leave; } // // check if the processing is still going on // if (FALSE == IsProcessingEnabled ()) { IASTracePrintf( "Worker Thread exiting as packet processing is not enabled" ); __leave; } // // get a socket to recv data on // static size_t nextSocket; sock = socketSet.fd_array[++nextSocket % iRetVal]; // // recv data now // SOCKADDR_IN sin; DWORD dwAddrSize = sizeof (SOCKADDR); dwSize = ::recvfrom ( sock, (PCHAR)pBuffer, (INT)dwSize, (INT)0, (PSOCKADDR)&sin, (INT*)&dwAddrSize ); // Request a new thread now if (!StartThreadIfNeeded(dwInfo)) { // We were unable to create a replacement thread, so this thread // will have to keep receiving packets for now. IASTraceString("WARNING StartThreadIfNeeded failed in CPacketReceiver::WorkerRoutine"); shouldCallAgain = true; } // // if failed to receive data, quit processing // MKarki 3/13/98 - Fix for Bug #147266 // Fix Summary: check for dwSize == 0 too // if ( 0 == dwSize ) { IASTraceString("WARNING failed to receive data, quit processing in CPacketReceiver::WorkerRoutine"); __leave; } wPeerPort = ntohs (sin.sin_port); dwPeerAddress = ntohl (sin.sin_addr.s_addr); if ( dwSize == SOCKET_ERROR ) { int error = WSAGetLastError(); IASTracePrintf ( "WARNING Worker Thread failed on recvfrom with error:%d", error ); switch (error) { case WSAEMSGSIZE: { ProcessInvalidPacketSize(dwInfo, pBuffer, dwPeerAddress); __leave; } default: __leave; } } // // reallocate buffer to size // pReAllocatedBuffer = reinterpret_cast (CoTaskMemAlloc (dwSize)); if (NULL == pReAllocatedBuffer) { IASTracePrintf ( "Unable to allocate memory for received Radius packet " "from Process Heap" ); __leave; } // // copy the information into this buffer // CopyMemory (pReAllocatedBuffer, pBuffer, dwSize); // // free the memory from the pool // m_InBufferPool.deallocate (pBuffer); pBuffer = NULL; // // success // bSuccess = TRUE; } __finally { if (FALSE == bSuccess) { // // do Cleanup // if (pBuffer) { m_InBufferPool.deallocate (pBuffer); } } else { // // Increment the packet count here // InterlockedIncrement (&g_lPacketCount); // // start processing data // HRESULT hr = ReceivePacket ( pReAllocatedBuffer, dwSize, dwPeerAddress, wPeerPort, sock, (PORTTYPE)dwInfo ); if (FAILED (hr)) { // // Decrement the packet count here // InterlockedDecrement (&g_lPacketCount); } } } return shouldCallAgain; } void WINAPI CPacketReceiver::CallbackRoutine(IAS_CALLBACK* context) throw () { ReceiverCallback* cback = static_cast(context); while (cback->self->WorkerRoutine(cback->dwInfo)) { IASTraceString("WARNING: reusing WorkerRoutine"); } CoTaskMemFree(cback); // decrement the global worker thread count InterlockedDecrement(&g_lThreadCount); } BOOL CPacketReceiver::StartThreadIfNeeded(DWORD dwInfo) { // check if the processing is still going on if (!IsProcessingEnabled()) { return TRUE; } ReceiverCallback* cback = static_cast( CoTaskMemAlloc(sizeof(ReceiverCallback)) ); if (cback == 0) { IASTraceString( "CoTaskMemAlloc failed in CPacketReceiver::StartThreadIfNeeded." ); return FALSE; } cback->CallbackRoutine = CallbackRoutine; cback->self = this; cback->dwInfo = dwInfo; InterlockedIncrement(&g_lThreadCount); // Request a new thread now if (!IASRequestThread(cback)) { InterlockedDecrement(&g_lThreadCount); CoTaskMemFree(cback); IASTraceString( "IASRequestThread failed in CPacketReceiver::StartThreadIfNeeded." ); return FALSE; } return TRUE; } //+++------------------------------------------------------------- // // Function: ProcessInvalidPacketSize // // Synopsis: Process the UDP packets received when the size of the packet // is bigger than MAX_PACKET_SIZE (4096) // Log the error. // // Arguments: [in] DWORD - info to give to thread // (comes from WorkerRoutine) // // [in] const void* pBuffer - contains the 4096 first bytes // of the packet received // [in] DWORD address - source address (host order) // // // Called By: CPacketReceiver::WorkerRoutine // //---------------------------------------------------------------- void CPacketReceiver::ProcessInvalidPacketSize( /*in*/ DWORD dwInfo, /*in*/ const void* pBuffer, /*in*/ DWORD address ) { // // packet received bigger than max size. // log error and generate audit event // // extract the IP address WCHAR srcAddr[16]; ias_inet_htow(address, srcAddr); IASTracePrintf( "Incorrect received packet from %S, size: greater than %d", srcAddr, MAX_PACKET_SIZE ); // // get client information for this RADIUS packet // BOOL bStatus = m_pCClients->FindObject(address); if ( bStatus == FALSE ) { // // Invalid Client // log error and generate audit event // IASTracePrintf( "No client with IP-Address:%S registered with server", srcAddr ); PCWSTR strings[] = { srcAddr }; IASReportEvent( RADIUS_E_INVALID_CLIENT, 1, 0, strings, NULL ); // // generate an Audit Log // m_pCReportEvent->Process( RADIUS_INVALID_CLIENT, (AUTH_PORTTYPE == (PORTTYPE)dwInfo)?ACCESS_REQUEST:ACCOUNTING_REQUEST, MAX_PACKET_SIZE, address, NULL, const_cast (pBuffer) ); } else { // // Valid client but packet received bigger than max size. // log error and generate audit event // PCWSTR strings[] = {srcAddr}; IASReportEvent( RADIUS_E_MALFORMED_PACKET, 1, MAX_PACKET_SIZE, strings, const_cast (pBuffer) ); // // generate an Audit Log // m_pCReportEvent->Process( RADIUS_MALFORMED_PACKET, (AUTH_PORTTYPE == (PORTTYPE)dwInfo)?ACCESS_REQUEST:ACCOUNTING_REQUEST, MAX_PACKET_SIZE, address, NULL, const_cast (pBuffer) ); } }