/**********************************************************************/ /** 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" 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 --*/ { 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; INITIALIZE_CRITICAL_SECTION( &m_csLock ); // // 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 --*/ { DWORD dwRet = 0; // // Tell watch thread to shut down // if ( !WSASetEvent( m_ahEvents[EXITTHREAD_INDEX] ) ) { DBGPRINTF((DBG_CONTEXT, "WSASetEvent failed : 0x%x\n", WSAGetLastError())); } // // wait for thread to shut down // if ( WaitForSingleObject( m_hWatchThread, INFINITE ) == WAIT_FAILED ) { DBGWARN((DBG_CONTEXT, "Waiting for thread shutdown failed : 0x%x\n", GetLastError())); } CloseHandle( m_hWatchThread ); DeleteCriticalSection( &m_csLock ); 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; // // 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; } } // // Didn't find the event, but function succeeded // if ( !*pfFound ) { return TRUE; } // // 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)); goto exit; } // // 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: { // // Remove the data associated with the socket // // // 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 // if ( !WSAResetEvent( m_ahEvents[CANUPDATE_INDEX] ) ) { dwRet = WSAGetLastError(); DBGWARN((DBG_CONTEXT, "WSAResetEvent failed : 0x%x\n", GetLastError())); goto exit; } // // signal that watch thread can start watching again // if ( !WSASetEvent( m_ahEvents[HAVEUPDATED_INDEX] ) ) { dwRet = WSAGetLastError(); DBGPRINTF((DBG_CONTEXT, "WSASetEvent failed : 0x%x\n", GetLastError())); goto exit; } } break; case WSA_WAIT_TIMEOUT: { IF_DEBUG( PASV ) { DBGWARN((DBG_CONTEXT, "Wait timed out ... \n")); } dwRet = ERROR_TIMEOUT; goto exit; } break; default: { DBGERROR((DBG_CONTEXT, "Invalid return from WSAWaitForMultipleEvents : 0x%x\n", dwWait)); dwRet = ERROR_INVALID_PARAMETER; } } exit: 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; 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] ) ) { dwRet = WSAGetLastError(); DBGWARN((DBG_CONTEXT, "WSASetEvent failed : 0x%x\n", dwRet)); goto exit; } // // 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 // if ( !WSAResetEvent( m_ahEvents[CANUPDATE_INDEX] ) ) { pUserData->DeReference(); m_dwNumEvents--; //make sure event isn't still seen as valid dwRet = WSAGetLastError(); DBGWARN((DBG_CONTEXT, "WSAResetEvent failed : 0x%x\n", GetLastError())); goto exit; } // // signal that watch thread can start watching again // if ( !WSASetEvent( m_ahEvents[HAVEUPDATED_INDEX] ) ) { pUserData->DeReference(); m_dwNumEvents--; //make sure event isn't still seen as valid dwRet = WSAGetLastError(); DBGWARN((DBG_CONTEXT, "WSASetEvent failed : 0x%x\n", GetLastError())); goto exit; } 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; goto exit; } break; default: { DBGERROR((DBG_CONTEXT, "Invalid return from WSAWaitForMultipleEvents : 0x%x\n", dwWait)); dwRet = ERROR_INVALID_PARAMETER; } } exit: return dwRet; } ACCEPT_CONTEXT_ENTRY::ACCEPT_CONTEXT_ENTRY() /*++ Constructor Arguments: None Returns: Nothing --*/ { 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 ) { delete m_pAcceptContext; } } DWORD CreateInitialAcceptContext() /*++ Creates the first PASV_ACCEPT_CONTEXT object 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 ); 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 // for ( pEntry = g_AcceptContextList.Flink; pEntry != &g_AcceptContextList; pEntry = pEntry->Flink ) { pContextEntry = CONTAINING_RECORD( pEntry, ACCEPT_CONTEXT_ENTRY , ListEntry ); pAcceptContext = pContextEntry->m_pAcceptContext; pAcceptContext->Lock(); if ( pAcceptContext->QueryNumEvents() < WSA_MAXIMUM_WAIT_EVENTS ) { dwRet = pAcceptContext->AddAcceptEvent( hEvent, pUserData ); fFoundOne = TRUE; } pAcceptContext->Unlock(); if ( fFoundOne ) break; } if (!fFoundOne ) { dwRet = ERROR_OUTOFMEMORY; //not quite the right error } 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; DWORD dwRet = ERROR_SUCCESS; BOOL fFound = FALSE; BOOL fSuccess = FALSE; // // Walk the list of contexts looking for the one that holds the event // event. // for ( pEntry = g_AcceptContextList.Flink; pEntry != &g_AcceptContextList; pEntry = pEntry->Flink ) { pContextEntry = CONTAINING_RECORD( pEntry, ACCEPT_CONTEXT_ENTRY , ListEntry ); pAcceptContext = pContextEntry->m_pAcceptContext; pAcceptContext->Lock(); fSuccess = pAcceptContext->RemoveAcceptEvent( hEvent, pUserData, &fFound ); if ( fFound ) { pAcceptContext->Unlock(); return fSuccess; } pAcceptContext->Unlock(); } 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; goto loop; } 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; } break; 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 ); goto loop; } } } 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; goto loop; } else if ( dwRet == WAIT_IO_COMPLETION ) { DBGWARN((DBG_CONTEXT, "Invalid value from WSAWaitForMultipleEvents !\n")); goto loop; } else { DBGERROR((DBG_CONTEXT, "WSAWaitForMultipleEvents returned 0x%x, error : 0x%x\n", dwRet, WSAGetLastError())); goto loop; } return 0; }