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
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;
|
|
}
|