/**********************************************************************/ /** Microsoft Windows NT **/ /** Copyright(c) Microsoft Corp., 1998 **/ /**********************************************************************/ /* acptctxt.cxx This file contains the implementations of the PASV_ACCEPT_CONTEXT and ACCEPT_CONTEXT_ENTRY classes used to deal with async PASV connections */ #include "ftpdp.hxx" #include "auxctrs.h" #include "acptctxt.hxx" LONG g_nAcceptContextEntries = 0; PASV_ACCEPT_CONTEXT::PASV_ACCEPT_CONTEXT() : m_dwErr( ERROR_SUCCESS ), m_dwNumEvents( 0 ), m_hWatchThread( NULL ), m_dwThreadId( 0 ), m_dwSignature( ACCEPT_CONTEXT_GOOD_SIG ) /*++ Constructor Arguments: None Returns: Nothing --*/ { m_ahEvents[NEEDUPDATE_INDEX] = WSA_INVALID_EVENT; m_ahEvents[CANUPDATE_INDEX] = WSA_INVALID_EVENT; m_ahEvents[HAVEUPDATED_INDEX] = WSA_INVALID_EVENT; m_ahEvents[EXITTHREAD_INDEX] = WSA_INVALID_EVENT; if ( ( m_ahEvents[NEEDUPDATE_INDEX] = WSACreateEvent() ) == WSA_INVALID_EVENT || ( m_ahEvents[CANUPDATE_INDEX] = WSACreateEvent() ) == WSA_INVALID_EVENT || ( m_ahEvents[HAVEUPDATED_INDEX] = WSACreateEvent() ) == WSA_INVALID_EVENT || ( m_ahEvents[EXITTHREAD_INDEX] = WSACreateEvent() ) == WSA_INVALID_EVENT ) { m_dwErr = WSAGetLastError(); DBGWARN((DBG_CONTEXT, "WSACreateEvent failed : 0x%x\n", m_dwErr)); return; } m_dwNumEvents = 4; // // Create the watching thread // m_hWatchThread = CreateThread( NULL, 0, AcceptThreadFunc, this, 0, &m_dwThreadId ); if ( !m_hWatchThread ) { m_dwErr = GetLastError(); DBGERROR((DBG_CONTEXT, "Failed to create thread to watch for PASV accept events : 0x%x\n", m_dwErr)); } } PASV_ACCEPT_CONTEXT::~PASV_ACCEPT_CONTEXT() /*++ Destructor Arguments: None Returns: Nothing --*/ { // // Tell watch thread to shut down // if ( (m_ahEvents[EXITTHREAD_INDEX] != WSA_INVALID_EVENT) && !WSASetEvent( m_ahEvents[EXITTHREAD_INDEX] ) ) { DBGPRINTF((DBG_CONTEXT, "WSASetEvent failed : 0x%x\n", WSAGetLastError())); } // // wait for thread to shut down // if ( m_hWatchThread != NULL ) { if ( WaitForSingleObject( m_hWatchThread, INFINITE ) == WAIT_FAILED ) { DBGWARN((DBG_CONTEXT, "Waiting for thread shutdown failed : 0x%x\n", GetLastError())); } CloseHandle( m_hWatchThread ); } if ( m_ahEvents[NEEDUPDATE_INDEX] != WSA_INVALID_EVENT ) { WSACloseEvent( m_ahEvents[NEEDUPDATE_INDEX] ); } if ( m_ahEvents[CANUPDATE_INDEX] != WSA_INVALID_EVENT ) { WSACloseEvent( m_ahEvents[CANUPDATE_INDEX] ); } if ( m_ahEvents[HAVEUPDATED_INDEX] != WSA_INVALID_EVENT ) { WSACloseEvent( m_ahEvents[HAVEUPDATED_INDEX] ); } if ( m_ahEvents[EXITTHREAD_INDEX] != WSA_INVALID_EVENT ) { WSACloseEvent( m_ahEvents[EXITTHREAD_INDEX] ); } m_dwSignature = ACCEPT_CONTEXT_BAD_SIG; } BOOL PASV_ACCEPT_CONTEXT::RemoveAcceptEvent( IN WSAEVENT hEvent, IN USER_DATA *pUserData, OUT PBOOL pfFound ) /*++ Removes the event to be signalled when a socket is accept()'able Arguments: hEvent - event to be removed pUserData - USER_DATA attached to signalled event pfFound - BOOL set to TRUE if event was found, FALSE if not Returns: BOOL indicating success or failure --*/ { *pfFound = FALSE; DWORD dwIndex = 0; DWORD dwRet = ERROR_SUCCESS; DWORD dwWait = 0; BOOL fRet; // // Signal that we want to update the list of events // if ( !WSASetEvent( m_ahEvents[NEEDUPDATE_INDEX] ) ) { dwRet = WSAGetLastError(); DBGPRINTF((DBG_CONTEXT, "WSASetEvent failed : 0x%x\n", dwRet)); DBG_ASSERT( 0 ); // to catch the cause for 682348 return FALSE; } // // Wait until we can update the list // dwWait = WSAWaitForMultipleEvents( 1, &(m_ahEvents[CANUPDATE_INDEX]), TRUE, 900000, // 15 minutes should do it.... FALSE ); switch (dwWait) { case WSA_WAIT_EVENT_0: // // Look for the wanted event // for ( DWORD i = LASTPREALLOC_INDEX; i < m_dwNumEvents; i++ ) { if ( m_ahEvents[i] == hEvent ) { DBG_ASSERT( m_apUserData[i] == pUserData ); *pfFound = TRUE; dwIndex = i; break; } } // // if found, remove the data associated with the socket // if ( *pfFound ) { // // we're not going to use the context any more, so remove the reference we hold to it // m_apUserData[dwIndex]->DeReference(); memmove( (PVOID) (m_ahEvents + dwIndex), (PVOID) (m_ahEvents + (dwIndex + 1) ), sizeof(WSAEVENT) * (m_dwNumEvents - dwIndex - 1) ); memmove( (PVOID) ( m_apUserData + dwIndex ), (PVOID) (m_apUserData + (dwIndex + 1) ), sizeof(LPUSER_DATA) * (m_dwNumEvents - dwIndex - 1) ); memmove( (PVOID) (m_adwNumTimeouts + dwIndex), (PVOID) (m_adwNumTimeouts + (dwIndex + 1) ), sizeof(DWORD) * (m_dwNumEvents - dwIndex - 1) ); m_dwNumEvents--; } // // reset to known state and signal that watch thread can // start watching again. make sure both get executed to // avoid handing the watch thread // fRet = WSAResetEvent( m_ahEvents[CANUPDATE_INDEX] ); fRet = WSASetEvent( m_ahEvents[HAVEUPDATED_INDEX] ) && fRet; if ( !fRet ) { dwRet = WSAGetLastError(); DBGWARN((DBG_CONTEXT, "WSA[Set|Reset]Event failed : 0x%x\n", GetLastError())); } break; case WSA_WAIT_TIMEOUT: IF_DEBUG( PASV ) { DBGWARN((DBG_CONTEXT, "Wait timed out ... \n")); } dwRet = ERROR_TIMEOUT; DBG_ASSERT( 0 ); // to catch the cause for 682348 break; default: DBGERROR((DBG_CONTEXT, "Invalid return from WSAWaitForMultipleEvents : 0x%x\n", dwWait)); DBG_ASSERT( 0 ); // to catch the cause for 682348 dwRet = ERROR_INVALID_PARAMETER; } return ( dwRet == ERROR_SUCCESS ? TRUE : FALSE ); } DWORD PASV_ACCEPT_CONTEXT::AddAcceptEvent( WSAEVENT hEvent, LPUSER_DATA pUserData ) /*++ Adds an event to be signalled when a socket is accept()'able Arguments: hEvent - event that will be signalled pUserData - USER_DATA context to attach to signalled event Returns: Error code --*/ { DWORD dwRet = 0; DWORD dwWait = 0; BOOL fRet; if ( m_dwNumEvents == WSA_MAXIMUM_WAIT_EVENTS ) { return ERROR_INVALID_PARAMETER; } // // Signal that we want to update the list of events // if ( !WSASetEvent( m_ahEvents[NEEDUPDATE_INDEX] ) ) { DBGWARN((DBG_CONTEXT, "WSASetEvent failed : 0x%x\n", dwRet)); return WSAGetLastError(); } // // Wait until we can update the list // dwWait = WSAWaitForMultipleEvents( 1, &(m_ahEvents[CANUPDATE_INDEX]), TRUE, 10000, //10 secs seems like a reasonable time to wait FALSE ); switch (dwWait) { case WSA_WAIT_EVENT_0: // // cool, we can update the list // m_ahEvents[m_dwNumEvents] = hEvent; // // add a reference to make sure nobody deletes the context out from under us // pUserData->Reference(); m_apUserData[m_dwNumEvents] = pUserData; m_adwNumTimeouts[m_dwNumEvents] = 0; m_dwNumEvents++; // // reset to known state and signal that watch thread can // start watching again. make sure both get executed to // avoid handing the watch thread // fRet = WSAResetEvent( m_ahEvents[CANUPDATE_INDEX] ); fRet = WSASetEvent( m_ahEvents[HAVEUPDATED_INDEX] ) && fRet; if (!fRet) { pUserData->DeReference(); m_dwNumEvents--; //make sure event isn't still seen as valid dwRet = WSAGetLastError(); DBGWARN((DBG_CONTEXT, "WSA[Set|Reset]Event failed : 0x%x\n", GetLastError())); break; } IF_DEBUG ( PASV ) { DBGPRINTF((DBG_CONTEXT, "Added event for context 0x%x at index %d\n", pUserData, m_dwNumEvents - 1)); } break; case WSA_WAIT_TIMEOUT: IF_DEBUG( PASV ) { DBGWARN((DBG_CONTEXT, "Timed out waiting for permission to update PASV event list ... \n")); } dwRet = ERROR_TIMEOUT; break; default: DBGERROR((DBG_CONTEXT, "Invalid return from WSAWaitForMultipleEvents : 0x%x\n", dwWait)); dwRet = ERROR_INVALID_PARAMETER; } return dwRet; } ACCEPT_CONTEXT_ENTRY::ACCEPT_CONTEXT_ENTRY() : m_pAcceptContext( NULL) /*++ Constructor Arguments: None Returns: Nothing --*/ { if (InterlockedIncrement( &g_nAcceptContextEntries) > g_dwMaxAcceptContextEntries) { return; } m_pAcceptContext = new PASV_ACCEPT_CONTEXT(); if ( !m_pAcceptContext ) { DBGERROR((DBG_CONTEXT, "Failed to allocate new PASV_ACCEPT_CONTEXT !\n")); } } ACCEPT_CONTEXT_ENTRY::~ACCEPT_CONTEXT_ENTRY() /*++ Destructor Arguments: None Returns: Nothing --*/ { if ( m_pAcceptContext ) { DBG_ASSERT(m_pAcceptContext->QueryNumEvents() <= LASTPREALLOC_INDEX); delete m_pAcceptContext; } InterlockedDecrement( &g_nAcceptContextEntries); } DWORD CreateAcceptContext(PACCEPT_CONTEXT_ENTRY *ppEntry) /*++ Creates a PASV_ACCEPT_CONTEXT object and link it to head of list Arguments: None Returns: Error indicating success/failure --*/ { ACCEPT_CONTEXT_ENTRY *pEntry = NULL; DWORD dwRet = ERROR_SUCCESS; if ( !(pEntry = new ACCEPT_CONTEXT_ENTRY() ) ) { DBGERROR((DBG_CONTEXT, "Failed to allocate new ACCEPT_CONTEXT_ENTRY !\n")); return ERROR_OUTOFMEMORY; } if ( NULL == pEntry->m_pAcceptContext ) { DBGERROR((DBG_CONTEXT, "Failed to allocate new ACCEPT_CONTEXT_ENTRY::m_pAcceptContext !\n")); delete pEntry; return ERROR_OUTOFMEMORY; } if ( ( dwRet = pEntry->m_pAcceptContext->ErrorStatus() ) != ERROR_SUCCESS ) { DBGERROR((DBG_CONTEXT, "Error occurred constructing PASV_ACCEPT_CONTEXT : 0x%x\n", pEntry->m_pAcceptContext->ErrorStatus())); delete pEntry; return dwRet; } InsertHeadList( &g_AcceptContextList, &pEntry->ListEntry ); if (ppEntry) { *ppEntry = pEntry; } return dwRet; } VOID DeleteAcceptContexts() /*++ Deletes all of the PASV_ACCEPT_CONTEXT objects Arguments: None Returns: Nothing --*/ { while ( !IsListEmpty( &g_AcceptContextList ) ) { ACCEPT_CONTEXT_ENTRY *pEntry = CONTAINING_RECORD( g_AcceptContextList.Flink, ACCEPT_CONTEXT_ENTRY, ListEntry ); RemoveEntryList( &(pEntry->ListEntry) ); delete pEntry; } } DWORD AddAcceptEvent( WSAEVENT hEvent, LPUSER_DATA pUserData ) /*++ Adds an accept event to an available PASV_ACCEPT_CONTEXT Arguments: hEvent - handle to event to be added pUserData - USER_DATA context to attach to event Returns: Error code indicating success/failure --*/ { LIST_ENTRY *pEntry = NULL; PPASV_ACCEPT_CONTEXT pAcceptContext = NULL; PACCEPT_CONTEXT_ENTRY pContextEntry = NULL; DWORD dwRet = ERROR_SUCCESS; BOOL fFoundOne = FALSE; pUserData->FakeIOTimes = 0; // // Walk the list of contexts looking for one that can handle an additional // event. // // NB : currently [9/20/98], that list contains only a -single- context, so we can handle // up to a maximum of (WSA_MAXIMUM_WAIT_EVENTS - 4) events. If we want to get really // fancy, we could add the necessary code to create a new PASV_ACCEPT_CONTEXT as necessary, // but that also creates a new thread, and actually opens us up even more to a Denial Of // Service attack, which is partly what this code is trying to avoid // // RobSol Aug 2002: The above comment is no longer relevant, as I added support for multiple // containers. I'm leaning the comment here just for historical reasons. Support for multiple // containers is added for two reasons: // 1. support the test stress tool, that creates clients and requests concurrently, such that // we indeed exhaust one container // 2. to allow a hypothetical ISP with half a meg concurrent users to support multiple concurrent // clients performing file transfer stress // the number of containers is configurable, and the default is 1. LockAcceptContextList(); for ( pEntry = g_AcceptContextList.Flink; pEntry != &g_AcceptContextList; pEntry = pEntry->Flink ) { pContextEntry = CONTAINING_RECORD( pEntry, ACCEPT_CONTEXT_ENTRY , ListEntry ); pAcceptContext = pContextEntry->m_pAcceptContext; if ( pAcceptContext->QueryNumEvents() < WSA_MAXIMUM_WAIT_EVENTS ) { dwRet = pAcceptContext->AddAcceptEvent( hEvent, pUserData ); fFoundOne = TRUE; } if ( fFoundOne ) break; } if (!fFoundOne ) { dwRet = CreateAcceptContext(&pContextEntry); if (dwRet == ERROR_SUCCESS) { pAcceptContext = pContextEntry->m_pAcceptContext; DBG_ASSERT( pAcceptContext->QueryNumEvents() < WSA_MAXIMUM_WAIT_EVENTS ); dwRet = pAcceptContext->AddAcceptEvent( hEvent, pUserData ); } } UnlockAcceptContextList(); return dwRet; } BOOL RemoveAcceptEvent( WSAEVENT hEvent, LPUSER_DATA pUserData ) /*++ Removes an accept event from the appropriate PASV_ACCEPT_CONTEXT Arguments: hEvent - handle to event to be removed pUserData - USER_DATA context attached to event Returns: BOOL indicating success/failure --*/ { LIST_ENTRY *pEntry = NULL; PPASV_ACCEPT_CONTEXT pAcceptContext = NULL; PACCEPT_CONTEXT_ENTRY pContextEntry = NULL; BOOL fFound = FALSE; BOOL fSuccess = FALSE; // // Walk the list of contexts looking for the one that holds the event // event. // LockAcceptContextList(); for ( pEntry = g_AcceptContextList.Flink; pEntry != &g_AcceptContextList; pEntry = pEntry->Flink ) { pContextEntry = CONTAINING_RECORD( pEntry, ACCEPT_CONTEXT_ENTRY , ListEntry ); pAcceptContext = pContextEntry->m_pAcceptContext; fSuccess = pAcceptContext->RemoveAcceptEvent( hEvent, pUserData, &fFound ); if ( fFound ) { DBG_ASSERT( fSuccess ); // to catch the cause for 682348 UnlockAcceptContextList(); return fSuccess; } } UnlockAcceptContextList(); DBG_ASSERT( 0 ); // to catch the cause for 682348 return TRUE; } DWORD WINAPI PASV_ACCEPT_CONTEXT::AcceptThreadFunc( LPVOID pvContext ) /*++ Thread function for the thread that waits on the accept events Arguments: pvContext - context pointer (pointer to PASV_ACCEPT_CONTEXT object) Returns: Nothing useful --*/ { PPASV_ACCEPT_CONTEXT pAcceptContext = NULL; WSAEVENT *phAcceptEvents = NULL; DWORD dwNumEvents = 0; DWORD dwRet = 0; WSAEVENT *phNeedUpdate = NULL; WSAEVENT *phCanUpdate = NULL; WSAEVENT *phHaveUpdated = NULL; WSAEVENT *phExitThread = NULL; DBG_ASSERT( pvContext ); pAcceptContext = (PPASV_ACCEPT_CONTEXT) pvContext; phAcceptEvents = pAcceptContext->m_ahEvents; dwNumEvents = pAcceptContext->m_dwNumEvents; phNeedUpdate = &phAcceptEvents[NEEDUPDATE_INDEX]; phCanUpdate = &phAcceptEvents[CANUPDATE_INDEX]; phHaveUpdated = &phAcceptEvents[HAVEUPDATED_INDEX]; phExitThread = &phAcceptEvents[EXITTHREAD_INDEX]; loop: // wait timeout should be fairly small, so we can go through list and purge all // sockets that have been inactive a given # of timeouts dwRet = WSAWaitForMultipleEvents( dwNumEvents, phAcceptEvents, FALSE, PASV_TIMEOUT_INTERVAL, FALSE ); if ( dwRet <= (WSA_WAIT_EVENT_0 + dwNumEvents - 1) ) { // // One of the events was signalled // DWORD dwIndex = dwRet - WSA_WAIT_EVENT_0; switch (dwIndex) { case (NEEDUPDATE_INDEX): // // Somebody wants to update the list of events to watch for, so signal that // they can do so and wait for them to tell us they're done with the update // if ( !WSAResetEvent( *phNeedUpdate ) ) //back to known state { DBGWARN((DBG_CONTEXT, "WSAResetEvent failed : 0x%x\n", WSAGetLastError())); } if ( !WSASetEvent( *phCanUpdate ) ) { DBGWARN((DBG_CONTEXT, "WSASetEvent failed : 0x%x\n", WSAGetLastError())); } if ( WSAWaitForMultipleEvents( 1, phHaveUpdated, TRUE, INFINITE, FALSE ) == WSA_WAIT_FAILED ) { DBGERROR((DBG_CONTEXT, "WSAWaitForMultipleEvents failed : 0x%x\n", WSAGetLastError())); } if ( !WSAResetEvent( *phHaveUpdated ) ) //back to known state { DBGWARN((DBG_CONTEXT, "WSAResetEvent failed : 0x%x\n", WSAGetLastError())); } dwNumEvents = pAcceptContext->m_dwNumEvents; break; case (CANUPDATE_INDEX): case (HAVEUPDATED_INDEX): // // Should never happen ! // IF_DEBUG ( PASV ) { DBGERROR((DBG_CONTEXT, "Invalid event signalled !\n")); } break; case (EXITTHREAD_INDEX): // // We're all done // IF_DEBUG ( PASV ) { DBGPRINTF((DBG_CONTEXT, "Exiting thread watching for PASV events....\n")); } return 0; default: { LPUSER_DATA pUserData = NULL; // // One of the sockets has become accept()'able. // IF_DEBUG ( PASV ) { DBGPRINTF((DBG_CONTEXT, "Got an acceptable socket, index : %i, context : 0x%x\n", dwIndex, pAcceptContext->m_apUserData[dwIndex])); } pUserData = pAcceptContext->m_apUserData[dwIndex]; // // Remove the data associated with the socket // memmove( (PVOID) (pAcceptContext->m_ahEvents + dwIndex), (PVOID) (pAcceptContext->m_ahEvents + (dwIndex + 1) ), sizeof(WSAEVENT) * (dwNumEvents - dwIndex - 1) ); memmove( (PVOID) ( pAcceptContext->m_apUserData + dwIndex ), (PVOID) (pAcceptContext->m_apUserData + (dwIndex + 1) ), sizeof(LPUSER_DATA) * (dwNumEvents - dwIndex - 1) ); memmove( (PVOID) (pAcceptContext->m_adwNumTimeouts + dwIndex), (PVOID) (pAcceptContext->m_adwNumTimeouts + (dwIndex + 1) ), sizeof(DWORD) * (dwNumEvents - dwIndex - 1) ); pAcceptContext->m_dwNumEvents--; dwNumEvents = pAcceptContext->m_dwNumEvents; // // deal with restarting processing // SignalAcceptableSocket( pUserData ); } } } else if ( dwRet == WSA_WAIT_TIMEOUT ) { // // wait timed out, so go through the list of events and remove those that have // timed out too often // for ( DWORD i = LASTPREALLOC_INDEX;//skip the events that don't have a fixed # of timeouts i < dwNumEvents; i++ ) { if ( pAcceptContext->m_adwNumTimeouts[i] == MAX_PASV_TIMEOUTS ) { DBGPRINTF((DBG_CONTEXT, "timing out socket at index %i, context 0x%x \n", i, pAcceptContext->m_apUserData[i])); CleanupTimedOutSocketContext( pAcceptContext->m_apUserData[i] ); memmove( (PVOID) (pAcceptContext->m_ahEvents + i), (PVOID) (pAcceptContext->m_ahEvents + (i+1) ), sizeof(WSAEVENT) * (dwNumEvents - i - 1) ); memmove( (PVOID) ( pAcceptContext->m_apUserData + i ), (PVOID) (pAcceptContext->m_apUserData + (i+1) ), sizeof(LPUSER_DATA) * (dwNumEvents - i - 1) ); memmove( (PVOID) (pAcceptContext->m_adwNumTimeouts + i), (PVOID) (pAcceptContext->m_adwNumTimeouts + (i+1) ), sizeof(DWORD) * (dwNumEvents - i - 1) ); // // need to readjust the index and number of items in the array // i--; dwNumEvents--; pAcceptContext->m_dwNumEvents--; } else { pAcceptContext->m_adwNumTimeouts[i]++; } } dwNumEvents = pAcceptContext->m_dwNumEvents; } else if ( dwRet == WAIT_IO_COMPLETION ) { DBGWARN((DBG_CONTEXT, "Invalid value from WSAWaitForMultipleEvents !\n")); } else if ( dwRet == WSA_WAIT_FAILED ) { dwRet = WSAGetLastError(); DBG_ASSERT( dwRet == WSA_INVALID_HANDLE ); // // we could get this error during shutdown if one of the events is // invalidated before we get the exit signal. test for it and exit // gracefully // if ( WSAWaitForMultipleEvents(1, phExitThread, TRUE, 0, FALSE ) == WSA_WAIT_EVENT_0) { return 0; } DBG_ASSERT( 0 ); DBG_ASSERT( WSAGetLastError() ); } else { DBGERROR((DBG_CONTEXT, "WSAWaitForMultipleEvents returned 0x%x, error : 0x%x\n", dwRet, WSAGetLastError())); } goto loop; }