Windows2003-3790/inetsrv/iis/svcs/smtp/server/pe_out.cxx
2020-09-30 16:53:55 +02:00

1456 lines
50 KiB
C++

/*++
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 <memory.h>
#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);
}