//----------------------------------------------------------------------------- // // // 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(this); } else if (IID_IConnectionRetryManager == riid) { *ppvObj = static_cast(this); } else if (IID_IConnectionManager == riid) { *ppvObj = static_cast(this); } else { *ppvObj = NULL; hr = E_NOINTERFACE; goto Exit; } static_cast(*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 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 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; }