1456 lines
50 KiB
C++
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);
|
|
}
|
|
|
|
|