/*++ Copyright (c) 2000 Microsoft Corporation. All rights reserved. --*/ // EBFTPDataFilter.cpp : Implementation of CEBFTPDataFilter #include "stdafx.h" #include "exeblock.h" #include "EBFTPDataFilter.h" #include "DataFilterFactory.h" #include "trace.h" const char x_szAccessDeniedMessage[] = "550 Access is denied. (blocked by exeblock Application filter)\r\n"; const DWORD x_ulAccessDeniedMessageLen = sizeof(x_szAccessDeniedMessage) / sizeof(x_szAccessDeniedMessage[0]) - 1; ///////////////////////////////////////////////////////////////////////////// // CEBFTPDataFilter #define CHECK_HR_ABORT_IF_FAIL \ if (FAILED(hr)) { \ _Abort(); \ return hr; \ } /******* SetPassive **********************************/ // Called when got a "227 Entering pasive mode" reply to // PASV command. This function registers data filter factory // for CEBScannerDataFilter /*************************************************************/ HRESULT CEBFTPDataFilter::_SetPassive(IN_ADDR Address, USHORT Port) { HRESULT hr; sockaddr_in SecondaryCon; CComPtr ipClassFactory; CComObject* opDFilterFacory; CComPtr piProxyAction; //** create Data Filter Factory ***** hr = CComObject::CreateInstance(&opDFilterFacory); if(FAILED(hr)) { return hr; } opDFilterFacory->AddRef(); hr = opDFilterFacory->QueryInterface(IID_IClassFactory,reinterpret_cast(&ipClassFactory)); if(FAILED(hr)) { opDFilterFacory->Release(); return hr; } opDFilterFacory->Initialize(m_spCallBackInterface,this,m_piSession, m_PreparedData); opDFilterFacory->Release(); SecondaryCon.sin_addr = Address; SecondaryCon.sin_family=AF_INET; SecondaryCon.sin_port=Port; //Attach CEBScannerDataFilter, when client attempts //to make secondary connection hr=m_piSession->SetDataFilterFactory(fwx_Connect_Tcp, (sockaddr*) &SecondaryCon, sizeof(SecondaryCon), ipClassFactory, fwx_dfpc_External, (IUnknown*)NULL, NULL, &piProxyAction); if (FAILED(hr)) { DEBUGPRINT((_T("Error in _SetPassive:SetDataFilterFactory: hr= %x\n"), hr)); return hr; } return S_OK; } /******* SetPort ***************************************/ // Called when client sends 'PORT h1,h2,h3,h4,p1,p2' command. // This function registers data filter factory // for CEBScannerDataFilter /*************************************************************/ HRESULT CEBFTPDataFilter::_SetPort(IN_ADDR Address, USHORT Port) { HRESULT hr; sockaddr_in SecondaryCon; CComPtr ipClassFactory; CComObject* opDFilterFacory; CComPtr piProxyAction; //** create Data Filter Factory ***** hr = CComObject::CreateInstance(&opDFilterFacory); if(FAILED(hr)) return hr; opDFilterFacory->AddRef(); hr = opDFilterFacory->QueryInterface(IID_IClassFactory,reinterpret_cast(&ipClassFactory)); if(FAILED(hr)) { opDFilterFacory->Release(); return hr; } opDFilterFacory->Initialize(m_spCallBackInterface,this,m_piSession, m_PreparedData); opDFilterFacory->Release(); SecondaryCon.sin_addr = Address; SecondaryCon.sin_family=AF_INET; SecondaryCon.sin_port=Port; //Attach CEBScannerDataFilter, when client accepts //secondary connection hr=m_piSession->SetDataFilterFactory(fwx_AcceptedConnection, (sockaddr*) &SecondaryCon, sizeof(SecondaryCon), ipClassFactory, fwx_dfpc_External, (IUnknown*)NULL, NULL, &piProxyAction); if (FAILED(hr)) { DEBUGPRINT((_T("Error in _SetPort:SetDataFilterFactory: hr= %x\n"), hr)); return hr; } return S_OK; } ///////////////////////////////////////////////////////////////////////////// // ParseStringIntoAddress // // Help function - // Convert a string of the form x,y,z,w,u,v to the IP address x.y.z.w // and the Port # u*256+v // // Return: // zero if the string is not in the expected format, // non-zero if successful. ///////////////////////////////////////////////////////////////////////////// BOOL ParseStringIntoAddress( LPSTR pszString, LPIN_ADDR pinetAddr, PUSHORT pport ) /*++ Parse a comma-separated list of six decimal numbers into an IP address and a port number. The address and the port are in network byte order ( most significant bytes first). Arguments: pszString - string to be parsed. Should be of the form: dd,dd,dd,dd,dd,dd where 'dd' us the decimal representation of a byte (0-255) pinetAddr - will receive the IP Address pport - will receive the port. Returns: BOOL - TRUE if arguments are OK. FALSE if syntax error. --*/ { INT i; UCHAR chBytes[6]; UINT uSum; uSum = 0; i = 0; while( *pszString != '\0' && *pszString != '\r' && *pszString != '\n' && i < 6) { UCHAR chCurrent = (UCHAR)*pszString++; if( ( chCurrent >= '0' ) && ( chCurrent <= '9' ) ) { uSum = ( uSum * 10 ) + chCurrent - '0'; if (uSum > 0xff) // bad input { return FALSE; } } else if( chCurrent == ',' ) { chBytes[i++] = (UCHAR) uSum; // safe casting since uSum<256 uSum = 0; } else { return FALSE; } } if( i != 5 ) { return FALSE; } chBytes[i] = (UCHAR) uSum; pinetAddr->S_un.S_un_b.s_b1 = chBytes[0]; pinetAddr->S_un.S_un_b.s_b2 = chBytes[1]; pinetAddr->S_un.S_un_b.s_b3 = chBytes[2]; pinetAddr->S_un.S_un_b.s_b4 = chBytes[3]; *pport = (USHORT)( chBytes[4] + ( chBytes[5] << 8 ) ); return TRUE; } // ParseStringIntoAddress() ///////////////////////////////////////////////////////////////////////////// // CEBFTPDataFilter::_Abort // // Abort the control connection by closeing both sockets. // Called when something really bad happened and we just can't continue // processing this connection. ///////////////////////////////////////////////////////////////////////////// void CEBFTPDataFilter::_Abort() { _CloseSockets(TRUE); // Abortive shutdown } ///////////////////////////////////////////////////////////////////////////// // CEBFTPDataFilter::_CloseSockets // // Close both sockets. // ///////////////////////////////////////////////////////////////////////////// void CEBFTPDataFilter::_CloseSockets(BOOLEAN fAbortive) { IFWXSocket *pIFWXSocket; pIFWXSocket = _GetInternalSocket(); if (pIFWXSocket) { pIFWXSocket->Close(fAbortive); pIFWXSocket->Release(); } pIFWXSocket = _GetExternalSocket(); if (pIFWXSocket) { pIFWXSocket->Close(fAbortive); pIFWXSocket->Release(); } } ////////////////////////////////////////////////////////////////////////////// // CEBFTPDataFilter::SetSockets // Implements: IFWXDataFilter::SetSockets // // Keep a reference to the external and internal sockets. And begin the data // pump. // ////////////////////////////////////////////////////////////////////////////// STDMETHODIMP CEBFTPDataFilter::SetSockets( IN IFWXSocket *piInternalSocket, IN IFWXSocket *piExternalSocket, IN IFWXConnection *piConnection, IN IUnknown *punkFilterContext ) { UNREFERENCED_PARAMETER(punkFilterContext); UNREFERENCED_PARAMETER(piConnection); _ASSERTE(piExternalSocket && piInternalSocket); // // Keep reference to the internal and external sockets. // Lock(); m_spInternalSocket = piInternalSocket; m_spExternalSocket = piExternalSocket; Unlock(); // // Waiting for server response // "220 server ready for new user" // return _SetState(WaitForResponse); } ////////////////////////////////////////////////////////////////////////////// // CEBFTPDataFilter::Detach // Implements: IFWXDataFilter::Detach // // Release the reference held to the external and internal sockets. ////////////////////////////////////////////////////////////////////////////// STDMETHODIMP CEBFTPDataFilter::Detach() { // // Remove from the list of active FTP control channels // // // Dereference sockets. // Lock(); m_spExternalSocket = NULL; m_spInternalSocket = NULL; Unlock(); return S_OK; } ////////////////////////////////////////////////////////////////////////////// // CEBFTPDataFilter::_SetState // // Prepare to read next line from either the FTP server (NewState is // WaitForResponse) or from the FTP client (NewState is WaitForCommand). // ////////////////////////////////////////////////////////////////////////////// HRESULT CEBFTPDataFilter::_SetState(FilterState NewState) { IFWXSocket *pIFWXSocket = NULL; HRESULT hr; DWORD Dummy; switch (NewState) { case WaitForResponse: // // Prepare for reading an FTP response. // m_FilterState = NewState; m_Continuation = FALSE; m_Code = m_OriginalCode = 0; m_GotResponse = FALSE; m_Digits = 0; m_PartialResponseBytes = 0; pIFWXSocket = _GetExternalSocket(); break; case WaitForCommand: // // Prepare to read FTP command from the client. // _ASSERTE(m_spPartialUserCmdBuf == NULL || m_FilterState == WaitForCommand); // // Allocate a buffer that will accumulate packets until // a complete command is read. // // We get here with allocated buffer if we rejected a previous // command. if (m_spPartialUserCmdBuf == NULL) { hr = m_spCallBackInterface->CreateBuffer( MAX_LINE_LEN, &m_spPartialUserCmdBuf); CHECK_HR_ABORT_IF_FAIL; hr = m_spPartialUserCmdBuf->GetBufferAndSize( (PBYTE*)&m_PartialUserCmd, &Dummy); CHECK_HR_ABORT_IF_FAIL; } m_FilterState = NewState; m_PartialUserCmdBytes = 0; pIFWXSocket = _GetInternalSocket(); break; default: _ASSERTE(FALSE); return E_UNEXPECTED; } if (pIFWXSocket) { hr = pIFWXSocket->Recv(NULL, this, 0); pIFWXSocket->Release(); CHECK_HR_ABORT_IF_FAIL; } return S_OK; } ////////////////////////////////////////////////////////////////////////////// // CEBFTPDataFilter::CompleteAsyncIO // // Implements IFWXIOCompletion::CompleteAsyncIO // // Handle completion of Recv calls. // ////////////////////////////////////////////////////////////////////////////// HRESULT CEBFTPDataFilter::CompleteAsyncIO( BOOL fSuccess, DWORD Win32ErrorCode, IFWXIOBuffer *pIOBuffer, UserContextType UserData, PSOCKADDR From, INT FromLen ) { UNREFERENCED_PARAMETER(FromLen); UNREFERENCED_PARAMETER(From); UNREFERENCED_PARAMETER(Win32ErrorCode); UNREFERENCED_PARAMETER(UserData); DWORD DataLength = 0; unsigned char *pchCurrent = NULL; unsigned char c = '\0'; unsigned char *pchLast = NULL; IFWXSocket *pIFWXSocket; BOOL fSend = FALSE; BOOL fOverride; HRESULT hr = S_OK; CComPtr spResponseOverride; if (pIOBuffer) { hr = pIOBuffer->GetBufferAndSize((PBYTE*)&pchCurrent, &DataLength); CHECK_HR_ABORT_IF_FAIL; pchLast = pchCurrent + DataLength; } // // Process end of stream and errors // if (DataLength == 0 || !fSuccess ) { _CloseSockets(!fSuccess); return S_OK; } switch (m_FilterState) { case WaitForResponse: // // Process this buffer // ReadLine: /* Parsing reponse line. FTP responses are of type 214- The following commands.... In case of mulit-line responses the format is 214- The following commands.... ...... .... 214 Finaltext multi-line resposnes are managed by m_Continuation below. */ while (pchCurrent < pchLast && m_Digits < 4) { c = *pchCurrent++; m_Digits++; m_PartialResponse[m_PartialResponseBytes++] = c; if (m_Digits < 4) { if (isdigit(c)) { m_Code = m_Code * 10 + (c - '0'); } else { if (m_Continuation) { // Don't get confused by continuation m_Digits = 4; m_Code = 0; } else { // Illegal response - abort. _Abort(); } } } else { _ASSERTE(m_Digits == 4); if (c == '-') { m_Continuation = TRUE; } else if (c == ' ' && m_Code == m_OriginalCode) { m_Continuation = FALSE; } } } while (pchCurrent < pchLast) { c = *pchCurrent++; if (m_PartialResponseBytes == m_PartialResponseAllocated) { if (!_ExtendResponseBuffer()) { _Abort(); return E_OUTOFMEMORY; } } m_PartialResponse[m_PartialResponseBytes++] = c; if (c == '\n') { if (m_Continuation) { if (m_OriginalCode == 0) m_OriginalCode = m_Code; m_Code = m_Digits = 0; goto ReadLine; } else { m_GotResponse = TRUE; } } } if (m_GotResponse) { Lock(); fOverride = m_spResponseOverride != NULL; if (m_fScanningFile) { _ASSERTE(!m_fSkippedReply); m_fSkippedReply = TRUE; // Don't skip an error reply if (m_Code/100 == 4 || m_Code/100 == 5) { m_fScanningFile = FALSE; fOverride = FALSE; m_spResponseOverride = NULL; } } if (fOverride) { spResponseOverride = m_spResponseOverride; m_spResponseOverride = NULL; } fSend = !fOverride && !m_fScanningFile; Unlock(); if (fOverride) { pIFWXSocket = _GetInternalSocket(); if (pIFWXSocket) { hr = pIFWXSocket->Send(spResponseOverride, NULL, 0); pIFWXSocket->Release(); CHECK_HR_ABORT_IF_FAIL; } } else if (fSend) { _SendResponse(m_PartialResponse, m_PartialResponseBytes); } if (m_Code == 227) { // // 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2) // char *pchOpen = strchr(m_PartialResponse, '('); if (pchOpen != NULL) { char *pchClose=strchr(pchOpen, ')'); if (pchClose) { *pchClose = 0; pchOpen++; IN_ADDR Address; USHORT Port; if (ParseStringIntoAddress( pchOpen, &Address, &Port)) { _SetPassive(Address, Port); } } } } if ((m_Code == 150) || (m_Code == 125)) { // 150 Openning data connection... // 125 Connection already open _MarkScanningFile(); } // // Response 1xx means an intermediate response. We need to // wait for another one. // All other responses mean we need to wait for a client // command. // if (m_Code/100 == 1) { _SetState(WaitForResponse); if (pchCurrent < pchLast) goto ReadLine; } else { // If pchCurrent < pchLast, we ignore it !!! // Can this ever happen? _ASSERTE(pchCurrent == pchLast); _SetState(WaitForCommand); } } else { // Need to read continuation pIFWXSocket = _GetExternalSocket(); if (pIFWXSocket) { hr = pIFWXSocket->Recv(NULL, this, 0); pIFWXSocket->Release(); CHECK_HR_ABORT_IF_FAIL; } } break; case WaitForCommand: while (pchCurrent < pchLast) { c = *pchCurrent++; m_PartialUserCmd[m_PartialUserCmdBytes++] = c; if (c == '\n') { break; } if (m_PartialUserCmdBytes == MAX_LINE_LEN-1) { static char achTooLong[] = "500 ftp proxy filter reports: Line too long\r\n"; IFWXIOBuffer *cbuf; hr = m_spCallBackInterface->CreateConstBuffer( (PBYTE)achTooLong, sizeof(achTooLong) - 1, &cbuf); CHECK_HR_ABORT_IF_FAIL; pIFWXSocket = _GetInternalSocket(); if (pIFWXSocket) { hr = pIFWXSocket->Send(cbuf, NULL, 0); pIFWXSocket->Release(); cbuf->Release(); CHECK_HR_ABORT_IF_FAIL; } _SetState(WaitForCommand); return S_OK; } } m_PartialUserCmd[m_PartialUserCmdBytes] = 0; if (c == '\n') { if (_strnicmp("PORT ", m_PartialUserCmd, 5) == 0) { char *p = m_PartialUserCmd + 5; while (*p == ' ') p++; // Skip blanks; IN_ADDR Address; USHORT Port; if (ParseStringIntoAddress(p, &Address, &Port)) { _SetPort(Address, Port); } } // // Check if the command is file uploads command that caused the server // to accept data. The sample doesn't support upload command and // access denied responeis returned // if (IsUploadFileCommand()) { _SendResponse(x_szAccessDeniedMessage, x_ulAccessDeniedMessageLen); _SetState(WaitForCommand); return S_OK; } // Pass this command IFWXIOBuffer * pbuf = m_spPartialUserCmdBuf; pbuf->AddRef(); m_spPartialUserCmdBuf = NULL; hr = pbuf->SetDataSize(m_PartialUserCmdBytes); _ASSERTE(SUCCEEDED(hr)); if (FAILED(hr)) pbuf->Release(); CHECK_HR_ABORT_IF_FAIL; pIFWXSocket = _GetExternalSocket(); if (pIFWXSocket) { hr = pIFWXSocket->Send(pbuf, NULL, 0); pIFWXSocket->Release(); if (FAILED(hr)) pbuf->Release(); CHECK_HR_ABORT_IF_FAIL; } pbuf->Release(); _SetState(WaitForResponse); } else { // Need to read more to get the command. pIFWXSocket = _GetInternalSocket(); if (pIFWXSocket) { hr = pIFWXSocket->Recv(NULL, this, 0); pIFWXSocket->Release(); CHECK_HR_ABORT_IF_FAIL; } } break; default: return E_UNEXPECTED; } return S_OK; } ////////////////////////////////////////////////////////////////////////////// // CEBFTPDataFilter::_SendAccumulatedReply // // Send the response that was accumulated in m_PartialResponse. // // ////////////////////////////////////////////////////////////////////////////// void CEBFTPDataFilter::_SendResponse(LPCSTR response, DWORD size) { IFWXIOBuffer *iobuf; HRESULT hr = m_spCallBackInterface->CreateBuffer(size, &iobuf); if (FAILED(hr)) { _Abort(); return; } IFWXSocket *pIFWXSocket = _GetInternalSocket(); if (pIFWXSocket) { DWORD Dummy; hr = iobuf->Append(response, size, &Dummy); if (SUCCEEDED(hr)) { hr = pIFWXSocket->Send(iobuf, NULL, 0); } if (FAILED(hr)) { _Abort(); } pIFWXSocket->Release(); } iobuf->Release(); } ////////////////////////////////////////////////////////////////////////////// // CEBFTPDataFilter::OverrideReply // // Override the next server reply with the reply in the pReplyOverride // buffer. // // Called from the scanner data-filter. // ////////////////////////////////////////////////////////////////////////////// void CEBFTPDataFilter::OverrideReply( IFWXIOBuffer *pReplyOverride ) { BOOL fSendNow = FALSE; Lock(); if (m_fScanningFile && m_fSkippedReply) { fSendNow = TRUE; } else { m_spResponseOverride = pReplyOverride; } m_fScanningFile = FALSE; Unlock(); if (fSendNow) { // The 226 or 250 response was already skipped IFWXSocket *pIFWXSocket = _GetInternalSocket(); if (pIFWXSocket) { HRESULT hr = pIFWXSocket->Send(pReplyOverride, NULL, 0); pIFWXSocket->Release(); if (FAILED(hr)) _Abort(); } } } ///////////////////////////////////////////////////////////////////////////// // CEBFTPDataFilter::Initialize // // Initialization of the CEBFTPDataFilter class. Called by the // session filter object when creating the data-filter. ///////////////////////////////////////////////////////////////////////////// HRESULT CEBFTPDataFilter::Initialize( CComObject *pSessionFilter, IFWXFirewall *pCallback, CComPtr pisession, IFWXPreparedData *PreparedData ) { m_PartialResponseAllocated = MAX_LINE_LEN; m_PartialResponse = new char[m_PartialResponseAllocated]; if (m_PartialResponse == NULL) return E_OUTOFMEMORY; m_spCallBackInterface = pCallback; m_spSessionFilter = pSessionFilter; m_piSession = pisession; m_PreparedData = PreparedData; return S_OK; } ///////////////////////////////////////////////////////////////////////////// // CEBFTPDataFilter::_ExtendResponseBuffer // // Reallocate a larger buffer for the FTP server response. ///////////////////////////////////////////////////////////////////////////// BOOL CEBFTPDataFilter::_ExtendResponseBuffer() { DWORD NewSize = max(m_PartialResponseAllocated * 3 / 2, m_PartialResponseBytes) ; char *NewBuf = new char[NewSize]; if (NewBuf) { CopyMemory(NewBuf, m_PartialResponse, m_PartialResponseBytes); delete [] m_PartialResponse; m_PartialResponse = NewBuf; m_PartialResponseAllocated = NewSize; return TRUE; } else { return FALSE; } } ///////////////////////////////////////////////////////////////////////////// // CEBFTPDataFilter::~CEBFTPDataFilter // // Destructore for CEBFTPDataFilter. ///////////////////////////////////////////////////////////////////////////// CEBFTPDataFilter::~CEBFTPDataFilter() { delete [] m_PartialResponse; } // // The routine check if the command is uploads file command that causes // the server to accept the data. the uploads commands are:STOR, STOU, APPE // bool CEBFTPDataFilter::IsUploadFileCommand(void) const { return ((_strnicmp("STOR ", m_PartialUserCmd, 5) == 0) || (_strnicmp("STOU ", m_PartialUserCmd, 5) == 0) || (_strnicmp("APPE ", m_PartialUserCmd, 5) == 0)); }