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

1048 lines
31 KiB
C++

/*++
Copyright (c) 1998 Microsoft Corporation
Module Name :
pe_dispi.cxx
Abstract:
This module provides the implementation for the protocol
event dispatchers
Author:
Keith Lau (KeithLau) 6/24/98
Project:
SMTP Server DLL
Revision History:
KeithLau Created
--*/
#define INCL_INETSRV_INCS
#include "smtpinc.h"
#include "initguid.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"
#include <smtpguid.h>
//
// Dispatcher implementation
//
#include "pe_dispi.hxx"
// =================================================================
// Outbound command generation dispatcher
//
//
// These are the commands that the default handlers hook
//
char *COutboundDispatcher::s_rgszDefaultCommand[PE_STATE_MAX_STATES] =
{
"ehlo",
"mail",
"rcpt",
NULL,
"quit"
};
//
// This is what we use to map an event type to an internal index
//
const GUID *COutboundDispatcher::s_rgrguidEventTypes[PE_STATE_MAX_STATES] =
{
&CATID_SMTP_ON_SESSION_START,
&CATID_SMTP_ON_MESSAGE_START,
&CATID_SMTP_ON_PER_RECIPIENT,
&CATID_SMTP_ON_BEFORE_DATA,
&CATID_SMTP_ON_SESSION_END
};
//
// Generic dispatcher methods
//
HRESULT CGenericProtoclEventDispatcher::GetLowerAnsiStringFromVariant(
CComVariant &vString,
LPSTR pszString,
DWORD *pdwLength
)
{
HRESULT hr = S_OK;
DWORD dwInLength;
if (!pszString)
return(E_POINTER);
if (!pdwLength)
return(E_INVALIDARG);
// Default to NULL
*pszString = NULL;
if (vString.vt == VT_BSTR)
{
DWORD dwLength = lstrlenW(vString.bstrVal) + 1;
// Set the size anyway
dwInLength = *pdwLength;
*pdwLength = dwLength;
if (dwLength > dwInLength)
return(HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER));
// copy the rule into an ascii string
if (WideCharToMultiByte(CP_ACP, 0, vString.bstrVal,
-1, pszString, dwLength, NULL, NULL) <= 0)
return(HRESULT_FROM_WIN32(GetLastError()));
// Convert to lower case once and for all to avoid strcmpi
while (*pszString)
{
*pszString = (CHAR)tolower(*pszString);
pszString++;
}
}
else
hr = E_INVALIDARG;
return(hr);
}
HRESULT CGenericProtoclEventDispatcher::InsertBinding(
LPPE_COMMAND_NODE *ppHeadNode,
LPPE_BINDING_NODE pBinding,
LPSTR pszCommandKeyword,
DWORD dwCommandKeywordSize
)
{
DWORD dwPriority;
BOOL fReorder = FALSE;
LPPE_COMMAND_NODE pNode;
TraceFunctEnter("CGenericProtoclEventDispatcher::InsertBinding");
if (!ppHeadNode || !pBinding || !pszCommandKeyword)
return(E_POINTER);
pNode = *ppHeadNode;
while (pNode)
{
// All strings come in lower case
if (!strcmp(pszCommandKeyword, pNode->pszCommandKeyword))
{
DebugTrace((LPARAM)0, "Found command %s", pszCommandKeyword);
// Now we determine if we have changed the max priority
// if we increased the priority (i.e. lower value), then
// we will have to reposition out node
dwPriority = pBinding->dwPriority;
if (pBinding->dwPriority < dwPriority)
{
pBinding->dwPriority = dwPriority;
fReorder = TRUE;
DebugTrace((LPARAM)0, "Reordering commands");
}
// Now insert the binding in the right order
LPPE_BINDING_NODE pWalk = pNode->pFirstBinding;
LPPE_BINDING_NODE pPrev = (LPPE_BINDING_NODE)&(pNode->pFirstBinding);
if (!pWalk)
{
pBinding->pNext = pNode->pFirstBinding;
pNode->pFirstBinding = pBinding;
}
else while (pWalk)
{
if (pWalk->dwPriority > dwPriority)
break;
pPrev = pWalk;
pWalk = pWalk->pNext;
}
pBinding->pNext = pWalk;
pPrev->pNext = pBinding;
if (!fReorder)
return(S_OK);
else
break;
}
// Next!
pNode = pNode->pNext;
}
if (!fReorder)
{
char *pTemp;
DWORD dwSize = sizeof(PE_COMMAND_NODE);
// Not found, create a new command node, plus a buffer
// for the command keyword
dwSize += dwCommandKeywordSize;
pTemp = new char [dwSize];
if (!pTemp)
return(E_OUTOFMEMORY);
pNode = (LPPE_COMMAND_NODE)pTemp;
pTemp = (char *)(pNode + 1);
DebugTrace((LPARAM)0,
"Creating node for command %s",
pszCommandKeyword);
strcpy(pTemp, pszCommandKeyword);
dwPriority = pBinding->dwPriority;
pBinding->pNext = NULL;
pNode->pszCommandKeyword = pTemp;
pNode->dwHighestPriority = dwPriority;
pNode->pFirstBinding = pBinding;
}
// Insert based on top priority
LPPE_COMMAND_NODE pWalk = *ppHeadNode;
LPPE_COMMAND_NODE pPrev = (LPPE_COMMAND_NODE)ppHeadNode;
if (!pWalk)
{
pNode->pNext = NULL;
*ppHeadNode = pNode;
return(S_OK);
}
while (pWalk)
{
if (pWalk->dwHighestPriority > dwPriority)
break;
pPrev = pWalk;
pWalk = pWalk->pNext;
}
pNode->pNext = pWalk;
pPrev->pNext = pNode;
TraceFunctLeave();
return(S_OK);
}
HRESULT CGenericProtoclEventDispatcher::InsertBindingWithHash(
LPPE_COMMAND_NODE *rgpHeadNodes,
DWORD dwHashSize,
LPPE_BINDING_NODE pBinding,
LPSTR pszCommandKeyword,
DWORD dwCommandKeywordSize
)
{
TraceFunctEnter("CGenericProtoclEventDispatcher::InsertBindingWithHash");
if (!rgpHeadNodes || !pBinding || !pszCommandKeyword)
return(E_POINTER);
DWORD dwHash = GetHashValue(
dwHashSize,
pszCommandKeyword,
dwCommandKeywordSize);
HRESULT hr = InsertBinding(
&(rgpHeadNodes[dwHash]),
pBinding,
pszCommandKeyword,
dwCommandKeywordSize);
TraceFunctLeave();
return(hr);
}
HRESULT CGenericProtoclEventDispatcher::FindCommandFromHash(
LPPE_COMMAND_NODE *rgpHeadNodes,
DWORD dwHashSize,
LPSTR pszCommandKeyword,
DWORD dwCommandKeywordSize,
LPPE_COMMAND_NODE *ppCommandNode
)
{
TraceFunctEnter("CGenericProtoclEventDispatcher::FindCommandFromHash");
if (!rgpHeadNodes || !pszCommandKeyword || !ppCommandNode)
return(E_POINTER);
DWORD dwHash = GetHashValue(
dwHashSize,
pszCommandKeyword,
dwCommandKeywordSize);
LPPE_COMMAND_NODE pNode = rgpHeadNodes[dwHash];
while (pNode)
{
if (!strcmp(pszCommandKeyword, pNode->pszCommandKeyword))
{
*ppCommandNode = pNode;
TraceFunctLeave();
return(S_OK);
}
pNode = pNode->pNext;
}
TraceFunctLeave();
return(S_FALSE);
}
HRESULT CGenericProtoclEventDispatcher::CleanupCommandNodes(
LPPE_COMMAND_NODE pHeadNode,
LPPE_COMMAND_NODE pSkipNode
)
{
LPPE_COMMAND_NODE pNode;
while (pHeadNode)
{
pNode = pHeadNode->pNext;
if (pHeadNode != pSkipNode)
{
char *pTemp = (char *)pHeadNode;
delete [] pTemp;
}
pHeadNode = pNode;
}
return(S_OK);
}
//
// Inbound dispatcher methods
//
HRESULT CInboundDispatcher::AllocBinding(
REFGUID rguidEventType,
IEventBinding *piBinding,
CBinding **ppNewBinding
)
{
if (ppNewBinding)
*ppNewBinding = NULL;
if (!piBinding || !ppNewBinding)
return(E_POINTER);
*ppNewBinding = new CInboundBinding(this);
if (*ppNewBinding == NULL)
return(E_OUTOFMEMORY);
return(S_OK);
}
HRESULT STDMETHODCALLTYPE CInboundDispatcher::ChainSinks(
IUnknown *pServer,
IUnknown *pSession,
IMailMsgProperties *pMsg,
ISmtpInCommandContext *pContext,
DWORD dwStopAtPriority,
LPPE_COMMAND_NODE pCommandNode,
LPPE_BINDING_NODE *ppResumeFrom
)
{
HRESULT hr = S_OK;
LPPE_BINDING_NODE pBinding = NULL;
TraceFunctEnterEx((LPARAM)this, "CInboundDispatcher::ChainSinks");
// These are the essential pointers that CANNOT be NULL
if (!pContext || !pCommandNode || !ppResumeFrom)
return(E_POINTER);
// What we do is strictly determined by the ppPreviousCommand
// and ppResumeFrom pointers. The logic is as follows:
//
// pCmdNode ppResumeFrom Action
// NULL X Error (E_POINTER)
// !NULL NULL Error (ERROR_NO_MORE_ITEMS)
// !NULL !NULL Start from the exact binding specified in
// *ppResumeFrom
//
// On exit, these pointers will be updated to the following:
//
// *ppResumeFrom - This will be set to the next binding to resume from
// This will be set to NULL if there are no more bindings
pBinding = *ppResumeFrom;
if (!pBinding)
{
_ASSERT(FALSE);
ErrorTrace((LPARAM)this, "ERROR! Empty binding chain!!");
return(HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS));
}
// We know exactly where to start chaining ... Do it.
// Break when:
// 1) The default handler binding is encountered, or
// 2) Sinks results are S_OK
// 3) No more bindings left
CInboundBinding *pInboundBinding;
IUnknown *pUnkSink = NULL;
ISmtpInCommandSink *pSink;
hr = S_OK;
*ppResumeFrom = pBinding;
while (pBinding && (hr == S_OK))
{
// One of the exiting conditions
if (pBinding->dwPriority > dwStopAtPriority)
break;
// Get the containing binding class
pInboundBinding = CONTAINING_RECORD(
pBinding, CInboundBinding, m_bnInfo);
// Call the sink
hr = m_piEventManager->CreateSink(
pInboundBinding->m_piBinding,
NULL,
&pUnkSink);
if (SUCCEEDED(hr))
{
hr = pUnkSink->QueryInterface(
IID_ISmtpInCommandSink,
(LPVOID *)&pSink);
pUnkSink->Release();
pUnkSink = NULL;
if (SUCCEEDED(hr))
{
hr = pSink->OnSmtpInCommand(
pServer,
pSession,
pMsg,
pContext);
pSink->Release();
if (hr == MAILTRANSPORT_S_PENDING)
break;
}
else
hr = S_OK;
}
else
hr = S_OK;
// Next
pBinding = pBinding->pNext;
*ppResumeFrom = pBinding;
}
TraceFunctLeaveEx((LPARAM)this);
return(hr);
}
//
// CInboundDispatcher::CInboundBinding methods
//
HRESULT CInboundDispatcher::CInboundBinding::Init(
IEventBinding *piBinding
)
{
HRESULT hr;
CComPtr<IEventPropertyBag> piEventProperties;
CComVariant vRule;
CHAR szCommandKeyword[256];
DWORD cchCommandKeyword = 0;
TraceFunctEnterEx((LPARAM)this,
"CInboundDispatcher::CInboundBinding::Init");
if (!piBinding || !m_pDispatcher)
return(E_POINTER);
// get the parent initialized
hr = CBinding::Init(piBinding);
if (FAILED(hr)) return hr;
// Store the priority
m_bnInfo.dwPriority = CBinding::m_dwPriority;
m_bnInfo.dwFlags = 0;
// get the binding database
hr = m_piBinding->get_SourceProperties(&piEventProperties);
if (FAILED(hr)) return hr;
// get the rule from the binding database
hr = piEventProperties->Item(&CComVariant("Rule"), &vRule);
if (FAILED(hr)) return hr;
// Process the VARIANT to obtain a lower-case ANSI string
if (hr == S_OK)
{
cchCommandKeyword = sizeof(szCommandKeyword);
hr = GetLowerAnsiStringFromVariant(
vRule,
szCommandKeyword,
&cchCommandKeyword);
}
// We cannot proceed without a rule, so we discard this binding
if (FAILED(hr))
{
ErrorTrace((LPARAM)this,
"Failed to get keyword, error %08x", hr);
return(hr);
}
if (!cchCommandKeyword || !(*szCommandKeyword))
return(E_INVALIDARG);
DebugTrace((LPARAM)this, "Rule: %s", szCommandKeyword);
// Call the dispatcher to insert the node into the command list
hr = CGenericProtoclEventDispatcher::InsertBindingWithHash(
m_pDispatcher->m_rgpCommandList,
m_pDispatcher->m_dwHashSize,
&m_bnInfo,
szCommandKeyword,
cchCommandKeyword);
if (SUCCEEDED(hr))
m_pDispatcher->m_fSinksInstalled = TRUE;
TraceFunctLeaveEx((LPARAM)this);
return(hr);
}
//
// Outbound dispatcher methods
//
void COutboundDispatcher::InitializeDefaultCommandBindings()
{
for (DWORD i = 0; i < PE_STATE_MAX_STATES; i++)
{
if (s_rgszDefaultCommand[i])
{
// Set up the list head
m_rgpCommandList[i] = &(m_rgcnDefaultCommand[i]);
// Set up the command node
m_rgpCommandList[i]->pNext = NULL;
m_rgpCommandList[i]->pszCommandKeyword = s_rgszDefaultCommand[i];
m_rgpCommandList[i]->dwHighestPriority = PRIO_DEFAULT;
m_rgpCommandList[i]->pFirstBinding = &(m_rgbnDefaultCommand[i]);
// Set up the binding node
m_rgbnDefaultCommand[i].pNext = NULL;
m_rgbnDefaultCommand[i].dwPriority = PRIO_DEFAULT;
m_rgbnDefaultCommand[i].dwFlags = PEBN_DEFAULT;
}
else
m_rgpCommandList[i] = NULL;
}
}
HRESULT COutboundDispatcher::AllocBinding(
REFGUID rguidEventType,
IEventBinding *piBinding,
CBinding **ppNewBinding
)
{
if (ppNewBinding)
*ppNewBinding = NULL;
if (!piBinding || !ppNewBinding)
return(E_POINTER);
*ppNewBinding = new COutboundBinding(this, rguidEventType);
if (*ppNewBinding == NULL)
return(E_OUTOFMEMORY);
return(S_OK);
}
HRESULT STDMETHODCALLTYPE COutboundDispatcher::ChainSinks(
IUnknown *pServer,
IUnknown *pSession,
IMailMsgProperties *pMsg,
ISmtpOutCommandContext *pContext,
DWORD dwEventType,
LPPE_COMMAND_NODE *ppPreviousCommand,
LPPE_BINDING_NODE *ppResumeFrom
)
{
HRESULT hr = S_OK;
LPPE_COMMAND_NODE pCommand = NULL;
LPPE_BINDING_NODE pBinding = NULL;
_ASSERT(dwEventType < PE_OET_INVALID_EVENT_TYPE);
TraceFunctEnterEx((LPARAM)this, "COutboundDispatcher::ChainSinks");
// These are the essential pointers that CANNOT be NULL
if (!pContext || !ppPreviousCommand || !ppResumeFrom)
return(E_POINTER);
// If we encounter a bad event type, just gracefully return
// without doing anythig
if (dwEventType >= PE_OET_INVALID_EVENT_TYPE)
{
*ppResumeFrom = NULL;
ErrorTrace((LPARAM)this,
"Skipping event due to bad event type %u",
dwEventType);
return(S_OK);
}
// What we do is strictly determined by the ppPreviousCommand
// and ppResumeFrom pointers. The logic is as follows:
//
// ppPrevCmd ppResumeFrom Action
// NULL NULL Start from the first command for this event type
// NULL !NULL Start from the first command for this event type
// !NULL NULL Start from the first binding for the command
// that follows *ppPreviousCommand
// !NULL !NULL Start from the exact binding specified in
// *ppResumeFrom
//
// On exit, these pointers will be updated to the following:
//
// *ppPreviousCommand - This will point to the command node of the command
// that was just processed
// *ppResumeFrom - This will be set to the next binding to resume from
// This will be set to NULL if there are no more bindings
if (!*ppPreviousCommand)
{
pCommand = m_rgpCommandList[dwEventType];
if (!pCommand)
{
// Ooooops! We peeked with SinksInstalled() but now we have no sinks
// This is an error but we will recover gracefully
_ASSERT(pCommand);
ErrorTrace((LPARAM)this, "ERROR! Empty command chain!!");
return(HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS));
}
// We check this downstream
pBinding = pCommand->pFirstBinding;
}
else if (*ppResumeFrom)
{
// We resume from the exact binding as specified
pCommand = *ppPreviousCommand;
pBinding = *ppResumeFrom;
}
else
{
// We start with the next command in the list
pCommand = (*ppPreviousCommand)->pNext;
if (!pCommand)
{
DebugTrace((LPARAM)this, "No more commands to chain");
*ppPreviousCommand = NULL;
*ppResumeFrom = NULL;
return(HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS));
}
// We check this downstream
pBinding = pCommand->pFirstBinding;
}
// DEBUG Check: we want the retail perf to be as high as possible
// and we want it to be fail safe as well. We will do an internal
// check here to make sure that any command node has at least one
// binding, and that we have bindings when we expect them.
#ifdef DEBUG
if (!pBinding)
{
// Ooooops! We have a command node without a binding. This is a blatant
// error but we recover gracefully
_ASSERT(pBinding);
ErrorTrace((LPARAM)this, "ERROR! Empty binding chain!!");
return(HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS));
}
#endif
// We know exactly where to start chaining ... Do it.
// Break when:
// 1) The default handler binding is encountered, or
// 2) Sinks results are S_OK
// 3) No more bindings left
COutboundBinding *pOutboundBinding;
IUnknown *pUnkSink = NULL;
ISmtpOutCommandSink *pSink;
COutboundContext *pCContext;
// Initialize pContext->m_pCurrentCommandContext
pCContext = (COutboundContext *)pContext;
pCContext->m_pCurrentCommandContext = pCommand;
hr = S_OK;
while (pBinding && (hr == S_OK))
{
// One of the exiting conditions
if (pBinding->dwFlags & PEBN_DEFAULT)
break;
// Get the containing binding class
pOutboundBinding = CONTAINING_RECORD(
pBinding, COutboundBinding, m_bnInfo);
// Call the sink
hr = m_piEventManager->CreateSink(
pOutboundBinding->m_piBinding,
NULL,
&pUnkSink);
if (SUCCEEDED(hr))
{
hr = pUnkSink->QueryInterface(
IID_ISmtpOutCommandSink,
(LPVOID *)&pSink);
pUnkSink->Release();
pUnkSink = NULL;
if (SUCCEEDED(hr))
{
// Pre-fill in the next binding to avoid
// race conditions in the async case
*ppResumeFrom = pBinding->pNext;
hr = pSink->OnSmtpOutCommand(
pServer,
pSession,
pMsg,
pContext);
pSink->Release();
if (hr == MAILTRANSPORT_S_PENDING)
hr = S_OK;
// break;
}
else
hr = S_OK;
}
else
hr = S_OK;
// Next
pBinding = pBinding->pNext;
}
// Return where to resume from ...
*ppPreviousCommand = pCommand;
*ppResumeFrom = pBinding;
TraceFunctLeaveEx((LPARAM)this);
return(hr);
}
//
// COutboundDispatcher::COutboundBinding methods
//
COutboundDispatcher::COutboundBinding::COutboundBinding(
COutboundDispatcher *pDispatcher,
REFGUID rguidEventType
)
{
_ASSERT(pDispatcher);
m_pDispatcher = pDispatcher;
// Based on the event type GUID, we can calculate which
// event type id this goes to
for (m_dwEventType = 0;
m_dwEventType < PE_OET_INVALID_EVENT_TYPE;
m_dwEventType++)
if (rguidEventType == *(s_rgrguidEventTypes[m_dwEventType]))
break;
// If the GUID is not recognized, then the final value of
// m_dwEventType will be PE_OET_INVALID_EVENT_TYPE, which
// we will not process in this dispatcher
}
HRESULT COutboundDispatcher::COutboundBinding::Init(
IEventBinding *piBinding
)
{
HRESULT hr;
CComPtr<IEventPropertyBag> piEventProperties;
CComVariant vRule;
CHAR szCommandKeyword[256];
DWORD cchCommandKeyword = 0;
TraceFunctEnterEx((LPARAM)this,
"COutboundDispatcher::COutboundBinding::Init");
if (!piBinding || !m_pDispatcher)
return(E_POINTER);
// If the event type GUID is unknown, invalidate this binding
if (m_dwEventType >= PE_OET_INVALID_EVENT_TYPE)
return(E_INVALIDARG);
// get the parent initialized
hr = CBinding::Init(piBinding);
if (FAILED(hr)) return hr;
// Store the priority
m_bnInfo.dwPriority = CBinding::m_dwPriority;
m_bnInfo.dwFlags = 0;
// get the binding database
hr = m_piBinding->get_SourceProperties(&piEventProperties);
if (FAILED(hr)) return hr;
// get the rule from the binding database
hr = piEventProperties->Item(&CComVariant("Rule"), &vRule);
if (FAILED(hr)) return hr;
// Process the VARIANT to obtain a lower-case ANSI string
if (hr == S_OK)
{
cchCommandKeyword = sizeof(szCommandKeyword);
hr = GetLowerAnsiStringFromVariant(
vRule,
szCommandKeyword,
&cchCommandKeyword);
}
else if(hr == S_FALSE)
{
//
// Treat no rule as an empty string rule
//
szCommandKeyword[0] = '\0';
cchCommandKeyword = 1;
}
// We cannot proceed without a rule, so we discard this binding
if (FAILED(hr))
{
ErrorTrace((LPARAM)this,
"Failed to get keyword, error %08x", hr);
return(hr);
}
if (!cchCommandKeyword)
return(E_INVALIDARG);
DebugTrace((LPARAM)this, "Rule: %s", szCommandKeyword);
// Call the dispatcher to insert the node into the command list
hr = CGenericProtoclEventDispatcher::InsertBinding(
&((m_pDispatcher->m_rgpCommandList)[m_dwEventType]),
&m_bnInfo,
szCommandKeyword,
cchCommandKeyword);
if (SUCCEEDED(hr))
m_pDispatcher->m_fSinksInstalled = TRUE;
TraceFunctLeaveEx((LPARAM)this);
return(hr);
}
//
// CResponseDispatcher mathods
//
HRESULT CResponseDispatcher::AllocBinding(
REFGUID rguidEventType,
IEventBinding *piBinding,
CBinding **ppNewBinding
)
{
if (ppNewBinding)
*ppNewBinding = NULL;
if (!piBinding || !ppNewBinding)
return(E_POINTER);
*ppNewBinding = new CResponseBinding(this);
if (*ppNewBinding == NULL)
return(E_OUTOFMEMORY);
return(S_OK);
}
HRESULT STDMETHODCALLTYPE CResponseDispatcher::ChainSinks(
IUnknown *pServer,
IUnknown *pSession,
IMailMsgProperties *pMsg,
ISmtpServerResponseContext *pContext,
DWORD dwStopAtPriority,
LPPE_COMMAND_NODE pCommandNode,
LPPE_BINDING_NODE *ppResumeFrom
)
{
HRESULT hr = S_OK;
LPPE_BINDING_NODE pBinding = NULL;
TraceFunctEnterEx((LPARAM)this, "CResponseDispatcher::ChainSinks");
// These are the essential pointers that CANNOT be NULL
if (!pContext || !pCommandNode || !ppResumeFrom)
return(E_POINTER);
// What we do is strictly determined by the ppPreviousCommand
// and ppResumeFrom pointers. The logic is as follows:
//
// pCmdNode ppResumeFrom Action
// NULL X Error (E_POINTER)
// !NULL NULL Error (ERROR_NO_MORE_ITEMS)
// !NULL !NULL Start from the exact binding specified in
// *ppResumeFrom
//
// On exit, these pointers will be updated to the following:
//
// *ppResumeFrom - This will be set to the next binding to resume from
// This will be set to NULL if there are no more bindings
pBinding = *ppResumeFrom;
if (!pBinding)
{
_ASSERT(FALSE);
ErrorTrace((LPARAM)this, "ERROR! Empty binding chain!!");
return(HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS));
}
// We know exactly where to start chaining ... Do it.
// Break when:
// 1) The default handler binding is encountered, or
// 2) Sinks results are S_OK
// 3) No more bindings left
CResponseBinding *pResponseBinding;
IUnknown *pUnkSink = NULL;
ISmtpServerResponseSink *pSink;
hr = S_OK;
*ppResumeFrom = pBinding;
while (pBinding && (hr == S_OK))
{
// One of the exiting conditions
if (pBinding->dwPriority > dwStopAtPriority)
break;
// Get the containing binding class
pResponseBinding = CONTAINING_RECORD(
pBinding, CResponseBinding, m_bnInfo);
// Call the sink
hr = m_piEventManager->CreateSink(
pResponseBinding->m_piBinding,
NULL,
&pUnkSink);
if (SUCCEEDED(hr))
{
hr = pUnkSink->QueryInterface(
IID_ISmtpServerResponseSink,
(LPVOID *)&pSink);
pUnkSink->Release();
pUnkSink = NULL;
if (SUCCEEDED(hr))
{
// Pre-fill in the next binding to avoid
// race conditions in the async case
hr = pSink->OnSmtpServerResponse(
pServer,
pSession,
pMsg,
pContext);
pSink->Release();
if (hr == MAILTRANSPORT_S_PENDING)
hr = S_OK;
// break;
}
else
hr = S_OK;
}
else
hr = S_OK;
// Next
pBinding = pBinding->pNext;
*ppResumeFrom = pBinding;
}
TraceFunctLeaveEx((LPARAM)this);
return(hr);
}
//
// CResponseDispatcher::CResponseBinding methods
//
HRESULT CResponseDispatcher::CResponseBinding::Init(
IEventBinding *piBinding
)
{
HRESULT hr;
CComPtr<IEventPropertyBag> piEventProperties;
CComVariant vRule;
CHAR szCommandKeyword[256];
DWORD cchCommandKeyword = 0;
TraceFunctEnterEx((LPARAM)this,
"CResponseDispatcher::CResponseBinding::Init");
if (!piBinding || !m_pDispatcher)
return(E_POINTER);
// get the parent initialized
hr = CBinding::Init(piBinding);
if (FAILED(hr)) return hr;
// Store the priority
m_bnInfo.dwPriority = CBinding::m_dwPriority;
m_bnInfo.dwFlags = 0;
// get the binding database
hr = m_piBinding->get_SourceProperties(&piEventProperties);
if (FAILED(hr)) return hr;
// get the rule from the binding database
hr = piEventProperties->Item(&CComVariant("Rule"), &vRule);
if (FAILED(hr)) return hr;
// Process the VARIANT to obtain a lower-case ANSI string
if (hr == S_OK)
{
cchCommandKeyword = sizeof(szCommandKeyword);
hr = GetLowerAnsiStringFromVariant(
vRule,
szCommandKeyword,
&cchCommandKeyword);
}
// We cannot proceed without a rule, so we discard this binding
if (FAILED(hr))
{
ErrorTrace((LPARAM)this,
"Failed to get keyword, error %08x", hr);
return(hr);
}
if (!cchCommandKeyword || !(*szCommandKeyword))
return(E_INVALIDARG);
DebugTrace((LPARAM)this, "Rule: %s", szCommandKeyword);
// Call the dispatcher to insert the node into the command list
hr = CGenericProtoclEventDispatcher::InsertBindingWithHash(
m_pDispatcher->m_rgpCommandList,
m_pDispatcher->m_dwHashSize,
&m_bnInfo,
szCommandKeyword,
cchCommandKeyword);
if (SUCCEEDED(hr))
m_pDispatcher->m_fSinksInstalled = TRUE;
TraceFunctLeaveEx((LPARAM)this);
return(hr);
}