/*++ Copyright (c) 1998 Microsoft Corporation Module Name : pe_out.cxx Abstract: This module defines the outbound protocol event classes Author: Keith Lau (KeithLau) 6/18/98 Project: SMTP Server DLL Revision History: --*/ /************************************************************ * Include Headers ************************************************************/ #define INCL_INETSRV_INCS #include "smtpinc.h" // // ATL includes // #define _ATL_NO_DEBUG_CRT #define _ASSERTE _ASSERT #define _WINDLL #include "atlbase.h" extern CComModule _Module; #include "atlcom.h" #undef _WINDLL // // SEO includes // #include "seo.h" #include "seolib.h" #include #include "smtpcli.hxx" #include "smtpout.hxx" // // Dispatcher implementation // #include "pe_dispi.hxx" //-------------------------------------------------------------------------------- // Description: // Parses a response from the remote SMTP and appends in in m_cabResponse, // setting the numerical status code. Multi-line are also handled by this // function. // // Arguments: // IN char *InputLine - Start of response to be parsed // IN DWORD BufSize - Size of response // IN OUT char **NextInputLine - // OUT LPDWORD pRemainingBufSize - // IN DWORD UndecryptedTailSize - If TLS is used, count of undecrypted bytes // // Returns: // S_OK if the response was successfully parsed and appended to // m_cabResponse and the error code was extracted and set on // m_cabResponse. // // E_INVALIDARG if the response was badly formed and could not be parsed // according the to rules specified in the RFC 2821. In this case // m_cabResponse is not set, and the connection should be dropped // (Note - It should be dropped without issuing QUIT. Trying to be // "polite" by gracefully ending the session opens up an unneccessary // risk by making us parse further badly formed data). // // Some other error HRESULT if there was some other temporary error. //-------------------------------------------------------------------------------- HRESULT SMTP_CONNOUT::GetNextResponse( char *InputLine, DWORD BufSize, char **NextInputLine, LPDWORD pRemainingBufSize, DWORD UndecryptedTailSize ) { HRESULT hr = S_OK; char *Buffer = NULL; char *pszSearch = NULL; BOOL fFullLine = FALSE; DWORD IntermediateSize = 0; CHAR chSave = 0; TraceFunctEnterEx((LPARAM)this, "SMTP_CONNOUT::GetNextResponse"); _ASSERT(InputLine); if (!InputLine) return(E_POINTER); // Start at the beginning Buffer = InputLine; // We only process full lines (i.e. ends with CRLF) while (pszSearch = IsLineComplete(Buffer, BufSize)) { // Calculate the size of the line IntermediateSize = (DWORD)((pszSearch - Buffer) + 2); //+2 for CRLF // // Make sure the response at least contains an error code = 3 digits + CRLF // If the response is syntactically invalid we should not fire the response // event when this routine returns, which would cause the response handler // to try and process potentially bad data. Instead, as soon as it is detected // that the response is badly formed, we will drop the connection by returning // and error to GlueDispatch. // if(IntermediateSize < 5 || !isdigit((UCHAR)InputLine[0]) || !isdigit((UCHAR)InputLine[1]) || !isdigit((UCHAR)InputLine[2])) { // Unparseable response chSave = *pszSearch; *pszSearch = '\0'; // Null terminate InputLine for ErrorTrace output ErrorTrace((LPARAM)this, "Cannot parse illegal response from remote SMTP: %s", InputLine); *pszSearch = chSave; TraceFunctLeaveEx((LPARAM)this); return E_INVALIDARG; } if(IntermediateSize == 5) { //We have a 250CRLF type response - which according to //Drums draft is now legit // Get the error code Buffer[3] = '\0'; m_ResponseContext.m_dwSmtpStatus = atoi(Buffer); Buffer [3] = CR; fFullLine = TRUE; } else { // If we encounter a multi-line response, we will keep // parsing if (Buffer[3] == '-') { // Try to append the line, this can only fail due // to out of memory hr = m_ResponseContext.m_cabResponse.Append( Buffer, IntermediateSize, NULL); if (FAILED(hr)) return(hr); // Go to the next line Buffer += IntermediateSize; BufSize -= IntermediateSize; continue; } if (Buffer[3] == ' ') { // Get the error code Buffer[3] = '\0'; m_ResponseContext.m_dwSmtpStatus = atoi(Buffer); Buffer [3] = ' '; fFullLine = TRUE; } else { // // If the response is syntactically illegal, drop the connection // immediately by returning E_INVALIDARG to GlueDispatch. We do // not try to further process badly formed data. // chSave = *pszSearch; *pszSearch = '\0'; // Null terminate InputLine for ErrorTrace output ErrorTrace((LPARAM)this, "Cannot parse illegal response from remote SMTP: %s", InputLine); *pszSearch = chSave; TraceFunctLeaveEx((LPARAM)this); return E_INVALIDARG; } } // Try to append the line, this can only fail due // to out of memory hr = m_ResponseContext.m_cabResponse.Append( Buffer, IntermediateSize, NULL); if (FAILED(hr)) return(hr); // Adjust the counters Buffer += IntermediateSize; BufSize -= IntermediateSize; // If we have a full response (single or multi-line) if (fFullLine) break; } if (fFullLine) { char cTerm = '\0'; // NULL-temrinate it hr = m_ResponseContext.m_cabResponse.Append(&cTerm, 1, NULL); if (FAILED(hr)) return(hr); else { // We got a full response. Mark the pointers for // the next response if (NextInputLine) *NextInputLine = Buffer; if (pRemainingBufSize) *pRemainingBufSize = BufSize; } } else { // We have either a partial or empty line, either case // we need another completion for more data hr = S_FALSE; MoveMemory((void *)QueryMRcvBuffer(), Buffer, BufSize + UndecryptedTailSize); m_cbParsable = BufSize; m_cbReceived = BufSize + UndecryptedTailSize; } TraceFunctLeaveEx((LPARAM)this); return(hr); } HRESULT SMTP_CONNOUT::BuildCommandQEntry( LPOUTBOUND_COMMAND_Q_ENTRY *ppEntry, BOOL *pfUseNative ) { // Build a command entry DWORD dwKeywordLength = 0; DWORD dwTotalLength = 0; LPSTR szTemp; LPSTR pTemp = NULL; LPOUTBOUND_COMMAND_Q_ENTRY pEntry; BOOL fUseNative = FALSE; if (!ppEntry) return(E_POINTER); *ppEntry = NULL; // Determine which response to use szTemp = m_OutboundContext.m_cabCommand.Buffer(); if (!*szTemp) { szTemp = m_OutboundContext.m_cabNativeCommand.Buffer(); dwTotalLength = m_OutboundContext.m_cabNativeCommand.Length(); fUseNative = TRUE; // If we have nothing in the buffer, we will return S_FALSE if (!*szTemp) return(S_FALSE); } else dwTotalLength = m_OutboundContext.m_cabCommand.Length(); _ASSERT(strlen(szTemp) == dwTotalLength-1); // Determine the length of the keyword while ((*szTemp != ' ') && (*szTemp != '\0') && (*szTemp != '\r')) { szTemp++; dwKeywordLength++; } dwKeywordLength++; // Determine the total required buffer size //NK** add a byte at end to hold the null dwTotalLength += (sizeof(OUTBOUND_COMMAND_Q_ENTRY) + dwKeywordLength + 1); pTemp = new CHAR [dwTotalLength]; if (!pTemp) return(E_OUTOFMEMORY); pEntry = (LPOUTBOUND_COMMAND_Q_ENTRY)pTemp; pTemp += sizeof(OUTBOUND_COMMAND_Q_ENTRY); pEntry->dwFlags = 0; // Set the pipelined flag if (m_OutboundContext.m_dwCommandStatus & EXPE_PIPELINED) pEntry->dwFlags |= PECQ_PIPELINED; // Copy the command keyword if (fUseNative) szTemp = m_OutboundContext.m_cabNativeCommand.Buffer(); else szTemp = m_OutboundContext.m_cabCommand.Buffer(); pEntry->pszCommandKeyword = (LPSTR)pTemp; if (dwKeywordLength > 1) { LPSTR pszTemp = szTemp; while (--dwKeywordLength) *pTemp++ = (CHAR)tolower(*pszTemp++); } *pTemp++ = '\0'; // Copy the full command pEntry->pszFullCommand = (LPSTR)pTemp; lstrcpy((LPSTR)pTemp, (LPSTR)szTemp); *ppEntry = pEntry; *pfUseNative = fUseNative; return(S_OK); } /////////////////////////////////////////////////////////////////// // // This function should only be called from GlueDispatch // // It micro-manages sink firing scenarios HRESULT SMTP_CONNOUT::OnOutboundCommandEvent( IUnknown *pServer, IUnknown *pSession, DWORD dwEventType, BOOL fRepeatLastCommand, PMFI pDefaultOutboundHandler ) { HRESULT hr = S_OK; BOOL fResult = TRUE; BOOL fFireEvent = TRUE; BOOL fFireDefaultHandler = FALSE; ISmtpOutCommandContext *pContext = (ISmtpOutCommandContext *) &m_OutboundContext; // d:\ex\staxpt\src\mail\smtp\server\pe_out.cxx(265) : fatal error C1001: INTERNAL COMPILER ERROR // (compiler file 'E:\utc\src\\P2\main.c', line 379) // Please choose the Technical Support command on the Visual C++ // Help menu, or open the Technical Support help file for more information //_ASSERT(pDefaultOutboundHandler); TraceFunctEnterEx((LPARAM) this, "SMTP_CONNOUT::OnOutboundCommandEvent"); // First, we want to make sure that both the command and response // dispatchers are not NULL. If they are NULL, then we just call the // default handler, if any. if (m_pOutboundDispatcher && m_pResponseDispatcher) { // Now, we take a peek at the bindings and see if we have // any sinks installed for this event type hr = m_pOutboundDispatcher->SinksInstalled(dwEventType); if (hr != S_OK) { fFireEvent = FALSE; DebugTrace((LPARAM)this, "There are no sink bindings for this event type"); } } else { fFireEvent = FALSE; DebugTrace((LPARAM)this, "Sinks will not be fired because dispatcher NULL"); } // Fire the event accordingly // The outbound event is different from the other protocol events // For this event, we really don't know when the default handler // should be fired. There are two scenarios when we know to fire // the default handler: // // 1) We call the next set of sinks. If the ChainSinks call returns // S_OK and pfFireDefaultHandler returns TRUE, then we will fire // the default handler // 2) If we are not able to fire sinks due to a missing dispatcher, // we will fire the default handler (if there is one), and call // it done. LPPE_COMMAND_NODE pCommandNode = NULL; LPPE_BINDING_NODE pBindingNode = NULL; if (fFireEvent) { // Set it up for the dispatcher pBindingNode = NULL; pCommandNode = m_OutboundContext.m_pCurrentCommandContext; // Now, if we are asked to repeat the last command, we will have to // set up the binding node if (fRepeatLastCommand) { _ASSERT(m_OutboundContext.m_pCurrentCommandContext); pBindingNode = m_OutboundContext.m_pCurrentCommandContext->pFirstBinding; } hr = m_pOutboundDispatcher->ChainSinks( pServer, pSession, m_pIMsg, pContext, dwEventType, &pCommandNode, &pBindingNode ); // If the event is consumed or if there are no more bindgins // left, we will jump to analyze the status codes. if ((hr == S_FALSE) || (hr == EXPE_S_CONSUMED)) goto Analysis; if (hr == HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS)) goto Analysis; if (FAILED(hr)) goto Abort; // See if we are asked to fire the default handler if (pBindingNode) { // We must have aborted due to default handler, verify this // Also verify that we have a default handler ... _ASSERT(pBindingNode->dwFlags & PEBN_DEFAULT); // The following line results in fatal error C1001: INTERNAL COMPILER ERROR //_ASSERT(pDefaultOutboundHandler); fFireDefaultHandler = TRUE; fResult = (this->*pDefaultOutboundHandler)(NULL, 0, 0); if(!fResult) m_OutboundContext.m_dwCommandStatus = EXPE_DROP_SESSION; // Call the dispatcher to process any remaining sinks pBindingNode = pBindingNode->pNext; if (pBindingNode) { fFireDefaultHandler = FALSE; hr = m_pOutboundDispatcher->ChainSinks( pServer, pSession, m_pIMsg, pContext, dwEventType, &pCommandNode, &pBindingNode ); } } } else if (pDefaultOutboundHandler) { // If we already fired the default handler, unless we are // asked to repeat, we are plain done if (!m_fNativeHandlerFired || fRepeatLastCommand) { fFireDefaultHandler = TRUE; fResult = (this->*pDefaultOutboundHandler)(NULL, 0, 0); if(!fResult) m_OutboundContext.m_dwCommandStatus = EXPE_DROP_SESSION; m_fNativeHandlerFired = TRUE; } else { // We are done ... hr = HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS); m_fNativeHandlerFired = FALSE; m_OutboundContext.m_dwCommandStatus = EXPE_SUCCESS; } } else { // No sinks and no default handler? We're done. hr = HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS); m_fNativeHandlerFired = FALSE; m_OutboundContext.m_dwCommandStatus = EXPE_SUCCESS; } Analysis: // Update the context values m_OutboundContext.m_pCurrentCommandContext = pCommandNode; m_OutboundContext.m_pCurrentBinding = pBindingNode; if (hr == HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS)) { // Reset the context values _ASSERT(!m_OutboundContext.m_pCurrentCommandContext); _ASSERT(!m_OutboundContext.m_pCurrentBinding); m_OutboundContext.m_dwCommandStatus = EXPE_SUCCESS; } else { // If we don't have a status code, something is wrong. We will // assert in debug and drop the connection in reality. // // This is possible when misbehaving high-priority sinks consume // the event without setting the status code. if (m_OutboundContext.m_dwCommandStatus == EXPE_UNHANDLED) { ErrorTrace((LPARAM)this, "The outbound status code is undefined!"); m_OutboundContext.m_dwCommandStatus = EXPE_DROP_SESSION; } } Abort: // Set a diagnostic if a sink caused the connection to be dropped if(!fFireDefaultHandler && m_OutboundContext.m_dwCommandStatus == EXPE_DROP_SESSION) { CHAR szCommand[128] = ""; CHAR *pszCommand = szCommand; DWORD cbCommand = sizeof(szCommand); if(FAILED(m_OutboundContext.QueryCommandKeyword(szCommand, &cbCommand))) pszCommand = NULL; SetDiagnosticInfo( AQUEUE_E_SINK_DROPPED_CONNECTION, pszCommand, NULL); } TraceFunctLeaveEx((LPARAM)this); return(hr); } HRESULT SMTP_CONNOUT::OnServerResponseEvent( IUnknown *pServer, IUnknown *pSession, PMFI pDefaultResponseHandler ) { HRESULT hr = S_OK; BOOL fResult = TRUE; BOOL fFireEvent = TRUE; BOOL fFireDefaultHandler = FALSE; LPPE_COMMAND_NODE pCommandNode = NULL; LPPE_BINDING_NODE pBindingNode = NULL; ISmtpServerResponseContext *pContext = (ISmtpServerResponseContext *) &m_ResponseContext; _ASSERT(m_ResponseContext.m_pCommand); TraceFunctEnterEx((LPARAM) this, "SMTP_CONNOUT::OnServerResponseEvent"); // First, we want to make sure that both the command and response // dispatchers are not NULL. If they are NULL, then we just call the // default handler, if any. if (m_pOutboundDispatcher && m_pResponseDispatcher) { // Now, we take a peek at the bindings and see if we have // any sinks installed for this command hr = m_pResponseDispatcher->SinksInstalled( m_ResponseContext.m_pCommand->pszCommandKeyword, &pCommandNode); if (hr != S_OK) { fFireEvent = FALSE; DebugTrace((LPARAM)this, "There are no sink bindings for this command"); } } else { fFireEvent = FALSE; DebugTrace((LPARAM)this, "Sinks will not be fired because dispatcher NULL"); } if (fFireEvent) { _ASSERT(pCommandNode); pBindingNode = pCommandNode->pFirstBinding; _ASSERT(pBindingNode); } // Fire the event accordingly hr = S_OK; if (pDefaultResponseHandler) { DebugTrace((LPARAM)this, "Calling sinks for native command <%s>", m_ResponseContext.m_pCommand->pszCommandKeyword); if (fFireEvent) { hr = m_pResponseDispatcher->ChainSinks( pServer, pSession, m_pIMsg, pContext, PRIO_DEFAULT, pCommandNode, &pBindingNode ); if ((hr == S_FALSE) || (hr == EXPE_S_CONSUMED)) goto Analysis; if (FAILED(hr)) goto Abort; } fFireDefaultHandler = TRUE; fResult = (this->*pDefaultResponseHandler)( m_ResponseContext.m_cabResponse.Buffer(), m_ResponseContext.m_cabResponse.Length(), 0); if(!fResult) m_ResponseContext.m_dwResponseStatus = EXPE_DROP_SESSION; if (fFireEvent && pBindingNode) fFireDefaultHandler = FALSE; hr = m_pResponseDispatcher->ChainSinks( pServer, pSession, m_pIMsg, pContext, PRIO_LOWEST, pCommandNode, &pBindingNode ); } else { DebugTrace((LPARAM)this, "Calling sinks for non-native command <%s>", m_ResponseContext.m_pCommand->pszCommandKeyword); if (fFireEvent) { hr = m_pResponseDispatcher->ChainSinks( pServer, pSession, m_pIMsg, pContext, PRIO_LOWEST, pCommandNode, &pBindingNode ); // Fall back to "*" sinks if ((hr == S_OK) && (m_ResponseContext.m_dwResponseStatus == EXPE_UNHANDLED)) { hr = m_pResponseDispatcher->SinksInstalled( "*", &pCommandNode); if (hr == S_OK) { _ASSERT(pCommandNode); pBindingNode = pCommandNode->pFirstBinding; _ASSERT(pBindingNode); hr = m_pResponseDispatcher->ChainSinks( pServer, pSession, m_pIMsg, pContext, PRIO_LOWEST, pCommandNode, &pBindingNode ); } else hr = S_OK; } } } Analysis: // If no sinks has handled the response, we will invoke // a default handler. This will fail and drop the connection if // the SMTP response code is anything other than a complete success. if (m_ResponseContext.m_dwResponseStatus == EXPE_UNHANDLED) { if (!IsSmtpCompleteSuccess(m_ResponseContext.m_dwSmtpStatus)) m_ResponseContext.m_dwResponseStatus = EXPE_DROP_SESSION; else m_ResponseContext.m_dwResponseStatus = EXPE_SUCCESS; hr = S_OK; } Abort: // Set a diagnostic if a sink caused the connection to be dropped if(!fFireDefaultHandler && m_ResponseContext.m_dwResponseStatus == EXPE_DROP_SESSION) { CHAR szCommand[128] = ""; CHAR *pszCommand = szCommand; DWORD cbCommand = sizeof(szCommand); if(FAILED(m_ResponseContext.QueryCommandKeyword(szCommand, &cbCommand))) pszCommand = NULL; SetDiagnosticInfo( AQUEUE_E_SINK_DROPPED_CONNECTION, pszCommand, NULL); } TraceFunctLeaveEx((LPARAM)this); return(hr); } #define MAX_OUTSTANDING_COMMANDS 500 BOOL SMTP_CONNOUT::GlueDispatch( const char *InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize, DWORD dwOutboundEventType, PMFI pDefaultOutboundHandler, PMFI pDefaultResponseHandler, LPSTR szDefaultResponseHandlerKeyword, BOOL *pfDoneWithEvent, BOOL *pfAbortEvent ) { HRESULT hr; BOOL fResult = TRUE; BOOL fRepeatLastCommand = FALSE; BOOL fSendBinaryBlob = FALSE; DWORD dwBuffer = 0; BOOL fStateChange = FALSE; BOOL fResponsesHandled = FALSE; PMFI pfnNextState = NULL; char chErrorPrefix = SMTP_COMPLETE_SUCCESS; TraceFunctEnterEx((LPARAM) this, "SMTP_CONNOUT::GlueDispatch"); _ASSERT(m_pInstance); _ASSERT(m_pIEventRouter); m_ResponseContext.m_pCommand = NULL; // Check args. if (!InputLine || !pfDoneWithEvent || !pfAbortEvent || !m_pInstance || !m_pIEventRouter) { ErrorTrace((LPARAM) this, "NULL Pointer"); TraceFunctLeaveEx((LPARAM)this); SetLastError(E_POINTER); return(FALSE); } if (dwOutboundEventType >= PE_OET_INVALID_EVENT_TYPE) { ErrorTrace((LPARAM) this, "Skipping event because of bad outbound event type"); TraceFunctLeaveEx((LPARAM)this); return(TRUE); } // By default we are still going *pfDoneWithEvent = FALSE; *pfAbortEvent = FALSE; // Get the dispatcher if we don't already have it ... if (!m_pOutboundDispatcher) { hr = m_pIEventRouter->GetDispatcherByClassFactory( CLSID_COutboundDispatcher, &g_cfOutbound, *(COutboundDispatcher::s_rgrguidEventTypes[dwOutboundEventType]), IID_ISmtpOutboundCommandDispatcher, (IUnknown **)&m_pOutboundDispatcher); if(!SUCCEEDED(hr)) { // If we fail, we will not fire protocol events m_pOutboundDispatcher = NULL; ErrorTrace((LPARAM) this, "Unable to get outbound dispatcher from router (%08x)", hr); } } // // We make sure we will not send out new commands until all our // queued commands' responses are received and processed. // if (InputLine) { // Handle responses first, followed by generating commands char *pResponses = (char *)InputLine; DWORD dwRemaining = ParameterSize; while (!m_FifoQ.IsEmpty()) { // Parse out the next response hr = GetNextResponse( pResponses, dwRemaining, &pResponses, &dwRemaining, UndecryptedTailSize); if (hr == S_OK) { // // jstamerj 1998/10/30 12:37:28: // Set the correct next state in the response context // m_ResponseContext.m_dwNextState = CResponseDispatcher::PeStateFromOutboundEventType((OUTBOUND_EVENT_TYPES)dwOutboundEventType); // We have a full response, now dequeue the corresponding // command entry m_ResponseContext.m_pCommand = (LPOUTBOUND_COMMAND_Q_ENTRY)m_FifoQ.Dequeue(); // This should not be NULL at any time! _ASSERT(m_ResponseContext.m_pCommand); DebugTrace((LPARAM)this, "Processing Response <%s> for <%s>", m_ResponseContext.m_cabResponse.Buffer(), m_ResponseContext.m_pCommand->pszFullCommand); // All except for the last item in the queue must be pipelined if (!m_FifoQ.IsEmpty()) { _ASSERT((m_ResponseContext.m_pCommand->dwFlags & PECQ_PIPELINED) != 0); } // Call the event handler hr = OnServerResponseEvent( m_pInstance->GetInstancePropertyBag(), GetSessionPropertyBag(), (strcmp(m_ResponseContext.m_pCommand->pszCommandKeyword, szDefaultResponseHandlerKeyword) == 0)? pDefaultResponseHandler: NULL ); // We will ignore all responses to commands that are // pipelined. If the command that caused this response is // not pipelined, it will fall through this loop and be // handled downstream. fResponsesHandled = TRUE; // Delete the command entry ... LPBYTE pTemp = (LPBYTE)m_ResponseContext.m_pCommand; delete [] pTemp; pTemp = NULL; //Check if we really want to continue if( m_ResponseContext.m_dwResponseStatus == EXPE_TRANSIENT_FAILURE || m_ResponseContext.m_dwResponseStatus == EXPE_COMPLETE_FAILURE ) { //Free up the command list while(pTemp = (LPBYTE)m_FifoQ.Dequeue()) { delete [] pTemp; pTemp = NULL; } //break out of the command loop break; } // We are done with this response, and the FIFO is not empty, // we will reset the context if (!m_FifoQ.IsEmpty()) m_ResponseContext.ResetResponseContext(); } else if (hr == S_FALSE) { // This means that the whole buffer received is not long enough // to hold the next response. We will wait for the remainder. // This might take several completions if needed. // Make sure we don't reset the response context at this point // or we will lose the previous buffer. break; } else if (hr == E_INVALIDARG) { // // Syntactically invalid response from other side. We will NDR // message and end the session. We set the following on the response // context so that SMTP_CONNOUT::DoCompletedMessage knows what to // do: // // - m_cabReponse is set to some string indicating what error // occurred. This should really be the actual response from // the server, but since that is garbage we put in something // that conforms to SMTP syntax. This is for our benefit only, // since the m_cabResponse is parsed by many internal functions. // We want those functions to see only syntactically validated // strings, and weed out all errors here. // // - m_dwSmtpStatus = SMTP_SERVICE_CLOSE_CODE // To indicate that the connection should be closed and that // we shouldn't try to deliver any more messages on this // connection. There is no *correct* error code here, so we // have to pick some reasonable error code. // // - m_dwReponseStatus = EXPE_COMPLETE_FAILURE to indicate that the // all messages on this connection must be NDR'ed. // // Delete the command entry LPBYTE pTemp = (LPBYTE)m_ResponseContext.m_pCommand; delete [] pTemp; // Free up the command list while(pTemp = (LPBYTE)m_FifoQ.Dequeue()) { delete [] pTemp; pTemp = NULL; } m_ResponseContext.ResetResponseContext(); #define SMTP_INVALID_SYTAX_ERROR "500 Non RFC-compliant response received" hr = m_ResponseContext.m_cabResponse.Append( SMTP_INVALID_SYTAX_ERROR, sizeof(SMTP_INVALID_SYTAX_ERROR), NULL); if(FAILED(hr)) { // Out of memory. Nothing more we can do - drop connection abruptly TraceFunctLeaveEx((LPARAM)this); return FALSE; } m_ResponseContext.m_dwResponseStatus = EXPE_COMPLETE_FAILURE; m_ResponseContext.m_dwSmtpStatus = SMTP_SERVICE_CLOSE_CODE; m_OutboundContext.m_pCurrentCommandContext = NULL; ErrorTrace((LPARAM)this, "Syntax error in response. Message" " will be NDR'ed"); fResponsesHandled = TRUE; break; } else { // Some temporary error, such as out-of-memory. ErrorTrace((LPARAM)this, "Failed to GetNextResponse (%08x)", hr); return FALSE; } } } // If we have exhausted all responses and still our command queue // is not empty, we need to pend another read for more responses if (!m_FifoQ.IsEmpty()) { DebugTrace((LPARAM)this, "Pending a read for more response data"); TraceFunctLeaveEx((LPARAM)this); return(TRUE); } // Okay, we are now done all queued responses. Before we send out more // commands, we want to check if a sink wanted to change the state. If // so, we will honor this state change ... if (fResponsesHandled) { switch (m_ResponseContext.m_dwResponseStatus) { case EXPE_SUCCESS: break; case EXPE_TRANSIENT_FAILURE: chErrorPrefix = SMTP_TRANSIENT_FAILURE; pfnNextState = DoCompletedMessage; fStateChange = TRUE; break; case EXPE_COMPLETE_FAILURE: chErrorPrefix = SMTP_COMPLETE_FAILURE; pfnNextState = DoCompletedMessage; fStateChange = TRUE; break; case EXPE_REPEAT_COMMAND: fRepeatLastCommand = TRUE; break; case EXPE_DROP_SESSION: DisconnectClient(); *pfDoneWithEvent = TRUE; *pfAbortEvent = TRUE; fResult = FALSE; break; case EXPE_CHANGE_STATE: // Figure out which state pointer to jump to switch (m_ResponseContext.m_dwNextState) { case PE_STATE_SESSION_START: pfnNextState = DoSessionStartEvent; break; case PE_STATE_MESSAGE_START: pfnNextState = DoMessageStartEvent; break; case PE_STATE_PER_RECIPIENT: pfnNextState = DoPerRecipientEvent; break; case PE_STATE_DATA_OR_BDAT: pfnNextState = DoBeforeDataEvent; break; case PE_STATE_SESSION_END: pfnNextState = DoSessionEndEvent; break; default: ErrorTrace((LPARAM)this, "Bad next state %u, ignoring ...", m_ResponseContext.m_dwNextState); } if (pfnNextState) { *pfDoneWithEvent = TRUE; fStateChange = TRUE; } break; case EXPE_UNHANDLED: _ASSERT(FALSE); break; default: _ASSERT(FALSE); } } // OK, now we are clear to send the next command ... // If the result is already FALSE, we are going to disconnect. Then there // is no point generating more commands ... // // Also, if we are returning from some other state, we just leave if (fResult && !fStateChange) { //NK** : I moved this from outside if to inside. Need to do this to preserve the response in cases where we hit //TRANSIENT or permanent errorn // Reset the response context m_ResponseContext.ResetResponseContext(); //we need to save the first pipelined address so we can //start at this address and check the replies m_FirstPipelinedAddress = m_NextAddress; do { BOOL fUseNative = FALSE; // Reset the context m_OutboundContext.ResetOutboundContext(); // Catch here: set what the next address is so that other // sinks can at least have a clue who the current recipient is. // // jstamerj 1998/10/22 17:26:54: Sinks are really // interested in the recipient index in the mailmsg, not // the index into our private array. Give them that // instead. // if((dwOutboundEventType == PE_OET_PER_RECIPIENT) && (m_NextAddress < m_NumRcpts)) m_OutboundContext.m_dwCurrentRecipient = m_RcptIndexList[m_NextAddress]; // Raise the outbound event ... hr = OnOutboundCommandEvent( m_pInstance->GetInstancePropertyBag(), GetSessionPropertyBag(), dwOutboundEventType, fRepeatLastCommand, pDefaultOutboundHandler); fRepeatLastCommand = FALSE; // See if we are done with this event type if (hr == HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS)) { DebugTrace((LPARAM)this, "Done processing this event, leaving ..."); *pfDoneWithEvent = TRUE; break; } // See if the last command should be repeated if ((m_OutboundContext.m_dwCommandStatus != EXPE_UNHANDLED) && (m_OutboundContext.m_dwCommandStatus & EXPE_REPEAT_COMMAND)) fRepeatLastCommand = TRUE; // See if we're sending blob rather then the command if ((m_OutboundContext.m_dwCommandStatus != EXPE_UNHANDLED) && (m_OutboundContext.m_dwCommandStatus & EXPE_BLOB_READY)) fSendBinaryBlob = TRUE; // Handle the status codes, minus the repeat flag ... switch (m_OutboundContext.m_dwCommandStatus & (~EXPE_REPEAT_COMMAND) & (~EXPE_BLOB_READY)) { case EXPE_PIPELINED: // If the remote server does not support pipelineing, // we will honor that. if(!IsOptionSet(PIPELINE_OPTION) || !m_pInstance->ShouldPipeLineOut()) m_OutboundContext.m_dwCommandStatus = EXPE_NOT_PIPELINED; // Fall Thru ... case EXPE_SUCCESS: { LPOUTBOUND_COMMAND_Q_ENTRY pEntry = NULL; LPSTR pBuffer; // Build the entry hr = BuildCommandQEntry(&pEntry, &fUseNative); if (FAILED(hr)) { chErrorPrefix = SMTP_TRANSIENT_FAILURE; pfnNextState = DoCompletedMessage; fStateChange = TRUE; break; } if (hr == S_OK) { // Push the command into the FIFO queue if (!m_FifoQ.Enqueue((LPVOID)pEntry)) { // Since pEntry is never NULL here, we will always succeed _ASSERT(FALSE); } // Write out the command to the real buffer // If the buffer is not big enough, this will write and // flush multiple times until the command is sent pBuffer = (fUseNative)? m_OutboundContext.m_cabNativeCommand.Buffer(): m_OutboundContext.m_cabCommand.Buffer(); if ( fSendBinaryBlob) { FormatBinaryBlob( m_OutboundContext.m_pbBlob, m_OutboundContext.m_cbBlob); } else { //FormatSmtpMessage will use the first string //as a format string. Since we obviously have no //arguments, any %'s in pBuffer will cause sprintf //to AV. We must force FormatSmtpMessage to not use //pBuffer as a format string. FormatSmtpMessage(FSM_LOG_ALL, "%s", pBuffer); } } } break; case EXPE_TRANSIENT_FAILURE: chErrorPrefix = SMTP_TRANSIENT_FAILURE; pfnNextState = DoCompletedMessage; fStateChange = TRUE; break; case EXPE_COMPLETE_FAILURE: chErrorPrefix = SMTP_COMPLETE_FAILURE; pfnNextState = DoCompletedMessage; fStateChange = TRUE; break; case EXPE_DROP_SESSION: DisconnectClient(); *pfAbortEvent = TRUE; *pfDoneWithEvent = TRUE; fResult = FALSE; break; case EXPE_UNHANDLED: default: DisconnectClient(); *pfAbortEvent = TRUE; *pfDoneWithEvent = TRUE; fResult = FALSE; } } while (m_FifoQ.IsEmpty() || ((m_OutboundContext.m_dwCommandStatus & EXPE_PIPELINED) && m_FifoQ.Length() < MAX_OUTSTANDING_COMMANDS)); // OK, we can flush these commands ... fResult = SendSmtpResponse(); } // Make sure we free the outbound dispatcher before // exiting or jumping states. if (*pfDoneWithEvent || fStateChange) { if(fStateChange) m_fNativeHandlerFired = FALSE; // Release the dispatcher if (m_pOutboundDispatcher) { m_pOutboundDispatcher->Release(); m_pOutboundDispatcher = NULL; } if (pfnNextState) { // Call the next state handler *pfDoneWithEvent = TRUE; *pfAbortEvent = TRUE; // // jstamerj 1998/11/01 18:51:01: Since we're jumping to // another state, reset the variable that keeps track // of which command we're firing. // m_OutboundContext.m_pCurrentCommandContext = NULL; DebugTrace((LPARAM)this, "Jumping to another state"); fResult = (this->*pfnNextState)(&chErrorPrefix, 1, 0); } } TraceFunctLeaveEx((LPARAM)this); return(fResult); } BOOL SMTP_CONNOUT::DoSessionStartEvent( char *InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize ) { BOOL fResult = TRUE; BOOL fDoneEvent = FALSE; BOOL fAbortEvent = FALSE; TraceFunctEnterEx((LPARAM)this, "SMTP_CONNOUT::DoSessionStartEvent"); // Always set the state to itself SetNextState (&SMTP_CONNOUT::DoSessionStartEvent); // Call the catch-all handler fResult = GlueDispatch( InputLine, ParameterSize, UndecryptedTailSize, PE_OET_SESSION_START, &SMTP_CONNOUT::DoEHLOCommand, &SMTP_CONNOUT::DoEHLOResponse, m_HeloSent?"helo":"ehlo", &fDoneEvent, &fAbortEvent); if (fDoneEvent && !fAbortEvent) { if (m_TlsState == MUST_DO_TLS) { SetNextState(&SMTP_CONNOUT::DoSTARTTLSCommand); fResult = DoSTARTTLSCommand(InputLine, ParameterSize, UndecryptedTailSize); } else if (m_MsgOptions & KNOWN_AUTH_FLAGS) { fResult = DoSASLCommand(InputLine, ParameterSize, UndecryptedTailSize); } else if (m_MsgOptions & EMPTY_CONNECTION_OPTION) { fResult = DoSessionEndEvent(InputLine, ParameterSize, UndecryptedTailSize); } else { fResult = DoMessageStartEvent(InputLine, ParameterSize, UndecryptedTailSize); } } TraceFunctLeaveEx((LPARAM)this); return(fResult); } BOOL SMTP_CONNOUT::DoMessageStartEvent( char *InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize ) { BOOL fResult = TRUE; BOOL fDoneEvent = FALSE; BOOL fAbortEvent = FALSE; TraceFunctEnterEx((LPARAM)this, "SMTP_CONNOUT::DoMessageStartEvent"); //send an ETRN request if it defined // The ETRN respond will eventually come back to this state // again but with the ETRN_SENT option set so it's like a small loop if((m_MsgOptions & DOMAIN_INFO_SEND_ETRN) && IsOptionSet(ETRN_OPTION) && !IsOptionSet(ETRN_SENT)) { return DoETRNCommand(); } // check to see if this is an empty turn command // we need to go back to startsession to issue the TURN // if ((m_pIMsg == NULL) && (m_MsgOptions & DOMAIN_INFO_SEND_TURN)) { if (m_Flags & TURN_ONLY_OPTION) { _ASSERT((m_pIMsg == NULL) && (m_MsgOptions & DOMAIN_INFO_SEND_TURN)); return StartSession(); } // Always set the state to itself SetNextState (&SMTP_CONNOUT::DoMessageStartEvent); // Call the catch-all handler fResult = GlueDispatch( InputLine, ParameterSize, UndecryptedTailSize, PE_OET_MESSAGE_START, &SMTP_CONNOUT::DoMAILCommand, &SMTP_CONNOUT::DoMAILResponse, "mail", &fDoneEvent, &fAbortEvent); if (fDoneEvent && !fAbortEvent) { // Change to the next state if done ... //m_NextAddress = 0; fResult = DoPerRecipientEvent(InputLine, ParameterSize, UndecryptedTailSize); } return(fResult); } BOOL SMTP_CONNOUT::DoPerRecipientEvent( char *InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize ) { BOOL fResult = TRUE; BOOL fDoneEvent = FALSE; BOOL fAbortEvent = FALSE; TraceFunctEnterEx((LPARAM)this, "SMTP_CONNOUT::DoPerRecipientEvent"); // Always set the state to itself SetNextState (&SMTP_CONNOUT::DoPerRecipientEvent); DebugTrace((LPARAM)this, "Processing recipient index %u", m_NextAddress); // Call the catch-all handler fResult = GlueDispatch( InputLine, ParameterSize, UndecryptedTailSize, PE_OET_PER_RECIPIENT, &SMTP_CONNOUT::DoRCPTCommand, &SMTP_CONNOUT::DoRCPTResponse, "rcpt", &fDoneEvent, &fAbortEvent); if (fDoneEvent && !fAbortEvent) { // Change to the next state if done ... if(m_NumFailedAddrs >= m_NumRcptSentSaved) { TraceFunctLeaveEx((LPARAM)this); SetNextState (&SMTP_CONNOUT::WaitForRSETResponse); if(m_NumRcptSentSaved) m_RsetReasonCode = ALL_RCPTS_FAILED; else m_RsetReasonCode = NO_RCPTS_SENT; return DoRSETCommand(NULL, 0, 0); } // OK, fire the "before data" event fResult = DoBeforeDataEvent(InputLine, ParameterSize, UndecryptedTailSize); } return(fResult); } BOOL SMTP_CONNOUT::DoBeforeDataEvent( char *InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize ) { BOOL fResult = TRUE; BOOL fDoneEvent = FALSE; BOOL fAbortEvent = FALSE; TraceFunctEnterEx((LPARAM)this, "SMTP_CONNOUT::DoBeforeDataEvent"); // Always set the state to itself SetNextState (&SMTP_CONNOUT::DoBeforeDataEvent); // Call the catch-all handler fResult = GlueDispatch( InputLine, ParameterSize, UndecryptedTailSize, PE_OET_BEFORE_DATA, NULL, NULL, "", &fDoneEvent, &fAbortEvent); if (fDoneEvent && !fAbortEvent) { // Change to the next state if done ... if (m_fUseBDAT) fResult = DoBDATCommand(InputLine, ParameterSize, UndecryptedTailSize); else fResult = DoDATACommand(InputLine, ParameterSize, UndecryptedTailSize); } TraceFunctLeaveEx((LPARAM)this); return(fResult); } BOOL SMTP_CONNOUT::DoSessionEndEvent( char *InputLine, DWORD ParameterSize, DWORD UndecryptedTailSize ) { BOOL fResult = TRUE; BOOL fDoneEvent = FALSE; BOOL fAbortEvent = FALSE; TraceFunctEnterEx((LPARAM)this, "SMTP_CONNOUT::DoSessionEndEvent"); // Always set the state to itself SetNextState (&SMTP_CONNOUT::DoSessionEndEvent); // Call the catch-all handler fResult = GlueDispatch( InputLine, ParameterSize, UndecryptedTailSize, PE_OET_SESSION_END, &SMTP_CONNOUT::DoQUITCommand, &SMTP_CONNOUT::WaitForQuitResponse, "quit", &fDoneEvent, &fAbortEvent); // Either way we cut it, if we are done, we will delete the connection if (fDoneEvent) fResult = FALSE; TraceFunctLeaveEx((LPARAM)this); return(fResult); }