Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2107 lines
69 KiB

//-----------------------------------------------------------------------------
//
//
// File: ConnMgr.cpp
//
// Description: Implementation of CConnMgr which provides the
// IConnectionManager interface.
//
// Author: mikeswa
//
// Copyright (C) 1997 Microsoft Corporation
//
//-----------------------------------------------------------------------------
#include "aqprecmp.h"
#include "retrsink.h"
#include "ConnMgr.h"
#include "fifoqimp.h"
#include "smtpconn.h"
#include "tran_evntlog.h"
VOID LookupQueueforETRN(PVOID pvContext, PVOID pvData, BOOL fWildcard,
BOOL *pfContinue, BOOL *pfDelete);
VOID CreateETRNDomainList(PVOID pvContext, PVOID pvData, BOOL fWildcard,
BOOL *pfContinue, BOOL *pfDelete);
//If we are not limiting the number of messages that each connection can handle,
//then lets use this as a guide to determine how many connections to create.
#define UNLIMITED_MSGS_PER_CONNECTION 20
//---[ CConnMgr::CConnMgr ]----------------------------------------------------
//
//
// Description:
// Default constructor for CConnMgr class.
// Parameters:
// -
// Returns:
// -
//
//-----------------------------------------------------------------------------
CConnMgr::CConnMgr() : CSyncShutdown()
{
HRESULT hr = S_OK;
m_paqinst = NULL;
m_pqol = NULL;
m_hNextConnectionEvent = NULL;
m_hShutdownEvent = NULL;
m_hReleaseAllEvent = NULL;
m_cConnections = 0;
m_cMaxLinkConnections = g_cMaxLinkConnections;
m_cMinMessagesPerConnection = g_cMinMessagesPerConnection;
m_cMaxMessagesPerConnection = g_cMaxMessagesPerConnection;
m_cMaxConnections = g_cMaxConnections;
m_cGetNextConnectionWaitTime = g_dwConnectionWaitMilliseconds;
m_dwConfigVersion = 0;
m_fStoppedByAdmin = FALSE;
}
//---[ CConnMgr::~CConnMgr ]-----------------------------------------------------
//
//
// Description:
// Default destructor for CConnMgr
// Parameters:
// -
// Returns:
// -
//
//-----------------------------------------------------------------------------
CConnMgr::~CConnMgr()
{
TraceFunctEnterEx((LPARAM) this, "CConnMgr::~CConnMgr");
if (NULL != m_hNextConnectionEvent)
{
if (!CloseHandle(m_hNextConnectionEvent))
{
DebugTrace((LPARAM) HRESULT_FROM_WIN32(GetLastError()),
"Unable to close handle for Get Next Connection Event");
}
}
if (NULL != m_hShutdownEvent)
{
if (!CloseHandle(m_hShutdownEvent))
{
DebugTrace((LPARAM) HRESULT_FROM_WIN32(GetLastError()),
"Unable to close handle for Connection Manger Shutdown Event");
}
}
if (NULL != m_hReleaseAllEvent)
{
if (!CloseHandle(m_hReleaseAllEvent))
{
DebugTrace((LPARAM) HRESULT_FROM_WIN32(GetLastError()),
"Unable to close handle for Connection Manger Release All Event");
}
}
TraceFunctLeave();
}
//---[ CConnMgr::HrInitialize ]------------------------------------------------
//
//
// Description:
// CConnMgr Initialization function.
// Parameters:
// paqinst ptr fo CAQSvrInst virtual instance object
// Returns:
// S_OK on success
//
//-----------------------------------------------------------------------------
HRESULT CConnMgr::HrInitialize(CAQSvrInst *paqinst)
{
TraceFunctEnterEx((LPARAM) this, "CConnMgr::HrInitialize");
HRESULT hr = S_OK;
IConnectionRetryManager *pIRetryMgr = NULL;
_ASSERT(paqinst);
paqinst->AddRef();
m_paqinst = paqinst;
//Create Manual reset event to release all waiting threads on shutdown
m_hShutdownEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (NULL == m_hShutdownEvent)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
//Create Queue of Links
m_pqol = new QueueOfLinks;
if (NULL == m_pqol)
{
hr = E_OUTOFMEMORY;
goto Exit;
}
m_hNextConnectionEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (NULL == m_hNextConnectionEvent)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
//Create Manual reset event to release all waiting threads on caller's request
m_hReleaseAllEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (NULL == m_hReleaseAllEvent)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
hr = QueryInterface(IID_IConnectionRetryManager, (PVOID *) &pIRetryMgr);
if (FAILED(hr))
goto Exit;
//Create the default retry handler object and initialize it
m_pDefaultRetryHandler = new CSMTP_RETRY_HANDLER();
if (!m_pDefaultRetryHandler)
{
hr = E_OUTOFMEMORY;
goto Exit;
}
//Addref IConnectionRetryManager here
//and release it during deinit
pIRetryMgr->AddRef();
hr = m_pDefaultRetryHandler->HrInitialize(pIRetryMgr);
if (FAILED(hr))
{
ErrorTrace((LPARAM) hr, "ERROR: Unable to initialize the retry handler!");
goto Exit;
}
Exit:
if (pIRetryMgr)
pIRetryMgr->Release();
TraceFunctLeave();
return hr;
}
//---[ CConnMgr::HrDeinitialize ]----------------------------------------------
//
//
// Description:
// CConnMgr Deinitialization function.
// Parameters:
// -
// Returns:
// S_OK on success
//
//-----------------------------------------------------------------------------
HRESULT CConnMgr::HrDeinitialize()
{
TraceFunctEnterEx((LPARAM) this, "CConnMgr::HrDeinitialize");
//Wait max of 3 minutes no-progress.
const DWORD CONNMGR_WAIT_SECONDS = 5;
const DWORD MAX_CONNMGR_SHUTDOWN_WAITS = 1200/CONNMGR_WAIT_SECONDS;
const DWORD MAX_CONNMGR_SHUTDOWN_WAITS_WITHOUT_PROGRESS = 180/CONNMGR_WAIT_SECONDS;
HRESULT hr = S_OK;
HRESULT hrQueue = S_OK;
CLinkMsgQueue *plmq = NULL;
DWORD cWaits = 0;
DWORD cWaitsSinceLastProgress = 0;
DWORD cConnectionsPrevious = 0;
if (m_paqinst)
m_paqinst->ServerStopHintFunction();
SignalShutdown();
if (NULL != m_hShutdownEvent)
{
if (!SetEvent(m_hShutdownEvent))
{
if (SUCCEEDED(hr))
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
if (NULL != m_pqol)
{
//Dequeue Links until empty
hrQueue = m_pqol->HrDequeue(&plmq);
while (SUCCEEDED(hrQueue))
{
_ASSERT(plmq);
plmq->Release();
hrQueue = m_pqol->HrDequeue(&plmq);
}
delete m_pqol;
m_pqol = NULL;
}
cConnectionsPrevious = m_cConnections;
while (m_cConnections)
{
cWaits++;
cWaitsSinceLastProgress++;
if (m_paqinst)
m_paqinst->ServerStopHintFunction();
Sleep(CONNMGR_WAIT_SECONDS * 1000);
if (m_cConnections != cConnectionsPrevious)
{
cConnectionsPrevious = m_cConnections;
cWaitsSinceLastProgress = 0;
}
if ((cWaits > MAX_CONNMGR_SHUTDOWN_WAITS) ||
(cWaitsSinceLastProgress > MAX_CONNMGR_SHUTDOWN_WAITS_WITHOUT_PROGRESS))
{
_ASSERT(0 && "SMTP not returning all connections");
ErrorTrace((LPARAM) this, "ERROR: %d Connections outstanding on shutdown", m_cConnections);
break;
}
}
//Must happen after we are done caller server stop hint functions
if (NULL != m_paqinst)
{
m_paqinst->Release();
m_paqinst = NULL;
}
//NK** To be safe do it as interlocked exchange
if (m_pDefaultRetryHandler)
{
m_pDefaultRetryHandler->HrDeInitialize();
m_pDefaultRetryHandler = NULL;
}
TraceFunctLeave();
return hr;
}
//---[ CConnMgr::HrNotify ]----------------------------------------------------
//
//
// Description:
// Method exposed to recieve a notification about a change in queue status
// Parameters:
// IN paqstats Notification object
// Returns:
// S_OK on success
//
//-----------------------------------------------------------------------------
HRESULT CConnMgr::HrNotify(IN CAQStats *paqstats, BOOL fAdd)
{
TraceFunctEnterEx((LPARAM) this, "CConnMgr::HrNotify");
HRESULT hr = S_OK;
CLinkMsgQueue *plmq = NULL;
DWORD cbDomainName = 0;
LPSTR szDomainName = NULL;
_ASSERT(paqstats);
plmq = paqstats->m_plmq;
_ASSERT(plmq); //ConnMgr notifications must have a link associated with then
if (paqstats->m_dwNotifyType & NotifyTypeNewLink)
{
hr = plmq->HrGetSMTPDomain(&cbDomainName, &szDomainName);
if (FAILED(hr))
goto Exit;
//must add new link to QueueOfLinks
plmq->IncrementConnMgrCount();
hr = m_pqol->HrEnqueue(plmq);
if (FAILED(hr))
{
plmq->DecrementConnMgrCount();
DebugTrace((LPARAM) hr, "ERROR: Unable to add new link to connection manager!");
goto Exit;
}
}
//See if we can (and *should*) create a connection
if ((m_cConnections < m_cMaxConnections) &&
plmq->fShouldConnect(m_cMaxLinkConnections, m_cMinMessagesPerConnection))
{
DebugTrace((LPARAM) m_hNextConnectionEvent, "INFO: Setting Next Connection Event");
if (!SetEvent(m_hNextConnectionEvent))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
}
Exit:
TraceFunctLeave();
return hr;
}
//---[ CConnMgr::ReleaseConnection ]-------------------------------------------
//
//
// Description:
// Releases the connection count when a connection is being destroyed
// Parameters:
// IN pSMTPConn SMTP Connection Object to release
// OUT pfForceCheckForDSNGeneration
// TRUE if there was a hard error and we must
// pass this link through DSN generation.
//
// This 2nd parameter does not mean that will will or will not NDR the
// messages... just that we will iterate over all of the messages in the
// link. If CMsgRef::fIsFatalError() returns TRUE for the current
// connection status (as passed into the DSN generation code, then messages
// will be NDR'd. One way to control this is by setting the
//
// Returns:
// -
//
//-----------------------------------------------------------------------------
void CConnMgr::ReleaseConnection(CSMTPConn *pSMTPConn,
BOOL *pfForceCheckForDSNGeneration)
{
TraceFunctEnterEx((LPARAM) this, "CConnMgr::ReleaseConnection");
DWORD dwDomainInfoFlags = 0;
DWORD dwLinkStateFlags = 0;
DWORD dwConnectionStatus = pSMTPConn->dwGetConnectionStatus();
DWORD dwConnectionFailureCount = 0;
HRESULT hr = S_OK;
CLinkMsgQueue *plmq = NULL;
DWORD cbDomain = 0;
LPSTR szDomain = NULL;
BOOL fCanRetry = FALSE;
BOOL fLocked = FALSE;
DWORD cConnections = 0;
CInternalDomainInfo *pIntDomainInfo= NULL;
CAQScheduleID *paqsched = NULL;
FILETIME ftNextRetry;
BOOL fShouldNotify = FALSE;
BOOL fMayNDRAllMessages = FALSE;
GUID guidRouting = GUID_NULL;
DWORD cMessages = 0;
ZeroMemory(&ftNextRetry, sizeof(FILETIME));
_ASSERT(pfForceCheckForDSNGeneration);
if (pfForceCheckForDSNGeneration)
*pfForceCheckForDSNGeneration = FALSE;
plmq = pSMTPConn->plmqGetLink();
_ASSERT(plmq); //connection must be associated with a link
paqsched = plmq->paqschedGetScheduleID();
_ASSERT(paqsched);
//Get the routing GUID
paqsched->GetGUID(&guidRouting);
hr = plmq->HrGetSMTPDomain(&cbDomain, &szDomain);
if (FAILED(hr))
{
_ASSERT(0); //I need to unstand when this can happen
DebugTrace((LPARAM) hr, "ERROR: HrGetSMTPDomain failed");
goto Exit;
}
if (!fTryShutdownLock())
{
hr = AQUEUE_E_SHUTDOWN;
goto Exit;
}
else
{
fLocked = TRUE;
_ASSERT(m_paqinst);
hr = plmq->HrGetDomainInfo(&cbDomain, &szDomain, &pIntDomainInfo);
if (FAILED(hr))
{
//It must match the "*" domain at least
_ASSERT(AQUEUE_E_INVALID_DOMAIN != hr);
DebugTrace((LPARAM) hr, "ERROR: HrGetInternalDomainInfo");
goto Exit;
}
_ASSERT(pIntDomainInfo);
dwDomainInfoFlags = pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags ;
cConnections = plmq->cGetConnections();
//NK** Update the link with the number of messages tried, failed, sent etc
//If the remaining count goes to 0 and trigger is set, we will disable the trigger
cMessages = plmq->cGetTotalMsgCount();
//If we no more messages on the link, we need to disable
//flags that caused one time triggering
if(!cMessages)
{
//No more messages on the link - we may need to unset some flags on the link
//If someone set this bit... then we should continue to notify them
if (plmq->dwGetLinkState() & LINK_STATE_CONNECT_IF_NO_MSGS)
fShouldNotify = TRUE;
if(dwDomainInfoFlags & DOMAIN_INFO_TURN_ONLY || dwDomainInfoFlags & DOMAIN_INFO_ETRN_ONLY )
dwLinkStateFlags |= LINK_STATE_PRIV_ETRN_ENABLED | LINK_STATE_PRIV_TURN_ENABLED;
}
//Disable the admin forced connection
// 2/1/99 - MikeSwa - We need to do this check every time, or we will
// continue to create connections for this link
if (plmq->dwGetLinkState() & LINK_STATE_ADMIN_FORCE_CONN)
dwLinkStateFlags |= LINK_STATE_ADMIN_FORCE_CONN;
//Call link function to *unset* flags
if (dwLinkStateFlags)
plmq->dwModifyLinkState(LINK_STATE_NO_ACTION, dwLinkStateFlags);
//The connection failed and this is the last outstanding connection to this domain
//Increment the failure count
dwConnectionFailureCount = plmq->cGetMessageFailureCount();
if(cConnections == 1 && (CONNECTION_STATUS_OK != dwConnectionStatus))
{
dwConnectionFailureCount = plmq->IncrementFailureCounts();
}
if (CONNECTION_STATUS_FAILED_LOOPBACK & dwConnectionStatus)
{
ErrorTrace((LPARAM) this,
"Loopback detected for domain %s (smarthost %s)",
pIntDomainInfo->m_DomainInfo.szDomainName,
pIntDomainInfo->m_DomainInfo.szSmartHostDomainName);
fMayNDRAllMessages = TRUE;
}
if (CONNECTION_STATUS_FAILED_NDR_UNDELIVERED & dwConnectionStatus)
{
//
// If not set treat the failure as retryable - see detailed
// comments in GenerateDSNsIfNecessary() in linkmsgq.cpp for
// details comments
//
dwLinkStateFlags = plmq->dwGetLinkState();
if (!(LINK_STATE_RETRY_ALL_DNS_FAILURES & dwLinkStateFlags))
{
ErrorTrace((LPARAM) plmq,
"hard failure for %s (smarthost %s) - flags 0x%08X",
pIntDomainInfo->m_DomainInfo.szDomainName,
pIntDomainInfo->m_DomainInfo.szSmartHostDomainName,
dwLinkStateFlags);
fMayNDRAllMessages = TRUE;
}
}
//
// Flag the link so that we generate DSNs on it and fall down to
// the retry handler sink. This link will be marked retry so that
// no new connections are created. Ultimately, the DSN generation
// code/MsgRef will make the final determination if we need to NDR,
// but this being set means that:
// - We think we have hit an NDR-able error
// - We will use the glitch retry (so new messages
// can be retried as well).
//
if (fMayNDRAllMessages)
{
if(pfForceCheckForDSNGeneration)
*pfForceCheckForDSNGeneration = TRUE;
//
// Trick the retry sink so it always uses the glitch retry
//
dwConnectionFailureCount = 1;
}
_ASSERT(m_pDefaultRetryHandler);
DebugTrace((LPARAM) this,
"INFO: ConnectionRelease for domain %s: %d failed, %d tried, status 0x%08X",
szDomain, pSMTPConn->cGetFailedMsgCount(),
pSMTPConn->cGetTriedMsgCount(),
pSMTPConn->dwGetConnectionStatus());
hr = m_pDefaultRetryHandler->ConnectionReleased(cbDomain, szDomain,
dwDomainInfoFlags, paqsched->dwGetScheduleID(),
guidRouting, dwConnectionStatus,
pSMTPConn->cGetFailedMsgCount(),
pSMTPConn->cGetTriedMsgCount(),
dwConnectionFailureCount, &fCanRetry, &ftNextRetry);
if (FAILED(hr))
{
DebugTrace((LPARAM) hr,
"ERROR: Failed to deal with released connection");
}
//Make sure that the proper flags are set WRT retry
if (fCanRetry)
{
if (dwConnectionStatus == CONNECTION_STATUS_OK)
plmq->ResetConnectionFailureCount();
//If this is a TURN/ETRN domain, we do not want to enable it unless
//another TURN/ETRN request comes... or a retry request is scheduled for
//later. The reason for this, is that we don't want to retry TURN/ETRN
//domains in the conventional sense, so the defaul retry sink ignores
//them except for "glitch" retries
if(dwDomainInfoFlags & (DOMAIN_INFO_TURN_ONLY | DOMAIN_INFO_ETRN_ONLY))
dwLinkStateFlags = LINK_STATE_PRIV_ETRN_ENABLED | LINK_STATE_PRIV_TURN_ENABLED;
else
dwLinkStateFlags = LINK_STATE_NO_ACTION;
dwLinkStateFlags = plmq->dwModifyLinkState(LINK_STATE_RETRY_ENABLED,
dwLinkStateFlags);
//Check for state change
if (!(LINK_STATE_RETRY_ENABLED & dwLinkStateFlags))
fShouldNotify = TRUE;
}
else
{
DebugTrace((LPARAM) this,"ASSERT_RETRY : ReleaseConnection about to clear flag for link 0x%08X", plmq);
dwLinkStateFlags = plmq->dwModifyLinkState(LINK_STATE_NO_ACTION,
LINK_STATE_RETRY_ENABLED);
DebugTrace((LPARAM) this,"ASSERT_RETRY : ReleaseConnection has cleared the flag for link 0x%08X", plmq);
//Check for state change
if (LINK_STATE_RETRY_ENABLED & dwLinkStateFlags)
fShouldNotify = TRUE;
if (ftNextRetry.dwHighDateTime || ftNextRetry.dwLowDateTime)
{
//Retry is telling us a retry time... report that.
//Set the next retry time that the retry sink tells us about
plmq->SetNextRetry(&ftNextRetry);
}
//
// Log something useful
//
LogConnectionFailedEvent(pSMTPConn, plmq, szDomain);
if (dwConnectionStatus == CONNECTION_STATUS_OK)
plmq->IncrementFailureCounts(); //we had a false positive
}
//Notify router/scheduler of any changes
if (fShouldNotify)
plmq->SendLinkStateNotification();
if (cConnections < m_cMaxConnections)
{
if (!SetEvent(m_hNextConnectionEvent))
DebugTrace((LPARAM) HRESULT_FROM_WIN32(GetLastError()), "Unable to set GetNextConnection Event");
}
}
Exit:
if (plmq)
plmq->Release();
//Decrement connection count
cConnections = InterlockedDecrement((long *) &m_cConnections);
DebugTrace((LPARAM) this, "INFO: Releasing Connection for link 0x%08X", plmq);
if (fLocked)
ShutdownUnlock();
if (pIntDomainInfo)
pIntDomainInfo->Release();
TraceFunctLeave();
}
//---[ CConnMgr::LogConnectionFailedEvent ]------------------------------------
//
//
// Description:
// Logs the specific connection failure event.
// Parameters:
// IN pSMTPConn SMTP Connection Object to release
// IN plmq ClinkMsgQueue for this link
// IN szDomain The domain name
// Returns:
// -
// History:
// 11/29/2001 - Mikeswa created (moved from ReleaseConnection)
//
//-----------------------------------------------------------------------------
void CConnMgr::LogConnectionFailedEvent(CSMTPConn *pSMTPConn,
CLinkMsgQueue *plmq,
LPSTR szDomain)
{
TraceFunctEnterEx((LPARAM)this, "CConnMgr::LogConnectionFailedEvent");
char szDiagnosticVerb[1024] = "";
char szDiagnosticError[1024] = "";
DWORD iMessage = AQUEUE_REMOTE_DELIVERY_FAILED;
HRESULT hrDiagnostic = PHATQ_E_CONNECTION_FAILED;
LPSTR szConnectedIPAddress = pSMTPConn ? pSMTPConn->szGetConnectedIPAddress() : NULL;
BOOL fLogIPAddress = FALSE;
DWORD iSubstringDiagnosticIndex = 1; //index of dianostic in substring
WORD wSubstringWordCount = 0;
const char *rgszSubstrings[] = {
szDomain,
NULL /* error message */,
szDiagnosticVerb,
szDiagnosticError,
};
const char *rgszSubstringWithIP[] = {
szConnectedIPAddress,
szDomain,
NULL /* error message */,
szDiagnosticVerb,
szDiagnosticError,
};
if (!plmq || !pSMTPConn || !m_paqinst)
{
ErrorTrace((LPARAM)this,
"Bogus pointers plmq %p, pSMTPConn %p, m_paqinst %p",
plmq, pSMTPConn, m_paqinst);
goto Exit;
}
plmq->GetDiagnosticInfo(szDiagnosticVerb,
sizeof(szDiagnosticVerb),
szDiagnosticError,
sizeof(szDiagnosticError),
&hrDiagnostic);
if (SUCCEEDED(hrDiagnostic))
{
//This means that the connection has failed,
//but there is no diagnostic information... this could
//be caused by several things, but we want to avoid
//logging a potentially bogus event.
//Set this error to something that looks useful, but
//is actually the transport equivalent on E_FAIL. We
//can use this to find when this was hit by looking at
//the error logs on retail builds.
hrDiagnostic = PHATQ_E_CONNECTION_FAILED;
ErrorTrace((LPARAM) this,
"Link Diagnostic was not set - defaulting");
}
//
// There are 4 different events we can log at this point. Each as a
// different number of words to substitute & a different place to put the
// diagnostic. Each has a different message ID. The variations are
// with/without SMTP protocol verbs and with/without IP address
//
// Failure fLogIPAddress Words Substring Array Diag Index
// ======================================================================
// no verb FALSE 2 rgszSubstrings 1
// Verb FALSE 4 rgszSubstrings 1
// no verb TRUE 3 rgszSubstringWithIP 2
// Verb TRUE 5 rgszSubstringWithIP 2
//
//
// Check if there is anything in the IP address string.
// If there is, we will use it.
//
if (szConnectedIPAddress && szConnectedIPAddress[0]) {
fLogIPAddress = TRUE;
}
//
// Is there any protocol verb data? If so, use this.
//
if (*szDiagnosticVerb != 0 || *szDiagnosticError != 0)
{
if (fLogIPAddress)
{
wSubstringWordCount = 5;
iSubstringDiagnosticIndex = 2;
iMessage = AQUEUE_REMOTE_DELIVERY_TO_IP_FAILED_DIAGNOSTIC;
}
else
{
wSubstringWordCount = 4;
iSubstringDiagnosticIndex = 1;
iMessage = AQUEUE_REMOTE_DELIVERY_FAILED_DIAGNOSTIC;
}
}
else
{
if (fLogIPAddress)
{
wSubstringWordCount = 3;
iSubstringDiagnosticIndex = 2;
iMessage = AQUEUE_REMOTE_DELIVERY_TO_IP_FAILED;
}
else
{
wSubstringWordCount = 2;
iSubstringDiagnosticIndex = 1;
iMessage = AQUEUE_REMOTE_DELIVERY_FAILED;
}
}
m_paqinst->HrTriggerLogEvent(
iMessage, // Message ID
TRAN_CAT_CONNECTION_MANAGER, // Category
wSubstringWordCount, // Word count of substring
fLogIPAddress ? rgszSubstringWithIP : rgszSubstrings,
EVENTLOG_WARNING_TYPE, // Type of the message
hrDiagnostic, // error code
LOGEVENT_LEVEL_MEDIUM, // Logging level
szDomain, // Key to identify this event
LOGEVENT_FLAG_PERIODIC, // Event logging option
iSubstringDiagnosticIndex, // format string's index in substring
GetModuleHandle(AQ_MODULE_NAME) // module handle to format a message
);
Exit:
TraceFunctLeave();
}
//---[ CConnMgr::QueryInterface ]------------------------------------------
//
//
// Description:
// QueryInterface for IAdvQueue
// Parameters:
//
// Returns:
// S_OK on success
//
// Notes:
// This implementation makes it possible for any server component to get
// the IConnectionManager interface.
//
//-----------------------------------------------------------------------------
STDMETHODIMP CConnMgr::QueryInterface(REFIID riid, LPVOID * ppvObj)
{
HRESULT hr = S_OK;
if (!ppvObj)
{
hr = E_INVALIDARG;
goto Exit;
}
if (IID_IUnknown == riid)
{
*ppvObj = static_cast<IConnectionRetryManager *>(this);
}
else if (IID_IConnectionRetryManager == riid)
{
*ppvObj = static_cast<IConnectionRetryManager *>(this);
}
else if (IID_IConnectionManager == riid)
{
*ppvObj = static_cast<IConnectionManager *>(this);
}
else
{
*ppvObj = NULL;
hr = E_NOINTERFACE;
goto Exit;
}
static_cast<IUnknown *>(*ppvObj)->AddRef();
Exit:
return hr;
}
//---[ CConnMgr::GetNextConnection ]-------------------------------------------
//
//
// Description:
// Implementation of IConnectionManager::GetNextConnection()
//
// Returns the next available connection. Will create a connection object
// and associate it with a link. If we are already at max connections, or
// no link needs a connection, then this call will block until a an
// appropriate connection can be made.
// Parameters:
// OUT pismtpconn SMTP Connection interface
// Returns:
// S_OK on success
//
//-----------------------------------------------------------------------------
STDMETHODIMP CConnMgr::GetNextConnection(ISMTPConnection ** ppISMTPConnection)
{
TraceFunctEnterEx((LPARAM) this, "CConnMgr::GetNextConnection");
const DWORD NUM_CONNECTION_OBJECTS = 3;
//Release event is the last event in array
const DWORD WAIT_OBJECT_RELEASE_EVENT = WAIT_OBJECT_0 + NUM_CONNECTION_OBJECTS -1;
HRESULT hr = S_OK;
DWORD cLinksToTry = 0;
DWORD cConnections = 0;
CLinkMsgQueue *plmq = NULL;
CSMTPConn *pSMTPConn = NULL;
bool fForceWait = false; //temporarily force thread to wait
bool fLocked = false;
DWORD cbDomain = 0;
LPSTR szDomain = NULL;
HANDLE rghWaitEvents[NUM_CONNECTION_OBJECTS] = {m_hShutdownEvent, m_hNextConnectionEvent, m_hReleaseAllEvent};
DWORD dwWaitResult;
DWORD cMaxConnections = 0;
DWORD cGetNextConnectionWaitTime = 30000; //make sure we never start in a busy wait loop
DWORD cMaxLinkConnections = 0;
DWORD cMinMessagesPerConnection = 0;
DWORD cMaxMessagesPerConnection = 0;
DWORD dwConfigVersion;
LONG cTimesQueued = 0; //# of times a link has been queue'd
BOOL fOwnConnectionCount = FALSE;
BOOL fMembersUnsafe = FALSE; //set to TRUE during shutdown situations
if (NULL == ppISMTPConnection)
{
hr = E_INVALIDARG;
goto Exit;
}
//Get config data
m_slPrivateData.ShareLock();
cMaxLinkConnections = m_cMaxLinkConnections;
cMaxMessagesPerConnection = m_cMaxMessagesPerConnection;
//Handle unlimited case
if (m_cMinMessagesPerConnection)
cMinMessagesPerConnection = m_cMinMessagesPerConnection;
else
cMinMessagesPerConnection = UNLIMITED_MSGS_PER_CONNECTION;
cMaxConnections = m_cMaxConnections;
cGetNextConnectionWaitTime = m_cGetNextConnectionWaitTime;
dwConfigVersion = m_dwConfigVersion;
m_slPrivateData.ShareUnlock();
cConnections = InterlockedIncrement((PLONG) &m_cConnections);
fOwnConnectionCount = TRUE;
cLinksToTry = m_pqol->cGetCount();
while (true)
{
//Use CSyncShutdown locking to prevent shutdown from happening under us
if (!fLocked)
{
if (!fTryShutdownLock())
{
hr = AQUEUE_E_SHUTDOWN;
goto Exit;
}
m_paqinst->RoutingShareLock();
fLocked = TRUE;
}
if (m_dwConfigVersion != dwConfigVersion)
{
//Config data has/is being updated aquire lock & get new data
m_slPrivateData.ShareLock();
cMaxLinkConnections = m_cMaxLinkConnections;
cMaxMessagesPerConnection = m_cMaxMessagesPerConnection;
//Handle unlimited case
if (m_cMinMessagesPerConnection)
cMinMessagesPerConnection = m_cMinMessagesPerConnection;
else
cMinMessagesPerConnection = UNLIMITED_MSGS_PER_CONNECTION;
cMaxConnections = m_cMaxConnections;
cGetNextConnectionWaitTime = m_cGetNextConnectionWaitTime;
dwConfigVersion = m_dwConfigVersion;
m_slPrivateData.ShareUnlock();
}
//$$REVIEW: If there is more than 1 thread waiting on GetNextConnection,
//then all threads will cycle through all availalbe links (if none are
//available for connections). However, it is very unlikely that this
//run through the queue will be neccessary after Milestone #1.
while ((0 == cLinksToTry) ||
(cConnections > cMaxConnections) || fForceWait ||
fConnectionsStoppedByAdmin())
{
InterlockedDecrement((PLONG) &m_cConnections);
fOwnConnectionCount = FALSE;
//Release lock for wait function
fLocked = false;
m_paqinst->RoutingShareUnlock();
ShutdownUnlock();
DebugTrace((LPARAM) m_cConnections, "INFO: Waiting in GetNextConnection");
_ASSERT(m_cGetNextConnectionWaitTime && "Configured for busy wait loop");
dwWaitResult = WaitForMultipleObjects(NUM_CONNECTION_OBJECTS,
rghWaitEvents, FALSE, cGetNextConnectionWaitTime);
//NOTE: We *cannot* touch member variables until we determine that
//we are not shutting down, because SMTP may have a thread in here
//after this object is destroyed.
DebugTrace((LPARAM) this, "INFO: Waking up in GetNextConnection");
if (WAIT_FAILED == dwWaitResult)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
else if (WAIT_OBJECT_0 == dwWaitResult) //shutdown event fired
{
DebugTrace((LPARAM) this, "INFO: Leaving GetNextConnection because of Shutdown event");
fMembersUnsafe = TRUE;
hr = AQUEUE_E_SHUTDOWN;
goto Exit;
}
else if (WAIT_OBJECT_RELEASE_EVENT == dwWaitResult)
{
DebugTrace((LPARAM) this, "INFO: Leaving GetNextConnection because ReleaseAllWaitingThreads called");
//Caller asked that all threads be released
*ppISMTPConnection = NULL;
hr = AQUEUE_E_SHUTDOWN;
fMembersUnsafe = TRUE;
goto Exit;
}
_ASSERT((WAIT_OBJECT_0 == dwWaitResult - 1) || (WAIT_TIMEOUT == dwWaitResult));
//Re-aquire lock
if (!fTryShutdownLock())
{
hr = AQUEUE_E_SHUTDOWN;
goto Exit;
}
else
{
m_paqinst->RoutingShareLock();
fLocked = true;
}
cLinksToTry = m_pqol->cGetCount();
fForceWait = false; //only force wait once in a row
cConnections = InterlockedIncrement((PLONG) &m_cConnections);
fOwnConnectionCount = TRUE;
}
_ASSERT(cConnections <= cMaxConnections);
cLinksToTry--;
//NK**Insted of Dequeue we should lock and peek to see if the link
//needs to be dequed
//If the peek is quick it will be better than dequeing and then
//enquing it in order
//Move this complete check into peek
hr = m_pqol->HrDequeue(&plmq);
if (FAILED(hr))
{
if (AQUEUE_E_QUEUE_EMPTY == hr) //not really an error
{
hr = S_OK;
fForceWait = true;
continue;
}
else
goto Exit; //need to handle case of empty queues a little better
}
hr = plmq->HrCreateConnectionIfNeeded(cMaxLinkConnections,
cMinMessagesPerConnection, cMaxMessagesPerConnection,
this, &pSMTPConn);
if (FAILED(hr))
{
ErrorTrace((LPARAM) this,
"ERROR: HrCreateConnectionIfNeeded failed - hr 0x%08X", hr);
goto Exit;
}
if (S_OK == hr)
{
_ASSERT(pSMTPConn);
//take this opportunity to see if it need queueing
cTimesQueued = plmq->DecrementConnMgrCount();
if (!cTimesQueued)
{
plmq->IncrementConnMgrCount();
hr = m_pqol->HrEnqueue(plmq);
//If we fail here, we are in serious trouble...
//A link has been lost - we should probably log an event $$TODO
if (FAILED(hr))
{
plmq->DecrementConnMgrCount();
DebugTrace((LPARAM) hr, "ERROR: Unable to requeue link 0x%8X", plmq);
goto Exit;
}
}
hr = plmq->HrGetSMTPDomain(&cbDomain, &szDomain);
if (FAILED(hr))
goto Exit;
DebugTrace((LPARAM) plmq, "INFO: Allocating new connection for domain %s", szDomain);
break;
}
else
{
_ASSERT(!pSMTPConn);
//The link does not need a connection - queue the link and look at
//the next in line.
//Check if this link can be delete (will increment ConnMgrCount if
//it can
plmq->RemoveLinkIfEmpty();
cTimesQueued = plmq->DecrementConnMgrCount();
if (!cTimesQueued)
{
plmq->IncrementConnMgrCount();
hr = m_pqol->HrEnqueue(plmq);
if (FAILED(hr))
{
plmq->DecrementConnMgrCount();
DebugTrace((LPARAM) hr,
"ERROR: Unable to requeue link 0x%8X", plmq);
goto Exit;
}
}
plmq->Release();
plmq = NULL;
}
_ASSERT(fLocked);
m_paqinst->RoutingShareUnlock();
ShutdownUnlock();
fLocked = false;
}
*ppISMTPConnection = (ISMTPConnection *) pSMTPConn;
fOwnConnectionCount = FALSE;
Exit:
//NOTE: We *cannot* touch member variables until we determine that
//we are not shutting down, because SMTP may have a thread in here
//after this object is destroyed.
//make sure connection count is correct if we couldn't create a connection
if (fOwnConnectionCount)
{
_ASSERT(!fMembersUnsafe);
InterlockedDecrement((PLONG) &m_cConnections);
}
if (NULL != plmq)
plmq->Release();
if (fLocked)
{
_ASSERT(!fMembersUnsafe);
m_paqinst->RoutingShareUnlock();
ShutdownUnlock();
}
if (FAILED(hr) && pSMTPConn)
{
if (hr != AQUEUE_E_SHUTDOWN)
ErrorTrace((LPARAM) this, "ERROR: GetNextConnection failed - hr 0x%08X", hr);
if (pSMTPConn)
{
pSMTPConn->Release();
*ppISMTPConnection = NULL;
}
}
TraceFunctLeave();
return hr;
}
//---[ ConnMgr::GetNamedConnection ]-------------------------------------------
//
//
// Description:
// Implements IConnectionManager::GetNamedConnection
//
// Returns a connection for the specifically requested connection (if it
// exists). Unlike GetNextConnection, this call will not block, it will
// immediately succeed or fail.
// Parameters:
// IN cbSMTPDomain Length of domain name (strlen)
// IN szSMTPDomain SMTP Domain of requested connection
// OUT ppismtpconn Returned SMTP Connection interface
// Returns:
// S_OK on success
// AQUEUE_E_INVALID_DOMAIN if no link exists for the domain
// AQUEUE_E_QUEUE_EMPTY if link exists but there are no messages on it
//
//-----------------------------------------------------------------------------
STDMETHODIMP CConnMgr::GetNamedConnection(
IN DWORD cbSMTPDomain,
IN char szSMTPDomain[],
OUT ISMTPConnection **ppISMTPConnection)
{
TraceFunctEnterEx((LPARAM) this, "CConnMgr::GetNamedConnection");
HRESULT hr = S_OK;
CDomainEntry *pdentry = NULL;
CAQScheduleID aqsched;
CSMTPConn *pSMTPConn = NULL;
CLinkMsgQueue *plmq = NULL;
CDomainEntryLinkIterator delit;
DWORD cMessages = 0;
DWORD cConnectionsOnLink = 0;
_ASSERT(ppISMTPConnection);
*ppISMTPConnection = NULL;
if (fConnectionsStoppedByAdmin()) //Can't create connections
{
hr = S_OK;
goto Exit;
}
//Check if it has a queue in DMT for it
hr = m_paqinst->HrGetDomainEntry(cbSMTPDomain, szSMTPDomain, &pdentry);
if (FAILED(hr))
{
//If we do not have a DMQ corresponding to it
//we should respond with zero message
if( hr != AQUEUE_E_INVALID_DOMAIN && hr != DOMHASH_E_NO_SUCH_DOMAIN)
{
hr = AQ_E_SMTP_ETRN_INTERNAL_ERROR;
}
else
{
hr = S_OK;
}
goto Exit;
}
//NK** : Can we live with this single call
//The assumption being that domain configured for TURN will
//always have only one link associated with it
hr = delit.HrInitialize(pdentry);
if (FAILED(hr))
{
//Treat as no-link case
ErrorTrace((LPARAM) this, "Initializing link iterator failed - hr 0x%08X", hr);
hr = S_OK;
goto Exit;
}
plmq = delit.plmqGetNextLinkMsgQueue(plmq);
if (!plmq)
{
//If we do not have a link corresponding to it
//we should report the error back to SMTP
hr = S_OK;
goto Exit;
}
//Check if there are connections for this link that exist
//
cConnectionsOnLink = plmq->cGetConnections();
if(cConnectionsOnLink)
{
//Do not allow multiple connections on TURN domains
//It does not make much sense
hr = S_OK;
goto Exit;
}
//get the msg count from the dmq
cMessages = plmq->cGetTotalMsgCount();
if(cMessages)
{
//Create the connection with no message limit
pSMTPConn = new CSMTPConn(this, plmq, 0);
if (NULL == pSMTPConn)
{
hr = E_OUTOFMEMORY;
goto Exit;
}
plmq->AddConnection(pSMTPConn);
*ppISMTPConnection = (ISMTPConnection *) pSMTPConn;
InterlockedIncrement((PLONG) &m_cConnections);
//Now enable the link for turned connections
plmq->dwModifyLinkState(LINK_STATE_PRIV_TURN_ENABLED, LINK_STATE_NO_ACTION);
goto Exit;
}
else
{
hr = S_OK;
goto Exit;
}
Exit:
if (pdentry)
pdentry->Release();
if(plmq)
plmq->Release();
TraceFunctLeave();
return hr;
}
//---[ CConnMgr::ReleaseWaitingThreads ]---------------------------------------
//
//
// Description:
// Releases all threads waiting on get next connection.
// Parameters:
// -
// Returns:
// AQUEUE_E_NOT_INITIALIZED if event handle does not exist
//
//-----------------------------------------------------------------------------
STDMETHODIMP CConnMgr::ReleaseWaitingThreads()
{
HRESULT hr = S_OK;
if (m_paqinst)
m_paqinst->SetShutdownHint();
if (NULL == m_hReleaseAllEvent)
{
hr = AQUEUE_E_NOT_INITIALIZED;
goto Exit;
}
//Since this is an manual-reset event, we will need to Set the Event
//NOTE: Using PulseEvent here would sometimes cause the system to hang
//on shutdown.
if (!SetEvent(m_hReleaseAllEvent))
hr = HRESULT_FROM_WIN32(GetLastError());
Exit:
return hr;
}
//---[ CreateETRNDomainList ]-----------------------------------------------------------
//
//
// Description:
// Implements CreateETRNDomainList. A function passed to the
// DCT iterator to create a list of subdomains corresponding to the ETRN requests
// of type @domain
//
// Parameters:
//
// Returns:
//
//
//---------------------------------------------------------------------------------
VOID CreateETRNDomainList(PVOID pvContext, PVOID pvData, BOOL fWildcard,
BOOL *pfContinue, BOOL *pfDelete)
{
CInternalDomainInfo *pIntDomainInfo = (CInternalDomainInfo*)pvData;
ETRNCTX *pETRNCtx = (ETRNCTX*) pvContext;
*pfContinue = TRUE;
*pfDelete = FALSE;
HRESULT hr = S_OK;
TraceFunctEnterEx((LPARAM) NULL, "ETRNSubDomains");
//We simply create a list of domains in DMT that match our pattern
//IDI stands for InternalDomainInfo
if( pETRNCtx && pIntDomainInfo)
{
//We add it to the array and add a reference to it
pETRNCtx->rIDIList[pETRNCtx->cIDICount] = pIntDomainInfo;
pIntDomainInfo->AddRef();
if(++pETRNCtx->cIDICount >= MAX_ETRNDOMAIN_PER_COMMAND)
{
_ASSERT(0);
pETRNCtx->hr = AQUEUE_E_ETRN_TOO_MANY_DOMAINS;
*pfContinue = FALSE;
}
}
else
{
if (pETRNCtx)
pETRNCtx->hr = AQ_E_SMTP_ETRN_INTERNAL_ERROR;
*pfContinue = FALSE;
}
TraceFunctLeave();
return;
}
//---[ LookupQueueforETRN ]--------------------------------------------------
//
//
// Description:
// Implements LookupQueueforETRN. A function passed to the
// DMT iterator to lookup all queues for a wild card domain
//
// Parameters:
// Returns:
//
//
//---------------------------------------------------------------------------------
VOID LookupQueueforETRN(PVOID pvContext, PVOID pvData, BOOL fWildcard,
BOOL *pfContinue, BOOL *pfDelete)
{
CDomainEntry *pdentry = (CDomainEntry*)pvData;
CLinkMsgQueue *plmq = NULL;
CDomainEntryLinkIterator delit;
CInternalDomainInfo *pIntDomainInfo =NULL;
ETRNCTX *pETRNCtx = (ETRNCTX*) pvContext;
char *szSMTPDomain = NULL;
DWORD cbSMTPDomain = 0;
DWORD cMessages = 0;
HRESULT hr = S_OK;
*pfContinue = TRUE;
*pfDelete = FALSE;
TraceFunctEnterEx((LPARAM) NULL, "ETRNSubDomains");
//If the Domain has messages it is candidate for ETRN
//Get the link msg queue from the DMT entry
hr = delit.HrInitialize(pdentry);
if (FAILED(hr))
goto Exit;
while (plmq = delit.plmqGetNextLinkMsgQueue(plmq))
{
//get the msg count from the dmq
cMessages = plmq->cGetTotalMsgCount();
if(cMessages)
{
//get the name of the domain we are currently considering
hr = pdentry->HrGetDomainName(&szSMTPDomain);
if (FAILED(hr))
{
//we had some internal error we need to stop iterating
//Set the Hr in context
DebugTrace((LPARAM) NULL, "Failed to get message count for %s", szSMTPDomain);
*pfContinue = FALSE;
pETRNCtx->hr = AQ_E_SMTP_ETRN_INTERNAL_ERROR;
goto Exit;
}
cbSMTPDomain = lstrlen(szSMTPDomain);
//Lookup it up in the DCT to see if there is an entry that conflicts with this
//If there is no exact match the lookup will comeup with the closest configured
//ancestor
hr = pETRNCtx->paqinst->HrGetInternalDomainInfo(cbSMTPDomain, szSMTPDomain, &pIntDomainInfo);
if (FAILED(hr))
{
//It must match the "*" domain at least
//Otherwise we had some internal error we need to stop iterating
//Set the Hr in context
*pfContinue = FALSE;
pETRNCtx->hr = AQ_E_SMTP_ETRN_INTERNAL_ERROR;
goto Exit;
}
else
{
_ASSERT(pIntDomainInfo);
//If that ancestor configured for ETRN and it is not the root, we enable it
//else we skip domain
//
if ((pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags & DOMAIN_INFO_ETRN_ONLY) &&
pIntDomainInfo->m_DomainInfo.cbDomainNameLength != 1)
{
pETRNCtx->cMessages += cMessages;
cMessages = 0;
//If it does - trigger the links.
DebugTrace((LPARAM) NULL, "Enabling ETRN for domain %s", szSMTPDomain);
plmq->dwModifyLinkState(
LINK_STATE_PRIV_ETRN_ENABLED | LINK_STATE_RETRY_ENABLED,
LINK_STATE_NO_ACTION);
}
} //If we have a valid IntDomainInfo
} //Message count is zero
} //looping over lmq's for entry
Exit:
if (pIntDomainInfo)
pIntDomainInfo->Release();
if (szSMTPDomain)
FreePv(szSMTPDomain);
if (plmq)
plmq->Release();
return;
}
//---[ CConnMgr::ETRNDomainList ]--------------------------------------------------
//
//
// Description:
// Implements IConnectionManager:ETRNDomainList. Used to ETRN appropriate
// domains based on the list of CInternalDomainInfo passed in
// Parameters:
//
// Returns:
//
//
//-----------------------------------------------------------------------------
HRESULT CConnMgr::ETRNDomainList(ETRNCTX *pETRNCtx)
{
CInternalDomainInfo *pIntDomainInfo = NULL;
BOOL fWildcard = FALSE;
HRESULT hr = S_OK;
DWORD i = 0;
TraceFunctEnterEx((LPARAM) this, "CConnMgr::ETRNDomain");
//NK** Do I need to sort the pointers for duplicates ?
if(!pETRNCtx->cIDICount)
{
//We have nothing in our list
//
hr = AQUEUE_E_INVALID_DOMAIN;
goto Exit;
}
for(; i < pETRNCtx->cIDICount; i++)
{
if(!(pIntDomainInfo = pETRNCtx->rIDIList[i]))
{
//Error happend
pETRNCtx->hr = AQ_E_SMTP_ETRN_INTERNAL_ERROR;
break;
}
//We go ahead only if the domain is marked for ETRN
if ((pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags & DOMAIN_INFO_ETRN_ONLY))
{
//check if this is wild card domain
fWildcard = FALSE;
if( pIntDomainInfo->m_DomainInfo.szDomainName[0] == '*' &&
pIntDomainInfo->m_DomainInfo.cbDomainNameLength != 1)
{
fWildcard = TRUE;
}
//If the domain in the list is a wild card entry then
if(fWildcard)
{
//So we have atleast one matching ETRN domain configured
if(pETRNCtx->hr == S_OK)
pETRNCtx->hr = AQ_S_SMTP_WILD_CARD_NODE;
//Lookup this domain and all its subdomains in the DMT
//skip over the leading "*."
hr = pETRNCtx->paqinst->HrIterateDMTSubDomains(pIntDomainInfo->m_DomainInfo.szDomainName + 2,
pIntDomainInfo->m_DomainInfo.cbDomainNameLength - 2,
(DOMAIN_ITR_FN)LookupQueueforETRN, pETRNCtx);
if (FAILED(hr) && hr != DOMHASH_E_NO_SUCH_DOMAIN && hr != AQUEUE_E_INVALID_DOMAIN)
{
DebugTrace((LPARAM) NULL, "ERROR calling HrIterateDMTSubDomains");
goto Exit;
}
} // wild card DCT entry
else
{
//Start the queue for the entry
hr = StartETRNQueue(pIntDomainInfo->m_DomainInfo.cbDomainNameLength,
pIntDomainInfo->m_DomainInfo.szDomainName,
pETRNCtx);
if (FAILED(hr))
{
//NK** This actually may not be an error
//If we do not have a DMQ corresponding to it
//we should respond with zero message
if( hr != AQUEUE_E_INVALID_DOMAIN && hr != DOMHASH_E_NO_SUCH_DOMAIN)
{
pETRNCtx->hr = AQ_E_SMTP_ETRN_INTERNAL_ERROR;
goto Exit;
}
else
continue;
}
} //not a wild card DCT entry
}
}
Exit:
TraceFunctLeave();
return hr;
}
//---[ CConnMgr::StartETRNQueue ]--------------------------------------------------
//
//
// Description:
// Implements CConnMgr::StartETRNQueuet. Used to start the queue for any
// domain configured for ETRN
// Parameters:
//
// Returns:
//
//
//-----------------------------------------------------------------------------
HRESULT CConnMgr::StartETRNQueue(IN DWORD cbSMTPDomain,
IN char szSMTPDomain[],
ETRNCTX *pETRNCtx)
{
CDomainEntry *pdentry = NULL;
CDomainEntryLinkIterator delit;
CLinkMsgQueue *plmq = NULL;
CAQSvrInst *paqinst = pETRNCtx->paqinst;
DWORD cMessages = 0;
HRESULT hr = S_OK;
TraceFunctEnterEx((LPARAM) this, "CConnMgr::ETRNDomain");
//So we have a domain configured for ETRN by this name
if( pETRNCtx->hr == S_OK)
pETRNCtx->hr = AQ_S_SMTP_VALID_ETRN_DOMAIN;
//Check if it has a queue in DMT for it
hr = pETRNCtx->paqinst->HrGetDomainEntry(cbSMTPDomain, szSMTPDomain, &pdentry);
if (FAILED(hr))
{
//If we do not have a DMQ corresponding to it
//we should respond with zero message
if( hr != AQUEUE_E_INVALID_DOMAIN && hr != DOMHASH_E_NO_SUCH_DOMAIN)
{
pETRNCtx->hr = AQ_E_SMTP_ETRN_INTERNAL_ERROR;
}
goto Exit;
}
hr = delit.HrInitialize(pdentry);
if (FAILED(hr))
goto Exit;
while (plmq = delit.plmqGetNextLinkMsgQueue(plmq))
{
//get the msg count from the dmq
cMessages = plmq->cGetTotalMsgCount();
if(cMessages)
{
pETRNCtx->cMessages += cMessages;
cMessages = 0;
//If it does - trigger the link.
DebugTrace((LPARAM) NULL, "Enabling ETRN for domain %s", szSMTPDomain);
plmq->dwModifyLinkState(
LINK_STATE_PRIV_ETRN_ENABLED | LINK_STATE_RETRY_ENABLED,
LINK_STATE_NO_ACTION);
}
}
Exit:
if (pdentry)
pdentry->Release();
if (plmq)
plmq->Release();
TraceFunctLeave();
return hr;
}
//---[ CConnMgr::ETRNDomain ]----------------------------------------------------
//
//
// Description:
// Implements IConnectionManager:ETRNDomain. Used to reqeust that a
// domain be ETRN'd (enabled for outbound connections).
// Parameters:
// IN cbSMTPDomain String length of domain name
// IN szSMTPDomain SMTP Domain name. Wildcarded names start with
// a "@" (eg "@foo.com");
// OUT pcMessages # of Messages queued for ETRN domain
// Returns:
// Remarks:
// If the received domain is wildcarded '@' then we follow this logic :
// Lookup this node and every subnode of this node in DCT. The lookup is done
// using table iterator and iterator function CreateETRNDomainList. For every entry
// that is found with ETRN flag set, lookup if any queues exist in DMT. If the
// queue exist and have messages in them then we enable the corresponding links.
// If the lookup in DCT yields a domain that is configured as wild card '*.",
// then we lookup all queus corresponding to all sub domains of that domain. We do
// this using the iterator and iterator function LookupQueueforETRN. For every queue
// found by iterator the function checks back in DMT if the domain is configured for
// ETRN. This is to take care of situations where one specific subdomain of a wild
// card configured domain may be not configured for etrn.
// Eg : *.foo.com => ETRN, but 1.foo.com => NO_ETRN
//
// Both calls to iterate are covered with reader locks. The lock stays valid for
// the duration of all iterations.
// The iterator function used during DCT iterations also adds reference to every
// InternalDomainInfo as we need the data to stay valid after the table lock is released.
//----------------------------------------------------------------------------------
STDMETHODIMP CConnMgr::ETRNDomain(
IN DWORD cbSMTPDomain,
IN char szSMTPDomain[],
OUT DWORD *pcMessages)
{
HRESULT hr = S_OK;
BOOL fLocked = FALSE;
CDomainEntry *pdentry = NULL;
CDestMsgQueue *pdmq = NULL;
CInternalDomainInfo *pIntDomainInfo =NULL;
BOOL fETRNSubDomains = FALSE;
char * szTmpDomain = szSMTPDomain;
ETRNCTX EtrnCtx;
EtrnCtx.hr = S_OK;
EtrnCtx.cMessages = 0;
EtrnCtx.paqinst = NULL;
EtrnCtx.cIDICount = 0;
EtrnCtx.rIDIList[0] = NULL;
TraceFunctEnterEx((LPARAM) this, "CConnMgr::ETRNDomain");
DWORD cMessages = 0;
*pcMessages = 0; //$$TODO - Get real values
if (!fTryShutdownLock())
{
hr = AQUEUE_E_SHUTDOWN;
goto Exit;
}
m_paqinst->RoutingShareLock();
fLocked = TRUE;
EtrnCtx.paqinst = m_paqinst;
//do we have a '@' request
if(*szTmpDomain == '@')
fETRNSubDomains = TRUE;
//If we do have '@' request, we need to skip the first chararcter
//and then look for every sub domain of the domain in the DCT
//For every subdomain that we find with ETRN flag, we will lookup the
//DMT to see if there is a queue
//If the entry we find in DCT is of wildcard type, we will lookup all
//subdomains of that domain in DMT looking for all queues destined for
//subdomains of the DCT entry.
if(fETRNSubDomains)
{
++szTmpDomain;
//Create a list of all subdomains of this domain in the DCT
hr = m_paqinst->HrIterateDCTSubDomains(szTmpDomain, lstrlen(szTmpDomain),
(DOMAIN_ITR_FN)CreateETRNDomainList, &EtrnCtx);
//If we fail to look up single domain
if (FAILED(hr))
{
if(hr == AQUEUE_E_INVALID_DOMAIN || hr == DOMHASH_E_NO_SUCH_DOMAIN)
{
DebugTrace((LPARAM)this, "ERROR calling HrIterateDCTSubdomains");
hr = hr = AQ_E_SMTP_ETRN_NODE_INVALID;
}
else
{
DebugTrace((LPARAM)this, "ERROR calling HrIterateDCTSubdomains");
hr = AQ_E_SMTP_ETRN_INTERNAL_ERROR;
}
goto Exit;
}
//Check if the lookup got us anything
if(!FAILED(EtrnCtx.hr))
{
//Check if any queus can be started for domains in the list
//Start if possible
hr = ETRNDomainList(&EtrnCtx);
if (FAILED(hr))
{
if(hr != AQUEUE_E_INVALID_DOMAIN && hr != DOMHASH_E_NO_SUCH_DOMAIN)
{
DebugTrace((LPARAM)this, "ERROR calling ETRNSubDomain");
hr = AQ_E_SMTP_ETRN_INTERNAL_ERROR;
goto Exit;
}
}
//If we saw atleast one matching domain
if(EtrnCtx.hr == AQ_S_SMTP_VALID_ETRN_DOMAIN || EtrnCtx.hr == AQ_S_SMTP_WILD_CARD_NODE)
{
*pcMessages = EtrnCtx.cMessages;
hr = EtrnCtx.hr;
}
else
hr = AQ_E_SMTP_ETRN_NODE_INVALID;
}
else
hr = AQ_E_SMTP_ETRN_INTERNAL_ERROR;
goto Exit;
}
else
{
//Lookup the domain in the domain cfg table and see if it has ETRN bit set
_ASSERT(m_paqinst);
hr = m_paqinst->HrGetInternalDomainInfo(cbSMTPDomain, szSMTPDomain, &pIntDomainInfo);
if (FAILED(hr))
{
//It must match the "*" domain at least
_ASSERT(AQUEUE_E_INVALID_DOMAIN != hr);
hr = AQ_E_SMTP_ETRN_INTERNAL_ERROR;
goto Exit;
}
else
{
_ASSERT(pIntDomainInfo);
EtrnCtx.rIDIList[0] = pIntDomainInfo;
EtrnCtx.cIDICount = 1;
//We will not ETRN if the closest ancestor is Root or two level
//NK** implement search for two level
if( pIntDomainInfo->m_DomainInfo.cbDomainNameLength == 1)
{
//Cannot ETRN based on the root domain
hr = AQ_E_SMTP_ETRN_NODE_INVALID;
goto Exit;
}
if ((pIntDomainInfo->m_DomainInfo.dwDomainInfoFlags & DOMAIN_INFO_ETRN_ONLY))
{
//Start the queue if exists for this domain
hr = StartETRNQueue(cbSMTPDomain, szSMTPDomain,&EtrnCtx);
if (FAILED(hr))
{
if(hr != AQUEUE_E_INVALID_DOMAIN && hr != DOMHASH_E_NO_SUCH_DOMAIN)
{
DebugTrace((LPARAM)this, "ERROR calling ETRNSubDomain");
hr = AQ_E_SMTP_ETRN_INTERNAL_ERROR;
goto Exit;
}
}
//If we saw atleast one matching domain
if(EtrnCtx.hr == AQ_S_SMTP_VALID_ETRN_DOMAIN || EtrnCtx.hr == AQ_S_SMTP_WILD_CARD_NODE)
{
*pcMessages = EtrnCtx.cMessages;
hr = AQ_S_SMTP_VALID_ETRN_DOMAIN;
}
else
hr = AQ_E_SMTP_ETRN_NODE_INVALID;
goto Exit;
}
else
{
//Cannot ETRN based on the root domain
hr = AQ_E_SMTP_ETRN_NODE_INVALID;
goto Exit;
}
}
}
Exit:
//wake up thread in GetNextConnection
if (SUCCEEDED(hr) &&SUCCEEDED(EtrnCtx.hr) && EtrnCtx.cMessages)
_VERIFY(SetEvent(m_hNextConnectionEvent));
if (fLocked)
{
m_paqinst->RoutingShareUnlock();
ShutdownUnlock();
}
//free up all InternalDomainInfo
for(DWORD i=0; i < EtrnCtx.cIDICount; i++)
if (EtrnCtx.rIDIList[i])
EtrnCtx.rIDIList[i]->Release();
if (pdentry)
pdentry->Release();
TraceFunctLeave();
return hr;
}
//---[ CConnMgr::ModifyLinkState ]---------------------------------------------
//
//
// Description:
// Link state can change so that connections can(not) be created for a link.
// Parameters:
// IN cbDomainName String length of domain name
// IN szDomainName Domain Name to enable
// IN dwScheduleID ScheduleID of <domain, schedule> pair
// IN rguidTransportSink GUID of router associated with link
// IN dwFlagsToSet Link State Flags to set
// IN dwFlagsToUnset Link State Flags to unset
// Returns:
// S_OK on success
// AQUEUE_E_INVALID_DOMAIN if domain does not exist
//
//-----------------------------------------------------------------------------
HRESULT CConnMgr::ModifyLinkState(
IN DWORD cbDomainName,
IN char szDomainName[],
IN DWORD dwScheduleID,
IN GUID rguidTransportSink,
IN DWORD dwFlagsToSet,
IN DWORD dwFlagsToUnset)
{
HRESULT hr = S_OK;
BOOL fLocked = FALSE;
CDomainEntry *pdentry = NULL;
CLinkMsgQueue *plmq = NULL;
CAQScheduleID aqsched(rguidTransportSink, dwScheduleID);
if (!cbDomainName || !szDomainName)
{
hr = E_INVALIDARG;
goto Exit;
}
if (!fTryShutdownLock())
{
hr = AQUEUE_E_SHUTDOWN;
goto Exit;
}
fLocked = TRUE;
_ASSERT(m_paqinst);
hr = m_paqinst->HrGetDomainEntry(cbDomainName, szDomainName, &pdentry);
if (FAILED(hr))
goto Exit;
hr = pdentry->HrGetLinkMsgQueue(&aqsched, &plmq);
if (FAILED(hr))
goto Exit;
_ASSERT(plmq);
//filter out the reserved bits for this "public" API
plmq->dwModifyLinkState(~LINK_STATE_RESERVED & dwFlagsToSet,
~LINK_STATE_RESERVED & dwFlagsToUnset);
Exit:
if (fLocked)
ShutdownUnlock();
if (pdentry)
pdentry->Release();
if (plmq)
plmq->Release();
return hr;
}
//---[ CConnMgr::UpdateConfigData ]-------------------------------------------
//
//
// Description:
// Will be used by catmsgq to update the metabase changes
//
// Parameters:
//
// Returns:
//
//-----------------------------------------------------------------------------
//
void CConnMgr::UpdateConfigData(IN AQConfigInfo *pAQConfigInfo)
{
BOOL fUpdated = FALSE;
RETRYCONFIG RetryConfig;
RetryConfig.dwRetryThreshold = g_dwRetryThreshold;
RetryConfig.dwGlitchRetrySeconds = g_dwGlitchRetrySeconds;
//
// This is registry configurable... make sure we have a sane
// value
//
if (!RetryConfig.dwGlitchRetrySeconds)
RetryConfig.dwGlitchRetrySeconds = 60;
RetryConfig.dwFirstRetrySeconds = g_dwFirstTierRetrySeconds;
RetryConfig.dwSecondRetrySeconds = g_dwSecondTierRetrySeconds;
RetryConfig.dwThirdRetrySeconds = g_dwThirdTierRetrySeconds;
RetryConfig.dwFourthRetrySeconds = g_dwFourthTierRetrySeconds;
m_slPrivateData.ExclusiveLock();
if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_MAX_CON &&
MEMBER_OK(pAQConfigInfo, cMaxConnections))
{
if ((m_cMaxConnections != pAQConfigInfo->cMaxConnections))
{
fUpdated = TRUE;
//g_cMaxConnections is the number connection objects we
//reserve with CPool... we can't go above that.
if (g_cMaxConnections < pAQConfigInfo->cMaxConnections)
m_cMaxConnections = g_cMaxConnections;
else
m_cMaxConnections = pAQConfigInfo->cMaxConnections;
}
}
if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_MAX_LINK &&
MEMBER_OK(pAQConfigInfo, cMaxLinkConnections))
{
if (m_cMaxLinkConnections != pAQConfigInfo->cMaxLinkConnections)
{
fUpdated = TRUE;
//g_cMaxConnections is the number connection objects we
//reserve with CPool... we can't go above that.
if (!pAQConfigInfo->cMaxLinkConnections ||
(g_cMaxConnections < pAQConfigInfo->cMaxLinkConnections))
m_cMaxLinkConnections = g_cMaxConnections;
else
m_cMaxLinkConnections = pAQConfigInfo->cMaxLinkConnections;
}
}
if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_MIN_MSG &&
MEMBER_OK(pAQConfigInfo, cMinMessagesPerConnection))
{
if (m_cMinMessagesPerConnection != pAQConfigInfo->cMinMessagesPerConnection)
{
fUpdated = TRUE;
m_cMinMessagesPerConnection = pAQConfigInfo->cMinMessagesPerConnection;
//Currently we set both these values based on the batching value from SMTP
m_cMaxMessagesPerConnection = pAQConfigInfo->cMinMessagesPerConnection;
}
}
if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_CON_WAIT &&
MEMBER_OK(pAQConfigInfo, dwConnectionWaitMilliseconds))
{
if (m_cGetNextConnectionWaitTime != pAQConfigInfo->dwConnectionWaitMilliseconds)
{
fUpdated = TRUE;
m_cGetNextConnectionWaitTime = pAQConfigInfo->dwConnectionWaitMilliseconds;
}
}
if (fUpdated) //only force updated when really required
InterlockedIncrement((PLONG) &m_dwConfigVersion);
m_slPrivateData.ExclusiveUnlock();
fUpdated = FALSE;
//Retry related config data
if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_CON_RETRY &&
MEMBER_OK(pAQConfigInfo, dwRetryThreshold))
{
fUpdated = TRUE;
RetryConfig.dwRetryThreshold = pAQConfigInfo->dwRetryThreshold;
}
if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_CON_RETRY &&
MEMBER_OK(pAQConfigInfo, dwFirstRetrySeconds))
{
fUpdated = TRUE;
RetryConfig.dwFirstRetrySeconds = pAQConfigInfo->dwFirstRetrySeconds;
}
if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_CON_RETRY &&
MEMBER_OK(pAQConfigInfo, dwSecondRetrySeconds))
{
fUpdated = TRUE;
RetryConfig.dwSecondRetrySeconds = pAQConfigInfo->dwSecondRetrySeconds;
}
if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_CON_RETRY &&
MEMBER_OK(pAQConfigInfo, dwThirdRetrySeconds))
{
fUpdated = TRUE;
RetryConfig.dwThirdRetrySeconds = pAQConfigInfo->dwThirdRetrySeconds;
}
if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_CON_RETRY &&
MEMBER_OK(pAQConfigInfo, dwFourthRetrySeconds))
{
fUpdated = TRUE;
RetryConfig.dwFourthRetrySeconds = pAQConfigInfo->dwFourthRetrySeconds;
}
if (pAQConfigInfo->dwAQConfigInfoFlags & AQ_CONFIG_INFO_CON_RETRY &&
fUpdated )
m_pDefaultRetryHandler->UpdateRetryData(&RetryConfig);
}
//---[ CConnMgr::RetryLink ]---------------------------------------------------
//
//
// Description:
// Implements IConnectionRetryManager::RetryLink, which enables the retry
// sink to enable a link for retry.
// Parameters:
// IN cbDomainName String length of domain name
// IN szDomainName Domain Name to enable
// IN dwScheduleID ScheduleID of <domain, schedule> pair
// IN rguidTransportSink GUID of router associated with link
// Returns:
// S_OK on success
// AQUEUE_E_INVALID_DOMAIN if domain does not exist
// History:
// 1/9/99 - MikeSwa Created (simplified routing sink)
//
//-----------------------------------------------------------------------------
STDMETHODIMP CConnMgr::RetryLink(
IN DWORD cbDomainName,
IN char szDomainName[],
IN DWORD dwScheduleID,
IN GUID rguidTransportSink)
{
HRESULT hr = S_OK;
hr = ModifyLinkState(cbDomainName, szDomainName, dwScheduleID,
rguidTransportSink, LINK_STATE_RETRY_ENABLED,
LINK_STATE_NO_ACTION);
//
// Kick the connections so we know to make one
//
KickConnections();
return hr;
}